Full Code of XiaoMi/soar for AI

dev 5ed8574ae59f cached
229 files
1.9 MB
727.0k tokens
812 symbols
1 requests
Download .txt
Showing preview only (2,066K chars total). Download the full file or copy to clipboard to get everything.
Repository: XiaoMi/soar
Branch: dev
Commit: 5ed8574ae59f
Files: 229
Total size: 1.9 MB

Directory structure:
gitextract_vewet1zv/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── feature_request.md
│   │   └── question.md
│   └── pull_request_template.md
├── .gitignore
├── .travis.yml
├── CHANGES.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── NOTICE.txt
├── README.md
├── README_EN.md
├── VERSION
├── advisor/
│   ├── doc.go
│   ├── explainer.go
│   ├── explainer_test.go
│   ├── heuristic.go
│   ├── heuristic_test.go
│   ├── index.go
│   ├── index_test.go
│   ├── rules.go
│   ├── rules_test.go
│   └── testdata/
│       ├── TestDigestExplainText.golden
│       ├── TestIndexAdviseNoEnv.golden
│       ├── TestListHeuristicRules.golden
│       ├── TestListTestSQLs.golden
│       └── TestMergeConflictHeuristicRules.golden
├── ast/
│   ├── doc.go
│   ├── meta.go
│   ├── meta_test.go
│   ├── node_array.go
│   ├── pretty.go
│   ├── pretty_test.go
│   ├── rewrite.go
│   ├── rewrite_test.go
│   ├── testdata/
│   │   ├── TestCompress.golden
│   │   ├── TestFormat.golden
│   │   ├── TestGetQuotedString.golden
│   │   ├── TestLeftNewLines.golden
│   │   ├── TestListRewriteRules.golden
│   │   ├── TestMergeAlterTables.golden
│   │   ├── TestNewLines.golden
│   │   ├── TestPretty.golden
│   │   ├── TestPrintPrettyStmtNode.golden
│   │   ├── TestPrintPrettyVitessStmtNode.golden
│   │   ├── TestQueryType.golden
│   │   ├── TestSchemaMetaInfo.golden
│   │   ├── TestSplitStatement.golden
│   │   ├── TestStmtNode2JSON.golden
│   │   ├── TestTokenize.golden
│   │   ├── TestTokenizer.golden
│   │   └── TestVitessStmtNode2JSON.golden
│   ├── tidb.go
│   ├── tidb_test.go
│   ├── token.go
│   ├── token_test.go
│   ├── vitess.go
│   └── vitess_test.go
├── cmd/
│   └── soar/
│       ├── doc.go
│       ├── soar.go
│       ├── soar_test.go
│       ├── testdata/
│       │   ├── Test_Main.golden
│       │   └── Test_Main_verboseInfo.golden
│       └── tool.go
├── common/
│   ├── cases.go
│   ├── chardet.go
│   ├── chardet_test.go
│   ├── config.go
│   ├── config_test.go
│   ├── doc.go
│   ├── example_test.go
│   ├── logger.go
│   ├── logger_test.go
│   ├── markdown.go
│   ├── markdown_test.go
│   ├── meta.go
│   ├── meta_test.go
│   ├── signal.go
│   ├── signal_test.go
│   ├── testdata/
│   │   ├── TestJSONFind.golden
│   │   ├── TestListReportTypes.golden
│   │   ├── TestMarkdown2Html.golden
│   │   ├── TestMarkdown2Html.md
│   │   ├── TestMarkdownHTMLHeader.golden
│   │   ├── TestParseDSN.golden
│   │   ├── TestPrintConfiguration.golden
│   │   ├── TestRemoveBOM.golden
│   │   ├── TestStringStorageReq.golden
│   │   ├── UTF-8.bom.sql
│   │   ├── chardet_BIG5.txt
│   │   ├── chardet_GB-18030.txt
│   │   └── chardet_UTF-8.txt
│   ├── tricks.go
│   ├── tricks_test.go
│   └── version.go
├── database/
│   ├── doc.go
│   ├── explain.go
│   ├── explain_test.go
│   ├── mysql.go
│   ├── mysql_test.go
│   ├── privilege.go
│   ├── privilege_test.go
│   ├── profiling.go
│   ├── profiling_test.go
│   ├── sampling.go
│   ├── show.go
│   ├── show_test.go
│   ├── testdata/
│   │   ├── TestEscape.golden
│   │   ├── TestExplain.golden
│   │   ├── TestExplainInfoTranslator.golden
│   │   ├── TestFindColumn.golden
│   │   ├── TestFormatProfiling.golden
│   │   ├── TestFormatTrace.golden
│   │   ├── TestMySQLExplainQueryCost.golden
│   │   ├── TestMySQLExplainWarnings.golden
│   │   ├── TestPrintMarkdownExplainTable.golden
│   │   ├── TestRemoveSQLComments.golden
│   │   ├── TestShowColumns.golden
│   │   ├── TestShowCreateDatabase.golden
│   │   ├── TestShowCreateTable.golden
│   │   ├── TestShowIndex.golden
│   │   ├── TestShowReference.golden
│   │   ├── TestShowTables.golden
│   │   └── TestTrace.golden
│   ├── trace.go
│   └── trace_test.go
├── deps.sh
├── doc/
│   ├── FAQ.md
│   ├── FAQ_en.md
│   ├── cheatsheet.md
│   ├── cheatsheet_en.md
│   ├── comparison.md
│   ├── comparison_en.md
│   ├── config.md
│   ├── editor_plugin.md
│   ├── environment.md
│   ├── example/
│   │   ├── digest_pt.py
│   │   ├── metalinter.json
│   │   ├── metalinter.sh
│   │   ├── metalinter.txt
│   │   ├── revive.toml
│   │   ├── slow.log.digest
│   │   └── soar.vim
│   ├── explain.md
│   ├── heuristic.md
│   ├── images/
│   │   └── logo.ascii
│   ├── indexing.md
│   ├── install.md
│   ├── install_en.md
│   ├── js/
│   │   └── pretty.js
│   ├── report_type.md
│   ├── rewrite.md
│   ├── roadmap.md
│   ├── structure.md
│   ├── thanks.md
│   ├── thanks_en.md
│   └── themes/
│       ├── foghorn.css
│       ├── ghostwriter.css
│       ├── github-dark.css
│       ├── github.css
│       ├── godspeed.css
│       ├── markdown-alt.css
│       ├── markdown.css
│       ├── markdown5.css
│       ├── markdown6.css
│       ├── markdown7.css
│       ├── markdown8.css
│       ├── markdown9.css
│       ├── markedapp-byword.css
│       ├── new-modern.css
│       ├── radar.css
│       ├── screen.css
│       ├── solarized-dark.css
│       ├── solarized-light.css
│       ├── torpedo.css
│       └── vostok.css
├── doc.go
├── env/
│   ├── doc.go
│   ├── env.go
│   ├── env_test.go
│   └── testdata/
│       └── TestNewVirtualEnv.golden
├── etc/
│   ├── soar.blacklist
│   └── soar.yaml
├── genver.sh
├── go.mod
├── go.sum
├── retool-install.sh
├── revive.toml
├── test/
│   ├── env.bats
│   ├── fixture/
│   │   ├── test_Check_Max_Join_Table_Count_Default.golden
│   │   ├── test_Check_Max_Join_Table_Count_Overflow.golden
│   │   ├── test_Check_Soar_Delimiter.golden
│   │   ├── test_Check_Soar_Max_Column_Count.golden
│   │   ├── test_Check_Soar_SQL_Fingerprint.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Alwaystrue_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Countstar_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Delimiter_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Distinctstar_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Dml2select_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Dmlorderby_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Having_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Mergealter_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Or2in_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Orderbynull_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Rmparenthesis_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Standard_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Star2columns_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Truncate_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Unionall_.golden
│   │   ├── test_Check_Soar_SQL_pretty_And_Compress_.golden
│   │   ├── test_Check_get_tables_from_SQL.golden
│   │   ├── test_Check_soar_for_pipe_input.golden
│   │   ├── test_Check_soar_query_for_input_file.golden
│   │   ├── test_Check_soar_report_for_html.golden
│   │   ├── test_Check_soar_report_for_json.golden
│   │   ├── test_Check_soar_report_for_markdown.golden
│   │   ├── test_Check_the_default_config_of_the_changes.golden
│   │   ├── test_Run_all_test_cases.golden
│   │   ├── test_Run_default_printconfig_cases.golden
│   │   └── test_Simple_Query_Optimizer.golden
│   ├── main.bats
│   ├── other.bats
│   ├── query.bats
│   ├── sql/
│   │   └── README.md
│   └── test_helper.bash
└── tools.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8

# tab_size = 4 spaces
[*.go]
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug Report
about: You're experiencing an issue with SOAR that is different than the documented behavior.

---

Please answer these questions before submitting your issue. Thanks!

1. What did you do?
If possible, provide a recipe for reproducing the error.


2. What did you expect to see?



3. What did you see instead?



4. What version of are you using (`soar -version`)?




================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature Request
about: If you have something you think SOAR could improve or add support for.

---

Please search the existing issues for relevant feature requests,  add upvotes to pre-existing requests.

#### Feature Description

A written overview of the feature.

#### Use Case(s)

Any relevant use-cases that you see.


================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: If you have a question, please check out our other community resources instead of opening an issue.

---

Issues on GitHub are intended to be related to bugs or feature requests, so we recommend using our other community resources instead of asking here.

- [SOAR Doc](http://github.com/XiaoMi/soar/blob/master/README.md)
- Any other questions can be asked in the community [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar)


================================================
FILE: .github/pull_request_template.md
================================================
<!--
Thank you for contributing to SOAR! Please read SOAR's [CONTRIBUTING](https://github.com/XiaoMi/soar/blob/master/CONTRIBUTING.md) document **BEFORE** filing this PR.
-->

### What problem does this PR solve? <!--add issue link with summary if exists-->


### What is changed and how it works?


### Check List <!--REMOVE the items that are not applicable-->

Tests <!-- At least one of them must be included. -->

 - Unit test
 - Integration test
 - Manual test (add detailed scripts or steps below)
 - No code

Code changes

 - Has exported function/method change
 - Has exported variable/fields change
 - Has interface methods change
 - Has persistent data change

Side effects

 - Possible performance regression
 - Increased code complexity
 - Breaking backward compatibility


================================================
FILE: .gitignore
================================================
bin/
release/
test/tmp/
doc/blueprint/
*.iml
*.swp
*.log
coverage.*
y.output

.DS_Store
.vscode/
.idea
_tools/

TestMarkdown2Html.html


================================================
FILE: .travis.yml
================================================
language: go

go:
  - "1.12.x"
  - "1.13.x"

sudo: required

go_import_path: github.com/XiaoMi/soar

dist: xenial

services:
  - docker

before_install:
  - docker pull mysql
  - sudo add-apt-repository ppa:duggan/bats --yes
  - sudo apt-get update -qq
  - sudo apt-get install -qq bats

script:
  - make build
  - make docker
  - make cover
  - make test-cli


================================================
FILE: CHANGES.md
================================================
# CHANGELOG

## 2019-08
- Fix RuleImplicitConversion(ARG.003) with INT and DECIMAL
- Fix RuleImplicitConversion duplicate suggest when use IN () operator

## 2019-07
- Fix #213 CLA.001 NO WHERE CONDITION
- Fix PRIMARY key append to multi column index
- fingerprint verbose mode add id

## 2019-05
- Fix issue #208 14c19f4 regression bug
- Add max_execution_time hint for explain query
- Fix #205 create index rewrite error

## 2019-04
- Add test case for STA.004
- RuleSpaceWithQuote add list range check
- Fix #199 -report-type=json add score
- Fix #98 JSON result format
- Fix index col compare case sensitive bug
- Fix ARG.008 cases: col = 1 OR col IS NULL
- Fix tokenize bug with multi type of quote

## 2019-02
- add go.mod for go1.11
- add new -report-type query-type
- add new heuristic rule SEC.004
- fix #196 wrong ip/password will cause soar -check-config hangup

## 2019-01

- add mysql environment verbose info
- add JSONFind function, which support JSON iterate
- add new test database `world_x`
- SplitStatement support optimizer hint `/*+xxx */`
- include [bats](https://github.com/bats-core/bats-core) bash auto test framework
- fix #173 with JSONFind `WHERE col = col = '' and col1 = 'xx'`
- fix #184 table status field datatype overflow
- fix explain result with multi rows error
- fix #178 JSON datatype only support utf8mb4

## 2018-12

- replace mysql database driver mymysql with go-sql-driver
- add new -report-type [ast-json, tiast-json]
- command line dsn args support '@', '/', ':' in password
- add new heuristic rule RES.009, "SELECT * FROM tbl WHERE col = col = 'abc'"
- add new heuristic rule RuleColumnNotAllowType COL.018
- add string escape function for security
- fix #122 single table select * don't auto-complete table name
- fix #171 support socket access type
- fix #58 sampling not deal with NULL able string
- fix #172 compatible with mysql 5.1, which explain has no Index_Comment column
- fix #163 column.Tp may be nil, which may raise panic
- fix #151 bit type not config as int, when two columns compare will give ARG.003 suggestion.
- 
## 2018-11

- add all third-party lib into vendor
- support `-report-type chardet`
- add more heuristic rules: TBL.008, KEY.010, ARG.012, KWR.004
- add -cleanup-test-database command-line arg
- add -check-config parameter
- fix #146 pretty cause syntax error
- fix #140 COL.012, COL.015 NULL type about TEXT/BLOB
- fix #141 empty output when query execute failed on mysql
- fix #89 index advisor give wrong database name, `optimizer_xx`
- fix #121 RemoveSQLComment trim space
- fix #120 trimspace before check single line comment
- fix mac os stdout print buffer truncate
- fix -config arg load file error
- fix #116 SplitStatement check if single comment line is in multi-line sql.
- fix #112 multi-line comment will cause line counter error, when -report-type=lint
- fix #110 remove bom before auditing
- fix #104 case insensitive regex @ CLA.009
- fix #87 RuleImplicitConversion value type mismatch check bug
- fix #38 always true where condition check
- abandon stdin terminal interactive mod, which may seems like hangup

## 2018-10

- Fix SplitStatement multistatement eof bug #66
- Fix pretty func hangup issue #47
- Fix some foolish code spell error
- Use travis for CI
- Fix Go 1.8 default GOPATH compatible issue BUG #5
- 2018-10-20 开源先锋日(OSCAR)对外正式开源发布代码

## 2018-09

- 修复多个启发式建议不准确BUG,优化部分建议文案使得建议更清晰
- 基于 TiDB Parser 完善多个 DDL 类型语句的建议
- 新增lint report-type类型,支持Vim Plugin优化建议输出
- 更新整理项目文档,开源准备
- 2018-09-21 Gdevops SOAR首次对外进行技术分享宣传

## 2018-08

- 利用 docker 临时容器进行 daily 测试
- 添加main_test全功能回归测试
- 修复在测试中发现的问题
- mymysql 合并 MySQL8.0 相关PR,修改vendor依赖
- 改善HeuristicRule中的文案
- 持续集成Vitess Parser的改进
- NewQuery4Audit 结构体中引入 TiDB Parser
- 通过TiAST完成大量与 DDL 相关的TODO
- 修改heuristic rules检查的返回值,提升拓展性
- 建议中引入Position,用于表示建议产生于SQL的位置
- 新增多个HeuristicRule
- Makefile中添加依赖检查,优化Makefile中逻辑,添加新功能
- 优化gometalinter性能,引入新的代码质量检测工具,提升代码质量
- 引入 retool 用于管理依赖的工具
- 优化 doc 文档

## 2018-07

- 补充文档,添加项目LOGO
- 改善代码质量提升测试覆盖度
- mymysql升级,支持MySQL 8.0
- 提供remove-comment小工具
- 提供索引重复检查小工具
- HeuristicRule 新增 RuleSpaceAfterDot
- 支持字符集和Collation不相同时的隐式数据类型转换的检查

## 2018-06

- 支持更多的SQL Rewrite规则
- 添加SQL执行超时限制
- 索引优化建议支持对约束的检查
- 修复数据采样中 NULL 值处理不正确的问题
- Explain 支持 last_query_cost

## 2018-05

- 添加数据采样功能
- 添加语句执行安全检查
- 支持DDL语法检查
- 支持DDL在测试环境的执行
- 支持隐式数据类型转换检查
- 支持索引去重
- 索引优化建议支持前缀索引
- 支持SQL Pretty输出

## 2018-04

- 支持语法检查
- 支持测试环境
- 支持MySQL原数据的获取
- 支持基于数据库环境信息给予索引优化建议
- 支持不依赖数据库原信息的简单索引优化建议
- 添加日志模块
- 引入配置文件

## 2018-03

- 基本架构设计
- 添加大量底层函数用于处理AST
- 添加Insert、Delete、Update 转写成 Select 的基本函数
- 支持MySQL Explain信息输出


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at zhangliang3@xiaomi.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: CONTRIBUTING.md
================================================
Ask questions at [Gitter](https://gitter.im/xiaomi-dba/soar).

[Open an issue](https://github.com/xiaomi/soar/issues/new) to discuss your plans before doing any work on SOAR.


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: Makefile
================================================
# This how we want to name the binary output
#
# use checkmake linter https://github.com/mrtazz/checkmake
# $ checkmake Makefile
#
BINARY=soar
GOPATH ?= $(shell go env GOPATH)
GO111MODULE:=auto
export GO111MODULE
# Ensure GOPATH is set before running build process.
ifeq "$(GOPATH)" ""
  $(error Please set the environment variable GOPATH before running `make`)
endif
PATH := ${GOPATH}/bin:$(PATH)
GCFLAGS=-gcflags "all=-trimpath=${GOPATH}"
VERSION_TAG := $(shell git describe --tags --always)
VERSION_VERSION := $(shell git log --date=iso --pretty=format:"%cd" -1) $(VERSION_TAG)
VERSION_COMPILE := $(shell date +"%F %T %z") by $(shell go version)
VERSION_BRANCH  := $(shell git rev-parse --abbrev-ref HEAD)
VERSION_GIT_DIRTY := $(shell git diff --no-ext-diff 2>/dev/null | wc -l | awk '{print $1}')
VERSION_DEV_PATH:= $(shell pwd)
LDFLAGS=-ldflags="-s -w -X 'github.com/XiaoMi/soar/common.Version=$(VERSION_VERSION)' -X 'github.com/XiaoMi/soar/common.Compile=$(VERSION_COMPILE)' -X 'github.com/XiaoMi/soar/common.Branch=$(VERSION_BRANCH)' -X 'github.com/XiaoMi/soar/common.GitDirty=$(VERSION_GIT_DIRTY)' -X 'github.com/XiaoMi/soar/common.DevPath=$(VERSION_DEV_PATH)'"

# These are the values we want to pass for VERSION  and BUILD
BUILD_TIME=`date +%Y%m%d%H%M`
COMMIT_VERSION=`git rev-parse HEAD`

# colors compatible setting
CRED:=$(shell tput setaf 1 2>/dev/null)
CGREEN:=$(shell tput setaf 2 2>/dev/null)
CYELLOW:=$(shell tput setaf 3 2>/dev/null)
CEND:=$(shell tput sgr0 2>/dev/null)

# Add mysql version for testing `MYSQL_RELEASE=percona MYSQL_VERSION=5.7 make docker`
# MySQL 5.1 `MYSQL_RELEASE=vsamov/mysql-5.1.73 make docker`
# MYSQL_RELEASE: mysql, percona, mariadb ...
# MYSQL_VERSION: latest, 8.0, 5.7, 5.6, 5.5 ...
# use mysql:latest as default
MYSQL_RELEASE := $(or ${MYSQL_RELEASE}, ${MYSQL_RELEASE}, mysql)
MYSQL_VERSION := $(or ${MYSQL_VERSION}, ${MYSQL_VERSION}, latest)

.PHONY: all
all: | fmt build

.PHONY: go_version_check
GO_VERSION_MIN=1.12
# Parse out the x.y or x.y.z version and output a single value x*10000+y*100+z (e.g., 1.9 is 10900)
# that allows the three components to be checked in a single comparison.
VER_TO_INT:=awk '{split(substr($$0, match ($$0, /[0-9\.]+/)), a, "."); print a[1]*10000+a[2]*100+a[3]}'
go_version_check:
	@echo "$(CGREEN)Go version check ...$(CEND)"
	@if test $(shell go version | $(VER_TO_INT) ) -lt \
  	$(shell echo "$(GO_VERSION_MIN)" | $(VER_TO_INT)); \
  	then printf "go version $(GO_VERSION_MIN)+ required, found: "; go version; exit 1; \
		else echo "go version check pass";	fi

# Dependency check
.PHONY: deps
deps:
	@echo "$(CGREEN)Dependency check ...$(CEND)"
	@bash ./deps.sh
	# The retool tools.json is setup from retool-install.sh
	# some packages download need more open internet access
	retool sync
	#retool do gometalinter.v2 --install

# Code format
.PHONY: fmt
fmt: go_version_check
	@echo "$(CGREEN)Run gofmt on all source files ...$(CEND)"
	@echo "gofmt -l -s -w ..."
	@ret=0 && for d in $$(go list -f '{{.Dir}}' ./... | grep -v /vendor/); do \
		gofmt -l -s -w $$d/*.go || ret=$$? ; \
	done ; exit $$ret

# Run golang test cases
.PHONY: test
test:
	@echo "$(CGREEN)Run all test cases ...$(CEND)"
	@go test $(LDFLAGS) -timeout 10m -race ./...
	@echo "test Success!"

# Rule golang test cases with `-update` flag
.PHONY: test-update
test-update:
	@echo "$(CGREEN)Run all test cases with -update flag ...$(CEND)"
	@go test $(LDFLAGS) ./... -update
	@echo "test-update Success!"

# Using bats test framework run all cli test cases
# https://github.com/sstephenson/bats
.PHONY: test-cli
test-cli: build
	@echo "$(CGREEN)Run all cli test cases ...$(CEND)"
	bats ./test
	@echo "test-cli Success!"

# Code Coverage
# colorful coverage numerical >=90% GREEN, <80% RED, Other YELLOW
.PHONY: cover
cover: test
	@echo "$(CGREEN)Run test cover check ...$(CEND)"
	@go test $(LDFLAGS) -coverpkg=./... -coverprofile=coverage.data ./... | column -t
	@go tool cover -html=coverage.data -o coverage.html
	@go tool cover -func=coverage.data -o coverage.txt
	@tail -n 1 coverage.txt | awk '{sub(/%/, "", $$NF); \
		if($$NF < 80) \
			{print "$(CRED)"$$0"%$(CEND)"} \
		else if ($$NF >= 90) \
			{print "$(CGREEN)"$$0"%$(CEND)"} \
		else \
			{print "$(CYELLOW)"$$0"%$(CEND)"}}'

# Builds the project
build: fmt
	@echo "$(CGREEN)Building ...$(CEND)"
	@mkdir -p bin
	@ret=0 && for d in $$(go list -f '{{if (eq .Name "main")}}{{.ImportPath}}{{end}}' ./...); do \
		b=$$(basename $${d}) ; \
		go build ${LDFLAGS} ${GCFLAGS} -o bin/$${b} $$d || ret=$$? ; \
	done ; exit $$ret
	@echo "build Success!"

# Installs our project: copies binaries
install: build
	@echo "$(CGREEN)Install ...$(CEND)"
	go install ./...
	@echo "install Success!"

# Generate doc use -list* command
.PHONY: doc
doc: build
	@echo "$(CGREEN)Auto generate doc ...$(CEND)"
	./bin/soar -list-heuristic-rules > doc/heuristic.md
	./bin/soar -list-rewrite-rules > doc/rewrite.md
	./bin/soar -list-report-types > doc/report_type.md

# Add or change a heuristic rule
.PHONY: heuristic
heuristic: doc
	@echo "$(CGREEN)Update Heuristic rule golden files ...$(CEND)"
	go test github.com/XiaoMi/soar/advisor -v -update -run TestListHeuristicRules
	go test github.com/XiaoMi/soar/advisor -v -update -run TestMergeConflictHeuristicRules
	docker stop soar-mysql 2>/dev/null || true

# Update all vendor
.PHONY: vendor
vendor: vitess pingcap-parser
# gometalinter
.PHONY: lint
lint: build
	@echo "$(CGREEN)Run linter check ...$(CEND)"
	CGO_ENABLED=0 GOMODULE111=off retool do gometalinter.v2 -j 1 --config doc/example/metalinter.json ./...
	GOMODULE111=off retool do revive -formatter friendly --exclude vendor/... -config doc/example/revive.toml ./...
	GOMODULE111=off retool do golangci-lint --tests=false run
	@echo "gometalinter check your code is pretty good"

.PHONY: release
release: build
	@echo "$(CGREEN)Cross platform building for release ...$(CEND)"
	@mkdir -p release
	@for GOOS in linux windows; do \
		for GOARCH in amd64; do \
			for d in $$(go list -f '{{if (eq .Name "main")}}{{.ImportPath}}{{end}}' ./...); do \
				b=$$(basename $${d}) ; \
				echo "Building $${b}.$${GOOS}-$${GOARCH} ..."; \
				CGO_ENABLED=0 GOOS=$${GOOS} GOARCH=$${GOARCH} go build ${GCFLAGS} ${LDFLAGS} -v -o release/$${b}.$${GOOS}-$${GOARCH} $$d 2>/dev/null ; \
			done ; \
		done ;\
	done
	@for GOOS in darwin; do \
		for GOARCH in arm64 amd64; do \
			for d in $$(go list -f '{{if (eq .Name "main")}}{{.ImportPath}}{{end}}' ./...); do \
				b=$$(basename $${d}) ; \
				echo "Building $${b}.$${GOOS}-$${GOARCH} ..."; \
				CGO_ENABLED=0 GOOS=$${GOOS} GOARCH=$${GOARCH} go build ${GCFLAGS} ${LDFLAGS} -v -o release/$${b}.$${GOOS}-$${GOARCH} $$d 2>/dev/null ; \
			done ; \
		done ;\
	done

.PHONY: docker
docker:
	@echo "$(CGREEN)Build mysql test environment ...$(CEND)"
	@docker stop soar-mysql 2>/dev/null || true
	@docker wait soar-mysql 2>/dev/null >/dev/null || true
	@echo "docker run --name soar-mysql $(MYSQL_RELEASE):$(MYSQL_VERSION)"
	@docker run --name soar-mysql --rm -d \
	-e MYSQL_ROOT_PASSWORD=1tIsB1g3rt \
	-e MYSQL_DATABASE=sakila \
	-p 3306:3306 \
	-v `pwd`/test/sql/init.sql.gz:/docker-entrypoint-initdb.d/init.sql.gz \
	$(MYSQL_RELEASE):$(MYSQL_VERSION) \
	--sql-mode ""

	@echo "waiting for sakila database initializing "
	@timeout=180; while [ $${timeout} -gt 0 ] ; do \
		if ! docker exec soar-mysql mysql --user=root --password=1tIsB1g3rt --host "127.0.0.1" --silent -NBe "do 1" >/dev/null 2>&1 ; then \
			timeout=`expr $$timeout - 1`; \
			printf '.' ;  sleep 1 ; \
		else \
			echo "." ; echo "mysql test environment is ready!" ; break ; \
		fi ; \
		if [ $$timeout = 0 ] ; then \
			echo "." ; echo "$(CRED)docker soar-mysql start timeout(180 s)!$(CEND)" ; exit 1 ; \
		fi ; \
	done

.PHONY: docker-connect
docker-connect:
	@docker exec -it soar-mysql mysql --user=root --password=1tIsB1g3rt --host "127.0.0.1" sakila

# attach docker container with bash interactive mode
.PHONY: docker-it
docker-it:
	docker exec -it soar-mysql /bin/bash

.PHONY: daily
daily: | deps fmt vendor docker cover doc lint release install test-cli clean logo
	@echo "$(CGREEN)daily build finished ...$(CEND)"

# vendor, docker will cost long time, if all those are ready, daily-quick will much more fast.
.PHONY: daily-quick
daily-quick: | deps fmt cover test-cli doc lint logo
	@echo "$(CGREEN)daily-quick build finished ...$(CEND)"

.PHONY: logo
logo:
	@echo "$(CYELLOW)"
	@cat doc/images/logo.ascii
	@echo "$(CEND)"

# Cleans our projects: deletes binaries
.PHONY: clean
clean:
	@echo "$(CGREEN)Cleanup ...$(CEND)"
	go clean
	@for GOOS in darwin linux windows; do \
	    for GOARCH in 386 amd64; do \
			rm -f ${BINARY}.$${GOOS}-$${GOARCH} ;\
		done ;\
	done
	rm -f ${BINARY} coverage.* test/tmp/*
	find . -name "*.log" -delete
	git clean -fi
	docker stop soar-mysql 2>/dev/null || true


================================================
FILE: NOTICE.txt
================================================

Copyright 2018 Xiaomi, Inc.  All Rights Reserved.
This product includes software developed by Xiaomi, Inc.
(http://www.mi.com/).
This product is licensed to you under the Apache License, Version 2.0
(the "License").  You may not use this product except in compliance with
the License.



================================================
FILE: README.md
================================================
# ![SOAR](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/logo.png)

[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://github.com/XiaoMi/soar/blob/master/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/XiaoMi/soar)](https://goreportcard.com/report/github.com/XiaoMi/soar)
[![Build Status](https://travis-ci.org/XiaoMi/soar.svg?branch=master)](https://travis-ci.org/XiaoMi/soar)
[![GoDoc](https://godoc.org/github.com/XiaoMi/soar?status.svg)](https://godoc.org/github.com/XiaoMi/soar)

[文档](http://github.com/XiaoMi/soar/tree/master/doc) | [FAQ](http://github.com/XiaoMi/soar/blob/master/doc/FAQ.md) | [变更记录](http://github.com/XiaoMi/soar/blob/master/CHANGES.md) | [路线图](http://github.com/XiaoMi/soar/blob/master/doc/roadmap.md) | [English](http://github.com/XiaoMi/soar/blob/master/README_EN.md)

## SOAR

SOAR(SQL Optimizer And Rewriter) 是一个对 SQL 进行优化和改写的自动化工具。 由小米人工智能与云平台的数据库团队开发与维护。

## 功能特点

* 跨平台支持(支持 Linux, Mac 环境,Windows 环境理论上也支持,不过未全面测试)
* 目前只支持 MySQL 语法族协议的 SQL 优化
* 支持基于启发式算法的语句优化
* 支持复杂查询的多列索引优化(UPDATE, INSERT, DELETE, SELECT)
* 支持 EXPLAIN 信息丰富解读
* 支持 SQL 指纹、压缩和美化
* 支持同一张表多条 ALTER 请求合并
* 支持自定义规则的 SQL 改写

## 快速入门

* [安装使用](http://github.com/XiaoMi/soar/blob/master/doc/install.md)
* [体系架构](http://github.com/XiaoMi/soar/blob/master/doc/structure.md)
* [配置文件](http://github.com/XiaoMi/soar/blob/master/doc/config.md)
* [常用命令](http://github.com/XiaoMi/soar/blob/master/doc/cheatsheet.md)
* [产品对比](http://github.com/XiaoMi/soar/blob/master/doc/comparison.md)
* [路线图](http://github.com/XiaoMi/soar/blob/master/doc/roadmap.md)

## 交流与反馈

* 欢迎通过 Github Issues 提交问题报告与建议
* QQ 群:779359816(未满) 758940447(已满)
* [Gitter](https://gitter.im/xiaomi-dba/soar) 推荐

 ![xiaomi_sa](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/xiaomi_sa.png)

## License

[Apache License 2.0](https://github.com/XiaoMi/soar/blob/master/LICENSE).


================================================
FILE: README_EN.md
================================================
# ![SOAR](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/logo.png)

[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://github.com/XiaoMi/soar/blob/master/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/XiaoMi/soar)](https://goreportcard.com/report/github.com/XiaoMi/soar)
[![Build Status](https://travis-ci.org/XiaoMi/soar.svg?branch=master)](https://travis-ci.org/XiaoMi/soar)
[![GoDoc](https://godoc.org/github.com/XiaoMi/soar?status.svg)](https://godoc.org/github.com/XiaoMi/soar)

[Docs](http://github.com/XiaoMi/soar/tree/master/doc) | [FAQ](http://github.com/XiaoMi/soar/blob/master/doc/FAQ_en.md) | [中文](http://github.com/XiaoMi/soar/blob/master/README.md)

## SOAR

SOAR (SQL Optimizer And Rewriter) is a tool, which can help SQL optimization and rewrite. It's developed and maintained by the DBA Team of Xiaomi AI&Cloud.

## Features

* Cross-platform support, such as Linux, Mac, and Windows
* Support Heuristic Rules Suggestion
* Support Complicate SQL Indexing Optimize
* Support EXPLAIN analyze for query plan
* Support SQL fingerprint, compress and built-in pretty print
* Support merge multi ALTER query into one SQL
* Support self-config rewrite rules from SQL Rewrite
* Suggestions were written in Chinese. But SOAR also gives many tools, which can be used without understanding Chinese.

## QuickStart

* [Install](http://github.com/XiaoMi/soar/blob/master/doc/install_en.md)
* [CheatSheet](http://github.com/XiaoMi/soar/blob/master/doc/cheatsheet_en.md)
* [Related works](http://github.com/XiaoMi/soar/blob/master/doc/comparison_en.md)

## Communication

* GitHub issues: bug reports, usage issues, feature requests
* [Gitter](https://gitter.im/xiaomi-dba/soar)
* IM QQ Group: 779359816

## License

[Apache License 2.0](https://github.com/XiaoMi/soar/blob/master/LICENSE).


================================================
FILE: VERSION
================================================
0.11.0


================================================
FILE: advisor/doc.go
================================================
/*
 * Copyright 2018 Xiaomi, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Package advisor contain heuristic rules, index rules and explain translator.
package advisor


================================================
FILE: advisor/explainer.go
================================================
/*
 * Copyright 2018 Xiaomi, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package advisor

import (
	"fmt"
	"strings"

	"github.com/XiaoMi/soar/common"
	"github.com/XiaoMi/soar/database"
)

var explainRuleID int

// [EXP.XXX]Rule
var explainRules map[string]Rule

// [table_name]"suggest text"
var tablesSuggests map[string][]string

// explain建议的形式
// Item: EXP.XXX
// Severity: L[0-8]
// Summary: full table scan, not use index, full index scan...
// Content: XX TABLE xxx

// checkExplainSelectType
func checkExplainSelectType(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if len(common.Config.ExplainWarnSelectType) == 1 {
		if common.Config.ExplainWarnSelectType[0] == "" {
			return
		}
	} else if len(common.Config.ExplainWarnSelectType) < 1 {
		return
	}

	if exp.ExplainFormat == database.JSONFormatExplain {
		// TODO
		// JSON 形式遍历分析不方便,转成 Row 格式也没有 SelectType 暂不处理
		return
	}
	for _, v := range common.Config.ExplainWarnSelectType {
		for _, row := range exp.ExplainRows {
			if row.SelectType == v && v != "" {
				tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("SelectType:%s", row.SelectType))
			}
		}
	}
}

// checkExplainAccessType 用户可以设置AccessType的建议级别,匹配到的查询会给出建议
func checkExplainAccessType(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if len(common.Config.ExplainWarnAccessType) == 1 {
		if common.Config.ExplainWarnAccessType[0] == "" {
			return
		}
	} else if len(common.Config.ExplainWarnAccessType) < 1 {
		return
	}

	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便,转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}
	for _, v := range common.Config.ExplainWarnAccessType {
		for _, row := range rows {
			if row.AccessType == v && v != "" {
				tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Scalability:%s", row.Scalability))
			}
		}
	}
}

/*
// TODO:
func checkExplainPossibleKeys(exp *database.ExplainInfo) {
}

func checkExplainKeyLen(exp *database.ExplainInfo) {
}

func checkExplainKey(exp *database.ExplainInfo) {
	// 小于最小使用试用key数量
	//return intval($explainResult) < intval($userCond);
	//explain-min-keys int
}

func checkExplainExtra(exp *database.ExplainInfo) {
	// 包含用户配置的逗号分隔关键词之一则提醒
	// return self::contains($explainResult, $userCond);
	// explain-warn-extra []string
}
*/

// checkExplainRef ...
func checkExplainRef(exp *database.ExplainInfo) {
	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便,转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}
	for i, row := range rows {
		if strings.Join(row.Ref, "") == "NULL" || strings.Join(row.Ref, "") == "" {
			if i == 0 && len(rows) > 1 {
				continue
			}
			tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Ref:null"))
		}
	}
}

// checkExplainRows ...
func checkExplainRows(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if common.Config.ExplainMaxRows <= 0 {
		return
	}

	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便,转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}

	for _, row := range rows {
		if row.Rows >= common.Config.ExplainMaxRows {
			tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Rows:%d", row.Rows))
		}
	}
}

// checkExplainFiltered ...
func checkExplainFiltered(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if common.Config.ExplainMaxFiltered <= 0.001 {
		return
	}

	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便,转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}
	for i, row := range rows {
		if i == 0 && len(rows) > 1 {
			continue
		}
		if row.Filtered >= common.Config.ExplainMaxFiltered {
			tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Filtered:%.2f%s", row.Filtered, "%"))
		}
	}
}

// ExplainAdvisor 基于explain信息给出建议
func ExplainAdvisor(exp *database.ExplainInfo) map[string]Rule {
	common.Log.Debug("ExplainAdvisor SQL: %v", exp.SQL)
	explainRuleID = 0
	explainRules = make(map[string]Rule)
	tablesSuggests = make(map[string][]string)

	checkExplainSelectType(exp)
	checkExplainAccessType(exp)
	checkExplainFiltered(exp)
	checkExplainRef(exp)
	checkExplainRows(exp)

	// 打印explain table
	content := database.PrintMarkdownExplainTable(exp)

	if common.Config.ShowWarnings {
		content += "\n" + database.MySQLExplainWarnings(exp)
	}

	// 对explain table中各项难于理解的值做解释
	cases := database.ExplainInfoTranslator(exp)

	// 添加last_query_cost
	if common.Config.ShowLastQueryCost {
		content += "\n" + database.MySQLExplainQueryCost(exp)
	}

	if content != "" {
		explainRules["EXP.000"] = Rule{
			Item:     "EXP.000",
			Severity: "L0",
			Summary:  "Explain信息",
			Content:  content,
			Case:     cases,
			Func:     (*Query4Audit).RuleOK,
		}
	}
	// TODO: 检查explain对应的表是否需要跳过,如dual,空表等
	return explainRules
}

// DigestExplainText 分析用户输入的EXPLAIN信息
func DigestExplainText(text string) {
	// explain信息就不要显示完美了,美不美自己看吧。
	common.Config.IgnoreRules = append(common.Config.IgnoreRules, "OK")

	if !IsIgnoreRule("EXP.") {
		explainInfo, err := database.ParseExplainText(text)
		if err != nil {
			common.Log.Error("main ParseExplainText Error: %v", err)
			return
		}
		expSuggest := ExplainAdvisor(explainInfo)
		_, output := FormatSuggest("", "", common.Config.ReportType, expSuggest)
		if common.Config.ReportType == "html" {
			fmt.Println(common.MarkdownHTMLHeader())
			fmt.Println(common.Markdown2HTML(output))
		} else {
			fmt.Println(output)
		}
	}
}


================================================
FILE: advisor/explainer_test.go
================================================
/*
 * Copyright 2018 Xiaomi, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package advisor

import (
	"testing"

	"github.com/XiaoMi/soar/common"
)

func TestDigestExplainText(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	var text = `+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+
| id | select_type | table   | type  | possible_keys                                           | key               | key_len | ref                       | rows | Extra       |
+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+
|  1 | SIMPLE      | country | index | PRIMARY,country_id                                      | country           | 152     | NULL                      |  109 | Using index |
|  1 | SIMPLE      | city    | ref   | idx_fk_country_id,idx_country_id_city,idx_all,idx_other | idx_fk_country_id | 2       | sakila.country.country_id |    2 | Using index |
+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+`
	common.Config.ReportType = "explain-digest"
	err := common.GoldenDiff(func() {
		DigestExplainText(text)
		orgReportType := common.Config.ReportType
		common.Config.ReportType = "html"
		DigestExplainText(text)
		common.Config.ReportType = orgReportType
	}, t.Name(), update)
	if nil != err {
		t.Fatal(err)
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}


================================================
FILE: advisor/heuristic.go
================================================
/*
 * Copyright 2018 Xiaomi, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package advisor

import (
	"bytes"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"unicode/utf8"

	"github.com/XiaoMi/soar/ast"
	"github.com/XiaoMi/soar/common"
	"github.com/XiaoMi/soar/database"

	"github.com/gedex/inflector"
	"github.com/percona/go-mysql/query"
	tidb "github.com/pingcap/parser/ast"
	"github.com/pingcap/parser/format"
	"github.com/pingcap/parser/mysql"
	"github.com/tidwall/gjson"
	"vitess.io/vitess/go/vt/sqlparser"
)

// RuleOK OK
func (q *Query4Audit) RuleOK() Rule {
	return HeuristicRules["OK"]
}

// RuleImplicitAlias ALI.001
func (q *Query4Audit) RuleImplicitAlias() Rule {
	var rule = q.RuleOK()
	tkns := ast.Tokenizer(q.Query)
	if len(tkns) == 0 {
		return rule
	}
	if tkns[0].Type != sqlparser.SELECT {
		return rule
	}
	for i, tkn := range tkns {
		if tkn.Type == sqlparser.ID && i+1 < len(tkns) && tkn.Type == tkns[i+1].Type {
			rule = HeuristicRules["ALI.001"]
			break
		}
	}
	return rule
}

// RuleStarAlias ALI.002
func (q *Query4Audit) RuleStarAlias() Rule {
	var rule = q.RuleOK()
	tkns := ast.Tokenizer(q.Query)
	for i, tkn := range tkns {
		if strings.HasSuffix(tkn.Val, "*") && i+1 < len(tkns) && strings.ToLower(tkns[i+1].Val) == "as" {
			rule = HeuristicRules["ALI.002"]
		}
	}
	return rule
}

// RuleSameAlias ALI.003
func (q *Query4Audit) RuleSameAlias() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch expr := node.(type) {
		case *sqlparser.AliasedExpr:
			switch n := expr.Expr.(type) {
			case *sqlparser.ColName:
				if n.Name.String() == expr.As.String() {
					rule = HeuristicRules["ALI.003"]
					return false, nil
				}
			}
		case *sqlparser.AliasedTableExpr:
			switch n := expr.Expr.(type) {
			case sqlparser.TableName:
				if n.Name.String() == expr.As.String() {
					rule = HeuristicRules["ALI.003"]
					return false, nil
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RulePrefixLike ARG.001
func (q *Query4Audit) RulePrefixLike() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch expr := node.(type) {
		case *sqlparser.ComparisonExpr:
			if strings.ToLower(expr.Operator) == "like" {
				switch sqlval := expr.Right.(type) {
				case *sqlparser.SQLVal:
					// prefix like with '%', '_'
					if sqlval.Type == 0 && (sqlval.Val[0] == 0x25 || sqlval.Val[0] == 0x5f) {
						rule = HeuristicRules["ARG.001"]
						return false, nil
					}
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleEqualLike ARG.002
func (q *Query4Audit) RuleEqualLike() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch expr := node.(type) {
		case *sqlparser.ComparisonExpr:
			if strings.ToLower(expr.Operator) == "like" {
				switch sqlval := expr.Right.(type) {
				case *sqlparser.SQLVal:
					// 1. string that not contain '%', '_'
					// 2. int, bit, float without wildcard
					var hasWildCard bool
					if sqlval.Type == 0 {
						for _, sqlElem := range sqlval.Val {
							if sqlElem == 0x25 || sqlElem == 0x5f {
								hasWildCard = true
							}
						}
					}
					if !hasWildCard {
						rule = HeuristicRules["ARG.002"]
						return false, nil
					}
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleImplicitConversion ARG.003
// 隐式类型转换检查:该项检查一定是在开启测试环境或线上环境情境下下进行的
func (idxAdv *IndexAdvisor) RuleImplicitConversion() Rule {
	/*
	* 两个参数至少有一个是 NULL 时,比较的结果也是 NULL,例外是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类型转换
	* 两个参数都是字符串,会按照字符串来比较,不做类型转换
	* 两个参数都是整数,按照整数来比较,不做类型转换
	* 十六进制的值和非数字做比较时,会被当做二进制串
	* 有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为 timestamp
	* 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较
	* 所有其他情况下,两个参数都会被转换为浮点数再进行比较
	 */
	rule := HeuristicRules["OK"]
	// 未开启测试环境不进行检查
	if common.Config.TestDSN.Disable {
		return rule
	}

	var content []string
	conditions := ast.FindAllCondition(idxAdv.Ast)
	for _, cond := range conditions {
		var colList []*common.Column
		var values []*sqlparser.SQLVal

		// condition 左右两侧有且只有如下几种可能:
		// 1. 列与列比较,如: col1 = col2
		// 2. 列与值比较,如: col = val
		// 3. 值与值比较,如: val1 = val2 暂不处理
		// 如果列包含在一个函数中,认为这个条件为值,如: col = func(col) 认定为 列与值比较
		switch node := cond.(type) {
		case *sqlparser.ComparisonExpr:
			// 获取 condition 左侧的信息
			switch nLeft := node.Left.(type) {
			case *sqlparser.SQLVal, sqlparser.ValTuple:
				err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
					switch val := node.(type) {
					case *sqlparser.SQLVal:
						values = append(values, val)
					}
					return true, nil
				}, nLeft)
				common.LogIfError(err, "")

			case *sqlparser.ColName:
				left := &common.Column{Name: nLeft.Name.String()}
				if !nLeft.Qualifier.Name.IsEmpty() {
					left.Table = nLeft.Qualifier.Name.String()
				}
				colList = append(colList, left)
			}

			// 获取 condition 右侧的信息
			switch nRight := node.Right.(type) {
			case *sqlparser.SQLVal, sqlparser.ValTuple:
				err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
					switch val := node.(type) {
					case *sqlparser.SQLVal:
						values = append(values, val)
					}
					return true, nil
				}, nRight)
				common.LogIfError(err, "")

			case *sqlparser.ColName:
				right := &common.Column{Name: nRight.Name.String()}
				if !nRight.Qualifier.Name.IsEmpty() {
					right.Table = nRight.Qualifier.Name.String()
				}
				colList = append(colList, right)
			}

			if len(colList) == 0 {
				continue
			}

			// 补全列信息
			colList = CompleteColumnsInfo(idxAdv.Ast, colList, idxAdv.vEnv)

			// 列与列比较
			if len(colList) == 2 {
				// 列信息补全后如果依然没有表信息,说明在该数据库中不存在该列
				// 如果列信息获取异常,可能会存在无法获取到数据类型的情况,对于这种情况将不会给予建议。
				needBreak := false
				for _, col := range colList {
					if col.Table == "" {
						common.Log.Warning("Column %s not exists", col.Name)
						needBreak = true
					}

					if col.DataType == "" {
						common.Log.Warning("Can't get column %s data type", col.Name)
						needBreak = true
					}

				}

				if needBreak {
					break
				}

				// 检查数据类型不一致导致的隐式数据转换
				type1 := common.GetDataTypeBase(colList[0].DataType)
				type2 := common.GetDataTypeBase(colList[1].DataType)
				common.Log.Debug("DataType: `%s`.`%s` (%s) VS `%s`.`%s` (%s)",
					colList[0].Table, colList[0].Name, type1,
					colList[1].Table, colList[1].Name, type2)
				// case-insensitive check type1, type2
				if !strings.EqualFold(type1, type2) {
					content = append(content, fmt.Sprintf("`%s`.`%s` (%s) VS `%s`.`%s` (%s) datatype not match",
						colList[0].Table, colList[0].Name, type1,
						colList[1].Table, colList[1].Name, type2))
					continue
				}

				// 检查字符集不一致导致的隐式数据转换
				common.Log.Debug("Charset: `%s`.`%s` (%s) VS `%s`.`%s` (%s)",
					colList[0].Table, colList[0].Name, colList[0].Character,
					colList[1].Table, colList[1].Name, colList[1].Character)
				if colList[0].Character != colList[1].Character {
					content = append(content, fmt.Sprintf("`%s`.`%s` (%s) VS `%s`.`%s` (%s) charset not match",
						colList[0].Table, colList[0].Name, colList[0].Character,
						colList[1].Table, colList[1].Name, colList[1].Character))
					continue
				}

				// 检查 collation 排序不一致导致的隐式数据转换
				common.Log.Debug("Collation: `%s`.`%s` (%s) VS `%s`.`%s` (%s)",
					colList[0].Table, colList[0].Name, colList[0].Collation,
					colList[1].Table, colList[1].Name, colList[1].Collation)
				if colList[0].Collation != colList[1].Collation {
					content = append(content, fmt.Sprintf("`%s`.`%s` (%s) VS `%s`.`%s` (%s) collation not match",
						colList[0].Table, colList[0].Name, colList[0].Collation,
						colList[1].Table, colList[1].Name, colList[1].Collation))
					continue
				}
			}

			typMap := map[sqlparser.ValType][]string{
				// date, time, datetime, timestamp, year
				sqlparser.StrVal: {
					"char", "varchar", "tinytext", "text", "mediumtext", "longtext",
					"date", "time", "datetime", "timestamp", "year",
					"tinyint", "smallint", "mediumint", "int", "integer", "bigint",
					"float", "double", "real", "decimal",
				},
				sqlparser.IntVal: {
					"tinyint", "smallint", "mediumint", "int", "integer", "bigint",
					"timestamp", "year", "bit", "decimal",
				},
				sqlparser.FloatVal: {
					"float", "double", "real", "decimal",
				},
			}

			typNameMap := map[sqlparser.ValType]string{
				sqlparser.StrVal:   "string",
				sqlparser.IntVal:   "int",
				sqlparser.FloatVal: "float",
			}

			// 列与值比较
			for _, val := range values {
				if colList[0].DataType == "" {
					common.Log.Warn("Can't get %s data type", colList[0].Name)
					break
				}

				isCovered := true
				if tps, ok := typMap[val.Type]; ok {
					for _, tp := range tps {
						// colList[0].DataType, eg. year(4)
						if strings.HasPrefix(colList[0].DataType, tp) {
							isCovered = false
						}
					}
				}

				if isCovered {
					if colList[0].Table == "" {
						common.Log.Warning("Column %s not exists", colList[0].Name)
						continue
					}

					c := fmt.Sprintf("%s表中列%s的定义是 %s 而不是 %s。",
						colList[0].Table, colList[0].Name, colList[0].DataType, typNameMap[val.Type])

					common.Log.Debug("Implicit data type conversion: %s", c)
					content = append(content, c)
				} else {
					// 检查时间格式,如:"", "2020-0a-01"
					switch strings.Split(colList[0].DataType, "(")[0] {
					case "date", "time", "datetime", "timestamp", "year":
						if !timeFormatCheck(string(val.Val)) {
							c := fmt.Sprintf("%s 表中列 %s 的时间格式错误,%s。", colList[0].Table, colList[0].Name, string(val.Val))
							common.Log.Debug("Implicit data type conversion: %s", c)
							content = append(content, c)
						}
						// TODO: 各种数据类型格式检查
					default:
					}
				}
			}

		case *sqlparser.RangeCond:
			// TODO
		case *sqlparser.IsExpr:
			// TODO
		}
	}
	if len(content) > 0 {
		rule = HeuristicRules["ARG.003"]
		rule.Content = strings.Join(common.RemoveDuplicatesItem(content), " ")
	}
	return rule
}

// timeFormatCheck 时间格式检查,格式正确返回 true,格式错误返回 false
func timeFormatCheck(t string) bool {
	// 不允许为空,但允许时间前后有空格
	t = strings.TrimSpace(t)
	// 仅允许 数字、减号、冒号、空格
	allowChars := regexp.MustCompile(`^[\-0-9:. ]+$`)
	return allowChars.MatchString(t)
}

// RuleNoWhere CLA.001 & CLA.014 & CLA.015
func (q *Query4Audit) RuleNoWhere() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.Select:
			for _, f := range n.From {
				switch f.(type) {
				case *sqlparser.JoinTableExpr:
					return false, nil
				}
			}
			if n.Where == nil && sqlparser.String(n.From) != "dual" {
				rule = HeuristicRules["CLA.001"]
				return false, nil
			}
		case *sqlparser.Delete:
			if n.Where == nil {
				rule = HeuristicRules["CLA.014"]
				return false, nil
			}
		case *sqlparser.Update:
			if n.Where == nil {
				rule = HeuristicRules["CLA.015"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleOrderByRand CLA.002
func (q *Query4Audit) RuleOrderByRand() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case sqlparser.OrderBy:
			for _, order := range n {
				switch expr := order.Expr.(type) {
				case *sqlparser.FuncExpr:
					if strings.ToLower(expr.Name.String()) == "rand" {
						rule = HeuristicRules["CLA.002"]
						return false, nil
					}
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleOffsetLimit CLA.003
func (q *Query4Audit) RuleOffsetLimit() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.Limit:
			if n != nil && n.Offset != nil {
				switch v := n.Offset.(type) {
				case *sqlparser.SQLVal:
					offset, err := strconv.Atoi(string(v.Val))
					// TODO: 检查一下Offset阈值,太小了给这个建议也没什么用,阈值写死了没加配置
					if err == nil && offset > 1000 {
						rule = HeuristicRules["CLA.003"]
						return false, nil
					}
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleGroupByConst CLA.004
func (q *Query4Audit) RuleGroupByConst() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case sqlparser.GroupBy:
			for _, group := range n {
				switch group.(type) {
				case *sqlparser.SQLVal:
					rule = HeuristicRules["CLA.004"]
					return false, nil
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleGroupByConst GRP.001
func (idxAdv *IndexAdvisor) RuleGroupByConst() Rule {
	rule := HeuristicRules["OK"]

	// 非GroupBy语句
	if len(idxAdv.groupBy) == 0 || len(idxAdv.whereEQ) == 0 {
		return rule
	}

	for _, groupByCols := range idxAdv.groupBy {
		for _, whereEQCols := range idxAdv.whereEQ {
			if (groupByCols.Name == whereEQCols.Name) &&
				(groupByCols.DB == whereEQCols.DB) &&
				(groupByCols.Table == whereEQCols.Table) {
				rule = HeuristicRules["GRP.001"]
				break
			}
		}
	}
	return rule
}

// RuleOrderByConst CLA.005
func (q *Query4Audit) RuleOrderByConst() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case sqlparser.OrderBy:
			for _, order := range n {
				switch order.Expr.(type) {
				case *sqlparser.SQLVal:
					rule = HeuristicRules["CLA.005"]
					return false, nil
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleOrderByConst CLA.005
// TODO: SELECT col FROM tbl WHERE col IN('NEWS') ORDER BY col;
func (idxAdv *IndexAdvisor) RuleOrderByConst() Rule {
	rule := HeuristicRules["OK"]

	// 非GroupBy语句
	if len(idxAdv.orderBy) == 0 || len(idxAdv.whereEQ) == 0 {
		return rule
	}

	for _, groupbyCols := range idxAdv.orderBy {
		for _, whereEQCols := range idxAdv.whereEQ {
			if (groupbyCols.Name == whereEQCols.Name) &&
				(groupbyCols.DB == whereEQCols.DB) &&
				(groupbyCols.Table == whereEQCols.Table) {
				rule = HeuristicRules["CLA.005"]
				break
			}
		}
	}
	return rule
}

// RuleDiffGroupByOrderBy CLA.006
func (q *Query4Audit) RuleDiffGroupByOrderBy() Rule {
	var rule = q.RuleOK()
	var groupbyTbls []sqlparser.TableIdent
	var orderbyTbls []sqlparser.TableIdent
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case sqlparser.GroupBy:
			// 检查group by涉及到表的个数
			for _, group := range n {
				switch g := group.(type) {
				case *sqlparser.ColName:
					tblExist := false
					for _, t := range groupbyTbls {
						if t.String() == g.Qualifier.Name.String() {
							tblExist = true
						}
					}
					if !tblExist {
						groupbyTbls = append(groupbyTbls, g.Qualifier.Name)
						if len(groupbyTbls) > 1 {
							rule = HeuristicRules["CLA.006"]
							return false, nil
						}
					}
				}
			}
		case sqlparser.OrderBy:
			// 检查order by涉及到表的个数
			for _, order := range n {
				switch o := order.Expr.(type) {
				case *sqlparser.ColName:
					tblExist := false
					for _, t := range orderbyTbls {
						if t.String() == o.Qualifier.Name.String() {
							tblExist = true
						}
					}
					if !tblExist {
						orderbyTbls = append(orderbyTbls, o.Qualifier.Name)
						if len(orderbyTbls) > 1 {
							rule = HeuristicRules["CLA.006"]
							return false, nil
						}
					}
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")

	if rule.Item == "OK" {
		// 检查group by, order by涉及到表的个数
		for _, g := range groupbyTbls {
			tblExist := false
			for _, o := range orderbyTbls {
				if g.String() == o.String() {
					tblExist = true
				}
			}
			if !tblExist && len(orderbyTbls) > 0 {
				rule = HeuristicRules["CLA.006"]
				return rule
			}
		}
	}

	return rule
}

// RuleExplicitOrderBy CLA.008
func (q *Query4Audit) RuleExplicitOrderBy() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.Select:
			// 有group by,但没有order by
			if n.GroupBy != nil && n.OrderBy == nil {
				rule = HeuristicRules["CLA.008"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleOrderByExpr CLA.009
func (q *Query4Audit) RuleOrderByExpr() Rule {
	var rule = q.RuleOK()
	var orderByCols []string
	var selectCols []string
	funcExp := regexp.MustCompile(`(?i)[a-z0-9]\(`)
	allowExp := regexp.MustCompile("(?i)[a-z0-9_,.` ()]")
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case sqlparser.OrderBy:
			orderBy := sqlparser.String(n)
			// 函数名方式,如:from_unixtime(col)
			if funcExp.MatchString(orderBy) {
				rule = HeuristicRules["CLA.009"]
				return false, nil
			}

			// 运算符方式,如:colA - colB
			trim := allowExp.ReplaceAllFunc([]byte(orderBy), func(s []byte) []byte {
				return []byte("")
			})
			if string(trim) != "" {
				rule = HeuristicRules["CLA.009"]
				return false, nil
			}

			for _, o := range strings.Split(strings.TrimPrefix(orderBy, " order by "), ",") {
				orderByCols = append(orderByCols, strings.TrimSpace(strings.Split(o, " ")[0]))
			}
		case *sqlparser.Select:
			for _, s := range n.SelectExprs {
				selectCols = append(selectCols, sqlparser.String(s))
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")

	// AS情况,如:SELECT colA-colB a FROM tbl ORDER BY a;
	for _, o := range orderByCols {
		if o == "" {
			continue
		}
		for _, s := range selectCols {
			if strings.HasSuffix(s, " as "+o) {
				buf := strings.TrimSuffix(s, " as "+o)
				// 运算符
				trim := allowExp.ReplaceAllFunc([]byte(buf), func(s []byte) []byte {
					return []byte("")
				})
				if string(trim) != "" {
					rule = HeuristicRules["CLA.009"]
				}
				// 函数
				if funcExp.MatchString(s) {
					rule = HeuristicRules["CLA.009"]
				}
			}
		}
	}
	return rule
}

// RuleGroupByExpr CLA.010
func (q *Query4Audit) RuleGroupByExpr() Rule {
	var rule = q.RuleOK()
	var groupByCols []string
	var selectCols []string
	funcExp := regexp.MustCompile(`(?i)[a-z0-9]\(`)
	allowExp := regexp.MustCompile("(?i)[a-z0-9_,.` ()]")
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case sqlparser.GroupBy:
			groupBy := sqlparser.String(n)
			// 函数名方式,如:from_unixtime(col)
			if funcExp.MatchString(groupBy) {
				rule = HeuristicRules["CLA.010"]
				return false, nil
			}

			// 运算符方式,如:colA - colB
			trim := allowExp.ReplaceAllFunc([]byte(groupBy), func(s []byte) []byte {
				return []byte("")
			})
			if string(trim) != "" {
				rule = HeuristicRules["CLA.010"]
				return false, nil
			}

			for _, o := range strings.Split(strings.TrimPrefix(groupBy, " group by "), ",") {
				groupByCols = append(groupByCols, strings.TrimSpace(strings.Split(o, " ")[0]))
			}
		case *sqlparser.Select:
			for _, s := range n.SelectExprs {
				selectCols = append(selectCols, sqlparser.String(s))
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")

	// AS情况,如:SELECT colA-colB a FROM tbl GROUP BY a;
	for _, g := range groupByCols {
		if g == "" {
			continue
		}
		for _, s := range selectCols {
			if strings.HasSuffix(s, " as "+g) {
				buf := strings.TrimSuffix(s, " as "+g)
				// 运算符
				trim := allowExp.ReplaceAllFunc([]byte(buf), func(s []byte) []byte {
					return []byte("")
				})
				if string(trim) != "" {
					rule = HeuristicRules["CLA.010"]
				}
				// 函数
				if funcExp.MatchString(s) {
					rule = HeuristicRules["CLA.010"]
				}
			}
		}
	}
	return rule
}

// RuleTblCommentCheck CLA.011
func (q *Query4Audit) RuleTblCommentCheck() Rule {
	var rule = q.RuleOK()
	switch node := q.Stmt.(type) {
	case *sqlparser.DDL:
		if strings.ToLower(node.Action) != "create" {
			return rule
		}
		if node.TableSpec == nil {
			return rule
		}
		if options := node.TableSpec.Options; options == "" {
			rule = HeuristicRules["CLA.011"]

		} else {
			reg := regexp.MustCompile("(?i)comment")
			if !reg.MatchString(options) {
				rule = HeuristicRules["CLA.011"]
			}
		}
	}
	return rule
}

// RuleSelectStar COL.001
func (q *Query4Audit) RuleSelectStar() Rule {
	var rule = q.RuleOK()
	// 先把count(*)替换为count(1)
	re := regexp.MustCompile(`(?i)count\s*\(\s*\*\s*\)`)
	sql := re.ReplaceAllString(q.Query, "count(1)")
	stmt, err := sqlparser.Parse(sql)
	if err != nil {
		common.Log.Debug("RuleSelectStar sqlparser.Parse Error: %v", err)
		return rule
	}
	err = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch node.(type) {
		case *sqlparser.StarExpr:
			rule = HeuristicRules["COL.001"]
			return false, nil
		}
		return true, nil
	}, stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleInsertColDef COL.002
func (q *Query4Audit) RuleInsertColDef() Rule {
	var rule = q.RuleOK()
	switch node := q.Stmt.(type) {
	case *sqlparser.Insert:
		if node.Columns == nil {
			rule = HeuristicRules["COL.002"]
			return rule
		}
	}
	return rule
}

// RuleAddDefaultValue COL.004
func (q *Query4Audit) RuleAddDefaultValue() Rule {
	var rule = q.RuleOK()
	for _, node := range q.TiStmt {
		switch n := node.(type) {
		case *tidb.CreateTableStmt:
			for _, c := range n.Cols {
				colDefault := false
				for _, o := range c.Options {
					// 忽略AutoIncrement类型的默认值检查
					if o.Tp == tidb.ColumnOptionDefaultValue || o.Tp == tidb.ColumnOptionAutoIncrement {
						colDefault = true
					}
				}

				switch c.Tp.Tp {
				case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON:
					colDefault = true
				}

				if !colDefault {
					rule = HeuristicRules["COL.004"]
					break
				}
			}
		case *tidb.AlterTableStmt:
			for _, s := range n.Specs {
				switch s.Tp {
				case tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:
					for _, c := range s.NewColumns {
						colDefault := false
						for _, o := range c.Options {
							// 忽略AutoIncrement类型的默认值检查
							if o.Tp == tidb.ColumnOptionDefaultValue || o.Tp == tidb.ColumnOptionAutoIncrement {
								colDefault = true
							}
						}

						switch c.Tp.Tp {
						case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON:
							colDefault = true
						}

						if !colDefault {
							rule = HeuristicRules["COL.004"]
							break
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleColCommentCheck COL.005
func (q *Query4Audit) RuleColCommentCheck() Rule {
	var rule = q.RuleOK()
	for _, node := range q.TiStmt {
		switch n := node.(type) {
		case *tidb.CreateTableStmt:
			for _, c := range n.Cols {
				colComment := false
				for _, o := range c.Options {
					if o.Tp == tidb.ColumnOptionComment {
						colComment = true
					}
				}
				if !colComment {
					rule = HeuristicRules["COL.005"]
					break
				}
			}
		case *tidb.AlterTableStmt:
			for _, s := range n.Specs {
				switch s.Tp {
				case tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:
					for _, c := range s.NewColumns {
						colComment := false
						for _, o := range c.Options {
							if o.Tp == tidb.ColumnOptionComment {
								colComment = true
							}
						}
						if !colComment {
							rule = HeuristicRules["COL.005"]
							break
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleIPString LIT.001
func (q *Query4Audit) RuleIPString() Rule {
	var rule = q.RuleOK()

	for _, stmt := range q.TiStmt {
		switch stmt.(type) {
		case *tidb.AlterUserStmt, *tidb.CreateUserStmt, *tidb.GrantStmt, *tidb.GrantRoleStmt,
			*tidb.RevokeRoleStmt, *tidb.RevokeStmt, *tidb.DropUserStmt:
			return rule
		}
	}

	re := regexp.MustCompile(`['"]\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`)
	if re.FindString(q.Query) != "" {
		rule = HeuristicRules["LIT.001"]
		if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
			rule.Position = position[0]
		}
	}
	return rule
}

// RuleDateNotQuote LIT.002
func (q *Query4Audit) RuleDateNotQuote() Rule {
	var rule = q.RuleOK()

	// by pass insert except, insert select
	switch n := q.Stmt.(type) {
	case *sqlparser.Insert:
		var insertSelect bool
		switch n.Rows.(type) {
		case *sqlparser.Select:
			insertSelect = true
		}
		if !insertSelect {
			return rule
		}
	}

	// 2010-01-01
	re := regexp.MustCompile(`.\d{4}\s*-\s*\d{1,2}\s*-\s*\d{1,2}\b`)
	sqls := re.FindAllString(q.Query, -1)
	for _, sql := range sqls {
		re = regexp.MustCompile(`^['"\w-].*`)
		if re.FindString(sql) == "" {
			rule = HeuristicRules["LIT.002"]
			if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
				rule.Position = position[0]
			}
			return rule
		}
	}

	// 10-01-01
	re = regexp.MustCompile(`.\d{2}\s*-\s*\d{1,2}\s*-\s*\d{1,2}\b`)
	sqls = re.FindAllString(q.Query, -1)
	for _, sql := range sqls {
		re = regexp.MustCompile(`^['"\w-].*`)
		if re.FindString(sql) == "" {
			if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
				rule.Position = position[0]
			}
			rule = HeuristicRules["LIT.002"]
		}
	}

	return rule
}

// RuleSQLCalcFoundRows KWR.001
func (q *Query4Audit) RuleSQLCalcFoundRows() Rule {
	var rule = q.RuleOK()
	tkns := ast.Tokenizer(q.Query)
	for _, tkn := range tkns {
		if strings.ToLower(tkn.Val) == "sql_calc_found_rows" {
			rule = HeuristicRules["KWR.001"]
			break
		}
	}
	return rule
}

// RuleCommaAnsiJoin JOI.001
func (q *Query4Audit) RuleCommaAnsiJoin() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.Select:
			ansiJoin := false
			commaJoin := false
			for _, f := range n.From {
				switch f.(type) {
				case *sqlparser.JoinTableExpr:
					ansiJoin = true
				case *sqlparser.AliasedTableExpr:
					commaJoin = true
				}
			}
			if ansiJoin && commaJoin {
				rule = HeuristicRules["JOI.001"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleDupJoin JOI.002
func (q *Query4Audit) RuleDupJoin() Rule {
	var rule = q.RuleOK()
	var tables []string
	switch q.Stmt.(type) {
	// TODO: 这里未检查UNION SELECT
	case *sqlparser.Union:
		return rule
	default:
		err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
			switch n := node.(type) {
			case *sqlparser.AliasedTableExpr:
				switch table := n.Expr.(type) {
				case sqlparser.TableName:
					for _, t := range tables {
						if t == table.Name.String() {
							rule = HeuristicRules["JOI.002"]
							return false, nil
						}
					}
					tables = append(tables, table.Name.String())
				}
			}
			return true, nil
		}, q.Stmt)
		common.LogIfError(err, "")
	}
	return rule
}

// RuleImpossibleOuterJoin JOI.003
// TODO: 未实现完
func (idxAdv *IndexAdvisor) RuleImpossibleOuterJoin() Rule {
	rule := HeuristicRules["OK"]

	var joinTables []string         // JOIN相关表名
	var whereEQTables []string      // WHERE等值判断条件表名
	var joinNotWhereTables []string // 是JOIN相关表,但未出现在WHERE等值判断条件中的表名

	// 非JOIN语句
	if len(idxAdv.joinCond) == 0 || len(idxAdv.whereEQ) == 0 {
		return rule
	}

	for _, l1 := range idxAdv.joinCond {
		for _, l2 := range l1 {
			if l2.Table != "" && strings.ToLower(l2.Table) != "dual" {
				joinTables = append(joinTables, l2.Table)
			}
		}
	}

	for _, w := range idxAdv.whereEQ {
		whereEQTables = append(whereEQTables, w.Table)
	}

	for _, j := range joinTables {
		found := false
		for _, w := range whereEQTables {
			if j == w {
				found = true
			}
		}
		if !found {
			joinNotWhereTables = append(joinNotWhereTables, j)
		}
	}

	// TODO:
	fmt.Println(joinNotWhereTables)
	/*
		if len(joinNotWhereTables) == 0 {
			rule = HeuristicRules["JOI.003"]
		}
	*/
	rule = HeuristicRules["JOI.003"]
	return rule
}

// TODO: JOI.004

// RuleNoDeterministicGroupby RES.001
func (q *Query4Audit) RuleNoDeterministicGroupby() Rule {
	var rule = q.RuleOK()
	var groupbyCols []*common.Column
	var selectCols []*common.Column
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.Select:
			// 过滤select列
			selectCols = ast.FindColumn(n.SelectExprs)
			// 过滤group by列
			groupbyCols = ast.FindColumn(n.GroupBy)
			// `select *`, but not `select count(*)`
			if strings.Contains(sqlparser.String(n), " * ") && len(groupbyCols) > 0 {
				rule = HeuristicRules["RES.001"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")

	// TODO:暂时只检查了列名,未对库表名进行检查,也未处理AS
	for _, s := range selectCols {
		// 无group by退出
		if len(groupbyCols) == 0 {
			break
		}
		found := false
		for _, g := range groupbyCols {
			if g.Name == s.Name {
				found = true
			}
		}
		if !found {
			rule = HeuristicRules["RES.001"]
			break
		}
	}
	return rule
}

// RuleNoDeterministicLimit RES.002
func (q *Query4Audit) RuleNoDeterministicLimit() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.Select:
			if n.Limit != nil && n.OrderBy == nil {
				rule = HeuristicRules["RES.002"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleUpdateDeleteWithLimit RES.003
func (q *Query4Audit) RuleUpdateDeleteWithLimit() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.Update:
		if s.Limit != nil {
			rule = HeuristicRules["RES.003"]
		}
	}
	return rule
}

// RuleUpdateDeleteWithOrderby RES.004
func (q *Query4Audit) RuleUpdateDeleteWithOrderby() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.Update:
		if s.OrderBy != nil {
			rule = HeuristicRules["RES.004"]
		}
	}
	return rule
}

// RuleUpdateSetAnd RES.005
func (q *Query4Audit) RuleUpdateSetAnd() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.Update:
		for _, c := range s.Exprs {
			switch c.Expr.(type) {
			case *sqlparser.Subquery:
			default:
				if strings.Contains(sqlparser.String(c), " and ") {
					rule = HeuristicRules["RES.005"]
				}
			}
		}
	}
	return rule
}

// RuleImpossibleWhere RES.006
func (q *Query4Audit) RuleImpossibleWhere() Rule {
	var rule = q.RuleOK()
	// BETWEEN 10 AND 5
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.RangeCond:
			if strings.ToLower(n.Operator) == "between" {
				from := 0
				to := 0
				switch s := n.From.(type) {
				case *sqlparser.SQLVal:
					from, _ = strconv.Atoi(string(s.Val))
				}
				switch s := n.To.(type) {
				case *sqlparser.SQLVal:
					to, _ = strconv.Atoi(string(s.Val))
				}
				if from > to {
					rule = HeuristicRules["RES.006"]
					return false, nil
				}
			}
		case *sqlparser.ComparisonExpr:
			factor := false
			switch n.Operator {
			case "!=", "<>":
			case "=", "<=>":
				factor = true
			default:
				return true, nil
			}

			var left []byte
			var right []byte

			// left
			switch l := n.Left.(type) {
			case *sqlparser.SQLVal:
				left = l.Val
			default:
				return true, nil
			}

			// right
			switch r := n.Right.(type) {
			case *sqlparser.SQLVal:
				right = r.Val
			default:
				return true, nil
			}

			// compare
			if (!bytes.Equal(left, right) && factor) || (bytes.Equal(left, right) && !factor) {
				rule = HeuristicRules["RES.006"]
			}
			return false, nil
		}

		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleMeaninglessWhere RES.007
func (q *Query4Audit) RuleMeaninglessWhere() Rule {
	var rule = q.RuleOK()

	var where *sqlparser.Where
	switch n := q.Stmt.(type) {
	case *sqlparser.Select:
		where = n.Where
	case *sqlparser.Update:
		where = n.Where
	case *sqlparser.Delete:
		where = n.Where
	}
	if where != nil {
		switch v := where.Expr.(type) {
		// WHERE 1
		case *sqlparser.SQLVal:
			switch string(v.Val) {
			case "0", "false":
			default:
				rule = HeuristicRules["RES.007"]
				return rule
			}
		// WHERE true
		case sqlparser.BoolVal:
			if v {
				rule = HeuristicRules["RES.007"]
				return rule
			}
		}
	}

	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		// WHERE id = 1 or 2
		case *sqlparser.OrExpr:
			// right always true
			switch v := n.Right.(type) {
			case *sqlparser.SQLVal:
				switch string(v.Val) {
				case "0", "false":
				default:
					rule = HeuristicRules["RES.007"]
				}
			case sqlparser.BoolVal:
				if v {
					rule = HeuristicRules["RES.007"]
				}
			}
			// left always true
			switch v := n.Left.(type) {
			case *sqlparser.SQLVal:
				switch string(v.Val) {
				case "0", "false":
				default:
					rule = HeuristicRules["RES.007"]
				}
			case sqlparser.BoolVal:
				if v {
					rule = HeuristicRules["RES.007"]
				}
			}
		// 1=1, 0=0
		case *sqlparser.ComparisonExpr:
			factor := false
			switch n.Operator {
			case "!=", "<>":
				factor = true
			case "=", "<=>":
			default:
				return true, nil
			}

			var left []byte
			var right []byte

			// left
			switch l := n.Left.(type) {
			case *sqlparser.SQLVal:
				left = l.Val
			default:
				return true, nil
			}

			// right
			switch r := n.Right.(type) {
			case *sqlparser.SQLVal:
				right = r.Val
			default:
				return true, nil
			}

			// compare
			if (bytes.Equal(left, right) && !factor) || (!bytes.Equal(left, right) && factor) {
				rule = HeuristicRules["RES.007"]
			}

			// TODO:
			// 2 > 1
			// true = 1
			// false != 1

			return false, nil
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleLoadFile RES.008
func (q *Query4Audit) RuleLoadFile() Rule {
	var rule = q.RuleOK()
	// 去除注释
	sql := database.RemoveSQLComments(q.Query)
	// 去除多余的空格和回车
	sql = strings.Join(strings.Fields(sql), " ")
	tks := ast.Tokenize(sql)
	for i, tk := range tks {
		// 注意:每个关键字token的结尾是带空格的,这里偷懒没trimspace直接加空格比较
		// LOAD DATA...
		if strings.ToLower(tk.Val) == "load " && i+1 < len(tks) &&
			strings.ToLower(tks[i+1].Val) == "data " {
			rule = HeuristicRules["RES.008"]
			break
		}

		// SELECT ... INTO OUTFILE
		if strings.ToLower(tk.Val) == "into " && i+1 < len(tks) &&
			(strings.ToLower(tks[i+1].Val) == "outfile " || strings.ToLower(tks[i+1].Val) == "dumpfile ") {
			rule = HeuristicRules["RES.008"]
			break
		}
	}
	return rule
}

// RuleMultiCompare RES.009
func (q *Query4Audit) RuleMultiCompare() Rule {
	var rule = q.RuleOK()
	if q.TiStmt != nil {
		json := ast.StmtNode2JSON(q.Query, "", "")
		whereJSON := common.JSONFind(json, "Where")
		for _, where := range whereJSON {
			conds := []string{where}
			conds = append(conds, common.JSONFind(where, "L")...)
			conds = append(conds, common.JSONFind(where, "R")...)
			for _, cond := range conds {
				if gjson.Get(cond, "Op").Int() == 7 && gjson.Get(cond, "L.Op").Int() == 7 {
					rule = HeuristicRules["RES.009"]
					return rule
				}
			}
		}
	}
	return rule
}

// RuleCreateOnUpdate RES.010
func (q *Query4Audit) RuleCreateOnUpdate() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					for _, op := range col.Options {
						if op.Tp == tidb.ColumnOptionOnUpdate {
							rule = HeuristicRules["RES.010"]
							return rule
						}
					}
				}

			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddColumns, tidb.AlterTableModifyColumn, tidb.AlterTableChangeColumn:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							for _, op := range col.Options {
								if op.Tp == tidb.ColumnOptionOnUpdate {
									rule = HeuristicRules["RES.010"]
									return rule
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleUpdateOnUpdate RES.011
func (idxAdv *IndexAdvisor) RuleUpdateOnUpdate() Rule {
	rule := HeuristicRules["OK"]
	// 未开启测试环境不进行检查
	if common.Config.TestDSN.Disable {
		return rule
	}
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch stmt := node.(type) {
		case *sqlparser.Update:
			for _, tbExpr := range stmt.TableExprs {
				ddl, err := idxAdv.vEnv.ShowCreateTable(sqlparser.String(tbExpr))
				if err != nil {
					common.Log.Error("RuleMaxTextColsCount create statement got failed: %s", err.Error())
					return false, err
				}
				if strings.Contains(ddl, "ON UPDATE") {
					rule = HeuristicRules["RES.011"]
					break
				}
			}
			for _, setExpr := range stmt.Exprs {
				tup := strings.Split(sqlparser.String(setExpr), " = ")
				if len(tup) == 2 && tup[0] == tup[1] {
					rule = HeuristicRules["OK"]
				}
			}
		}
		return true, nil
	}, idxAdv.Ast)
	common.LogIfError(err, "")
	return rule
}

// RuleStandardINEQ STA.001
func (q *Query4Audit) RuleStandardINEQ() Rule {
	var rule = q.RuleOK()
	re := regexp.MustCompile(`(!=)`)
	if re.FindString(q.Query) != "" {
		rule = HeuristicRules["STA.001"]
		if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
			rule.Position = position[0]
		}
	}
	return rule
}

// RuleUseKeyWord KWR.002
func (q *Query4Audit) RuleUseKeyWord() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		if q.TiStmt == nil {
			common.Log.Error("TiStmt is nil, SQL: %s", q.Query)
			return rule
		}

		for _, tiStmtNode := range q.TiStmt {
			switch stmt := tiStmtNode.(type) {
			case *tidb.AlterTableStmt:
				// alter
				for _, spec := range stmt.Specs {
					for _, column := range spec.NewColumns {
						if ast.IsMysqlKeyword(column.Name.String()) {
							return HeuristicRules["KWR.002"]
						}
					}
				}

			case *tidb.CreateTableStmt:
				// create
				if ast.IsMysqlKeyword(stmt.Table.Name.String()) {
					return HeuristicRules["KWR.002"]
				}

				for _, col := range stmt.Cols {
					if ast.IsMysqlKeyword(col.Name.String()) {
						return HeuristicRules["KWR.002"]
					}
				}
			}

		}
	}

	return rule
}

// RulePluralWord KWR.003
// Reference: https://en.wikipedia.org/wiki/English_plurals
func (q *Query4Audit) RulePluralWord() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		if q.TiStmt == nil {
			common.Log.Error("TiStmt is nil, SQL: %s", q.Query)
			return rule
		}

		for _, tiStmtNode := range q.TiStmt {
			switch stmt := tiStmtNode.(type) {
			case *tidb.AlterTableStmt:
				// alter
				for _, spec := range stmt.Specs {
					for _, column := range spec.NewColumns {
						if inflector.Singularize(column.Name.String()) != column.Name.String() {
							return HeuristicRules["KWR.003"]
						}
					}
				}

			case *tidb.CreateTableStmt:
				// create
				if inflector.Singularize(stmt.Table.Name.String()) != stmt.Table.Name.String() {
					return HeuristicRules["KWR.003"]
				}

				for _, col := range stmt.Cols {
					if inflector.Singularize(col.Name.String()) != col.Name.String() {
						return HeuristicRules["KWR.003"]
					}
				}
			}

		}

	}
	return rule
}

// RuleMultiBytesWord KWR.004
func (q *Query4Audit) RuleMultiBytesWord() Rule {
	// TODO: 目前使用 utf8 字符集检查,其他字符集输入可能会有问题
	var rule = q.RuleOK()
	for _, tk := range ast.Tokenize(q.Query) {
		switch tk.Type {
		case ast.TokenTypeBacktickQuote, ast.TokenTypeWord:
			if utf8.RuneCountInString(tk.Val) != len(tk.Val) {
				rule = HeuristicRules["KWR.004"]
			}
		default:
		}
	}
	return rule
}

// RuleInvisibleUnicode KWR.005
func (q *Query4Audit) RuleInvisibleUnicode() Rule {
	var rule = q.RuleOK()
	for _, tk := range ast.Tokenizer(q.Query) {
		// 多字节的肉眼不可见字符经过 Tokenizer 后被切成了单字节字符。
		// strings.Contains 中的内容也肉眼不可见,需要使用 cat -A 查看代码
		switch tk.Val {
		case string([]byte{194}), string([]byte{160}): // non-broken-space C2 A0
			if strings.Contains(q.Query, ` `) {
				rule = HeuristicRules["KWR.005"]
				return rule
			}
		case string([]byte{226}), string([]byte{128}), string([]byte{139}): // zero-width space E2 80 8B
			if strings.Contains(q.Query, `​`) {
				rule = HeuristicRules["KWR.005"]
				return rule
			}
		default:
		}
	}
	return rule
}

// RuleInsertSelect LCK.001
func (q *Query4Audit) RuleInsertSelect() Rule {
	var rule = q.RuleOK()
	switch n := q.Stmt.(type) {
	case *sqlparser.Insert:
		switch n.Rows.(type) {
		case *sqlparser.Select:
			rule = HeuristicRules["LCK.001"]
		}
	}
	return rule
}

// RuleInsertOnDup LCK.002
func (q *Query4Audit) RuleInsertOnDup() Rule {
	var rule = q.RuleOK()
	switch n := q.Stmt.(type) {
	case *sqlparser.Insert:
		if n.OnDup != nil {
			rule = HeuristicRules["LCK.002"]
			return rule
		}
	}
	return rule
}

// RuleInSubquery SUB.001
func (q *Query4Audit) RuleInSubquery() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch node.(type) {
		case *sqlparser.Subquery:
			rule = HeuristicRules["SUB.001"]
			return false, nil
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleSubqueryDepth SUB.004
func (q *Query4Audit) RuleSubqueryDepth() Rule {
	var rule = q.RuleOK()
	if depth := ast.GetSubqueryDepth(q.Stmt); depth > common.Config.MaxSubqueryDepth {
		rule = HeuristicRules["SUB.004"]
	}
	return rule
}

// RuleSubQueryLimit SUB.005
// 只有 IN 的 SUBQUERY 限制了 LIMIT, FROM 子句中的 SUBQUERY 并未限制 LIMIT
func (q *Query4Audit) RuleSubQueryLimit() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.ComparisonExpr:
			if n.Operator == "in" {
				switch r := n.Right.(type) {
				case *sqlparser.Subquery:
					switch s := r.Select.(type) {
					case *sqlparser.Select:
						if s.Limit != nil {
							rule = HeuristicRules["SUB.005"]
							return false, nil
						}
					}
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleSubQueryFunctions SUB.006
func (q *Query4Audit) RuleSubQueryFunctions() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch node.(type) {
		case *sqlparser.Subquery:
			err = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
				switch node.(type) {
				case *sqlparser.FuncExpr:
					rule = HeuristicRules["SUB.006"]
					return false, nil
				}
				return true, nil
			}, node)
			common.LogIfError(err, "")
		}

		if rule.Item == "OK" {
			return true, nil
		}
		return false, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleUNIONLimit SUB.007
func (q *Query4Audit) RuleUNIONLimit() Rule {
	var rule = q.RuleOK()
	for _, tiStmtNode := range q.TiStmt {
		switch stmt := tiStmtNode.(type) {
		// SetOprStmt represents "union/except/intersect statement"
		case *tidb.SetOprStmt:
			if stmt.Limit != nil {
				for _, sel := range stmt.SelectList.Selects {
					switch n := sel.(type) {
					case *tidb.SelectStmt:
						if n.Limit == nil {
							rule = HeuristicRules["SUB.007"]
						}
					case *tidb.SetOprSelectList:
						for _, s := range n.Selects {
							switch s1 := s.(type) {
							case *tidb.SelectStmt:
								if s1.Limit == nil {
									rule = HeuristicRules["SUB.007"]
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleMultiValueAttribute LIT.003
func (q *Query4Audit) RuleMultiValueAttribute() Rule {
	var rule = q.RuleOK()
	re := regexp.MustCompile(`(?i)(id\s+regexp)`)
	if re.FindString(q.Query) != "" {
		rule = HeuristicRules["LIT.003"]
		if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
			rule.Position = position[0]
		}
	}
	return rule
}

// RuleAddDelimiter LIT.004
func (q *Query4Audit) RuleAddDelimiter() Rule {
	var rule = q.RuleOK()
	re := regexp.MustCompile(`(?i)(^use\s+[0-9a-z_-]*)|(^show\s+databases)`)
	if re.FindString(q.Query) != "" && !strings.HasSuffix(q.Query, common.Config.Delimiter) {
		rule = HeuristicRules["LIT.004"]
		if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
			rule.Position = position[0]
		}
	}
	return rule
}

// RuleRecursiveDependency KEY.003
func (q *Query4Audit) RuleRecursiveDependency() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				// create statement
				for _, ref := range node.Constraints {
					if ref != nil && ref.Tp == tidb.ConstraintForeignKey {
						rule = HeuristicRules["KEY.003"]
					}
				}

			case *tidb.AlterTableStmt:
				// alter table statement
				for _, spec := range node.Specs {
					if spec.Constraint != nil && spec.Constraint.Tp == tidb.ConstraintForeignKey {
						rule = HeuristicRules["KEY.003"]
					}
				}
			}
		}
	}

	if rule.Item == "KEY.003" {
		re := regexp.MustCompile(`(?i)(\s+references\s+)`)
		if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
			rule.Position = position[0]
		}
	}

	return rule
}

// RuleImpreciseDataType COL.009
func (q *Query4Audit) RuleImpreciseDataType() Rule {
	var rule = q.RuleOK()
	if q.TiStmt != nil {
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				// Create table statement
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeFloat, mysql.TypeDouble, mysql.TypeNewDecimal:
						rule = HeuristicRules["COL.009"]
					}
				}

			case *tidb.AlterTableStmt:
				// Alter table statement
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeFloat, mysql.TypeDouble, mysql.TypeNewDecimal:
								rule = HeuristicRules["COL.009"]
							}
						}
					}
				}

			case *tidb.InsertStmt:
				// Insert statement
				for _, values := range node.Lists {
					for _, value := range values {
						switch value.GetType().Tp {
						case mysql.TypeNewDecimal, mysql.TypeFloat:
							rule = HeuristicRules["COL.009"]
						}
					}
				}

			case *tidb.SelectStmt:
				// Select statement
				switch where := node.Where.(type) {
				case *tidb.BinaryOperationExpr:
					switch where.R.GetType().Tp {
					case mysql.TypeNewDecimal, mysql.TypeFloat:
						rule = HeuristicRules["COL.009"]
					}
				}
			}
		}
	}

	return rule
}

// RuleValuesInDefinition COL.010
func (q *Query4Audit) RuleValuesInDefinition() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeSet, mysql.TypeEnum, mysql.TypeBit:
						rule = HeuristicRules["COL.010"]
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeSet, mysql.TypeEnum, mysql.TypeBit:
								rule = HeuristicRules["COL.010"]
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleIndexAttributeOrder KEY.004
func (q *Query4Audit) RuleIndexAttributeOrder() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateIndexStmt:
				if len(node.IndexPartSpecifications) > 1 {
					rule = HeuristicRules["KEY.004"]
					break
				}
			case *tidb.CreateTableStmt:
				for _, constraint := range node.Constraints {
					// 当一条索引中包含多个列的时候给予建议
					if len(constraint.Keys) > 1 {
						rule = HeuristicRules["KEY.004"]
						break
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					if spec.Tp == tidb.AlterTableAddConstraint && len(spec.Constraint.Keys) > 1 {
						rule = HeuristicRules["KEY.004"]
						break
					}
				}
			}
		}
	}
	return rule
}

// RuleNullUsage COL.011
func (q *Query4Audit) RuleNullUsage() Rule {
	var rule = q.RuleOK()
	re := regexp.MustCompile(`(?i)(\s+null\s+)`)
	if re.FindString(q.Query) != "" {
		rule = HeuristicRules["COL.011"]
		if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
			rule.Position = position[0]
		}
	}
	return rule
}

// RuleStringConcatenation FUN.003
func (q *Query4Audit) RuleStringConcatenation() Rule {
	var rule = q.RuleOK()
	// 匹配 or 关键字
	matchF := func(s string) bool {
		// 状态:进入下一状态要匹配的字符
		path := map[int]rune{
			-1: '\'', // 特殊状态,左侧有单引号,等待匹配单引号
			0:  ' ',  // 初始态
			1:  'o',
			2:  'r',
			3:  ' ',
		}
		status := 0
		skip := false
		for _, c := range s {
			if skip {
				skip = false
				continue
			}
			switch c {
			case '\\':
				skip = true
			case path[status]:
				status += 1 // next status
			case '\'':
				status = -1
			default:
				// 碰到其他字符,如果status = -1,说明在引号中间,保持不变
				if status != -1 {
					status = 0
				}
			}
			if status == 4 {
				return true
			}
		}
		return false
	}
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.Select:
			for _, expr := range n.SelectExprs {
				if matchF(sqlparser.String(expr)) {
					rule = HeuristicRules["FUN.003"]
					return false, nil
				}
			}

		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")

	return rule
}

// RuleSysdate FUN.004
func (q *Query4Audit) RuleSysdate() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.FuncExpr:
			if strings.ToLower(n.Name.String()) == "sysdate" {
				rule = HeuristicRules["FUN.004"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleCountConst FUN.005
func (q *Query4Audit) RuleCountConst() Rule {
	var rule = q.RuleOK()
	fingerprint := query.Fingerprint(q.Query)
	countReg := regexp.MustCompile(`(?i)count\(\s*[0-9a-z?]*\s*\)`)
	if countReg.MatchString(fingerprint) {
		rule = HeuristicRules["FUN.005"]
		if position := countReg.FindIndex([]byte(q.Query)); len(position) > 0 {
			rule.Position = position[0]
		}
	}
	return rule
}

// RuleSumNPE FUN.006
func (q *Query4Audit) RuleSumNPE() Rule {
	var rule = q.RuleOK()
	fingerprint := query.Fingerprint(q.Query)
	// TODO: https://github.com/XiaoMi/soar/issues/143
	// https://dev.mysql.com/doc/refman/8.0/en/group-by-functions.html
	sumReg := regexp.MustCompile(`(?i)sum\(\s*[0-9a-z?]*\s*\)`)
	isnullReg := regexp.MustCompile(`(?i)isnull\(sum\(\s*[0-9a-z?]*\s*\)\)`)
	if sumReg.MatchString(fingerprint) && !isnullReg.MatchString(fingerprint) {
		// TODO: check wether column define with not null flag
		rule = HeuristicRules["FUN.006"]
		if position := isnullReg.FindIndex([]byte(q.Query)); len(position) > 0 {
			rule.Position = position[0]
		}
	}
	return rule
}

// RuleForbiddenTrigger FUN.007
func (q *Query4Audit) RuleForbiddenTrigger() Rule {
	var rule = q.RuleOK()

	// 由于vitess对某些语法的支持不完善,使得如创建临时表等语句无法通过语法检查
	// 所以这里使用正则对触发器、临时表、存储过程等进行匹配
	// 但是目前支持的也不是非常全面,有待完善匹配规则
	// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键

	forbidden := []*regexp.Regexp{
		regexp.MustCompile(`(?i)CREATE\s+TRIGGER\s+`),
	}

	for _, reg := range forbidden {
		if reg.MatchString(q.Query) {
			rule = HeuristicRules["FUN.007"]
			if position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {
				rule.Position = position[0]
			}
			break
		}
	}
	return rule
}

// RuleForbiddenProcedure FUN.008
func (q *Query4Audit) RuleForbiddenProcedure() Rule {
	var rule = q.RuleOK()

	// 由于vitess对某些语法的支持不完善,使得如创建临时表等语句无法通过语法检查
	// 所以这里使用正则对触发器、临时表、存储过程等进行匹配
	// 但是目前支持的也不是非常全面,有待完善匹配规则
	// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键

	forbidden := []*regexp.Regexp{
		regexp.MustCompile(`(?i)CREATE\s+PROCEDURE\s+`),
	}

	for _, reg := range forbidden {
		if reg.MatchString(q.Query) {
			rule = HeuristicRules["FUN.008"]
			if position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {
				rule.Position = position[0]
			}
			break
		}
	}
	return rule
}

// RuleForbiddenFunction FUN.009
func (q *Query4Audit) RuleForbiddenFunction() Rule {
	var rule = q.RuleOK()

	// 由于vitess对某些语法的支持不完善,使得如创建临时表等语句无法通过语法检查
	// 所以这里使用正则对触发器、临时表、存储过程等进行匹配
	// 但是目前支持的也不是非常全面,有待完善匹配规则
	// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键

	forbidden := []*regexp.Regexp{
		regexp.MustCompile(`(?i)CREATE\s+FUNCTION\s+`),
	}

	for _, reg := range forbidden {
		if reg.MatchString(q.Query) {
			rule = HeuristicRules["FUN.009"]
			if position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {
				rule.Position = position[0]
			}
			break
		}
	}
	return rule
}

// RulePatternMatchingUsage ARG.007
func (q *Query4Audit) RulePatternMatchingUsage() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.Select:
		re := regexp.MustCompile(`(?i)(\bregexp\b)|(\bsimilar to\b)`)
		if re.FindString(q.Query) != "" {
			rule = HeuristicRules["ARG.007"]
		}
	}
	return rule
}

// RuleSpaghettiQueryAlert CLA.012
func (q *Query4Audit) RuleSpaghettiQueryAlert() Rule {
	var rule = q.RuleOK()
	if len(query.Fingerprint(q.Query)) > common.Config.SpaghettiQueryLength {
		rule = HeuristicRules["CLA.012"]
	}
	return rule
}

// RuleReduceNumberOfJoin JOI.005
func (q *Query4Audit) RuleReduceNumberOfJoin() Rule {
	var rule = q.RuleOK()
	var tables []string
	switch q.Stmt.(type) {
	// TODO: UNION有可能有多张表,这里未检查UNION SELECT
	case *sqlparser.Union:
		return rule
	default:
		err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
			switch n := node.(type) {
			case *sqlparser.AliasedTableExpr:
				switch table := n.Expr.(type) {
				case sqlparser.TableName:
					exist := false
					for _, t := range tables {
						if t == table.Name.String() {
							exist = true
							break
						}
					}
					if !exist {
						tables = append(tables, table.Name.String())
					}
				}
			}
			return true, nil
		}, q.Stmt)
		common.LogIfError(err, "")
	}
	if len(tables) > common.Config.MaxJoinTableCount {
		rule = HeuristicRules["JOI.005"]
	}
	return rule
}

// RuleDistinctUsage DIS.001
func (q *Query4Audit) RuleDistinctUsage() Rule {
	// Distinct
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.Select:
		re := regexp.MustCompile(`(?i)(\bdistinct\b)`)
		if len(re.FindAllString(q.Query, -1)) > common.Config.MaxDistinctCount {
			rule = HeuristicRules["DIS.001"]
		}
	}
	return rule
}

// RuleCountDistinctMultiCol DIS.002
func (q *Query4Audit) RuleCountDistinctMultiCol() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.FuncExpr:
			str := strings.ToLower(sqlparser.String(n))
			if strings.HasPrefix(str, "count") && strings.Contains(str, ",") {
				rule = HeuristicRules["DIS.002"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleDistinctStar DIS.003
func (q *Query4Audit) RuleDistinctStar() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.Select:
		meta := ast.GetMeta(q.Stmt, nil)
		for _, m := range meta {
			if len(m.Table) == 1 {
				// distinct tbl.* from tbl和 distinct *
				re := regexp.MustCompile(`(?i)((\s+distinct\s*\*)|(\s+distinct\s+[0-9a-z_` + "`" + `]*\.\*))`)
				if re.MatchString(q.Query) {
					rule = HeuristicRules["DIS.003"]
				}
			}
			break
		}
	}
	return rule
}

// RuleHavingClause CLA.013
func (q *Query4Audit) RuleHavingClause() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch expr := node.(type) {
		case *sqlparser.Select:
			if expr.Having != nil {
				rule = HeuristicRules["CLA.013"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleUpdatePrimaryKey CLA.016
func (idxAdv *IndexAdvisor) RuleUpdatePrimaryKey() Rule {
	rule := HeuristicRules["OK"]
	switch node := idxAdv.Ast.(type) {
	case *sqlparser.Update:
		var setColumns []*common.Column

		err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
			switch node.(type) {
			case *sqlparser.UpdateExpr:
				// 获取 set 操作的全部 column
				setColumns = append(setColumns, ast.FindAllCols(node)...)
			}
			return true, nil
		}, node)
		common.LogIfError(err, "")
		setColumns = idxAdv.calcCardinality(CompleteColumnsInfo(idxAdv.Ast, setColumns, idxAdv.vEnv))
		for _, col := range setColumns {
			idxMeta := idxAdv.IndexMeta[idxAdv.vEnv.DBHash(col.DB)][col.Table]
			if idxMeta == nil {
				return rule
			}
			for _, idx := range idxMeta.Rows {
				if strings.ToLower(idx.KeyName) == "primary" {
					if col.Name == idx.ColumnName {
						rule = HeuristicRules["CLA.016"]
						return rule
					}
					continue
				}
			}
		}
	}

	return rule
}

// RuleNestedSubQueries JOI.006
func (q *Query4Audit) RuleNestedSubQueries() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch node.(type) {
		case *sqlparser.Subquery:
			rule = HeuristicRules["JOI.006"]
			return false, nil
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleMultiDeleteUpdate JOI.007
func (q *Query4Audit) RuleMultiDeleteUpdate() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.Delete, *sqlparser.Update:
		err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
			switch node.(type) {
			case *sqlparser.JoinTableExpr:
				rule = HeuristicRules["JOI.007"]
				return false, nil
			}
			return true, nil
		}, q.Stmt)
		common.LogIfError(err, "")
	}
	return rule
}

// RuleMultiDBJoin JOI.008
func (q *Query4Audit) RuleMultiDBJoin() Rule {
	var rule = q.RuleOK()
	meta := ast.GetMeta(q.Stmt, nil)
	dbCount := 0
	for range meta {
		dbCount++
	}

	if dbCount > 1 {
		err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
			switch node.(type) {
			case *sqlparser.JoinTableExpr:
				rule = HeuristicRules["JOI.008"]
				return false, nil
			}
			return true, nil
		}, q.Stmt)
		common.LogIfError(err, "")
	}
	return rule
}

// RuleORUsage ARG.008
func (q *Query4Audit) RuleORUsage() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.Select:
		err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
			switch n := node.(type) {
			case *sqlparser.OrExpr:
				switch n.Left.(type) {
				case *sqlparser.IsExpr:
					// IS TRUE|FALSE|NULL eg. a = 1 or a IS NULL 这种情况也需要考虑
					return true, nil
				}
				switch n.Right.(type) {
				case *sqlparser.IsExpr:
					// IS TRUE|FALSE|NULL eg. a = 1 or a IS NULL 这种情况也需要考虑
					return true, nil
				}

				if strings.Fields(sqlparser.String(n.Left))[0] != strings.Fields(sqlparser.String(n.Right))[0] {
					// 不同字段需要区分开,不同字段的 OR 不能改写为 IN
					return true, nil
				}

				rule = HeuristicRules["ARG.008"]
				return false, nil
			}
			return true, nil
		}, q.Stmt)
		common.LogIfError(err, "")
	}
	return rule
}

// RuleSpaceWithQuote ARG.009
func (q *Query4Audit) RuleSpaceWithQuote() Rule {
	var rule = q.RuleOK()
	for _, tk := range ast.Tokenize(q.Query) {
		if tk.Type == ast.TokenTypeQuote {
			if len(tk.Val) >= 2 {
				// 序列化的Val是带引号,所以要取第2个和倒数第二个,这样也就不用担心len<2了。
				switch tk.Val[1] {
				case ' ':
					rule = HeuristicRules["ARG.009"]
				}
				switch tk.Val[len(tk.Val)-2] {
				case ' ':
					rule = HeuristicRules["ARG.009"]
				}
			}
		}
	}
	return rule
}

// RuleHint ARG.010
// TODO: sql_no_cache, straight join
func (q *Query4Audit) RuleHint() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.IndexHints:
			if n != nil {
				rule = HeuristicRules["ARG.010"]
			}
			return false, nil
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleNot ARG.011
func (q *Query4Audit) RuleNot() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.ComparisonExpr:
			if strings.HasPrefix(strings.ToLower(n.Operator), "not") {
				rule = HeuristicRules["ARG.011"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleInsertValues ARG.012
func (q *Query4Audit) RuleInsertValues() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.Insert:
		switch val := s.Rows.(type) {
		case sqlparser.Values:
			if len(val) > common.Config.MaxValueCount {
				rule = HeuristicRules["ARG.012"]
			}
		}
	}
	return rule
}

// RuleFullWidthQuote ARG.013
func (q *Query4Audit) RuleFullWidthQuote() Rule {
	var rule = q.RuleOK()
	for _, node := range q.TiStmt {
		switch n := node.(type) {
		case *tidb.CreateTableStmt, *tidb.AlterTableStmt:
			var sb strings.Builder
			ctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)
			if err := n.Restore(ctx); err == nil {
				if strings.Contains(sb.String(), `“”`) || strings.Contains(sb.String(), `‘’`) {
					rule = HeuristicRules["ARG.013"]
				}
			}
		}
	}
	return rule
}

// RuleUNIONUsage SUB.002
func (q *Query4Audit) RuleUNIONUsage() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.Union:
		if strings.ToLower(s.Type) == "union" {
			rule = HeuristicRules["SUB.002"]
		}
	}
	return rule
}

// RuleDistinctJoinUsage SUB.003
func (q *Query4Audit) RuleDistinctJoinUsage() Rule {
	var rule = q.RuleOK()
	switch expr := q.Stmt.(type) {
	case *sqlparser.Select:
		if expr.Distinct != "" {
			if expr.From != nil {
				if len(expr.From) > 1 {
					rule = HeuristicRules["SUB.003"]
				}
			}
		}
	}
	return rule
}

// RuleReadablePasswords SEC.002
func (q *Query4Audit) RuleReadablePasswords() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		re := regexp.MustCompile(`(?i)(password)|(password)|(pwd)`)
		for _, tiStmt := range q.TiStmt {
			// create table stmt
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString,
						mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob:
						if re.FindString(q.Query) != "" {
							return HeuristicRules["SEC.002"]
						}
					}
				}

			case *tidb.AlterTableStmt:
				// alter table stmt
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableModifyColumn, tidb.AlterTableChangeColumn, tidb.AlterTableAddColumns:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString,
								mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob:
								if re.FindString(q.Query) != "" {
									return HeuristicRules["SEC.002"]
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleDataDrop SEC.003
func (q *Query4Audit) RuleDataDrop() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.DBDDL:
		if strings.ToLower(s.Action) == "drop" {
			rule = HeuristicRules["SEC.003"]
		}
	case *sqlparser.DDL:
		if strings.ToLower(s.Action) == "drop" || strings.ToLower(s.Action) == "truncate" {
			rule = HeuristicRules["SEC.003"]
		}
	case *sqlparser.Delete:
		rule = HeuristicRules["SEC.003"]
	}
	return rule
}

// RuleInjection SEC.004
func (q *Query4Audit) RuleInjection() Rule {
	var rule = q.RuleOK()
	if q.TiStmt != nil {
		json := ast.StmtNode2JSON(q.Query, "", "")
		fs := common.JSONFind(json, "FnName")
		for _, f := range fs {
			functionName := gjson.Get(f, "L")
			switch functionName.String() {
			case "sleep", "benchmark", "get_lock", "release_lock":
				// Ref: https://www.k0rz3n.com/2019/02/01/一篇文章带你深入理解%20SQL%20盲注/
				rule = HeuristicRules["SEC.004"]
			}
		}
	}
	return rule
}

// RuleCompareWithFunction FUN.001
func (q *Query4Audit) RuleCompareWithFunction() Rule {
	var rule = q.RuleOK()

	// `select id from t where num/2 = 100`,
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		// Vitess 中有些函数进行了单独定义不在 FuncExpr 中,如: substring。所以不能直接用 FuncExpr 判断。
		switch n := node.(type) {
		case *sqlparser.ComparisonExpr:
			switch n.Left.(type) {
			case *sqlparser.SQLVal, *sqlparser.ColName:
			default:
				rule = HeuristicRules["FUN.001"]
				return false, nil
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")

	// select id from t where substring(name,1,3)='abc';
	for _, tiStmt := range q.TiStmt {
		switch tiStmt.(type) {
		case *tidb.SelectStmt, *tidb.UpdateStmt, *tidb.DeleteStmt:
			json := ast.StmtNode2JSON(q.Query, "", "")
			whereJSON := common.JSONFind(json, "Where")
			for _, where := range whereJSON {
				if len(common.JSONFind(where, "FnName")) > 0 {
					rule = HeuristicRules["FUN.001"]
				}
				break
			}
		}
	}

	return rule
}

// RuleCountStar FUN.002
func (q *Query4Audit) RuleCountStar() Rule {
	var rule = q.RuleOK()
	switch n := q.Stmt.(type) {
	case *sqlparser.Select:
		// count(N), count(col), count(*)
		re := regexp.MustCompile(`(?i)(count\(\s*[*0-9a-z_` + "`" + `]*\s*\))`)
		if re.FindString(q.Query) != "" && n.Where != nil {
			rule = HeuristicRules["FUN.002"]
		}
	}
	return rule
}

// RuleTruncateTable SEC.001
func (q *Query4Audit) RuleTruncateTable() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.DDL:
		if strings.ToLower(s.Action) == "truncate" {
			rule = HeuristicRules["SEC.001"]
		}
	}
	return rule
}

// RuleIn ARG.005 && ARG.004 && ARG.014
func (q *Query4Audit) RuleIn() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case *sqlparser.ComparisonExpr:
			switch strings.ToLower(n.Operator) {
			case "in":
				switch r := n.Right.(type) {
				case *sqlparser.Subquery:
					// by pass sub query
					// id in (select id from tb where xxx)
					break
				case sqlparser.ValTuple:
					// IN (NULL)
					for _, v := range r {
						switch v.(type) {
						case *sqlparser.NullVal:
							rule = HeuristicRules["ARG.004"]
							return false, nil

						case *sqlparser.ColName:
							// id in (1, 2, id), always true.
							rule = HeuristicRules["ARG.014"]
							return false, nil
						}
					}
					if len(r) > common.Config.MaxInCount {
						rule = HeuristicRules["ARG.005"]
						return false, nil
					}
					//default: // debug
					//	fmt.Println("Type: ", reflect.TypeOf(n.Right).String())
				}
			case "not in":
				switch r := n.Right.(type) {
				case sqlparser.ValTuple:
					// NOT IN (NULL)
					for _, v := range r {
						switch v.(type) {
						case *sqlparser.NullVal:
							rule = HeuristicRules["ARG.004"]
							return false, nil
						}
					}
				}
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleIsNullIsNotNull ARG.006
func (q *Query4Audit) RuleIsNullIsNotNull() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.Select:
		re := regexp.MustCompile(`(?i)is\s*(not)?\s+null\b`)
		if re.FindString(q.Query) != "" {
			rule = HeuristicRules["ARG.006"]
		}
	}
	return rule
}

// RuleVarcharVSChar COL.008
func (q *Query4Audit) RuleVarcharVSChar() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					// 在 TiDB 的 AST 中,char 和 binary 的 type 都是 mysql.TypeString
					// 只是 binary 数据类型的 character 和 collate 是 binary
					case mysql.TypeString:
						rule = HeuristicRules["COL.008"]
					}
				}

			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeString:
								rule = HeuristicRules["COL.008"]
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleCreateDualTable TBL.003
func (q *Query4Audit) RuleCreateDualTable() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.DDL:
		if s.Table.Name.String() == "dual" {
			rule = HeuristicRules["TBL.003"]

		}
	}
	return rule
}

// RuleAlterCharset ALT.001
func (q *Query4Audit) RuleAlterCharset() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableOption:
						for _, option := range spec.Options {
							if option.Tp == tidb.TableOptionCharset ||
								option.Tp == tidb.TableOptionCollate {
								//增加CONVERT TO的判断
								convertReg, _ := regexp.Compile("convert\\b\\s+to")
								if convertReg.Match([]byte(strings.ToLower(q.Query))) {
									break
								} else {
									rule = HeuristicRules["ALT.001"]
									break
								}
							}
						}
					}

					if rule.Item == "ALT.001" {
						break
					}
				}
			}
		}
	}
	return rule
}

// RuleAlterDropColumn ALT.003
func (q *Query4Audit) RuleAlterDropColumn() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableDropColumn:
						rule = HeuristicRules["ALT.003"]
					}
				}
			}
		}

		if rule.Item == "ALT.003" {
			re := regexp.MustCompile(`(?i)(drop\s+column)`)
			if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
				rule.Position = position[0]
			}
		}
	}
	return rule
}

// RuleAlterDropKey ALT.004
func (q *Query4Audit) RuleAlterDropKey() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableDropPrimaryKey,
						tidb.AlterTableDropIndex,
						tidb.AlterTableDropForeignKey:
						rule = HeuristicRules["ALT.004"]
					}
				}
			}
		}
	}
	return rule
}

// RuleBLOBNotNull COL.012
func (q *Query4Audit) RuleBLOBNotNull() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON:
						for _, opt := range col.Options {
							if opt.Tp == tidb.ColumnOptionNotNull {
								rule = HeuristicRules["COL.012"]
								break
							}
						}
						if mysql.HasNotNullFlag(col.Tp.Flag) {
							rule = HeuristicRules["COL.012"]
							break
						}
					}
				}

			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddColumns, tidb.AlterTableModifyColumn, tidb.AlterTableChangeColumn:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON:
								for _, opt := range col.Options {
									if opt.Tp == tidb.ColumnOptionNotNull {
										rule = HeuristicRules["COL.012"]
										break
									}
								}
								if mysql.HasNotNullFlag(col.Tp.Flag) {
									rule = HeuristicRules["COL.012"]
									break
								}
							}
						}
					}
				}
			}
		}
	}

	return rule
}

// RuleTooManyKeys KEY.005
func (q *Query4Audit) RuleTooManyKeys() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				if len(node.Constraints) > common.Config.MaxIdxCount {
					rule = HeuristicRules["KEY.005"]
				}
			}
		}
	}
	return rule
}

// RuleTooManyKeyParts KEY.006
func (q *Query4Audit) RuleTooManyKeyParts() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, constraint := range node.Constraints {
					if len(constraint.Keys) > common.Config.MaxIdxColsCount {
						return HeuristicRules["KEY.006"]
					}

					if constraint.Refer != nil && len(constraint.Refer.IndexPartSpecifications) > common.Config.MaxIdxColsCount {
						return HeuristicRules["KEY.006"]
					}
				}

			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddConstraint:
						if spec.Constraint != nil {
							if len(spec.Constraint.Keys) > common.Config.MaxIdxColsCount {
								return HeuristicRules["KEY.006"]
							}

							if spec.Constraint.Refer != nil {
								if len(spec.Constraint.Refer.IndexPartSpecifications) > common.Config.MaxIdxColsCount {
									return HeuristicRules["KEY.006"]
								}
							}
						}
					}
				}
			}
		}
	}

	return rule
}

// RulePKNotInt KEY.007 && KEY.001
func (q *Query4Audit) RulePKNotInt() Rule {
	var rule = q.RuleOK()
	var pk sqlparser.ColIdent
	switch s := q.Stmt.(type) {
	case *sqlparser.DDL:
		if strings.ToLower(s.Action) == "create" {
			if s.TableSpec == nil {
				return rule
			}
			for _, idx := range s.TableSpec.Indexes {
				if strings.ToLower(idx.Info.Type) == "primary key" {
					if len(idx.Columns) == 1 {
						pk = idx.Columns[0].Column
						break
					}
				}
			}

			// 未指定主键
			if pk.String() == "" {
				rule = HeuristicRules["KEY.007"]
				return rule
			}

			// 主键非int, bigint类型
			for _, col := range s.TableSpec.Columns {
				if pk.String() == col.Name.String() {
					switch strings.ToLower(col.Type.Type) {
					case "int", "bigint", "integer":
						if !col.Type.Unsigned {
							rule = HeuristicRules["KEY.007"]
						}
						if !col.Type.Autoincrement {
							rule = HeuristicRules["KEY.001"]
						}
					default:
						rule = HeuristicRules["KEY.007"]
					}
				}
			}
		}
	}
	return rule
}

// RuleOrderByMultiDirection KEY.008
func (q *Query4Audit) RuleOrderByMultiDirection() Rule {
	var rule = q.RuleOK()
	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch n := node.(type) {
		case sqlparser.OrderBy:
			order := ""
			for _, col := range strings.Split(sqlparser.String(n), ",") {
				orders := strings.Split(col, " ")
				if order != "" && order != orders[len(orders)-1] {
					rule = HeuristicRules["KEY.008"]
					return false, nil
				}
				order = orders[len(orders)-1]
			}
		}
		return true, nil
	}, q.Stmt)
	common.LogIfError(err, "")
	return rule
}

// RuleUniqueKeyDup KEY.009
// TODO: 目前只是给建议,期望能够实现自动检查
func (q *Query4Audit) RuleUniqueKeyDup() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateIndexStmt:
				// create index
				if node.KeyType == tidb.IndexKeyTypeUnique {
					re := regexp.MustCompile(`(?i)(create\s+(unique)\s)`)
					rule = HeuristicRules["KEY.009"]
					if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
						rule.Position = position[0]
					}
					return rule
				}

			case *tidb.AlterTableStmt:
				// alter table add constraint
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddConstraint:
						if spec.Constraint == nil {
							continue
						}
						switch spec.Constraint.Tp {
						case tidb.ConstraintPrimaryKey, tidb.ConstraintUniq, tidb.ConstraintUniqKey, tidb.ConstraintUniqIndex:
							re := regexp.MustCompile(`(?i)(add\s+(unique)\s)`)
							rule = HeuristicRules["KEY.009"]
							if position := re.FindIndex([]byte(q.Query)); len(position) > 0 {
								rule.Position = position[0]
							}
							return rule
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleFulltextIndex KEY.010
func (q *Query4Audit) RuleFulltextIndex() Rule {
	var rule = q.RuleOK()

	/* // TiDB parser
	for _, tiStmt := range q.TiStmt {
		switch tiStmt.(type) {
		case *tidb.CreateTableStmt, *tidb.AlterTableStmt:
		default:
			return rule
		}
	}
	*/
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
	default:
		return rule
	}

	tks := ast.Tokenize(q.Query)
	for _, tk := range tks {
		switch tk.Type {
		case ast.TokenTypeWord:
			if strings.TrimSpace(strings.ToLower(tk.Val)) == "fulltext" {
				rule = HeuristicRules["KEY.010"]
			}
		default:
		}
	}
	return rule
}

// RuleTimestampDefault COL.013
func (q *Query4Audit) RuleTimestampDefault() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeTimestamp, mysql.TypeDate, mysql.TypeDatetime, mysql.TypeNewDate:
						hasDefault := false
						var sb strings.Builder
						ctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)
						for _, option := range col.Options {
							if option.Tp == tidb.ColumnOptionDefaultValue {
								hasDefault = true
								if err := option.Restore(ctx); err == nil {
									if strings.HasPrefix(strings.ToLower(sb.String()), `default '0`) ||
										strings.HasPrefix(strings.ToLower(sb.String()), `default 0`) {
										hasDefault = false
									}
								}
							}
						}
						if !hasDefault {
							rule = HeuristicRules["COL.013"]
							break
						}
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddColumns,
						tidb.AlterTableModifyColumn,
						tidb.AlterTableChangeColumn,
						tidb.AlterTableAlterColumn:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							var sb strings.Builder
							ctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)
							switch col.Tp.Tp {
							case mysql.TypeTimestamp, mysql.TypeDate, mysql.TypeDatetime, mysql.TypeNewDate:
								hasDefault := false
								for _, option := range col.Options {
									if option.Tp == tidb.ColumnOptionDefaultValue {
										hasDefault = true
										if err := option.Restore(ctx); err == nil {
											if strings.HasPrefix(strings.ToLower(sb.String()), `default '0`) ||
												strings.HasPrefix(strings.ToLower(sb.String()), `default 0`) ||
												strings.HasPrefix(strings.ToLower(sb.String()), `default _utf8mb4'0`) ||
												strings.HasPrefix(strings.ToLower(sb.String()), `default _utf8'0`) {
												hasDefault = false
											}
										}
									}
								}
								if !hasDefault {
									rule = HeuristicRules["COL.013"]
									break
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleAutoIncrementInitNotZero TBL.004
func (q *Query4Audit) RuleAutoIncrementInitNotZero() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, opt := range node.Options {
					if opt.Tp == tidb.TableOptionAutoIncrement && opt.UintValue > 1 {
						rule = HeuristicRules["TBL.004"]
					}
				}

			}
		}
	}
	return rule
}

// RuleColumnWithCharset COL.014
func (q *Query4Audit) RuleColumnWithCharset() Rule {
	var rule = q.RuleOK()
	tks := ast.Tokenize(q.Query)
	for _, tk := range tks {
		if tk.Type == ast.TokenTypeWord {
			switch strings.TrimSpace(strings.ToLower(tk.Val)) {
			//character移到后面检查
			case "national", "nvarchar", "nchar", "nvarchar(", "nchar(":
				rule = HeuristicRules["COL.014"]
				return rule
			}
		}
	}
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					if col.Tp.Charset != "" || col.Tp.Collate != "" {
						if col.Tp.Charset == "binary" || col.Tp.Collate == "binary" {
							continue
						} else {
							rule = HeuristicRules["COL.014"]
							break
						}
					}
					//在这里检查character
					characterReg, _ := regexp.Compile("character set")
					if characterReg.Match([]byte(strings.ToLower(q.Query))) {
						rule = HeuristicRules["COL.014"]
						break
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAlterColumn, tidb.AlterTableChangeColumn,
						tidb.AlterTableModifyColumn, tidb.AlterTableAddColumns:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							if col.Tp.Charset != "" || col.Tp.Collate != "" {
								if col.Tp.Charset == "binary" || col.Tp.Collate == "binary" {
									continue
								} else {
									rule = HeuristicRules["COL.014"]
									break
								}
							}
							characterReg, _ := regexp.Compile("character set")
							if characterReg.Match([]byte(strings.ToLower(q.Query))) {
								rule = HeuristicRules["COL.014"]
								break
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleTableCharsetCheck TBL.005
func (q *Query4Audit) RuleTableCharsetCheck() Rule {
	var rule = q.RuleOK()
	var allow bool
	var hasCharset bool

	switch q.Stmt.(type) {
	case *sqlparser.DDL, *sqlparser.DBDDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, opt := range node.Options {
					if opt.Tp == tidb.TableOptionCharset {
						hasCharset = true
						for _, ch := range common.Config.AllowCharsets {
							if strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.StrValue)) {
								allow = true
								break
							}
						}
					}
				}

			case *tidb.CreateDatabaseStmt:
				for _, opt := range node.Options {
					if opt.Tp == tidb.DatabaseOptionCharset {
						hasCharset = true
						for _, ch := range common.Config.AllowCharsets {
							if strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.Value)) {
								allow = true
								break
							}
						}
					}
				}

			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableOption:
						for _, opt := range spec.Options {
							if opt.Tp == tidb.TableOptionCharset {
								hasCharset = true
								for _, ch := range common.Config.AllowCharsets {
									if strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.StrValue)) {
										allow = true
										break
									}
								}
							}
						}
					}
				}
			}
		}
	}

	// 未指定字符集使用MySQL默认配置字符集,我们认为MySQL的配置是被优化过的。
	if hasCharset && !allow {
		rule = HeuristicRules["TBL.005"]
	}
	return rule
}

// RuleForbiddenView TBL.006
func (q *Query4Audit) RuleForbiddenView() Rule {
	var rule = q.RuleOK()

	// 由于vitess对某些语法的支持不完善,使得如创建临时表等语句无法通过语法检查
	// 所以这里使用正则对触发器、临时表、存储过程等进行匹配
	// 但是目前支持的也不是非常全面,有待完善匹配规则
	// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键

	forbidden := []*regexp.Regexp{
		regexp.MustCompile(`(?i)CREATE\s+VIEW\s+`),
		regexp.MustCompile(`(?i)REPLACE\s+VIEW\s+`),
	}

	for _, reg := range forbidden {
		if reg.MatchString(q.Query) {
			rule = HeuristicRules["TBL.006"]
			if position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {
				rule.Position = position[0]
			}
			break
		}
	}
	return rule
}

// RuleForbiddenTempTable TBL.007
func (q *Query4Audit) RuleForbiddenTempTable() Rule {
	var rule = q.RuleOK()

	// 由于vitess对某些语法的支持不完善,使得如创建临时表等语句无法通过语法检查
	// 所以这里使用正则对触发器、临时表、存储过程等进行匹配
	// 但是目前支持的也不是非常全面,有待完善匹配规则
	// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键

	forbidden := []*regexp.Regexp{
		regexp.MustCompile(`(?i)CREATE\s+TEMPORARY\s+TABLE\s+`),
	}

	for _, reg := range forbidden {
		if reg.MatchString(q.Query) {
			rule = HeuristicRules["TBL.007"]
			if position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {
				rule.Position = position[0]
			}
			break
		}
	}
	return rule
}

// RuleTableCollateCheck TBL.008
func (q *Query4Audit) RuleTableCollateCheck() Rule {
	var rule = q.RuleOK()
	var allow bool
	var hasCollate bool

	switch q.Stmt.(type) {
	case *sqlparser.DDL, *sqlparser.DBDDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, opt := range node.Options {
					if opt.Tp == tidb.TableOptionCollate {
						hasCollate = true
						for _, ch := range common.Config.AllowCollates {
							if strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.StrValue)) {
								allow = true
								break
							}
						}
					}
				}

			case *tidb.CreateDatabaseStmt:
				for _, opt := range node.Options {
					if opt.Tp == tidb.DatabaseOptionCollate {
						hasCollate = true
						for _, ch := range common.Config.AllowCollates {
							if strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.Value)) {
								allow = true
								break
							}
						}
					}
				}

			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableOption:
						for _, opt := range spec.Options {
							if opt.Tp == tidb.TableOptionCollate {
								hasCollate = true
								for _, ch := range common.Config.AllowCollates {
									if strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.StrValue)) {
										allow = true
										break
									}
								}
							}
						}
					}
				}
			}
		}
	}

	// 未指定字符集使用MySQL默认配置COLLATE,我们认为MySQL的配置是被优化过的。
	if hasCollate && !allow {
		rule = HeuristicRules["TBL.008"]
	}
	return rule
}

// RuleBlobDefaultValue COL.015
func (q *Query4Audit) RuleBlobDefaultValue() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob, mysql.TypeLongBlob, mysql.TypeJSON:
						for _, opt := range col.Options {
							if opt.Tp == tidb.ColumnOptionDefaultValue && opt.Expr.GetType().Tp != mysql.TypeNull {
								rule = HeuristicRules["COL.015"]
								break
							}
						}
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableModifyColumn, tidb.AlterTableAlterColumn,
						tidb.AlterTableChangeColumn, tidb.AlterTableAddColumns:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob, mysql.TypeLongBlob, mysql.TypeJSON:
								for _, opt := range col.Options {
									if opt.Tp == tidb.ColumnOptionDefaultValue && opt.Expr.GetType().Tp != mysql.TypeNull {
										rule = HeuristicRules["COL.015"]
										break
									}
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleIntPrecision COL.016
func (q *Query4Audit) RuleIntPrecision() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeLong:
						if (col.Tp.Flen < 10 || col.Tp.Flen > 11) && col.Tp.Flen > 0 {
							// 有些语言 ORM 框架会生成 int(11),有些语言的框架生成 int(10)
							rule = HeuristicRules["COL.016"]
							break
						}
					case mysql.TypeLonglong:
						if (col.Tp.Flen != 20) && col.Tp.Flen > 0 {
							rule = HeuristicRules["COL.016"]
							break
						}
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn,
						tidb.AlterTableAlterColumn, tidb.AlterTableModifyColumn:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeLong:
								if (col.Tp.Flen < 10 || col.Tp.Flen > 11) && col.Tp.Flen > 0 {
									// 有些语言 ORM 框架会生成 int(11),有些语言的框架生成 int(10)
									rule = HeuristicRules["COL.016"]
									break
								}
							case mysql.TypeLonglong:
								if col.Tp.Flen != 20 && col.Tp.Flen > 0 {
									rule = HeuristicRules["COL.016"]
									break
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleVarcharLength COL.017
func (q *Query4Audit) RuleVarcharLength() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeVarchar, mysql.TypeVarString:
						if col.Tp.Flen > common.Config.MaxVarcharLength {
							rule = HeuristicRules["COL.017"]
							break
						}
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn,
						tidb.AlterTableAlterColumn, tidb.AlterTableModifyColumn:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeVarchar, mysql.TypeVarString:
								if col.Tp.Flen > common.Config.MaxVarcharLength {
									rule = HeuristicRules["COL.017"]
									break
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleColumnNotAllowType COL.018
func (q *Query4Audit) RuleColumnNotAllowType() Rule {
	var rule = q.RuleOK()

	if len(common.Config.ColumnNotAllowType) == 0 {
		return rule
	}

	switch s := q.Stmt.(type) {
	case *sqlparser.DDL:
		switch strings.ToLower(s.Action) {
		case "create", "alter":
			tks := ast.Tokenize(q.Query)
			for _, tk := range tks {
				if tk.Type == ast.TokenTypeWord {
					for _, tp := range common.Config.ColumnNotAllowType {
						if len(tk.Val) <= len(tp)+1 &&
							strings.HasPrefix(strings.ToLower(tk.Val), strings.ToLower(tp)) {
							rule = HeuristicRules["COL.018"]
							break
						}
					}
				}
				if rule.Item != "OK" {
					break
				}
			}
		}
	}
	return rule
}

// RuleTimePrecision COL.019
func (q *Query4Audit) RuleTimePrecision() Rule {
	var rule = q.RuleOK()

	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeDatetime, mysql.TypeTimestamp, mysql.TypeDuration:
						if col.Tp.Decimal > 0 {
							rule = HeuristicRules["COL.019"]
						}
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableChangeColumn, tidb.AlterTableAlterColumn,
						tidb.AlterTableModifyColumn, tidb.AlterTableAddColumns:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							switch col.Tp.Tp {
							case mysql.TypeDatetime, mysql.TypeTimestamp, mysql.TypeDuration:
								if col.Tp.Decimal > 0 {
									rule = HeuristicRules["COL.019"]
								}
							}
						}
					}
				}
			}
		}
	}

	return rule
}

// RuleNoOSCKey KEY.002
func (q *Query4Audit) RuleNoOSCKey() Rule {
	var rule = q.RuleOK()
	switch s := q.Stmt.(type) {
	case *sqlparser.DDL:
		if strings.ToLower(s.Action) == "create" {
			pkReg := regexp.MustCompile(`(?i)(primary\s+key)`)
			if !pkReg.MatchString(q.Query) {
				ukReg := regexp.MustCompile(`(?i)(unique\s+((key)|(index)))`)
				if !ukReg.MatchString(q.Query) {
					rule = HeuristicRules["KEY.002"]
				}
			}
		}
	}
	return rule
}

// RuleTooManyFields COL.006
func (q *Query4Audit) RuleTooManyFields() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				if len(node.Cols) > common.Config.MaxColCount {
					rule = HeuristicRules["COL.006"]
				}
			}
		}
	}
	return rule
}

// RuleMaxTextColsCount COL.007
func (q *Query4Audit) RuleMaxTextColsCount() Rule {
	var textColsCount int
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					switch col.Tp.Tp {
					case mysql.TypeBlob, mysql.TypeLongBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob:
						textColsCount++
					}
				}
			}
		}
	}
	if textColsCount > common.Config.MaxTextColsCount {
		rule = HeuristicRules["COL.007"]
	}

	return rule
}

// RuleMaxTextColsCount COL.007 checking for existed table
func (idxAdv *IndexAdvisor) RuleMaxTextColsCount() Rule {
	rule := HeuristicRules["OK"]
	// 未开启测试环境不进行检查
	if common.Config.TestDSN.Disable {
		return rule
	}

	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
		switch stmt := node.(type) {
		case *sqlparser.DDL:
			if strings.ToLower(stmt.Action) != "alter" {
				return true, nil
			}

			// 添加字段的语句会在初始化环境的时候被执行
			// 只需要获取该标的 CREATE 语句,后再对该语句进行检查即可
			ddl, err := idxAdv.vEnv.ShowCreateTable(stmt.Table.Name.String())
			if err != nil {
				common.Log.Error("RuleMaxTextColsCount create statement got failed: %s", err.Error())
				return false, err
			}

			q, err := NewQuery4Audit(ddl)
			if err != nil {
				return false, err
			}

			r := q.RuleMaxTextColsCount()
			if r.Item != "OK" {
				rule = r
				return false, nil
			}
		}
		return true, nil
	}, idxAdv.Ast)
	common.LogIfError(err, "")
	return rule
}

// RuleAllowEngine TBL.002
func (q *Query4Audit) RuleAllowEngine() Rule {
	var rule = q.RuleOK()
	var hasDefaultEngine bool
	var allowedEngine bool
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, opt := range node.Options {
					if opt.Tp == tidb.TableOptionEngine {
						hasDefaultEngine = true
						// 使用了非推荐的存储引擎
						for _, engine := range common.Config.AllowEngines {
							if strings.EqualFold(opt.StrValue, engine) {
								allowedEngine = true
							}
						}
						// common.Config.AllowEngines 为空时不给予建议
						if !allowedEngine && len(common.Config.AllowEngines) > 0 {
							rule = HeuristicRules["TBL.002"]
							break
						}
					}
				}
				// 建表语句未指定表的存储引擎
				if !hasDefaultEngine {
					rule = HeuristicRules["TBL.002"]
					break
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableOption:
						for _, opt := range spec.Options {
							if opt.Tp == tidb.TableOptionEngine {
								// 使用了非推荐的存储引擎
								for _, engine := range common.Config.AllowEngines {
									if strings.EqualFold(opt.StrValue, engine) {
										allowedEngine = true
									}
								}
								// common.Config.AllowEngines 为空时不给予建议
								if !allowedEngine && len(common.Config.AllowEngines) > 0 {
									rule = HeuristicRules["TBL.002"]
									break
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RulePartitionNotAllowed TBL.001
func (q *Query4Audit) RulePartitionNotAllowed() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				if node.Partition != nil {
					rule = HeuristicRules["TBL.001"]
					break
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					if len(spec.PartDefinitions) > 0 {
						rule = HeuristicRules["TBL.001"]
						break
					}
				}
			}
		}
	}
	return rule
}

// RuleAutoIncUnsigned COL.003:
func (q *Query4Audit) RuleAutoIncUnsigned() Rule {
	var rule = q.RuleOK()
	switch q.Stmt.(type) {
	case *sqlparser.DDL:
		for _, tiStmt := range q.TiStmt {
			switch node := tiStmt.(type) {
			case *tidb.CreateTableStmt:
				for _, col := range node.Cols {
					if col.Tp == nil {
						continue
					}
					for _, opt := range col.Options {
						if opt.Tp == tidb.ColumnOptionAutoIncrement {
							if !mysql.HasUnsignedFlag(col.Tp.Flag) {
								rule = HeuristicRules["COL.003"]
								break
							}
						}

						if rule.Item == "COL.003" {
							break
						}
					}
				}
			case *tidb.AlterTableStmt:
				for _, spec := range node.Specs {
					switch spec.Tp {
					case tidb.AlterTableChangeColumn, tidb.AlterTableAlterColumn,
						tidb.AlterTableModifyColumn, tidb.AlterTableAddColumns:
						for _, col := range spec.NewColumns {
							if col.Tp == nil {
								continue
							}
							for _, opt := range col.Options {
								if opt.Tp == tidb.ColumnOptionAutoIncrement {
									if !mysql.HasUnsignedFlag(col.Tp.Flag) {
										rule = HeuristicRules["COL.003"]
										break
									}
								}

								if rule.Item == "COL.003" {
									break
								}
							}
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleSpaceAfterDot STA.002
func (q *Query4Audit) RuleSpaceAfterDot() Rule {
	var rule = q.RuleOK()
	tks := ast.Tokenize(q.Query)
	for i, tk := range tks {
		switch tk.Type {

		// SELECT * FROM db. tbl
		// SELECT tbl. col FROM tbl
		case ast.TokenTypeWord:
			if len(tks) > i+1 &&
				tks[i+1].Type == ast.TokenTypeWhitespace &&
				strings.HasSuffix(tk.Val, ".") {
				common.Log.Debug("RuleSpaceAfterDot: ", tk.Val, tks[i+1].Val)
				rule = HeuristicRules["STA.002"]
				return rule
			}
		default:
		}
	}
	return rule
}

// RuleIdxPrefix STA.003
func (q *Query4Audit) RuleIdxPrefix() Rule {
	var rule = q.RuleOK()
	for _, node := range q.TiStmt {
		switch n := node.(type) {
		case *tidb.CreateTableStmt:
			for _, c := range n.Constraints {
				switch c.Tp {
				case tidb.ConstraintIndex, tidb.ConstraintKey:
					if !strings.HasPrefix(c.Name, common.Config.IdxPrefix) {
						rule = HeuristicRules["STA.003"]
					}
				case tidb.ConstraintUniq, tidb.ConstraintUniqKey, tidb.ConstraintUniqIndex:
					if !strings.HasPrefix(c.Name, common.Config.UkPrefix) {
						rule = HeuristicRules["STA.003"]
					}
				}
			}
		case *tidb.AlterTableStmt:
			for _, s := range n.Specs {
				switch s.Tp {
				case tidb.AlterTableAddConstraint:
					switch s.Constraint.Tp {
					case tidb.ConstraintIndex, tidb.ConstraintKey:
						if !strings.HasPrefix(s.Constraint.Name, common.Config.IdxPrefix) {
							rule = HeuristicRules["STA.003"]
						}
					case tidb.ConstraintUniq, tidb.ConstraintUniqKey, tidb.ConstraintUniqIndex:
						if !strings.HasPrefix(s.Constraint.Name, common.Config.UkPrefix) {
							rule = HeuristicRules["STA.003"]
						}
					}
				}
			}
		}
	}
	return rule
}

// RuleStandardName STA.004
func (q *Query4Audit) RuleStandardName() Rule {
	var rule = q.RuleOK()
	allowReg := regexp.MustCompile(`(?i)[a-z0-9_` + "`" + `]`)
	for _, tk := range ast.Tokenize(q.Query) {
		if tk.Val == "``" {
			rule = HeuristicRules["STA.004"]
		}

		switch tk.Type {
		// 反引号中可能有乱七八糟的东西
		case ast.TokenTypeBacktickQuote:
			// 特殊字符,连续下划线
			if allowReg.ReplaceAllString(tk.Val, "") != "" || strings.Contains(tk.Val, "__") {
				rule = HeuristicRules["STA.004"]
			}
			// 统一大小写
			if !(strings.ToLower(tk.Val) == tk.Val || strings.ToUpper(tk.Val) == tk.Val) {
				rule = HeuristicRules["STA.004"]
			}
		case ast.TokenTypeWord:
			// TOKEN_TYPE_WORD 中处理连续下划线的情况,其他情况容易误伤
			if strings.Contains(tk.Val, "__") {
				rule = HeuristicRules["STA.004"]
			}
		default:
		}
	}
	return rule
}

// MergeConflictHeuristicRules merge conflict rules
func MergeConflictHeuristicRules(rules map[string]Rule) map[string]Rule {
	// KWR.001 VS ERR.000
	// select sql_calc_found_rows * from film
	if _, ok := rules["KWR.001"]; ok {
		delete(rules, "ERR.000")
	}

	// SUB.001 VS OWN.004 VS JOI.006
	if _, ok := rules["SUB.001"]; ok {
		delete(rules, "ARG.005")
		delete(rules, "JOI.006")
	}

	// SUB.004 VS SUB.001
	if _, ok := rules["SUB.004"]; ok {
		delete(rules, "SUB.001")
	}

	// KEY.007 VS KEY.002
	if _, ok := rules["KEY.007"]; ok {
		delete(rules, "KEY.002")
	}

	// JOI.002 VS JOI.006
	if _, ok := rules["JOI.002"]; ok {
		delete(rules, "JOI.006")
	}

	// JOI.008 VS JOI.007
	if _, ok := rules["JOI.008"]; ok {
		delete(rules, "JOI.007")
	}
	return rules
}

// RuleMySQLError ERR.XXX
func RuleMySQLError(item string, err error) Rule {

	type MySQLError struct {
		ErrCode   string
		ErrString string
	}

	// tidb parser 语法检查出错返回的是ERR.000
	switch item {
	case "ERR.000":
		return Rule{
			Item:     item,
			Summary:  "No available MySQL environment, build-in sql parse failed: " + err.Error(),
			Severity: "L8",
			Content:  err.Error(),
		}
	}

	errStr := err.Error()
	// Error 1071: Specified key was too long; max key length is 3072 bytes
	errReg := regexp.MustCompile(`(?i)Error ([0-9]+): (.*)`)
	if strings.HasPrefix(errStr, "Received") {
		// Received #1146 error from MySQL server: "table xxx doesn't exist"
		errReg = regexp.MustCompile(`(?i)Received #([0-9]+) error from MySQL server: ['"](.*)['"]`)
	}

	msg := errReg.FindStringSubmatch(errStr)
	var mysqlError MySQLError

	if len(msg) == 3 {
		if msg[1] != "" && msg[2] != "" {
			mysqlError = MySQLError{
				ErrCode:   msg[1],
				ErrString: msg[2],
			}
		}
	} else {
		var errcode string
		if strings.HasPrefix(err.Error(), "syntax error at position") {
			errcode = "1064"
		}
		mysqlError = MySQLError{
			ErrCode:   errcode,
			ErrString: err.Error(),
		}
	}
	switch mysqlError.ErrCode {
	// 1146 ER_NO_SUCH_TABLE
	case "", "1146":
		return Rule{
			Item:     item,
			Summary:  "MySQL execute failed: ",
			Severity: "L0",
			Content:  "",
		}
	default:
		return Rule{
			Item:     item,
			Summary:  "MySQL execute failed",
			Severity: "L8",
			Content:  mysqlError.ErrString,
		}
	}
}


================================================
FILE: advisor/heuristic_test.go
================================================
/*
 * Copyright 2018 Xiaomi, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package advisor

import (
	"errors"
	"sort"
	"testing"

	"github.com/XiaoMi/soar/common"

	"github.com/XiaoMi/soar/env"
	"github.com/kr/pretty"
	"vitess.io/vitess/go/vt/sqlparser"
)

// ALI.001
func TestRuleImplicitAlias(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"select col c from tbl where id < 1000",
			"select col from tbl tb where id < 1000",
		},
		{
			"select 1",
		},
	}
	for _, sql := range sqls[0] {
		q, _ := NewQuery4Audit(sql)
		rule := q.RuleImplicitAlias()
		if rule.Item != "ALI.001" {
			t.Error("Rule not match:", rule.Item, "Expect : ALI.001")
		}
	}
	for _, sql := range sqls[1] {
		q, _ := NewQuery4Audit(sql)
		rule := q.RuleImplicitAlias()
		if rule.Item != "OK" {
			t.Error("Rule not match:", rule.Item, "Expect : OK")
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// ALI.002
func TestRuleStarAlias(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"select tbl.* AS c1,c2,c3 from tbl where id < 1000",
			"SELECT * as",
		},
		{
			`SELECT c1, c2, c3, FROM tb WHERE id < 1000 AND content="mytest* as test"`,
			`select *`,
		},
	}
	for _, sql := range sqls[0] {
		q, _ := NewQuery4Audit(sql)
		rule := q.RuleStarAlias()
		if rule.Item != "ALI.002" {
			t.Error("Rule not match:", rule.Item, "Expect : ALI.002")
		}
	}
	for _, sql := range sqls[1] {
		q, _ := NewQuery4Audit(sql)
		rule := q.RuleStarAlias()
		if rule.Item != "OK" {
			t.Error("Rule not match:", rule.Item, "Expect : OK")
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// ALI.003
func TestRuleSameAlias(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select col as col from tbl where id < 1000",
		"select col from tbl as tbl where id < 1000",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleSameAlias()
			if rule.Item != "ALI.003" {
				t.Error("Rule not match:", rule.Item, "Expect : ALI.003")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// ARG.001
func TestRulePrefixLike(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select col from tbl where id like '%abc'",
		"select col from tbl where id like '_abc'",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RulePrefixLike()
			if rule.Item != "ARG.001" {
				t.Error("Rule not match:", rule.Item, "Expect : ARG.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// ARG.002
func TestRuleEqualLike(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"select col from tbl where id like 'abc'",
			"select col from tbl where id like 1",
		},
		{
			"select col from tbl where id like 'abc%'",
			"select col from tbl where id like '%abc'",
			"select col from tbl where id like 'a%c'", // issue #273
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleEqualLike()
			if rule.Item != "ARG.002" {
				t.Error("Rule not match:", rule.Item, "Expect : ARG.002")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleEqualLike()
			if rule.Item == "ARG.002" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// ARG.003
// TODO:

func TestTimeFormatError(t *testing.T) {
	rightTimes := []string{
		`2020-01-01`,
		`2020-01-01 23:59:59`,
		`2020-01-01 23:59:59.0`,   // 0ms
		`2020-01-01 23:59:59.123`, // 123ms
	}
	for _, rt := range rightTimes {
		if !timeFormatCheck(rt) {
			t.Error("wrong time format")
		}
	}

	wrongTimes := []string{
		``,                    // 空时间
		`2020-01-01 abc`,      // 含英文字符
		`2020–02-15 23:59:59`, // 2020 后面的不是减号,是个 连接符
	}
	for _, wt := range wrongTimes {
		if timeFormatCheck(wt) {
			t.Error("wrong time format")
		}
	}
}

// CLA.001
func TestRuleNoWhere(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{"select col from tbl",
			"delete from tbl",
			"update tbl set col=1",
			"insert into city (country_id) select country_id from country",
		},
		{
			`select 1;`,
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleNoWhere()
			if rule.Item != "CLA.001" && rule.Item != "CLA.014" && rule.Item != "CLA.015" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.001/CLA.014/CLA.015")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleNoWhere()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.002
func TestRuleOrderByRand(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select col from tbl where id = 1 order by rand()",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleOrderByRand()
			if rule.Item != "CLA.002" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.002")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.003
func TestRuleOffsetLimit(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select c1,c2 from tbl where name=xx order by number limit 1 offset 2000",
		"select c1,c2 from tbl where name=xx order by number limit 2000,1",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleOffsetLimit()
			if rule.Item != "CLA.003" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.003")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.004
func TestRuleGroupByConst(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select col1,col2 from tbl where col1='abc' group by 1",
		"select col1,col2 from tbl group by 1",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleGroupByConst()
			if rule.Item != "CLA.004" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.004")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.005
func TestRuleOrderByConst(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		// "select id from test where id=1 order by id",
		"select id from test where id=1 order by 1",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleOrderByConst()
			if rule.Item != "CLA.005" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.005")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.006
func TestRuleDiffGroupByOrderBy(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col",
		"select tb1.col, tb2.col from tb1, tb2 where id=1 order by tb1.col, tb2.col",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleDiffGroupByOrderBy()
			if rule.Item != "CLA.006" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.006")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.008
func TestRuleExplicitOrderBy(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select c1,c2,c3 from t1 where c1='foo' group by c2",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleExplicitOrderBy()
			if rule.Item != "CLA.008" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.008")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.009
func TestRuleOrderByExpr(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"SELECT col FROM tbl order by cola - cl;",                // order by 列运算
			"SELECT cola - cl col FROM tbl order by col;",            // 别名为列运算
			"SELECT cola FROM tbl order by from_unixtime(col);",      // order by 函数运算
			"SELECT from_unixtime(col) cola FROM tbl order by cola;", // 别名为函数运算
		},
		{
			`SELECT tbl.col FROM tbl ORDER BY col`,
			"SELECT sum(col) AS col FROM tbl ORDER BY dt",
			"SELECT tbl.col FROM tb, tbl WHERE tbl.tag_id = tb.id ORDER BY tbl.col",
			"SELECT col FROM tbl order by `timestamp`;",           // 列名为关键字
			"select col from tb where cl = 1 order by APPLY_TIME", // issue #104 case sensitive
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleOrderByExpr()
			if rule.Item != "CLA.009" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.009")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleOrderByExpr()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.010
func TestRuleGroupByExpr(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"SELECT col FROM tbl GROUP by cola - col;",
		"SELECT cola - col col FROM tbl GROUP by col;",
		"SELECT cola FROM tbl GROUP by from_unixtime(col);",
		"SELECT from_unixtime(col) cola FROM tbl GROUP by cola;",

		// 反面例子
		// `SELECT tbl.col FROM tbl GROUP BY col`,
		// "SELECT dt, sum(col) AS col FROM tbl GROUP BY dt",
		// "SELECT tbl.col FROM tb, tbl WHERE tbl.tag_id = tb.id GROUP BY tbl.col",
		// "SELECT col FROM tbl GROUP by `timestamp`;", // 列名为关键字
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleGroupByExpr()
			if rule.Item != "CLA.010" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.010")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.011
func TestRuleTblCommentCheck(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"CREATE TABLE `test1`( `ID` bigint(20) NOT NULL AUTO_INCREMENT," +
			" `c1` varchar(128) DEFAULT NULL, `c2` varchar(300) DEFAULT NULL," +
			" `c3` varchar(32) DEFAULT NULL, `c4` int(11) NOT NULL, `c5` double NOT NULL," +
			" `c6` text NOT NULL, PRIMARY KEY (`ID`), KEY `idx_c3_c2_c4_c5_c6` " +
			"(`c3`,`c2`(255),`c4`,`c5`,`c6`(255)), KEY `idx_c3_c2_c4` (`c3`,`c2`,`c4`)) " +
			"ENGINE = InnoDB DEFAULT CHARSET=utf8",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleTblCommentCheck()
			if rule.Item != "CLA.011" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.011")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// COL.001
func TestRuleSelectStar(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select * from tbl where id=1",
		"select col, * from tbl where id=1",
		// 反面例子
		// "select count(*) from film where id=1",
		// `select count(* ) from film where id=1`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleSelectStar()
			if rule.Item != "COL.001" {
				t.Error("Rule not match:", rule.Item, "Expect : COL.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// COL.002
func TestRuleInsertColDef(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"insert into tbl values(1,'name')",
			"replace into tbl values(1,'name')",
		},
		{
			"insert into tb (col) values ('hello world')",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleInsertColDef()
			if rule.Item != "COL.002" {
				t.Error("Rule not match:", rule.Item, "Expect : COL.002")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleInsertColDef()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// COL.004
func TestRuleAddDefaultValue(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"create table test(id int)",
			`ALTER TABLE test change id id varchar(10);`,
			`ALTER TABLE test modify id varchar(10);`,
		},
		{
			`ALTER TABLE test modify id varchar(10) DEFAULT '';`,
			`ALTER TABLE test CHANGE id id varchar(10) DEFAULT '';`,
			"create table test(id int not null default 0 comment '用户id')",
			`create table tb (a text)`,
			`alter table tb add a text`,
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleAddDefaultValue()
			if rule.Item != "COL.004" {
				t.Error("Rule not match:", rule.Item, "Expect : COL.004")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleAddDefaultValue()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// COL.005
func TestRuleColCommentCheck(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"create table test(id int not null default 0)",
			`alter table test add column a int`,
			`ALTER TABLE t1 CHANGE b b INT NOT NULL;`,
		},
		{
			"create table test(id int not null default 0 comment '用户id')",
			`alter table test add column a int comment 'test'`,
			`ALTER TABLE t1 AUTO_INCREMENT = 13;`,
			`ALTER TABLE t1 CHANGE b b INT NOT NULL COMMENT 'test';`,
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleColCommentCheck()
			if rule.Item != "COL.005" {
				t.Error("Rule not match:", rule.Item, "Expect : COL.005")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleColCommentCheck()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// LIT.001
func TestRuleIPString(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"insert into tbl (IP,name) values('10.20.306.122','test')",
		},
		{
			`CREATE USER IF NOT EXISTS 'test'@'1.1.1.1';`,
			"ALTER USER 'test'@'1.1.1.1' IDENTIFIED WITH 'mysql_native_password' AS '*xxxxx' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK;",
			"GRANT SELECT ON `test`.* TO 'test'@'1.1.1.1';",
			`GRANT USAGE ON *.* TO 'test'@'1.1.1.1';`,
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleIPString()
			if rule.Item != "LIT.001" {
				t.Error("Rule not match:", rule.Item, "Expect : LIT.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleIPString()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// LIT.002
func TestRuleDateNotQuote(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"select col1,col2 from tbl where time < 2018-01-10",
			"select col1,col2 from tbl where time < 18-01-10",
			"INSERT INTO tb1 SELECT * FROM tb2 WHERE time < 2020-01-10",
			`select * from tb where col < ' 2022-01-10'`,
		},
		{
			"select col1,col2 from tbl where time < '2018-01-10'",
			"INSERT INTO `tb` (`col`) VALUES ('timestamp=2019-12-16')",
			"insert into tb (col) values (' 2020-09-15 ')",
			"replace into tb (col) values (' 2020-09-15 ')",
			"INSERT INTO tb1 SELECT * FROM tb2 WHERE time < '2020-01-10'",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleDateNotQuote()
			if rule.Item != "LIT.002" {
				t.Error("Rule not match:", rule.Item, "Expect : LIT.002")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleDateNotQuote()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// KWR.001
func TestRuleSQLCalcFoundRows(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select SQL_CALC_FOUND_ROWS col from tbl where id>1000",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleSQLCalcFoundRows()
			if rule.Item != "KWR.001" {
				t.Error("Rule not match:", rule.Item, "Expect : KWR.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// JOI.001
func TestRuleCommaAnsiJoin(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1 and t1.c3=t3.c1 where id>1000;`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleCommaAnsiJoin()
			if rule.Item != "JOI.001" {
				t.Error("Rule not match:", rule.Item, "Expect : JOI.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// JOI.002
func TestRuleDupJoin(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1;`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleDupJoin()
			if rule.Item != "JOI.002" {
				t.Error("Rule not match:", rule.Item, "Expect : JOI.002")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.001
func TestRuleNoDeterministicGroupby(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		// 正面CASE
		{
			"select c1,c2,c3 from t1 where c2='foo' group by c2",
			"select col, col2, sum(col1) from tb group by col",
			"select col, col1 from tb group by col,sum(col1)",
			"select * from tb group by col",
		},

		// 反面CASE
		{
			"select id from film",
			"select col, sum(col1) from tb group by col",
			"select * from file",
			"SELECT COUNT(*) AS cnt, language_id FROM film GROUP BY language_id;",
			"SELECT COUNT(*) AS cnt FROM film GROUP BY language_id;",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleNoDeterministicGroupby()
			if rule.Item != "RES.001" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleNoDeterministicGroupby()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.002
func TestRuleNoDeterministicLimit(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select col1,col2 from tbl where name='tony' limit 10",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleNoDeterministicLimit()
			if rule.Item != "RES.002" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.002")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.003
func TestRuleUpdateDeleteWithLimit(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;",
		},
		{
			"UPDATE film SET length = 120 WHERE title = 'abc';",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleUpdateDeleteWithLimit()
			if rule.Item != "RES.003" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.003")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleUpdateDeleteWithLimit()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.004
func TestRuleUpdateDeleteWithOrderby(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title;",
		},
		{
			"UPDATE film SET length = 120 WHERE title = 'abc';",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleUpdateDeleteWithOrderby()
			if rule.Item != "RES.004" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.004")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleUpdateDeleteWithOrderby()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.005
func TestRuleUpdateSetAnd(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"update tbl set col = 1 AND cl = 2 where col=3;",
			"update table1 set a = ( select a from table2 where b=1 and c=2) and b=1 where d=2",
		},
		{
			"update tbl set col = 1 ,cl = 2 where col=3;",
			// https://github.com/XiaoMi/soar/issues/226
			"update table1 set a = ( select a from table2 where b=1 and c=2), b=1, c=2 where d=2",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleUpdateSetAnd()
			if rule.Item != "RES.005" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.005")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleUpdateSetAnd()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.006
func TestRuleImpossibleWhere(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"select * from tbl where 1 != 1;",
			"select * from tbl where 'a' != 'a';",
			"select * from tbl where col between 10 AND 5;",
		},
		{
			"select * from tbl where 1 = 1;",
			"select * from tbl where 'a' != 1;",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleImpossibleWhere()
			if rule.Item != "RES.006" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.006, SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleImpossibleWhere()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK, SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.007
func TestRuleMeaninglessWhere(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"select * from tbl where 1 = 1;",
			"select * from tbl where 'a' = 'a';",
			"select * from tbl where 'a' != 1;",
			"select * from tbl where 'a';",
			"select * from tbl where 'a' limit 1;",
			"select * from tbl where 1;",
			"select * from tbl where 1 limit 1;",
			"select * from tbl where id = 1 or 2;",
			"select * from tbl where true;",
			"select * from tbl where 'true';",
		},
		{
			"select * from tbl where false;",
			"select * from tbl where 'false';",
			"select * from tbl where 0;",
			"select * from tbl where '0';",
			"select * from tbl where 2 = 1;",
			"select * from tbl where 'b' = 'a';",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleMeaninglessWhere()
			if rule.Item != "RES.007" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.007, SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleMeaninglessWhere()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK, SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.008
func TestRuleLoadFile(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;",
			"LOAD    DATA INFILE 'data.txt' INTO TABLE db2.my_table;",
			"LOAD /*COMMENT*/DATA INFILE 'data.txt' INTO TABLE db2.my_table;",
			`SELECT a,b,a+b INTO OUTFILE '/tmp/result.txt' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' FROM test_table;`,
		},
		{
			"SELECT id, data INTO @x, @y FROM test.t1 LIMIT 1;",
		},
	}
	for _, sql := range sqls[0] {
		q := &Query4Audit{Query: sql}
		rule := q.RuleLoadFile()
		if rule.Item != "RES.008" {
			t.Error("Rule not match:", rule.Item, "Expect : RES.008, SQL: ", sql)
		}
	}

	for _, sql := range sqls[1] {
		q := &Query4Audit{Query: sql}
		rule := q.RuleLoadFile()
		if rule.Item != "OK" {
			t.Error("Rule not match:", rule.Item, "Expect : OK, SQL: ", sql)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.009
func TestRuleMultiCompare(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"SELECT * FROM tbl WHERE col = col = 'abc'",
			"SELECT * FROM tbl WHERE col = 'def' and col = col = 'abc'",
			"SELECT * FROM tbl WHERE col = 'def' or col = col = 'abc'",
			"SELECT * FROM tbl WHERE col = col = 'abc' and col = 'def'",
			"UPDATE tbl set col = 1 WHERE col = col = 'abc'",
			"DELETE FROM tbl WHERE col = col = 'abc'",
		},
		{
			"SELECT * FROM tbl WHERE col = 'abc'",
			// https://github.com/XiaoMi/soar/issues/169
			"SELECT * FROM tbl WHERE col = 'abc' and c = 1",
			"update tb set c = 1 where a = 2 and b = 3",
			"delete from tb where a = 2 and b = 3",
		},
	}

	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleMultiCompare()
			if rule.Item != "RES.009" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.009, SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleMultiCompare()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK, SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.010
func TestRuleCreateOnUpdate(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			`CREATE TABLE category (
  category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(25) NOT NULL,
  last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (category_id)
)`,
		},
		{
			`CREATE TABLE category (
  category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(25) NOT NULL,
  last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY  (category_id)
)`,
		},
	}

	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleCreateOnUpdate()
			if rule.Item != "RES.010" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.010, SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleCreateOnUpdate()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK, SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// RES.011
func TestRuleUpdateOnUpdate(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			`UPDATE category SET name='ActioN' WHERE category_id=1`,
		},
		{
			`select * from film limit 1`,
			"UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1",
		},
	}

	for _, sql := range sqls[0] {
		vEnv.BuildVirtualEnv(rEnv, sql)
		stmt, syntaxErr := sqlparser.Parse(sql)
		if syntaxErr != nil {
			t.Error(syntaxErr)
		}

		q := &Query4Audit{Query: sql, Stmt: stmt}
		idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
		if err != nil {
			t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
		}

		if idxAdvisor != nil {
			rule := idxAdvisor.RuleUpdateOnUpdate()
			if rule.Item != "RES.011" {
				t.Error("Rule not match:", rule.Item, "Expect : RES.011, SQL:", sql)
			}
		}
	}

	for _, sql := range sqls[1] {
		vEnv.BuildVirtualEnv(rEnv, sql)
		stmt, syntaxErr := sqlparser.Parse(sql)
		if syntaxErr != nil {
			t.Error(syntaxErr)
		}

		q := &Query4Audit{Query: sql, Stmt: stmt}
		idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
		if err != nil {
			t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
		}

		if idxAdvisor != nil {
			rule := idxAdvisor.RuleUpdateOnUpdate()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK, SQL:", sql)
			}
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// STA.001
func TestRuleStandardINEQ(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select col1,col2 from tbl where type!=0",
		// "select col1,col2 from tbl where type<>0",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleStandardINEQ()
			if rule.Item != "STA.001" {
				t.Error("Rule not match:", rule.Item, "Expect : STA.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// KWR.002
func TestRuleUseKeyWord(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"CREATE TABLE tbl (`select` int)",
			"CREATE TABLE `select` (a int)",
			"ALTER TABLE tbl ADD COLUMN `select` varchar(10)",
		},
		{
			"CREATE TABLE tbl (a int)",
			"ALTER TABLE tbl ADD COLUMN col varchar(10)",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleUseKeyWord()
			if rule.Item != "KWR.002" {
				t.Error("Rule not match:", rule.Item, "Expect : KWR.002")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleUseKeyWord()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// KWR.003
func TestRulePluralWord(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"CREATE TABLE tbl (`people` int)",
			"CREATE TABLE people (a int)",
			"ALTER TABLE tbl ADD COLUMN people varchar(10)",
		},
		{
			"CREATE TABLE tbl (`person` int)",
			"ALTER TABLE tbl ADD COLUMN person varchar(10)",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RulePluralWord()
			if rule.Item != "KWR.003" {
				t.Error("Rule not match:", rule.Item, "Expect : KWR.003")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RulePluralWord()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// KWR.004
func TestRuleMultiBytesWord(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			"select col as 列 from tb",
			"select col as `列` from tb",
		},
		{
			"select col as c from tb",
			"select '列'",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleMultiBytesWord()
			if rule.Item != "KWR.004" {
				t.Error("Rule not match:", rule.Item, "Expect : KWR.004")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleMultiBytesWord()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// KWR.005
func TestRuleInvisibleUnicode(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	// 不可见的 unicode 可以通过 https://unicode-table.com 复制得到
	sqls := [][]string{
		{
			`select 1`,   // SQL 中包含 non-broken-space
			`select​ 1;`, // SQL 中包含 zero-width space
		},
		{
			"select 1",    // 正常 SQL
			`select "1 "`, // 值中包含 non-broken-space
			`select "1​"`, // 值中包含 zero-width space
		},
	}
	for _, sql := range sqls[0] {
		q, _ := NewQuery4Audit(sql)
		// 含有特殊 unicode 字符的 SQL 语法肯定是不通过的
		rule := q.RuleInvisibleUnicode()
		if rule.Item != "KWR.005" {
			t.Error("Rule not match:", rule.Item, "Expect : KWR.005")
		}
	}
	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleInvisibleUnicode()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// LCK.001
func TestRuleInsertSelect(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`INSERT INTO tbl SELECT * FROM tbl2;`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleInsertSelect()
			if rule.Item != "LCK.001" {
				t.Error("Rule not match:", rule.Item, "Expect : LCK.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// LCK.002
func TestRuleInsertOnDup(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleInsertOnDup()
			if rule.Item != "LCK.002" {
				t.Error("Rule not match:", rule.Item, "Expect : LCK.002")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// SUB.001
func TestRuleInSubquery(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select col1,col2,col3 from table1 where col2 in(select col from table2)",
		"SELECT col1,col2,col3 from table1 where col2 =(SELECT col2 FROM `table1` limit 1)",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleInSubquery()
			if rule.Item != "SUB.001" {
				t.Error("Rule not match:", rule.Item, "Expect : SUB.001")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// LIT.003
func TestRuleMultiValueAttribute(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		"select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'",
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleMultiValueAttribute()
			if rule.Item != "LIT.003" {
				t.Error("Rule not match:", rule.Item, "Expect : LIT.003")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// LIT.003
func TestRuleAddDelimiter(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			`use sakila
		select * from film`,
			`use sakila`,
			`show databases`,
		},
		{
			`use sakila;`,
		},
	}
	for _, sql := range sqls[0] {
		q, _ := NewQuery4Audit(sql)

		rule := q.RuleAddDelimiter()
		if rule.Item != "LIT.004" {
			t.Error("Rule not match:", rule.Item, "Expect : LIT.004")
		}
	}
	for _, sql := range sqls[1] {
		q, _ := NewQuery4Audit(sql)

		rule := q.RuleAddDelimiter()
		if rule.Item != "OK" {
			t.Error("Rule not match:", rule.Item, "Expect : OK")
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// KEY.003
func TestRuleRecursiveDependency(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			`CREATE TABLE tab2 (
          p_id BIGINT UNSIGNED NOT NULL,
          a_id BIGINT UNSIGNED NOT NULL,
          PRIMARY KEY (p_id, a_id),
          FOREIGN KEY (p_id) REFERENCES tab1(p_id),
          FOREIGN KEY (a_id) REFERENCES tab3(a_id)
         );`,
			`ALTER TABLE tbl2 add FOREIGN KEY (p_id) REFERENCES tab1(p_id);`,
		},
		{
			`ALTER TABLE tbl2 ADD KEY p_id (p_id);`,
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleRecursiveDependency()
			if rule.Item != "KEY.003" {
				t.Error("Rule not match:", rule.Item, "Expect : KEY.003")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleRecursiveDependency()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// COL.009
func TestRuleImpreciseDataType(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			`CREATE TABLE tab2 (
          p_id BIGINT UNSIGNED NOT NULL,
          a_id BIGINT UNSIGNED NOT NULL,
          hours float NOT null,
          PRIMARY KEY (p_id, a_id)
         );`,
			`alter table tbl add column c float not null;`,
			`insert into tb (col) values (0.00001);`,
			`select * from tb where col = 0.00001;`,
		},
		{
			"REPLACE INTO `storage` (`hostname`,`storagehost`, `filename`, `starttime`, `binlogstarttime`, `uploadname`, `binlogsize`, `filesize`, `md5`, `status`) VALUES (1, 1, 1, 1, 1, 1, ?, ?);",
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleImpreciseDataType()
			if rule.Item != "COL.009" {
				t.Error("Rule not match:", rule.Item, "Expect : COL.009")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleImpreciseDataType()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// COL.010
func TestRuleValuesInDefinition(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`create table tab1(status ENUM('new', 'in progress', 'fixed'))`,
		`alter table tab1 add column status ENUM('new', 'in progress', 'fixed')`,
	}

	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleValuesInDefinition()
			if rule.Item != "COL.010" {
				t.Error("Rule not match:", rule.Item, "Expect : COL.010")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// KEY.004
func TestRuleIndexAttributeOrder(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`create index idx1 on tab(last_name,first_name);`,
		`alter table tab add index idx1 (last_name,first_name);`,
		`CREATE TABLE test (id int,blob_col BLOB, INDEX(blob_col(10),id));`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleIndexAttributeOrder()
			if rule.Item != "KEY.004" {
				t.Error("Rule not match:", rule.Item, "Expect : KEY.004")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// COL.011
func TestRuleNullUsage(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`select c1,c2,c3 from tab where c4 is null or c4 <> 1;`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleNullUsage()
			if rule.Item != "COL.011" {
				t.Error("Rule not match:", rule.Item, "Expect : COL.011")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// FUN.003
func TestRuleStringConcatenation(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tab;`,
		`update t1 set c1 = v1 where id in (select id || 1 from t2)`,
		`update t1 set c1 = v1 where id in (select id 
			|| 1 from t2)`,
		`update t1 set c1 = v1 where id in (select id 
				|| 
		1 from t2)`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleStringConcatenation()
			if rule.Item != "FUN.003" {
				t.Error("Rule not match:", rule.Item, "Expect : FUN.003", "SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	okSqls := []string{
		`select "a || b" from tab;`,
		`update t1 set c1 = "{\"key\":\"text or text\"}" where id = 1`,
		`update t1 set c1 = "{\"key\":\"text||text\"}" where id = 1`,
		`select " ' or ' " from tab;`,
		`select " or ' " from tab;`,
		`select "a or b" from tab;`,
		"INSERT INTO `order` VALUES (8, \"test\", \"order.salesType==200||order.salesType==201||order.salesType==202\")",
	}
	for _, sql := range okSqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleStringConcatenation()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK", "SQL: ", sql)
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
// FUN.004
func TestRuleSysdate(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`select sysdate();`,
		`select Sysdate();`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleSysdate()
			if rule.Item != "FUN.004" {
				t.Error("Rule not match:", rule.Item, "Expect : FUN.004")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// FUN.005
func TestRuleCountConst(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			`select count(1) from tbl;`,
			`select count(col) from tbl;`,
		},
		{
			`select count(*) from tbl`,
			`select count(DISTINCT col) from tbl`,
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleCountConst()
			if rule.Item != "FUN.005" {
				t.Error("Rule not match:", rule.Item, "Expect : FUN.005")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleCountConst()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// FUN.006
func TestRuleSumNPE(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := [][]string{
		{
			`select sum(1) from tbl;`,
			`select sum(col) from tbl;`,
		},
		{
			`SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl`,
		},
	}
	for _, sql := range sqls[0] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleSumNPE()
			if rule.Item != "FUN.006" {
				t.Error("Rule not match:", rule.Item, "Expect : FUN.006")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}

	for _, sql := range sqls[1] {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleSumNPE()
			if rule.Item != "OK" {
				t.Error("Rule not match:", rule.Item, "Expect : OK")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// ARG.007
func TestRulePatternMatchingUsage(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]';`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RulePatternMatchingUsage()
			if rule.Item != "ARG.007" {
				t.Error("Rule not match:", rule.Item, "Expect : ARG.007")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// CLA.012
func TestRuleSpaghettiQueryAlert(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`select 1`,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			common.Config.SpaghettiQueryLength = 1
			rule := q.RuleSpaghettiQueryAlert()
			if rule.Item != "CLA.012" {
				t.Error("Rule not match:", rule.Item, "Expect : CLA.012")
			}
		} else {
			t.Error("sqlparser.Parse Error:", err)
		}
	}
	common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}

// JOI.005
func TestRuleReduceNumberOfJoin(t *testing.T) {
	common.Log.Debug("Entering function: %s", common.GetFunctionName())
	sqls := []string{
		`select bp1.p_id, b1.d_d as l, b1.b_id from b1 join bp1 on (b1.b_id = bp1.b_id) left outer join (b1 as b2 join bp2 on (b2.b_id = bp2.b_id)) on (bp1.p_id = bp2.p_id ) join bp21 on (b1.b_id = bp1.b_id) join bp31 on (b1.b_id = bp1.b_id) join bp41 on (b1.b_id = bp1.b_id) where b2.b_id = 0; `,
	}
	for _, sql := range sqls {
		q, err := NewQuery4Audit(sql)
		if err == nil {
			rule := q.RuleReduceNumberOfJoin()
			if rule.Item != "JOI.005" {
				t.Error("Rule not match:", rule.Item, "Expect : JOI.005")
			}
		} else {
			t.Error("sqlparser.Parse Error:",
Download .txt
gitextract_vewet1zv/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── feature_request.md
│   │   └── question.md
│   └── pull_request_template.md
├── .gitignore
├── .travis.yml
├── CHANGES.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── NOTICE.txt
├── README.md
├── README_EN.md
├── VERSION
├── advisor/
│   ├── doc.go
│   ├── explainer.go
│   ├── explainer_test.go
│   ├── heuristic.go
│   ├── heuristic_test.go
│   ├── index.go
│   ├── index_test.go
│   ├── rules.go
│   ├── rules_test.go
│   └── testdata/
│       ├── TestDigestExplainText.golden
│       ├── TestIndexAdviseNoEnv.golden
│       ├── TestListHeuristicRules.golden
│       ├── TestListTestSQLs.golden
│       └── TestMergeConflictHeuristicRules.golden
├── ast/
│   ├── doc.go
│   ├── meta.go
│   ├── meta_test.go
│   ├── node_array.go
│   ├── pretty.go
│   ├── pretty_test.go
│   ├── rewrite.go
│   ├── rewrite_test.go
│   ├── testdata/
│   │   ├── TestCompress.golden
│   │   ├── TestFormat.golden
│   │   ├── TestGetQuotedString.golden
│   │   ├── TestLeftNewLines.golden
│   │   ├── TestListRewriteRules.golden
│   │   ├── TestMergeAlterTables.golden
│   │   ├── TestNewLines.golden
│   │   ├── TestPretty.golden
│   │   ├── TestPrintPrettyStmtNode.golden
│   │   ├── TestPrintPrettyVitessStmtNode.golden
│   │   ├── TestQueryType.golden
│   │   ├── TestSchemaMetaInfo.golden
│   │   ├── TestSplitStatement.golden
│   │   ├── TestStmtNode2JSON.golden
│   │   ├── TestTokenize.golden
│   │   ├── TestTokenizer.golden
│   │   └── TestVitessStmtNode2JSON.golden
│   ├── tidb.go
│   ├── tidb_test.go
│   ├── token.go
│   ├── token_test.go
│   ├── vitess.go
│   └── vitess_test.go
├── cmd/
│   └── soar/
│       ├── doc.go
│       ├── soar.go
│       ├── soar_test.go
│       ├── testdata/
│       │   ├── Test_Main.golden
│       │   └── Test_Main_verboseInfo.golden
│       └── tool.go
├── common/
│   ├── cases.go
│   ├── chardet.go
│   ├── chardet_test.go
│   ├── config.go
│   ├── config_test.go
│   ├── doc.go
│   ├── example_test.go
│   ├── logger.go
│   ├── logger_test.go
│   ├── markdown.go
│   ├── markdown_test.go
│   ├── meta.go
│   ├── meta_test.go
│   ├── signal.go
│   ├── signal_test.go
│   ├── testdata/
│   │   ├── TestJSONFind.golden
│   │   ├── TestListReportTypes.golden
│   │   ├── TestMarkdown2Html.golden
│   │   ├── TestMarkdown2Html.md
│   │   ├── TestMarkdownHTMLHeader.golden
│   │   ├── TestParseDSN.golden
│   │   ├── TestPrintConfiguration.golden
│   │   ├── TestRemoveBOM.golden
│   │   ├── TestStringStorageReq.golden
│   │   ├── UTF-8.bom.sql
│   │   ├── chardet_BIG5.txt
│   │   ├── chardet_GB-18030.txt
│   │   └── chardet_UTF-8.txt
│   ├── tricks.go
│   ├── tricks_test.go
│   └── version.go
├── database/
│   ├── doc.go
│   ├── explain.go
│   ├── explain_test.go
│   ├── mysql.go
│   ├── mysql_test.go
│   ├── privilege.go
│   ├── privilege_test.go
│   ├── profiling.go
│   ├── profiling_test.go
│   ├── sampling.go
│   ├── show.go
│   ├── show_test.go
│   ├── testdata/
│   │   ├── TestEscape.golden
│   │   ├── TestExplain.golden
│   │   ├── TestExplainInfoTranslator.golden
│   │   ├── TestFindColumn.golden
│   │   ├── TestFormatProfiling.golden
│   │   ├── TestFormatTrace.golden
│   │   ├── TestMySQLExplainQueryCost.golden
│   │   ├── TestMySQLExplainWarnings.golden
│   │   ├── TestPrintMarkdownExplainTable.golden
│   │   ├── TestRemoveSQLComments.golden
│   │   ├── TestShowColumns.golden
│   │   ├── TestShowCreateDatabase.golden
│   │   ├── TestShowCreateTable.golden
│   │   ├── TestShowIndex.golden
│   │   ├── TestShowReference.golden
│   │   ├── TestShowTables.golden
│   │   └── TestTrace.golden
│   ├── trace.go
│   └── trace_test.go
├── deps.sh
├── doc/
│   ├── FAQ.md
│   ├── FAQ_en.md
│   ├── cheatsheet.md
│   ├── cheatsheet_en.md
│   ├── comparison.md
│   ├── comparison_en.md
│   ├── config.md
│   ├── editor_plugin.md
│   ├── environment.md
│   ├── example/
│   │   ├── digest_pt.py
│   │   ├── metalinter.json
│   │   ├── metalinter.sh
│   │   ├── metalinter.txt
│   │   ├── revive.toml
│   │   ├── slow.log.digest
│   │   └── soar.vim
│   ├── explain.md
│   ├── heuristic.md
│   ├── images/
│   │   └── logo.ascii
│   ├── indexing.md
│   ├── install.md
│   ├── install_en.md
│   ├── js/
│   │   └── pretty.js
│   ├── report_type.md
│   ├── rewrite.md
│   ├── roadmap.md
│   ├── structure.md
│   ├── thanks.md
│   ├── thanks_en.md
│   └── themes/
│       ├── foghorn.css
│       ├── ghostwriter.css
│       ├── github-dark.css
│       ├── github.css
│       ├── godspeed.css
│       ├── markdown-alt.css
│       ├── markdown.css
│       ├── markdown5.css
│       ├── markdown6.css
│       ├── markdown7.css
│       ├── markdown8.css
│       ├── markdown9.css
│       ├── markedapp-byword.css
│       ├── new-modern.css
│       ├── radar.css
│       ├── screen.css
│       ├── solarized-dark.css
│       ├── solarized-light.css
│       ├── torpedo.css
│       └── vostok.css
├── doc.go
├── env/
│   ├── doc.go
│   ├── env.go
│   ├── env_test.go
│   └── testdata/
│       └── TestNewVirtualEnv.golden
├── etc/
│   ├── soar.blacklist
│   └── soar.yaml
├── genver.sh
├── go.mod
├── go.sum
├── retool-install.sh
├── revive.toml
├── test/
│   ├── env.bats
│   ├── fixture/
│   │   ├── test_Check_Max_Join_Table_Count_Default.golden
│   │   ├── test_Check_Max_Join_Table_Count_Overflow.golden
│   │   ├── test_Check_Soar_Delimiter.golden
│   │   ├── test_Check_Soar_Max_Column_Count.golden
│   │   ├── test_Check_Soar_SQL_Fingerprint.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Alwaystrue_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Countstar_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Delimiter_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Distinctstar_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Dml2select_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Dmlorderby_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Having_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Mergealter_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Or2in_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Orderbynull_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Rmparenthesis_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Standard_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Star2columns_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Truncate_.golden
│   │   ├── test_Check_Soar_SQL_Rewrite_Unionall_.golden
│   │   ├── test_Check_Soar_SQL_pretty_And_Compress_.golden
│   │   ├── test_Check_get_tables_from_SQL.golden
│   │   ├── test_Check_soar_for_pipe_input.golden
│   │   ├── test_Check_soar_query_for_input_file.golden
│   │   ├── test_Check_soar_report_for_html.golden
│   │   ├── test_Check_soar_report_for_json.golden
│   │   ├── test_Check_soar_report_for_markdown.golden
│   │   ├── test_Check_the_default_config_of_the_changes.golden
│   │   ├── test_Run_all_test_cases.golden
│   │   ├── test_Run_default_printconfig_cases.golden
│   │   └── test_Simple_Query_Optimizer.golden
│   ├── main.bats
│   ├── other.bats
│   ├── query.bats
│   ├── sql/
│   │   └── README.md
│   └── test_helper.bash
└── tools.json
Download .txt
SYMBOL INDEX (812 symbols across 57 files)

FILE: advisor/explainer.go
  function checkExplainSelectType (line 42) | func checkExplainSelectType(exp *database.ExplainInfo) {
  function checkExplainAccessType (line 67) | func checkExplainAccessType(exp *database.ExplainInfo) {
  function checkExplainRef (line 113) | func checkExplainRef(exp *database.ExplainInfo) {
  function checkExplainRows (line 130) | func checkExplainRows(exp *database.ExplainInfo) {
  function checkExplainFiltered (line 150) | func checkExplainFiltered(exp *database.ExplainInfo) {
  function ExplainAdvisor (line 172) | func ExplainAdvisor(exp *database.ExplainInfo) map[string]Rule {
  function DigestExplainText (line 214) | func DigestExplainText(text string) {

FILE: advisor/explainer_test.go
  function TestDigestExplainText (line 25) | func TestDigestExplainText(t *testing.T) {

FILE: advisor/heuristic.go
  method RuleOK (line 41) | func (q *Query4Audit) RuleOK() Rule {
  method RuleImplicitAlias (line 46) | func (q *Query4Audit) RuleImplicitAlias() Rule {
  method RuleStarAlias (line 65) | func (q *Query4Audit) RuleStarAlias() Rule {
  method RuleSameAlias (line 77) | func (q *Query4Audit) RuleSameAlias() Rule {
  method RulePrefixLike (line 105) | func (q *Query4Audit) RulePrefixLike() Rule {
  method RuleEqualLike (line 128) | func (q *Query4Audit) RuleEqualLike() Rule {
  method RuleImplicitConversion (line 161) | func (idxAdv *IndexAdvisor) RuleImplicitConversion() Rule {
  function timeFormatCheck (line 376) | func timeFormatCheck(t string) bool {
  method RuleNoWhere (line 385) | func (q *Query4Audit) RuleNoWhere() Rule {
  method RuleOrderByRand (line 418) | func (q *Query4Audit) RuleOrderByRand() Rule {
  method RuleOffsetLimit (line 440) | func (q *Query4Audit) RuleOffsetLimit() Rule {
  method RuleGroupByConst (line 464) | func (q *Query4Audit) RuleGroupByConst() Rule {
  method RuleGroupByConst (line 484) | func (idxAdv *IndexAdvisor) RuleGroupByConst() Rule {
  method RuleOrderByConst (line 506) | func (q *Query4Audit) RuleOrderByConst() Rule {
  method RuleOrderByConst (line 527) | func (idxAdv *IndexAdvisor) RuleOrderByConst() Rule {
  method RuleDiffGroupByOrderBy (line 549) | func (q *Query4Audit) RuleDiffGroupByOrderBy() Rule {
  method RuleExplicitOrderBy (line 620) | func (q *Query4Audit) RuleExplicitOrderBy() Rule {
  method RuleOrderByExpr (line 638) | func (q *Query4Audit) RuleOrderByExpr() Rule {
  method RuleGroupByExpr (line 701) | func (q *Query4Audit) RuleGroupByExpr() Rule {
  method RuleTblCommentCheck (line 764) | func (q *Query4Audit) RuleTblCommentCheck() Rule {
  method RuleSelectStar (line 788) | func (q *Query4Audit) RuleSelectStar() Rule {
  method RuleInsertColDef (line 811) | func (q *Query4Audit) RuleInsertColDef() Rule {
  method RuleAddDefaultValue (line 824) | func (q *Query4Audit) RuleAddDefaultValue() Rule {
  method RuleColCommentCheck (line 879) | func (q *Query4Audit) RuleColCommentCheck() Rule {
  method RuleIPString (line 920) | func (q *Query4Audit) RuleIPString() Rule {
  method RuleDateNotQuote (line 942) | func (q *Query4Audit) RuleDateNotQuote() Rule {
  method RuleSQLCalcFoundRows (line 989) | func (q *Query4Audit) RuleSQLCalcFoundRows() Rule {
  method RuleCommaAnsiJoin (line 1002) | func (q *Query4Audit) RuleCommaAnsiJoin() Rule {
  method RuleDupJoin (line 1029) | func (q *Query4Audit) RuleDupJoin() Rule {
  method RuleImpossibleOuterJoin (line 1060) | func (idxAdv *IndexAdvisor) RuleImpossibleOuterJoin() Rule {
  method RuleNoDeterministicGroupby (line 1110) | func (q *Query4Audit) RuleNoDeterministicGroupby() Rule {
  method RuleNoDeterministicLimit (line 1152) | func (q *Query4Audit) RuleNoDeterministicLimit() Rule {
  method RuleUpdateDeleteWithLimit (line 1169) | func (q *Query4Audit) RuleUpdateDeleteWithLimit() Rule {
  method RuleUpdateDeleteWithOrderby (line 1181) | func (q *Query4Audit) RuleUpdateDeleteWithOrderby() Rule {
  method RuleUpdateSetAnd (line 1193) | func (q *Query4Audit) RuleUpdateSetAnd() Rule {
  method RuleImpossibleWhere (line 1211) | func (q *Query4Audit) RuleImpossibleWhere() Rule {
  method RuleMeaninglessWhere (line 1276) | func (q *Query4Audit) RuleMeaninglessWhere() Rule {
  method RuleLoadFile (line 1386) | func (q *Query4Audit) RuleLoadFile() Rule {
  method RuleMultiCompare (line 1413) | func (q *Query4Audit) RuleMultiCompare() Rule {
  method RuleCreateOnUpdate (line 1434) | func (q *Query4Audit) RuleCreateOnUpdate() Rule {
  method RuleUpdateOnUpdate (line 1477) | func (idxAdv *IndexAdvisor) RuleUpdateOnUpdate() Rule {
  method RuleStandardINEQ (line 1511) | func (q *Query4Audit) RuleStandardINEQ() Rule {
  method RuleUseKeyWord (line 1524) | func (q *Query4Audit) RuleUseKeyWord() Rule {
  method RulePluralWord (line 1566) | func (q *Query4Audit) RulePluralWord() Rule {
  method RuleMultiBytesWord (line 1607) | func (q *Query4Audit) RuleMultiBytesWord() Rule {
  method RuleInvisibleUnicode (line 1623) | func (q *Query4Audit) RuleInvisibleUnicode() Rule {
  method RuleInsertSelect (line 1646) | func (q *Query4Audit) RuleInsertSelect() Rule {
  method RuleInsertOnDup (line 1659) | func (q *Query4Audit) RuleInsertOnDup() Rule {
  method RuleInSubquery (line 1672) | func (q *Query4Audit) RuleInSubquery() Rule {
  method RuleSubqueryDepth (line 1687) | func (q *Query4Audit) RuleSubqueryDepth() Rule {
  method RuleSubQueryLimit (line 1697) | func (q *Query4Audit) RuleSubQueryLimit() Rule {
  method RuleSubQueryFunctions (line 1722) | func (q *Query4Audit) RuleSubQueryFunctions() Rule {
  method RuleUNIONLimit (line 1748) | func (q *Query4Audit) RuleUNIONLimit() Rule {
  method RuleMultiValueAttribute (line 1779) | func (q *Query4Audit) RuleMultiValueAttribute() Rule {
  method RuleAddDelimiter (line 1792) | func (q *Query4Audit) RuleAddDelimiter() Rule {
  method RuleRecursiveDependency (line 1805) | func (q *Query4Audit) RuleRecursiveDependency() Rule {
  method RuleImpreciseDataType (line 1841) | func (q *Query4Audit) RuleImpreciseDataType() Rule {
  method RuleValuesInDefinition (line 1903) | func (q *Query4Audit) RuleValuesInDefinition() Rule {
  method RuleIndexAttributeOrder (line 1941) | func (q *Query4Audit) RuleIndexAttributeOrder() Rule {
  method RuleNullUsage (line 1974) | func (q *Query4Audit) RuleNullUsage() Rule {
  method RuleStringConcatenation (line 1987) | func (q *Query4Audit) RuleStringConcatenation() Rule {
  method RuleSysdate (line 2044) | func (q *Query4Audit) RuleSysdate() Rule {
  method RuleCountConst (line 2061) | func (q *Query4Audit) RuleCountConst() Rule {
  method RuleSumNPE (line 2075) | func (q *Query4Audit) RuleSumNPE() Rule {
  method RuleForbiddenTrigger (line 2093) | func (q *Query4Audit) RuleForbiddenTrigger() Rule {
  method RuleForbiddenProcedure (line 2118) | func (q *Query4Audit) RuleForbiddenProcedure() Rule {
  method RuleForbiddenFunction (line 2143) | func (q *Query4Audit) RuleForbiddenFunction() Rule {
  method RulePatternMatchingUsage (line 2168) | func (q *Query4Audit) RulePatternMatchingUsage() Rule {
  method RuleSpaghettiQueryAlert (line 2181) | func (q *Query4Audit) RuleSpaghettiQueryAlert() Rule {
  method RuleReduceNumberOfJoin (line 2190) | func (q *Query4Audit) RuleReduceNumberOfJoin() Rule {
  method RuleDistinctUsage (line 2226) | func (q *Query4Audit) RuleDistinctUsage() Rule {
  method RuleCountDistinctMultiCol (line 2240) | func (q *Query4Audit) RuleCountDistinctMultiCol() Rule {
  method RuleDistinctStar (line 2258) | func (q *Query4Audit) RuleDistinctStar() Rule {
  method RuleHavingClause (line 2278) | func (q *Query4Audit) RuleHavingClause() Rule {
  method RuleUpdatePrimaryKey (line 2295) | func (idxAdv *IndexAdvisor) RuleUpdatePrimaryKey() Rule {
  method RuleNestedSubQueries (line 2332) | func (q *Query4Audit) RuleNestedSubQueries() Rule {
  method RuleMultiDeleteUpdate (line 2347) | func (q *Query4Audit) RuleMultiDeleteUpdate() Rule {
  method RuleMultiDBJoin (line 2365) | func (q *Query4Audit) RuleMultiDBJoin() Rule {
  method RuleORUsage (line 2388) | func (q *Query4Audit) RuleORUsage() Rule {
  method RuleSpaceWithQuote (line 2422) | func (q *Query4Audit) RuleSpaceWithQuote() Rule {
  method RuleHint (line 2444) | func (q *Query4Audit) RuleHint() Rule {
  method RuleNot (line 2461) | func (q *Query4Audit) RuleNot() Rule {
  method RuleInsertValues (line 2478) | func (q *Query4Audit) RuleInsertValues() Rule {
  method RuleFullWidthQuote (line 2493) | func (q *Query4Audit) RuleFullWidthQuote() Rule {
  method RuleUNIONUsage (line 2511) | func (q *Query4Audit) RuleUNIONUsage() Rule {
  method RuleDistinctJoinUsage (line 2523) | func (q *Query4Audit) RuleDistinctJoinUsage() Rule {
  method RuleReadablePasswords (line 2539) | func (q *Query4Audit) RuleReadablePasswords() Rule {
  method RuleDataDrop (line 2587) | func (q *Query4Audit) RuleDataDrop() Rule {
  method RuleInjection (line 2605) | func (q *Query4Audit) RuleInjection() Rule {
  method RuleCompareWithFunction (line 2623) | func (q *Query4Audit) RuleCompareWithFunction() Rule {
  method RuleCountStar (line 2661) | func (q *Query4Audit) RuleCountStar() Rule {
  method RuleTruncateTable (line 2675) | func (q *Query4Audit) RuleTruncateTable() Rule {
  method RuleIn (line 2687) | func (q *Query4Audit) RuleIn() Rule {
  method RuleIsNullIsNotNull (line 2741) | func (q *Query4Audit) RuleIsNullIsNotNull() Rule {
  method RuleVarcharVSChar (line 2754) | func (q *Query4Audit) RuleVarcharVSChar() Rule {
  method RuleCreateDualTable (line 2795) | func (q *Query4Audit) RuleCreateDualTable() Rule {
  method RuleAlterCharset (line 2808) | func (q *Query4Audit) RuleAlterCharset() Rule {
  method RuleAlterDropColumn (line 2844) | func (q *Query4Audit) RuleAlterDropColumn() Rule {
  method RuleAlterDropKey (line 2871) | func (q *Query4Audit) RuleAlterDropKey() Rule {
  method RuleBLOBNotNull (line 2893) | func (q *Query4Audit) RuleBLOBNotNull() Rule {
  method RuleTooManyKeys (line 2951) | func (q *Query4Audit) RuleTooManyKeys() Rule {
  method RuleTooManyKeyParts (line 2968) | func (q *Query4Audit) RuleTooManyKeyParts() Rule {
  method RulePKNotInt (line 3010) | func (q *Query4Audit) RulePKNotInt() Rule {
  method RuleOrderByMultiDirection (line 3056) | func (q *Query4Audit) RuleOrderByMultiDirection() Rule {
  method RuleUniqueKeyDup (line 3079) | func (q *Query4Audit) RuleUniqueKeyDup() Rule {
  method RuleFulltextIndex (line 3122) | func (q *Query4Audit) RuleFulltextIndex() Rule {
  method RuleTimestampDefault (line 3154) | func (q *Query4Audit) RuleTimestampDefault() Rule {
  method RuleAutoIncrementInitNotZero (line 3231) | func (q *Query4Audit) RuleAutoIncrementInitNotZero() Rule {
  method RuleColumnWithCharset (line 3251) | func (q *Query4Audit) RuleColumnWithCharset() Rule {
  method RuleTableCharsetCheck (line 3320) | func (q *Query4Audit) RuleTableCharsetCheck() Rule {
  method RuleForbiddenView (line 3384) | func (q *Query4Audit) RuleForbiddenView() Rule {
  method RuleForbiddenTempTable (line 3410) | func (q *Query4Audit) RuleForbiddenTempTable() Rule {
  method RuleTableCollateCheck (line 3435) | func (q *Query4Audit) RuleTableCollateCheck() Rule {
  method RuleBlobDefaultValue (line 3499) | func (q *Query4Audit) RuleBlobDefaultValue() Rule {
  method RuleIntPrecision (line 3548) | func (q *Query4Audit) RuleIntPrecision() Rule {
  method RuleVarcharLength (line 3605) | func (q *Query4Audit) RuleVarcharLength() Rule {
  method RuleColumnNotAllowType (line 3650) | func (q *Query4Audit) RuleColumnNotAllowType() Rule {
  method RuleTimePrecision (line 3682) | func (q *Query4Audit) RuleTimePrecision() Rule {
  method RuleNoOSCKey (line 3727) | func (q *Query4Audit) RuleNoOSCKey() Rule {
  method RuleTooManyFields (line 3745) | func (q *Query4Audit) RuleTooManyFields() Rule {
  method RuleMaxTextColsCount (line 3762) | func (q *Query4Audit) RuleMaxTextColsCount() Rule {
  method RuleMaxTextColsCount (line 3790) | func (idxAdv *IndexAdvisor) RuleMaxTextColsCount() Rule {
  method RuleAllowEngine (line 3830) | func (q *Query4Audit) RuleAllowEngine() Rule {
  method RulePartitionNotAllowed (line 3888) | func (q *Query4Audit) RulePartitionNotAllowed() Rule {
  method RuleAutoIncUnsigned (line 3913) | func (q *Query4Audit) RuleAutoIncUnsigned() Rule {
  method RuleSpaceAfterDot (line 3968) | func (q *Query4Audit) RuleSpaceAfterDot() Rule {
  method RuleIdxPrefix (line 3991) | func (q *Query4Audit) RuleIdxPrefix() Rule {
  method RuleStandardName (line 4030) | func (q *Query4Audit) RuleStandardName() Rule {
  function MergeConflictHeuristicRules (line 4061) | func MergeConflictHeuristicRules(rules map[string]Rule) map[string]Rule {
  function RuleMySQLError (line 4097) | func RuleMySQLError(item string, err error) Rule {

FILE: advisor/heuristic_test.go
  function TestRuleImplicitAlias (line 32) | func TestRuleImplicitAlias(t *testing.T) {
  function TestRuleStarAlias (line 61) | func TestRuleStarAlias(t *testing.T) {
  function TestRuleSameAlias (line 91) | func TestRuleSameAlias(t *testing.T) {
  function TestRulePrefixLike (line 112) | func TestRulePrefixLike(t *testing.T) {
  function TestRuleEqualLike (line 133) | func TestRuleEqualLike(t *testing.T) {
  function TestTimeFormatError (line 176) | func TestTimeFormatError(t *testing.T) {
  function TestRuleNoWhere (line 202) | func TestRuleNoWhere(t *testing.T) {
  function TestRuleOrderByRand (line 240) | func TestRuleOrderByRand(t *testing.T) {
  function TestRuleOffsetLimit (line 260) | func TestRuleOffsetLimit(t *testing.T) {
  function TestRuleGroupByConst (line 281) | func TestRuleGroupByConst(t *testing.T) {
  function TestRuleOrderByConst (line 302) | func TestRuleOrderByConst(t *testing.T) {
  function TestRuleDiffGroupByOrderBy (line 323) | func TestRuleDiffGroupByOrderBy(t *testing.T) {
  function TestRuleExplicitOrderBy (line 344) | func TestRuleExplicitOrderBy(t *testing.T) {
  function TestRuleOrderByExpr (line 364) | func TestRuleOrderByExpr(t *testing.T) {
  function TestRuleGroupByExpr (line 407) | func TestRuleGroupByExpr(t *testing.T) {
  function TestRuleTblCommentCheck (line 436) | func TestRuleTblCommentCheck(t *testing.T) {
  function TestRuleSelectStar (line 461) | func TestRuleSelectStar(t *testing.T) {
  function TestRuleInsertColDef (line 485) | func TestRuleInsertColDef(t *testing.T) {
  function TestRuleAddDefaultValue (line 523) | func TestRuleAddDefaultValue(t *testing.T) {
  function TestRuleColCommentCheck (line 566) | func TestRuleColCommentCheck(t *testing.T) {
  function TestRuleIPString (line 608) | func TestRuleIPString(t *testing.T) {
  function TestRuleDateNotQuote (line 648) | func TestRuleDateNotQuote(t *testing.T) {
  function TestRuleSQLCalcFoundRows (line 692) | func TestRuleSQLCalcFoundRows(t *testing.T) {
  function TestRuleCommaAnsiJoin (line 712) | func TestRuleCommaAnsiJoin(t *testing.T) {
  function TestRuleDupJoin (line 732) | func TestRuleDupJoin(t *testing.T) {
  function TestRuleNoDeterministicGroupby (line 752) | func TestRuleNoDeterministicGroupby(t *testing.T) {
  function TestRuleNoDeterministicLimit (line 799) | func TestRuleNoDeterministicLimit(t *testing.T) {
  function TestRuleUpdateDeleteWithLimit (line 819) | func TestRuleUpdateDeleteWithLimit(t *testing.T) {
  function TestRuleUpdateDeleteWithOrderby (line 856) | func TestRuleUpdateDeleteWithOrderby(t *testing.T) {
  function TestRuleUpdateSetAnd (line 893) | func TestRuleUpdateSetAnd(t *testing.T) {
  function TestRuleImpossibleWhere (line 933) | func TestRuleImpossibleWhere(t *testing.T) {
  function TestRuleMeaninglessWhere (line 973) | func TestRuleMeaninglessWhere(t *testing.T) {
  function TestRuleLoadFile (line 1024) | func TestRuleLoadFile(t *testing.T) {
  function TestRuleMultiCompare (line 1056) | func TestRuleMultiCompare(t *testing.T) {
  function TestRuleCreateOnUpdate (line 1103) | func TestRuleCreateOnUpdate(t *testing.T) {
  function TestRuleUpdateOnUpdate (line 1151) | func TestRuleUpdateOnUpdate(t *testing.T) {
  function TestRuleStandardINEQ (line 1208) | func TestRuleStandardINEQ(t *testing.T) {
  function TestRuleUseKeyWord (line 1229) | func TestRuleUseKeyWord(t *testing.T) {
  function TestRulePluralWord (line 1268) | func TestRulePluralWord(t *testing.T) {
  function TestRuleMultiBytesWord (line 1307) | func TestRuleMultiBytesWord(t *testing.T) {
  function TestRuleInvisibleUnicode (line 1345) | func TestRuleInvisibleUnicode(t *testing.T) {
  function TestRuleInsertSelect (line 1382) | func TestRuleInsertSelect(t *testing.T) {
  function TestRuleInsertOnDup (line 1402) | func TestRuleInsertOnDup(t *testing.T) {
  function TestRuleInSubquery (line 1422) | func TestRuleInSubquery(t *testing.T) {
  function TestRuleMultiValueAttribute (line 1443) | func TestRuleMultiValueAttribute(t *testing.T) {
  function TestRuleAddDelimiter (line 1463) | func TestRuleAddDelimiter(t *testing.T) {
  function TestRuleRecursiveDependency (line 1496) | func TestRuleRecursiveDependency(t *testing.T) {
  function TestRuleImpreciseDataType (line 1539) | func TestRuleImpreciseDataType(t *testing.T) {
  function TestRuleValuesInDefinition (line 1584) | func TestRuleValuesInDefinition(t *testing.T) {
  function TestRuleIndexAttributeOrder (line 1606) | func TestRuleIndexAttributeOrder(t *testing.T) {
  function TestRuleNullUsage (line 1628) | func TestRuleNullUsage(t *testing.T) {
  function TestRuleStringConcatenation (line 1648) | func TestRuleStringConcatenation(t *testing.T) {
  function TestRuleSysdate (line 1694) | func TestRuleSysdate(t *testing.T) {
  function TestRuleCountConst (line 1715) | func TestRuleCountConst(t *testing.T) {
  function TestRuleSumNPE (line 1755) | func TestRuleSumNPE(t *testing.T) {
  function TestRulePatternMatchingUsage (line 1793) | func TestRulePatternMatchingUsage(t *testing.T) {
  function TestRuleSpaghettiQueryAlert (line 1813) | func TestRuleSpaghettiQueryAlert(t *testing.T) {
  function TestRuleReduceNumberOfJoin (line 1834) | func TestRuleReduceNumberOfJoin(t *testing.T) {
  function TestRuleDistinctUsage (line 1854) | func TestRuleDistinctUsage(t *testing.T) {
  function TestRuleCountDistinctMultiCol (line 1874) | func TestRuleCountDistinctMultiCol(t *testing.T) {
  function TestRuleDistinctStar (line 1913) | func TestRuleDistinctStar(t *testing.T) {
  function TestRuleHavingClause (line 1953) | func TestRuleHavingClause(t *testing.T) {
  function TestRuleForbiddenTrigger (line 1973) | func TestRuleForbiddenTrigger(t *testing.T) {
  function TestRuleForbiddenProcedure (line 1990) | func TestRuleForbiddenProcedure(t *testing.T) {
  function TestRuleForbiddenFunction (line 2007) | func TestRuleForbiddenFunction(t *testing.T) {
  function TestRuleForbiddenView (line 2024) | func TestRuleForbiddenView(t *testing.T) {
  function TestRuleForbiddenTempTable (line 2042) | func TestRuleForbiddenTempTable(t *testing.T) {
  function TestRuleNestedSubQueries (line 2059) | func TestRuleNestedSubQueries(t *testing.T) {
  function TestRuleMultiDeleteUpdate (line 2079) | func TestRuleMultiDeleteUpdate(t *testing.T) {
  function TestRuleMultiDBJoin (line 2100) | func TestRuleMultiDBJoin(t *testing.T) {
  function TestRuleORUsage (line 2122) | func TestRuleORUsage(t *testing.T) {
  function TestRuleSpaceWithQuote (line 2159) | func TestRuleSpaceWithQuote(t *testing.T) {
  function TestRuleHint (line 2200) | func TestRuleHint(t *testing.T) {
  function TestRuleNot (line 2241) | func TestRuleNot(t *testing.T) {
  function TestRuleInsertValues (line 2279) | func TestRuleInsertValues(t *testing.T) {
  function TestRuleFullWidthQuote (line 2319) | func TestRuleFullWidthQuote(t *testing.T) {
  function TestRuleUNIONUsage (line 2357) | func TestRuleUNIONUsage(t *testing.T) {
  function TestRuleDistinctJoinUsage (line 2377) | func TestRuleDistinctJoinUsage(t *testing.T) {
  function TestRuleSubQueryLimit (line 2397) | func TestRuleSubQueryLimit(t *testing.T) {
  function TestRuleSubQueryFunctions (line 2434) | func TestRuleSubQueryFunctions(t *testing.T) {
  function TestRuleUNIONLimit (line 2471) | func TestRuleUNIONLimit(t *testing.T) {
  function TestRuleReadablePasswords (line 2510) | func TestRuleReadablePasswords(t *testing.T) {
  function TestRuleDataDrop (line 2531) | func TestRuleDataDrop(t *testing.T) {
  function TestRuleInjection (line 2554) | func TestRuleInjection(t *testing.T) {
  function TestCompareWithFunction (line 2594) | func TestCompareWithFunction(t *testing.T) {
  function TestRuleCountStar (line 2644) | func TestRuleCountStar(t *testing.T) {
  function TestRuleTruncateTable (line 2664) | func TestRuleTruncateTable(t *testing.T) {
  function TestRuleIn (line 2685) | func TestRuleIn(t *testing.T) {
  function TestRuleIsNullIsNotNull (line 2708) | func TestRuleIsNullIsNotNull(t *testing.T) {
  function TestRuleVarcharVSChar (line 2729) | func TestRuleVarcharVSChar(t *testing.T) {
  function TestRuleCreateDualTable (line 2751) | func TestRuleCreateDualTable(t *testing.T) {
  function TestRuleAlterCharset (line 2771) | func TestRuleAlterCharset(t *testing.T) {
  function TestRuleAlterDropColumn (line 2818) | func TestRuleAlterDropColumn(t *testing.T) {
  function TestRuleAlterDropKey (line 2855) | func TestRuleAlterDropKey(t *testing.T) {
  function TestRuleCantBeNull (line 2893) | func TestRuleCantBeNull(t *testing.T) {
  function TestRuleTooManyKeyParts (line 2933) | func TestRuleTooManyKeyParts(t *testing.T) {
  function TestRuleTooManyKeys (line 2955) | func TestRuleTooManyKeys(t *testing.T) {
  function TestRulePKNotInt (line 2977) | func TestRulePKNotInt(t *testing.T) {
  function TestRuleOrderByMultiDirection (line 3020) | func TestRuleOrderByMultiDirection(t *testing.T) {
  function TestRuleUniqueKeyDup (line 3062) | func TestRuleUniqueKeyDup(t *testing.T) {
  function TestRuleFulltextIndex (line 3102) | func TestRuleFulltextIndex(t *testing.T) {
  function TestRuleTimestampDefault (line 3143) | func TestRuleTimestampDefault(t *testing.T) {
  function TestRuleAutoIncrementInitNotZero (line 3187) | func TestRuleAutoIncrementInitNotZero(t *testing.T) {
  function TestRuleColumnWithCharset (line 3227) | func TestRuleColumnWithCharset(t *testing.T) {
  function TestRuleTableCharsetCheck (line 3268) | func TestRuleTableCharsetCheck(t *testing.T) {
  function TestRuleTableCollateCheck (line 3308) | func TestRuleTableCollateCheck(t *testing.T) {
  function TestRuleBlobDefaultValue (line 3346) | func TestRuleBlobDefaultValue(t *testing.T) {
  function TestRuleIntPrecision (line 3399) | func TestRuleIntPrecision(t *testing.T) {
  function TestRuleVarcharLength (line 3445) | func TestRuleVarcharLength(t *testing.T) {
  function TestRuleColumnNotAllowType (line 3487) | func TestRuleColumnNotAllowType(t *testing.T) {
  function TestRuleTimePrecision (line 3529) | func TestRuleTimePrecision(t *testing.T) {
  function TestRuleNoOSCKey (line 3570) | func TestRuleNoOSCKey(t *testing.T) {
  function TestRuleTooManyFields (line 3609) | func TestRuleTooManyFields(t *testing.T) {
  function TestRuleMaxTextColsCount (line 3631) | func TestRuleMaxTextColsCount(t *testing.T) {
  function TestRuleMaxTextColsCountWithEnv (line 3653) | func TestRuleMaxTextColsCountWithEnv(t *testing.T) {
  function TestRuleAllowEngine (line 3725) | func TestRuleAllowEngine(t *testing.T) {
  function TestRulePartitionNotAllowed (line 3763) | func TestRulePartitionNotAllowed(t *testing.T) {
  function TestRuleAutoIncUnsigned (line 3790) | func TestRuleAutoIncUnsigned(t *testing.T) {
  function TestRuleIdxPrefix (line 3811) | func TestRuleIdxPrefix(t *testing.T) {
  function TestRuleStandardName (line 3851) | func TestRuleStandardName(t *testing.T) {
  function TestRuleSpaceAfterDot (line 3893) | func TestRuleSpaceAfterDot(t *testing.T) {
  function TestRuleMySQLError (line 3932) | func TestRuleMySQLError(t *testing.T) {
  function TestMergeConflictHeuristicRules (line 3941) | func TestMergeConflictHeuristicRules(t *testing.T) {

FILE: advisor/index.go
  constant IndexNameMaxLength (line 35) | IndexNameMaxLength = 64
  type IndexAdvisor (line 39) | type IndexAdvisor struct
    method IndexAdvise (line 182) | func (idxAdv *IndexAdvisor) IndexAdvise() IndexAdvises {
    method idxColsTypeCheck (line 362) | func (idxAdv *IndexAdvisor) idxColsTypeCheck(idxList []IndexInfo) []In...
    method mergeIndexes (line 479) | func (idxAdv *IndexAdvisor) mergeIndexes(idxList []IndexInfo) []IndexI...
    method buildJoinIndex (line 625) | func (idxAdv *IndexAdvisor) buildJoinIndex(meta common.Meta) []IndexIn...
    method buildIndex (line 646) | func (idxAdv *IndexAdvisor) buildIndex(idxList map[string]map[string][...
    method buildIndexWithNoEnv (line 693) | func (idxAdv *IndexAdvisor) buildIndexWithNoEnv(indexList map[string]m...
    method mergeIndex (line 725) | func (idxAdv *IndexAdvisor) mergeIndex(idxList map[string]map[string][...
    method calcCardinality (line 909) | func (idxAdv *IndexAdvisor) calcCardinality(cols []*common.Column) []*...
    method HeuristicCheck (line 1038) | func (idxAdv *IndexAdvisor) HeuristicCheck(q Query4Audit) map[string]R...
  type IndexInfo (line 53) | type IndexInfo struct
  type IndexAdvises (line 62) | type IndexAdvises
    method Format (line 964) | func (idxAdvs IndexAdvises) Format() map[string]Rule {
  function mergeAdvices (line 65) | func mergeAdvices(dst []IndexInfo, src ...IndexInfo) IndexAdvises {
  function NewAdvisor (line 89) | func NewAdvisor(env *env.VirtualEnv, rEnv database.Connector, q Query4Au...
  function getRandomIndexSuffix (line 593) | func getRandomIndexSuffix() string {
  function rmSelfDupIndex (line 598) | func rmSelfDupIndex(indexes []IndexInfo) []IndexInfo {
  function CompleteColumnsInfo (line 768) | func CompleteColumnsInfo(stmt sqlparser.Statement, cols []*common.Column...
  function DuplicateKeyChecker (line 1064) | func DuplicateKeyChecker(conn *database.Connector, databases ...string) ...

FILE: advisor/index_test.go
  function TestMain (line 40) | func TestMain(m *testing.M) {
  function TestRuleImplicitConversion (line 70) | func TestRuleImplicitConversion(t *testing.T) {
  function TestRuleImpossibleOuterJoin (line 151) | func TestRuleImpossibleOuterJoin(t *testing.T) {
  function TestIndexAdvisorRuleGroupByConst (line 185) | func TestIndexAdvisorRuleGroupByConst(t *testing.T) {
  function TestIndexAdvisorRuleOrderByConst (line 247) | func TestIndexAdvisorRuleOrderByConst(t *testing.T) {
  function TestRuleUpdatePrimaryKey (line 309) | func TestRuleUpdatePrimaryKey(t *testing.T) {
  function TestIndexAdvise (line 369) | func TestIndexAdvise(t *testing.T) {
  function TestIndexAdviseNoEnv (line 400) | func TestIndexAdviseNoEnv(t *testing.T) {
  function TestDuplicateKeyChecker (line 431) | func TestDuplicateKeyChecker(t *testing.T) {
  function TestMergeAdvices (line 440) | func TestMergeAdvices(t *testing.T) {
  function TestIdxColsTypeCheck (line 464) | func TestIdxColsTypeCheck(t *testing.T) {
  function TestGetRandomIndexSuffix (line 510) | func TestGetRandomIndexSuffix(t *testing.T) {

FILE: advisor/rules.go
  type Query4Audit (line 37) | type Query4Audit struct
  function NewQuery4Audit (line 44) | func NewQuery4Audit(sql string, options ...string) (*Query4Audit, error) {
  type Rule (line 71) | type Rule struct
  function init (line 113) | func init() {
  function InitHeuristicRules (line 118) | func InitHeuristicRules() {
  function IsIgnoreRule (line 1214) | func IsIgnoreRule(item string) bool {
  function InBlackList (line 1229) | func InBlackList(sql string) bool {
  function FormatSuggest (line 1250) | func FormatSuggest(sql string, currentDB string, format string, suggests...
  type JSONSuggest (line 1491) | type JSONSuggest struct
  function formatJSON (line 1502) | func formatJSON(sql string, db string, suggest map[string]Rule) string {
  function ListHeuristicRules (line 1583) | func ListHeuristicRules(rules ...map[string]Rule) {
  function ListTestSQLs (line 1606) | func ListTestSQLs() {

FILE: advisor/rules_test.go
  function TestListTestSQLs (line 26) | func TestListTestSQLs(t *testing.T) {
  function TestListHeuristicRules (line 40) | func TestListHeuristicRules(t *testing.T) {
  function TestInBlackList (line 49) | func TestInBlackList(t *testing.T) {
  function TestIsIgnoreRule (line 64) | func TestIsIgnoreRule(t *testing.T) {
  function TestNewQuery4Audit (line 73) | func TestNewQuery4Audit(t *testing.T) {

FILE: ast/meta.go
  function GetTableFromExprs (line 28) | func GetTableFromExprs(exprs sqlparser.TableExprs, metas ...common.Meta)...
  function GetMeta (line 70) | func GetMeta(stmt sqlparser.Statement, meta common.Meta) common.Meta {
  function appendTable (line 128) | func appendTable(tb sqlparser.TableName, as string, meta map[string]*com...
  function mergeAlias (line 150) | func mergeAlias(db, tb, as string, meta map[string]*common.DB) {
  function FindColumn (line 200) | func FindColumn(node sqlparser.SQLNode) []*common.Column {
  function inEqIndexAble (line 225) | func inEqIndexAble(node sqlparser.SQLNode) bool {
  function FindWhereEQ (line 263) | func FindWhereEQ(node sqlparser.SQLNode) []*common.Column {
  function FindWhereINEQ (line 269) | func FindWhereINEQ(node sqlparser.SQLNode) []*common.Column {
  function FindEQColsInWhere (line 276) | func FindEQColsInWhere(node sqlparser.SQLNode) []*common.Column {
  function FindINEQColsInWhere (line 334) | func FindINEQColsInWhere(node sqlparser.SQLNode) []*common.Column {
  function FindGroupByCols (line 377) | func FindGroupByCols(node sqlparser.SQLNode) []*common.Column {
  function FindOrderByCols (line 411) | func FindOrderByCols(node sqlparser.SQLNode) []*common.Column {
  function FindJoinTable (line 448) | func FindJoinTable(node sqlparser.SQLNode, meta common.Meta) common.Meta {
  function findJoinTable (line 477) | func findJoinTable(expr sqlparser.TableExpr, meta common.Meta) {
  function FindJoinCols (line 529) | func FindJoinCols(node sqlparser.SQLNode) [][]*common.Column {
  function FindEQColsInJoinCond (line 595) | func FindEQColsInJoinCond(node sqlparser.SQLNode) []*common.Column {
  function FindINEQColsInJoinCond (line 610) | func FindINEQColsInJoinCond(node sqlparser.SQLNode) []*common.Column {
  function FindSubquery (line 626) | func FindSubquery(depth int, node sqlparser.SQLNode, queries ...string) ...
  function FindAllCondition (line 666) | func FindAllCondition(node sqlparser.SQLNode) []interface{} {
  type Expression (line 681) | type Expression
  constant WhereExpression (line 685) | WhereExpression Expression = "where"
  constant JoinExpression (line 687) | JoinExpression Expression = "join"
  constant GroupByExpression (line 689) | GroupByExpression Expression = "group by"
  constant OrderByExpression (line 691) | OrderByExpression Expression = "order by"
  function FindAllCols (line 695) | func FindAllCols(node sqlparser.SQLNode, targets ...Expression) []*commo...
  function GetSubqueryDepth (line 751) | func GetSubqueryDepth(node sqlparser.SQLNode) int {
  function getColumnName (line 765) | func getColumnName(node sqlparser.SQLNode) (*sqlparser.ColName, string) {

FILE: ast/meta_test.go
  function TestMain (line 34) | func TestMain(m *testing.M) {
  function TestGetTableFromExprs (line 53) | func TestGetTableFromExprs(t *testing.T) {
  function TestGetParseTableWithStmt (line 71) | func TestGetParseTableWithStmt(t *testing.T) {
  function TestFindCondition (line 86) | func TestFindCondition(t *testing.T) {
  function TestFindGroupBy (line 110) | func TestFindGroupBy(t *testing.T) {
  function TestFindOrderBy (line 129) | func TestFindOrderBy(t *testing.T) {
  function TestFindSubquery (line 149) | func TestFindSubquery(t *testing.T) {
  function TestFindJoinTable (line 172) | func TestFindJoinTable(t *testing.T) {
  function TestFindJoinCols (line 197) | func TestFindJoinCols(t *testing.T) {
  function TestFindJoinColBeWhereEQ (line 223) | func TestFindJoinColBeWhereEQ(t *testing.T) {
  function TestFindJoinColBeWhereINEQ (line 247) | func TestFindJoinColBeWhereINEQ(t *testing.T) {
  function TestFindAllCondition (line 271) | func TestFindAllCondition(t *testing.T) {
  function TestFindColumn (line 301) | func TestFindColumn(t *testing.T) {
  function TestFindAllCols (line 322) | func TestFindAllCols(t *testing.T) {
  function TestGetSubqueryDepth (line 354) | func TestGetSubqueryDepth(t *testing.T) {
  function TestAppendTable (line 383) | func TestAppendTable(t *testing.T) {

FILE: ast/node_array.go
  type NodeItem (line 30) | type NodeItem struct
  type NodeList (line 39) | type NodeList struct
    method Add (line 58) | func (l *NodeList) Add(node sqlparser.SQLNode) *NodeItem {
    method Remove (line 86) | func (l *NodeList) Remove(node *NodeItem) error {
    method First (line 116) | func (l *NodeList) First() *NodeItem {
    method Last (line 121) | func (l *NodeList) Last() *NodeItem {
  function NewNodeList (line 46) | func NewNodeList(statement sqlparser.Statement) *NodeList {

FILE: ast/pretty.go
  function Pretty (line 30) | func Pretty(sql string, method string) (output string) {
  function format (line 53) | func format(query string) string {

FILE: ast/pretty_test.go
  function TestPretty (line 127) | func TestPretty(t *testing.T) {
  function TestIsKeyword (line 145) | func TestIsKeyword(t *testing.T) {
  function TestRemoveComments (line 165) | func TestRemoveComments(t *testing.T) {

FILE: ast/rewrite.go
  type Rule (line 35) | type Rule struct
  function init (line 46) | func init() {
  function ListRewriteRules (line 232) | func ListRewriteRules(rules []Rule) {
  type Rewrite (line 257) | type Rewrite struct
    method Rewrite (line 279) | func (rw *Rewrite) Rewrite() *Rewrite {
    method RewriteDelimiter (line 306) | func (rw *Rewrite) RewriteDelimiter() *Rewrite {
    method RewriteStandard (line 316) | func (rw *Rewrite) RewriteStandard() *Rewrite {
    method RewriteAlwaysTrue (line 324) | func (rw *Rewrite) RewriteAlwaysTrue() (reWriter *Rewrite) {
    method RewriteCountStar (line 588) | func (rw *Rewrite) RewriteCountStar() *Rewrite {
    method RewriteInnoDB (line 610) | func (rw *Rewrite) RewriteInnoDB() *Rewrite {
    method RewriteAutoIncrement (line 631) | func (rw *Rewrite) RewriteAutoIncrement() *Rewrite {
    method RewriteIntWidth (line 648) | func (rw *Rewrite) RewriteIntWidth() *Rewrite {
    method RewriteStar2Columns (line 675) | func (rw *Rewrite) RewriteStar2Columns() *Rewrite {
    method RewriteInsertColumns (line 759) | func (rw *Rewrite) RewriteInsertColumns() *Rewrite {
    method RewriteHaving (line 820) | func (rw *Rewrite) RewriteHaving() *Rewrite {
    method RewriteAddOrderByNull (line 852) | func (rw *Rewrite) RewriteAddOrderByNull() *Rewrite {
    method RewriteOr2Union (line 874) | func (rw *Rewrite) RewriteOr2Union() *Rewrite {
    method RewriteUnionAll (line 879) | func (rw *Rewrite) RewriteUnionAll() *Rewrite {
    method RewriteOr2In (line 893) | func (rw *Rewrite) RewriteOr2In() *Rewrite {
    method RewriteInNull (line 1145) | func (rw *Rewrite) RewriteInNull() *Rewrite {
    method RewriteRmParenthesis (line 1150) | func (rw *Rewrite) RewriteRmParenthesis() *Rewrite {
    method rmParenthesis (line 1157) | func (rw *Rewrite) rmParenthesis() {
    method RewriteRemoveDMLOrderBy (line 1235) | func (rw *Rewrite) RewriteRemoveDMLOrderBy() *Rewrite {
    method RewriteGroupByConst (line 1274) | func (rw *Rewrite) RewriteGroupByConst() *Rewrite {
    method RewriteSubQuery2Join (line 1301) | func (rw *Rewrite) RewriteSubQuery2Join() *Rewrite {
    method sub2Join (line 1367) | func (rw *Rewrite) sub2Join(parent, sub string) (string, error) {
    method RewriteJoin2SubQuery (line 1565) | func (rw *Rewrite) RewriteJoin2SubQuery() *Rewrite {
    method RewriteDistinctStar (line 1570) | func (rw *Rewrite) RewriteDistinctStar() *Rewrite {
    method RewriteTruncate (line 1594) | func (rw *Rewrite) RewriteTruncate() *Rewrite {
    method RewriteDML2Select (line 1610) | func (rw *Rewrite) RewriteDML2Select() *Rewrite {
    method RewriteReg2Select (line 1629) | func (rw *Rewrite) RewriteReg2Select() *Rewrite {
  function NewRewrite (line 265) | func NewRewrite(sql string) *Rewrite {
  function isAlwaysTrue (line 340) | func isAlwaysTrue(expr *sqlparser.ComparisonExpr) bool {
  function omitAlwaysTrue (line 396) | func omitAlwaysTrue(node *NodeItem) {
  method or2in (line 911) | func (node *NodeItem) or2in() {
  function mergeExprs (line 988) | func mergeExprs(left, right sqlparser.Expr) *sqlparser.ComparisonExpr {
  function removeDup (line 1096) | func removeDup(vt ...sqlparser.Expr) sqlparser.ValTuple {
  function columnFromWhere (line 1534) | func columnFromWhere(col *sqlparser.ColName, meta common.Meta, columns c...
  function delete2Select (line 1648) | func delete2Select(stmt *sqlparser.Delete) string {
  function regDelete2Select (line 1661) | func regDelete2Select(sql string) string {
  function update2Select (line 1672) | func update2Select(stmt *sqlparser.Update) string {
  function regUpdate2Select (line 1686) | func regUpdate2Select(sql string) string {
  function insert2Select (line 1697) | func insert2Select(stmt *sqlparser.Insert) string {
  function AlterAffectTable (line 1708) | func AlterAffectTable(stmt sqlparser.Statement) string {
  function MergeAlterTables (line 1727) | func MergeAlterTables(sqls ...string) map[string]string {
  function RewriteRuleMatch (line 1803) | func RewriteRuleMatch(name string) bool {

FILE: ast/rewrite_test.go
  function TestRewrite (line 27) | func TestRewrite(t *testing.T) {
  function TestRewriteStar2Columns (line 106) | func TestRewriteStar2Columns(t *testing.T) {
  function TestRewriteInsertColumns (line 170) | func TestRewriteInsertColumns(t *testing.T) {
  function TestRewriteHaving (line 214) | func TestRewriteHaving(t *testing.T) {
  function TestRewriteAddOrderByNull (line 239) | func TestRewriteAddOrderByNull(t *testing.T) {
  function TestRewriteRemoveDMLOrderBy (line 256) | func TestRewriteRemoveDMLOrderBy(t *testing.T) {
  function TestRewriteGroupByConst (line 277) | func TestRewriteGroupByConst(t *testing.T) {
  function TestRewriteStandard (line 308) | func TestRewriteStandard(t *testing.T) {
  function TestRewriteCountStar (line 325) | func TestRewriteCountStar(t *testing.T) {
  function TestRewriteInnoDB (line 346) | func TestRewriteInnoDB(t *testing.T) {
  function TestRewriteAutoIncrement (line 367) | func TestRewriteAutoIncrement(t *testing.T) {
  function TestRewriteIntWidth (line 388) | func TestRewriteIntWidth(t *testing.T) {
  function TestRewriteAlwaysTrue (line 417) | func TestRewriteAlwaysTrue(t *testing.T) {
  function TestRewriteSubQuery2Join (line 501) | func TestRewriteSubQuery2Join(t *testing.T) {
  function TestRewriteDML2Select (line 533) | func TestRewriteDML2Select(t *testing.T) {
  function TestRewriteReg2Select (line 590) | func TestRewriteReg2Select(t *testing.T) {
  function TestRewriteDistinctStar (line 615) | func TestRewriteDistinctStar(t *testing.T) {
  function TestMergeAlterTables (line 653) | func TestMergeAlterTables(t *testing.T) {
  function TestRewriteUnionAll (line 709) | func TestRewriteUnionAll(t *testing.T) {
  function TestRewriteTruncate (line 725) | func TestRewriteTruncate(t *testing.T) {
  function TestRewriteOr2In (line 742) | func TestRewriteOr2In(t *testing.T) {
  function TestRmParenthesis (line 785) | func TestRmParenthesis(t *testing.T) {
  function TestListRewriteRules (line 814) | func TestListRewriteRules(t *testing.T) {

FILE: ast/tidb.go
  function TiParse (line 37) | func TiParse(sql, charset, collation string) ([]ast.StmtNode, error) {
  function removeIncompatibleWords (line 57) | func removeIncompatibleWords(sql string) string {
  function PrintPrettyStmtNode (line 88) | func PrintPrettyStmtNode(sql, charset, collation string) {
  function StmtNode2JSON (line 99) | func StmtNode2JSON(sql, charset, collation string) string {
  function SchemaMetaInfo (line 116) | func SchemaMetaInfo(sql string, defaultDatabase string) []string {

FILE: ast/tidb_test.go
  function TestPrintPrettyStmtNode (line 26) | func TestPrintPrettyStmtNode(t *testing.T) {
  function TestStmtNode2JSON (line 43) | func TestStmtNode2JSON(t *testing.T) {
  function TestSchemaMetaInfo (line 60) | func TestSchemaMetaInfo(t *testing.T) {
  function TestRemoveIncompatibleWords (line 90) | func TestRemoveIncompatibleWords(t *testing.T) {

FILE: ast/token.go
  constant TokenTypeWhitespace (line 30) | TokenTypeWhitespace       = 0
  constant TokenTypeWord (line 31) | TokenTypeWord             = 1
  constant TokenTypeQuote (line 32) | TokenTypeQuote            = 2
  constant TokenTypeBacktickQuote (line 33) | TokenTypeBacktickQuote    = 3
  constant TokenTypeReserved (line 34) | TokenTypeReserved         = 4
  constant TokenTypeReservedToplevel (line 35) | TokenTypeReservedToplevel = 5
  constant TokenTypeReservedNewline (line 36) | TokenTypeReservedNewline  = 6
  constant TokenTypeBoundary (line 37) | TokenTypeBoundary         = 7
  constant TokenTypeComment (line 38) | TokenTypeComment          = 8
  constant TokenTypeBlockComment (line 39) | TokenTypeBlockComment     = 9
  constant TokenTypeNumber (line 40) | TokenTypeNumber           = 10
  constant TokenTypeError (line 41) | TokenTypeError            = 11
  constant TokenTypeVariable (line 42) | TokenTypeVariable         = 12
  function init (line 128) | func init() {
  type Token (line 576) | type Token struct
  function Tokenizer (line 583) | func Tokenizer(sql string) []Token {
  function IsMysqlKeyword (line 616) | func IsMysqlKeyword(name string) bool {
  function getNextToken (line 622) | func getNextToken(buf string, previous Token) Token {
  function getQuotedString (line 766) | func getQuotedString(buf string) string {
  function Tokenize (line 788) | func Tokenize(sql string) []Token {
  function Compress (line 845) | func Compress(sql string) string {
  function SplitStatement (line 854) | func SplitStatement(buf []byte, delimiter []byte) (string, string, []byt...
  function LeftNewLines (line 970) | func LeftNewLines(buf []byte) int {
  function NewLines (line 984) | func NewLines(buf []byte) int {
  function QueryType (line 995) | func QueryType(sql string) string {

FILE: ast/token_test.go
  function TestTokenize (line 28) | func TestTokenize(t *testing.T) {
  function TestTokenizer (line 42) | func TestTokenizer(t *testing.T) {
  function TestGetQuotedString (line 69) | func TestGetQuotedString(t *testing.T) {
  function TestCompress (line 97) | func TestCompress(t *testing.T) {
  function TestFormat (line 111) | func TestFormat(t *testing.T) {
  function TestSplitStatement (line 125) | func TestSplitStatement(t *testing.T) {
  function TestLeftNewLines (line 213) | func TestLeftNewLines(t *testing.T) {
  function TestNewLines (line 234) | func TestNewLines(t *testing.T) {
  function TestQueryType (line 255) | func TestQueryType(t *testing.T) {

FILE: ast/vitess.go
  function PrintPrettyVitessStmtNode (line 29) | func PrintPrettyVitessStmtNode(sql string) {
  function VitessStmtNode2JSON (line 40) | func VitessStmtNode2JSON(sql string) string {

FILE: ast/vitess_test.go
  function TestPrintPrettyVitessStmtNode (line 26) | func TestPrintPrettyVitessStmtNode(t *testing.T) {
  function TestVitessStmtNode2JSON (line 43) | func TestVitessStmtNode2JSON(t *testing.T) {

FILE: cmd/soar/soar.go
  function main (line 36) | func main() {

FILE: cmd/soar/soar_test.go
  function TestMain (line 32) | func TestMain(m *testing.M) {
  function Test_Main (line 52) | func Test_Main(_ *testing.T) {
  function Test_Main_More (line 63) | func Test_Main_More(_ *testing.T) {
  function Test_Main_initQuery (line 81) | func Test_Main_initQuery(t *testing.T) {
  function Test_Main_reportTool (line 103) | func Test_Main_reportTool(t *testing.T) {
  function Test_Main_helpTools (line 115) | func Test_Main_helpTools(t *testing.T) {
  function Test_Main_verboseInfo (line 155) | func Test_Main_verboseInfo(t *testing.T) {

FILE: cmd/soar/tool.go
  function initConfig (line 34) | func initConfig() {
  function checkConfig (line 72) | func checkConfig() int {
  function helpTools (line 123) | func helpTools() (isContinue bool, exitCode int) {
  function reportTool (line 163) | func reportTool(sql string, bom []byte) (isContinue bool, exitCode int) {
  function initQuery (line 196) | func initQuery(query string) string {
  function shutdown (line 234) | func shutdown(vEnv *env.VirtualEnv, rEnv *database.Connector) {
  function verboseInfo (line 245) | func verboseInfo() {

FILE: common/cases.go
  function init (line 22) | func init() {

FILE: common/chardet.go
  function Chardet (line 25) | func Chardet(buf []byte) string {
  function CheckCharsetByBOM (line 60) | func CheckCharsetByBOM(buf []byte) string {
  function RemoveBOM (line 78) | func RemoveBOM(buf []byte) (string, []byte) {

FILE: common/chardet_test.go
  function TestChardet (line 25) | func TestChardet(t *testing.T) {
  function TestRemoveBOM (line 45) | func TestRemoveBOM(t *testing.T) {
  function TestCheckCharsetByBOM (line 58) | func TestCheckCharsetByBOM(t *testing.T) {

FILE: common/config.go
  type Configuration (line 52) | type Configuration struct
    method readConfigFile (line 555) | func (conf *Configuration) readConfigFile(path string) error {
  type Dsn (line 229) | type Dsn struct
    method newMySQLConfig (line 296) | func (env *Dsn) newMySQLConfig() (*mysql.Config, error) {
  function newDSN (line 254) | func newDSN(cfg *mysql.Config) *Dsn {
  function parseDSN (line 336) | func parseDSN(odbc string, d *Dsn) *Dsn {
  function ParseDSN (line 446) | func ParseDSN(odbc string, d *Dsn) *Dsn {
  function FormatDSN (line 456) | func FormatDSN(env *Dsn) string {
  function SoarVersion (line 468) | func SoarVersion() {
  function usage (line 476) | func usage() {
  function PrintConfiguration (line 544) | func PrintConfiguration() {
  function readCmdFlags (line 578) | func readCmdFlags() error {
  function ParseConfig (line 781) | func ParseConfig(configFile string) error {
  type ReportType (line 840) | type ReportType struct
  function ListReportTypes (line 960) | func ListReportTypes() {
  function ArgConfig (line 978) | func ArgConfig() string {

FILE: common/config_test.go
  function TestMain (line 31) | func TestMain(m *testing.M) {
  function TestParseConfig (line 50) | func TestParseConfig(t *testing.T) {
  function TestReadConfigFile (line 59) | func TestReadConfigFile(t *testing.T) {
  function TestParseDSN (line 68) | func TestParseDSN(t *testing.T) {
  function TestListReportTypes (line 119) | func TestListReportTypes(t *testing.T) {
  function TestArgConfig (line 128) | func TestArgConfig(t *testing.T) {
  function TestPrintConfiguration (line 156) | func TestPrintConfiguration(t *testing.T) {

FILE: common/example_test.go
  function ExampleFormatDSN (line 21) | func ExampleFormatDSN() {
  function ExampleIsColsPart (line 38) | func ExampleIsColsPart() {
  function ExampleSortedKey (line 58) | func ExampleSortedKey() {

FILE: common/logger.go
  function init (line 35) | func init() {
  function LoggerInit (line 41) | func LoggerInit() {
  function Caller (line 61) | func Caller() string {
  function GetFunctionName (line 82) | func GetFunctionName() string {
  function fileName (line 94) | func fileName(original string) string {
  function LogIfError (line 103) | func LogIfError(err error, format string, v ...interface{}) {
  function LogIfWarn (line 117) | func LogIfWarn(err error, format string, v ...interface{}) {

FILE: common/logger_test.go
  function TestLogger (line 24) | func TestLogger(t *testing.T) {
  function TestCaller (line 31) | func TestCaller(t *testing.T) {
  function TestGetFunctionName (line 38) | func TestGetFunctionName(t *testing.T) {
  function TestIfError (line 45) | func TestIfError(t *testing.T) {
  function TestIfWarn (line 51) | func TestIfWarn(t *testing.T) {

FILE: common/markdown.go
  function MarkdownEscape (line 40) | func MarkdownEscape(str string) string {
  function loadExternalResource (line 48) | func loadExternalResource(resource string) string {
  function MarkdownHTMLHeader (line 84) | func MarkdownHTMLHeader() string {
  function Markdown2HTML (line 116) | func Markdown2HTML(buf string) string {
  function Score (line 134) | func Score(score int) string {

FILE: common/markdown_test.go
  function TestMarkdownEscape (line 28) | func TestMarkdownEscape(_ *testing.T) {
  function TestMarkdown2Html (line 42) | func TestMarkdown2Html(t *testing.T) {
  function TestScore (line 69) | func TestScore(t *testing.T) {
  function TestLoadExternalResource (line 86) | func TestLoadExternalResource(t *testing.T) {
  function TestMarkdownHTMLHeader (line 99) | func TestMarkdownHTMLHeader(t *testing.T) {

FILE: common/meta.go
  type Meta (line 25) | type Meta
    method Tables (line 118) | func (b Meta) Tables(db string) []string {
    method SetDefault (line 130) | func (b Meta) SetDefault(defaultDB string) Meta {
  type DB (line 28) | type DB struct
  function NewDB (line 34) | func NewDB(db string) *DB {
  type Table (line 42) | type Table struct
  function NewTable (line 49) | func NewTable(tb string) *Table {
  type KeyType (line 58) | type KeyType
  type Column (line 61) | type Column struct
    method Equal (line 82) | func (col *Column) Equal(column *Column) bool {
    method GetDataBytes (line 257) | func (col *Column) GetDataBytes(dbVersion int) int {
  type TableColumns (line 79) | type TableColumns
  function IsColsPart (line 89) | func IsColsPart(a, b []*Column) bool {
  function JoinColumnsName (line 107) | func JoinColumnsName(cols []*Column, sep string) string {
  function MergeColumn (line 165) | func MergeColumn(dst []*Column, src ...*Column) []*Column {
  function ColumnSort (line 189) | func ColumnSort(colList []*Column) []*Column {
  function GetDataTypeBase (line 207) | func GetDataTypeBase(dataType string) string {
  function GetDataTypeLength (line 223) | func GetDataTypeLength(dataType string) []int {
  function numericStorageReq (line 290) | func numericStorageReq(dataType string) int {
  function timeStorageReq (line 356) | func timeStorageReq(dataType string, version int) int {
  function StringStorageReq (line 466) | func StringStorageReq(dataType string, charset string) int {

FILE: common/meta_test.go
  function TestGetDataTypeLength (line 24) | func TestGetDataTypeLength(t *testing.T) {
  function TestGetDataTypeBase (line 45) | func TestGetDataTypeBase(t *testing.T) {
  function TestGetDataBytes (line 61) | func TestGetDataBytes(t *testing.T) {
  function TestStringStorageReq (line 147) | func TestStringStorageReq(t *testing.T) {

FILE: common/signal.go
  function HandleSignal (line 26) | func HandleSignal(f func()) {

FILE: common/signal_test.go
  function TestHandleSignal (line 24) | func TestHandleSignal(t *testing.T) {

FILE: common/tricks.go
  function GoldenDiff (line 34) | func GoldenDiff(f func(), name string, update *bool) error {
  function captureOutput (line 65) | func captureOutput(f func()) string {
  function SortedKey (line 95) | func SortedKey(m interface{}) []string {
  function jsonFind (line 111) | func jsonFind(json string, name string, find *[]string) (next []string) {
  function JSONFind (line 130) | func JSONFind(json string, name string) []string {
  function RemoveDuplicatesItem (line 146) | func RemoveDuplicatesItem(duplicate []string) []string {

FILE: common/tricks_test.go
  function TestCaptureOutput (line 26) | func TestCaptureOutput(t *testing.T) {
  function TestJSONFind (line 55) | func TestJSONFind(t *testing.T) {
  function TestRemoveDuplicatesItem (line 404) | func TestRemoveDuplicatesItem(t *testing.T) {

FILE: database/explain.go
  constant TraditionalFormatExplain (line 40) | TraditionalFormatExplain = iota
  constant JSONFormatExplain (line 41) | JSONFormatExplain
  constant TraditionalExplainType (line 52) | TraditionalExplainType = iota
  constant ExtendedExplainType (line 53) | ExtendedExplainType
  constant PartitionsExplainType (line 54) | PartitionsExplainType
  type ExplainInfo (line 67) | type ExplainInfo struct
  type ExplainRow (line 77) | type ExplainRow struct
  type ExplainWarning (line 94) | type ExplainWarning struct
  type ExplainJSONCostInfo (line 105) | type ExplainJSONCostInfo struct
  type ExplainJSONMaterializedFromSubquery (line 115) | type ExplainJSONMaterializedFromSubquery struct
  type ExplainJSONTable (line 126) | type ExplainJSONTable struct
  type ExplainJSONNestedLoop (line 147) | type ExplainJSONNestedLoop struct
  type ExplainJSONBufferResult (line 152) | type ExplainJSONBufferResult struct
  type ExplainJSONSubqueries (line 159) | type ExplainJSONSubqueries struct
  type ExplainJSONGroupingOperation (line 166) | type ExplainJSONGroupingOperation struct
  type ExplainJSONDuplicatesRemoval (line 176) | type ExplainJSONDuplicatesRemoval struct
  type ExplainJSONOrderingOperation (line 185) | type ExplainJSONOrderingOperation struct
  type ExplainJSONQueryBlock (line 195) | type ExplainJSONQueryBlock struct
  type ExplainJSONUnionResult (line 212) | type ExplainJSONUnionResult struct
  type ExplainJSON (line 220) | type ExplainJSON struct
  function findTablesInJSON (line 392) | func findTablesInJSON(explainJSON string, depth int) {
  function FormatJSONIntoTraditional (line 428) | func FormatJSONIntoTraditional(explainJSON string) []ExplainRow {
  function ConvertExplainJSON2Row (line 465) | func ConvertExplainJSON2Row(explainJSON *ExplainJSON) []ExplainRow {
  method supportExplainWrite (line 475) | func (db *Connector) supportExplainWrite() (bool, error) {
  method explainAbleSQL (line 511) | func (db *Connector) explainAbleSQL(sql string) (string, error) {
  method explainQuery (line 567) | func (db *Connector) explainQuery(sql string, explainType int, formatTyp...
  function MySQLExplainWarnings (line 607) | func MySQLExplainWarnings(exp *ExplainInfo) string {
  function MySQLExplainQueryCost (line 617) | func MySQLExplainQueryCost(exp *ExplainInfo) string {
  function ExplainInfoTranslator (line 638) | func ExplainInfoTranslator(exp *ExplainInfo) string {
  function ParseExplainText (line 742) | func ParseExplainText(content string) (exp *ExplainInfo, err error) {
  function parseTraditionalExplainText (line 770) | func parseTraditionalExplainText(content string) (explainRows []ExplainR...
  function parseVerticalExplainText (line 866) | func parseVerticalExplainText(content string) (explainRows []ExplainRow,...
  function parseJSONExplainText (line 945) | func parseJSONExplainText(content string) (*ExplainJSON, error) {
  function ParseExplainResult (line 952) | func ParseExplainResult(res QueryResult, formatType int) (exp *ExplainIn...
  method Explain (line 1068) | func (db *Connector) Explain(sql string, explainType int, formatType int...
  function PrintMarkdownExplainTable (line 1100) | func PrintMarkdownExplainTable(exp *ExplainInfo) string {

FILE: database/explain_test.go
  function TestExplain (line 2336) | func TestExplain(t *testing.T) {
  function TestParseExplainText (line 2359) | func TestParseExplainText(t *testing.T) {
  function TestFindTablesInJson (line 2375) | func TestFindTablesInJson(t *testing.T) {
  function TestFormatJsonIntoTraditional (line 2386) | func TestFormatJsonIntoTraditional(t *testing.T) {
  function TestPrintMarkdownExplainTable (line 2396) | func TestPrintMarkdownExplainTable(t *testing.T) {
  function TestExplainInfoTranslator (line 2412) | func TestExplainInfoTranslator(t *testing.T) {
  function TestMySQLExplainWarnings (line 2427) | func TestMySQLExplainWarnings(t *testing.T) {
  function TestMySQLExplainQueryCost (line 2442) | func TestMySQLExplainQueryCost(t *testing.T) {
  function TestSupportExplainWrite (line 2458) | func TestSupportExplainWrite(t *testing.T) {
  function TestExplainAbleSQL (line 2467) | func TestExplainAbleSQL(t *testing.T) {

FILE: database/mysql.go
  type Connector (line 38) | type Connector struct
    method Query (line 73) | func (db *Connector) Query(sql string, params ...interface{}) (QueryRe...
    method Version (line 127) | func (db *Connector) Version() (int, error) {
    method SingleIntValue (line 156) | func (db *Connector) SingleIntValue(option string) (int, error) {
    method ColumnCardinality (line 175) | func (db *Connector) ColumnCardinality(tb, col string) float64 {
    method IsView (line 242) | func (db *Connector) IsView(tbName string) bool {
    method dangerousQuery (line 328) | func (db *Connector) dangerousQuery(query string) bool {
  type QueryResult (line 48) | type QueryResult struct
  function NewConnector (line 56) | func NewConnector(dsn *common.Dsn) (*Connector, error) {
  function RemoveSQLComments (line 307) | func RemoveSQLComments(sql string) string {
  constant TimeFormat (line 360) | TimeFormat = "2006-01-02 15:04:05.000000000"
  function TimeString (line 363) | func TimeString(t time.Time) string {
  function NullString (line 374) | func NullString(buf []byte) string {
  function NullFloat (line 382) | func NullFloat(buf []byte) float64 {
  function NullInt (line 391) | func NullInt(buf []byte) int64 {
  function quoteEscape (line 400) | func quoteEscape(source string) string {
  function stringEscape (line 419) | func stringEscape(source string) string {
  function Escape (line 466) | func Escape(source string, NoBackslashEscapes bool) string {

FILE: database/mysql_test.go
  function TestMain (line 35) | func TestMain(m *testing.M) {
  function TestQuery (line 64) | func TestQuery(t *testing.T) {
  function TestColumnCardinality (line 85) | func TestColumnCardinality(t *testing.T) {
  function TestDangerousSQL (line 97) | func TestDangerousSQL(t *testing.T) {
  function TestWarningsAndQueryCost (line 116) | func TestWarningsAndQueryCost(t *testing.T) {
  function TestVersion (line 139) | func TestVersion(t *testing.T) {
  function TestRemoveSQLComments (line 149) | func TestRemoveSQLComments(t *testing.T) {
  function TestSingleIntValue (line 187) | func TestSingleIntValue(t *testing.T) {
  function TestIsView (line 199) | func TestIsView(t *testing.T) {
  function TestNullString (line 214) | func TestNullString(t *testing.T) {
  function TestEscape (line 228) | func TestEscape(t *testing.T) {

FILE: database/privilege.go
  method CurrentUser (line 28) | func (db *Connector) CurrentUser() (string, string, error) {
  method HasSelectPrivilege (line 57) | func (db *Connector) HasSelectPrivilege() bool {
  method HasAllPrivilege (line 86) | func (db *Connector) HasAllPrivilege() bool {

FILE: database/privilege_test.go
  function TestCurrentUser (line 25) | func TestCurrentUser(t *testing.T) {
  function TestHasSelectPrivilege (line 37) | func TestHasSelectPrivilege(t *testing.T) {
  function TestHasAllPrivilege (line 45) | func TestHasAllPrivilege(t *testing.T) {

FILE: database/profiling.go
  type Profiling (line 30) | type Profiling struct
  type ProfilingRow (line 35) | type ProfilingRow struct
  method Profiling (line 42) | func (db *Connector) Profiling(sql string, params ...interface{}) ([]Pro...
  function FormatProfiling (line 118) | func FormatProfiling(rows []ProfilingRow) string {

FILE: database/profiling_test.go
  function TestProfiling (line 27) | func TestProfiling(t *testing.T) {
  function TestFormatProfiling (line 41) | func TestFormatProfiling(t *testing.T) {

FILE: database/sampling.go
  method SamplingData (line 50) | func (db *Connector) SamplingData(onlineConn *Connector, tables ...strin...
  method startSampling (line 102) | func (db *Connector) startSampling(onlineConn *sql.DB, database, table s...
  method doSampling (line 181) | func (db *Connector) doSampling(table, colDef, values string) error {

FILE: database/show.go
  type TableStatInfo (line 32) | type TableStatInfo struct
  type tableStatusRow (line 39) | type tableStatusRow struct
  type deleteComaType (line 74) | type deleteComaType
  constant _ (line 77) | _ deleteComaType = iota
  constant CS (line 78) | CS
  constant PART (line 79) | PART
  function newTableStat (line 83) | func newTableStat(tableName string) *TableStatInfo {
  method ShowTables (line 91) | func (db *Connector) ShowTables() ([]string, error) {
  method ShowTableStatus (line 120) | func (db *Connector) ShowTableStatus(tableName string) (*TableStatInfo, ...
  type TableIndexInfo (line 181) | type TableIndexInfo struct
    method FindIndex (line 282) | func (tbIndex *TableIndexInfo) FindIndex(arg IndexSelectKey, value str...
  type TableIndexRow (line 187) | type TableIndexRow struct
  function NewTableIndexInfo (line 206) | func NewTableIndexInfo(tableName string) *TableIndexInfo {
  method ShowIndex (line 214) | func (db *Connector) ShowIndex(tableName string) (*TableIndexInfo, error) {
  type IndexSelectKey (line 271) | type IndexSelectKey
  constant IndexKeyName (line 275) | IndexKeyName    = IndexSelectKey("KeyName")
  constant IndexColumnName (line 276) | IndexColumnName = IndexSelectKey("ColumnName")
  constant IndexIndexType (line 277) | IndexIndexType  = IndexSelectKey("IndexType")
  constant IndexNonUnique (line 278) | IndexNonUnique  = IndexSelectKey("NonUnique")
  type TableDesc (line 331) | type TableDesc struct
    method Columns (line 405) | func (td TableDesc) Columns() []string {
  type TableDescValue (line 337) | type TableDescValue struct
  function NewTableDesc (line 350) | func NewTableDesc(tableName string) *TableDesc {
  method ShowColumns (line 358) | func (db *Connector) ShowColumns(tableName string) (*TableDesc, error) {
  method showCreate (line 414) | func (db *Connector) showCreate(createType, name string) (string, error) {
  method ShowCreateDatabase (line 463) | func (db *Connector) ShowCreateDatabase(dbName string) (string, error) {
  method ShowCreateTable (line 474) | func (db *Connector) ShowCreateTable(tableName string) (string, error) {
  method FindColumn (line 525) | func (db *Connector) FindColumn(name, dbName string, tables ...string) (...
  method IsForeignKey (line 598) | func (db *Connector) IsForeignKey(dbName, tbName, column string) bool {
  type Reference (line 619) | type Reference
  type ReferenceValue (line 622) | type ReferenceValue struct
  method ShowReference (line 631) | func (db *Connector) ShowReference(dbName string, tbName ...string) ([]R...

FILE: database/show_test.go
  function TestShowTableStatus (line 28) | func TestShowTableStatus(t *testing.T) {
  function TestShowTables (line 54) | func TestShowTables(t *testing.T) {
  function TestShowCreateDatabase (line 75) | func TestShowCreateDatabase(t *testing.T) {
  function TestShowCreateTable (line 86) | func TestShowCreateTable(t *testing.T) {
  function TestShowIndex (line 112) | func TestShowIndex(t *testing.T) {
  function TestShowColumns (line 132) | func TestShowColumns(t *testing.T) {
  function TestFindColumn (line 152) | func TestFindColumn(t *testing.T) {
  function TestIsFKey (line 167) | func TestIsFKey(t *testing.T) {
  function TestShowReference (line 175) | func TestShowReference(t *testing.T) {

FILE: database/trace.go
  type Trace (line 31) | type Trace struct
  type TraceRow (line 36) | type TraceRow struct
  method Trace (line 44) | func (db *Connector) Trace(sql string, params ...interface{}) ([]TraceRo...
  function FormatTrace (line 126) | func FormatTrace(rows []TraceRow) string {

FILE: database/trace_test.go
  function TestTrace (line 27) | func TestTrace(t *testing.T) {
  function TestFormatTrace (line 46) | func TestFormatTrace(t *testing.T) {

FILE: doc/example/digest_pt.py
  function printStatInfo (line 13) | def printStatInfo(buf):
  function printSqlAdvisor (line 23) | def printSqlAdvisor(buf):
  function getUseDB (line 47) | def getUseDB(line):
  function parsePtQueryDisget (line 51) | def parsePtQueryDisget(f):
  function main (line 79) | def main():

FILE: doc/js/pretty.js
  function E (line 5) | function E(n) {
  function n (line 19) | function n(e) {
  function n (line 61) | function n(e, E) {
  function n (line 71) | function n(e) {
  function e (line 90) | function e(E, t) {
  function n (line 138) | function n(e) {
  function e (line 153) | function e(E) {
  function t (line 328) | function t(e) {
  function n (line 351) | function n(e) {
  function n (line 358) | function n(e) {
  function n (line 364) | function n(e) {
  function t (line 377) | function t(e) {
  function n (line 392) | function n(e) {
  function t (line 403) | function t(e) {
  function n (line 408) | function n(e) {
  function n (line 419) | function n(e) {
  function e (line 434) | function e(E) {
  function n (line 456) | function n(e) {
  function e (line 468) | function e() {
  function n (line 496) | function n(e) {
  function e (line 505) | function e(E) {
  function n (line 518) | function n(e) {
  function e (line 535) | function e(E) {
  function n (line 557) | function n(e) {
  function e (line 574) | function e(E) {
  function n (line 594) | function n(e) {
  function e (line 611) | function e(E) {
  function n (line 633) | function n(e) {
  function e (line 650) | function e(E) {
  function t (line 698) | function t(e) {
  function t (line 703) | function t(e, E, t, n) {
  function t (line 710) | function t(e) {
  function n (line 717) | function n(e, E, t) {
  function t (line 725) | function t(e) {
  function n (line 730) | function n(e) {
  function t (line 748) | function t(e, E) {
  function t (line 758) | function t(e, E, t) {
  function n (line 767) | function n(e, E, t) {
  function n (line 774) | function n(e, E) {
  function t (line 832) | function t(e, E) {
  function t (line 837) | function t(e) {
  function t (line 848) | function t(e, E) {
  function n (line 855) | function n(e, E, t) {
  function n (line 866) | function n(e) {
  function t (line 876) | function t(e) {
  function t (line 888) | function t(e, E) {
  function t (line 895) | function t(e, E, t) {
  function n (line 902) | function n(e) {
  function t (line 910) | function t(e) {
  function t (line 933) | function t(e, E) {
  function n (line 938) | function n(e) {
  function n (line 946) | function n(e) {
  function n (line 960) | function n(e) {
  function n (line 979) | function n(e) {
  function t (line 1001) | function t(e) {
  function t (line 1007) | function t(e) {
  function n (line 1013) | function n(e, E, t) {
  function t (line 1022) | function t() {
  function n (line 1027) | function n(e) {
  function n (line 1040) | function n(e) {
  function n (line 1048) | function n(e) {
  function n (line 1070) | function n(e, E, t) {
  function escape2Html (line 1091) | function escape2Html(str) {
  function load (line 1104) | function load() {

FILE: env/env.go
  type VirtualEnv (line 34) | type VirtualEnv struct
    method RealDB (line 115) | func (vEnv *VirtualEnv) RealDB(hash string) string {
    method DBHash (line 127) | func (vEnv *VirtualEnv) DBHash(db string) string {
    method CleanUp (line 135) | func (vEnv *VirtualEnv) CleanUp() bool {
    method CleanupTestDatabase (line 156) | func (vEnv *VirtualEnv) CleanupTestDatabase() {
    method BuildVirtualEnv (line 235) | func (vEnv *VirtualEnv) BuildVirtualEnv(rEnv *database.Connector, SQLs...
    method createDatabase (line 389) | func (vEnv *VirtualEnv) createDatabase(rEnv *database.Connector) error {
    method createTable (line 450) | func (vEnv *VirtualEnv) createTable(rEnv *database.Connector, tbName s...
    method GenTableColumns (line 510) | func (vEnv *VirtualEnv) GenTableColumns(meta common.Meta) common.Table...
  function NewVirtualEnv (line 47) | func NewVirtualEnv(vEnv *database.Connector) *VirtualEnv {
  function BuildEnv (line 59) | func BuildEnv() (*VirtualEnv, *database.Connector) {
  function ChangeDB (line 201) | func ChangeDB(env *database.Connector, sql string) {
  function CurrentDB (line 215) | func CurrentDB(sql, db string) string {

FILE: env/env_test.go
  function TestMain (line 38) | func TestMain(m *testing.M) {
  function TestNewVirtualEnv (line 67) | func TestNewVirtualEnv(t *testing.T) {
  function TestCleanupTestDatabase (line 140) | func TestCleanupTestDatabase(t *testing.T) {
  function TestGenTableColumns (line 170) | func TestGenTableColumns(t *testing.T) {
  function TestCreateTable (line 238) | func TestCreateTable(t *testing.T) {
  function TestCreateDatabase (line 281) | func TestCreateDatabase(t *testing.T) {
Condensed preview — 229 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,197K chars).
[
  {
    "path": ".editorconfig",
    "chars": 162,
    "preview": "[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\n\n# tab_size = 4 spaces\n[*.go]\nindent_style = tab\nindent"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 389,
    "preview": "---\nname: Bug Report\nabout: You're experiencing an issue with SOAR that is different than the documented behavior.\n\n---\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 332,
    "preview": "---\nname: Feature Request\nabout: If you have something you think SOAR could improve or add support for.\n\n---\n\nPlease sea"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "chars": 489,
    "preview": "---\nname: Question\nabout: If you have a question, please check out our other community resources instead of opening an i"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 785,
    "preview": "<!--\nThank you for contributing to SOAR! Please read SOAR's [CONTRIBUTING](https://github.com/XiaoMi/soar/blob/master/CO"
  },
  {
    "path": ".gitignore",
    "chars": 135,
    "preview": "bin/\nrelease/\ntest/tmp/\ndoc/blueprint/\n*.iml\n*.swp\n*.log\ncoverage.*\ny.output\n\n.DS_Store\n.vscode/\n.idea\n_tools/\n\nTestMark"
  },
  {
    "path": ".travis.yml",
    "chars": 360,
    "preview": "language: go\n\ngo:\n  - \"1.12.x\"\n  - \"1.13.x\"\n\nsudo: required\n\ngo_import_path: github.com/XiaoMi/soar\n\ndist: xenial\n\nservi"
  },
  {
    "path": "CHANGES.md",
    "chars": 4570,
    "preview": "# CHANGELOG\n\n## 2019-08\n- Fix RuleImplicitConversion(ARG.003) with INT and DECIMAL\n- Fix RuleImplicitConversion duplicat"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3219,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 175,
    "preview": "Ask questions at [Gitter](https://gitter.im/xiaomi-dba/soar).\n\n[Open an issue](https://github.com/xiaomi/soar/issues/new"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "Makefile",
    "chars": 8799,
    "preview": "# This how we want to name the binary output\n#\n# use checkmake linter https://github.com/mrtazz/checkmake\n# $ checkmake "
  },
  {
    "path": "NOTICE.txt",
    "chars": 287,
    "preview": "\nCopyright 2018 Xiaomi, Inc.  All Rights Reserved.\nThis product includes software developed by Xiaomi, Inc.\n(http://www."
  },
  {
    "path": "README.md",
    "chars": 2001,
    "preview": "# ![SOAR](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/logo.png)\n\n[![Gitter](https://badges.gitter.im"
  },
  {
    "path": "README_EN.md",
    "chars": 1950,
    "preview": "# ![SOAR](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/logo.png)\n\n[![Gitter](https://badges.gitter.im"
  },
  {
    "path": "VERSION",
    "chars": 7,
    "preview": "0.11.0\n"
  },
  {
    "path": "advisor/doc.go",
    "chars": 693,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/explainer.go",
    "chars": 6154,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/explainer_test.go",
    "chars": 2235,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/heuristic.go",
    "chars": 103174,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/heuristic_test.go",
    "chars": 108797,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/index.go",
    "chars": 32225,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/index_test.go",
    "chars": 14306,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/rules.go",
    "chars": 59891,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/rules_test.go",
    "chars": 2431,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "advisor/testdata/TestDigestExplainText.golden",
    "chars": 43269,
    "preview": "##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered "
  },
  {
    "path": "advisor/testdata/TestIndexAdviseNoEnv.golden",
    "chars": 11818,
    "preview": "map[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度"
  },
  {
    "path": "advisor/testdata/TestListHeuristicRules.golden",
    "chars": 32304,
    "preview": "# 启发式规则建议\n\n[toc]\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item**:ALI.001\n* **Severity**:L0\n* **Content**:在列或表别名(如\"tbl AS alias\")中, 明"
  },
  {
    "path": "advisor/testdata/TestListTestSQLs.golden",
    "chars": 8050,
    "preview": "SELECT * FROM film WHERE length = 86;\nSELECT * FROM film WHERE length IS NULL;\nSELECT * FROM film HAVING title = 'abc';\n"
  },
  {
    "path": "advisor/testdata/TestMergeConflictHeuristicRules.golden",
    "chars": 38540,
    "preview": "advisor.Rule{Item:\"ALI.001\", Severity:\"L0\", Summary:\"建议使用 AS 关键字显示声明一个别名\", Content:\"在列或表别名(如\\\"tbl AS alias\\\")中, 明确使用 AS "
  },
  {
    "path": "ast/doc.go",
    "chars": 672,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/meta.go",
    "chars": 21442,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/meta_test.go",
    "chars": 14665,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/node_array.go",
    "chars": 2650,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/pretty.go",
    "chars": 9510,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/pretty_test.go",
    "chars": 9358,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/rewrite.go",
    "chars": 47774,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/rewrite_test.go",
    "chars": 28531,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/testdata/TestCompress.golden",
    "chars": 16098,
    "preview": "SELECT * FROM film WHERE length = 86;\nSELECT * FROM film WHERE length = 86;\nSELECT * FROM film WHERE length IS NULL;\nSEL"
  },
  {
    "path": "ast/testdata/TestFormat.golden",
    "chars": 18595,
    "preview": "SELECT * FROM film WHERE length = 86;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 86;\nSELECT * FROM film WHERE le"
  },
  {
    "path": "ast/testdata/TestGetQuotedString.golden",
    "chars": 530,
    "preview": "orignal: \"hello world\"\nquoted: \"hello world\"\norignal: `hello world`\nquoted: `hello world`\norignal: 'hello world'\nquoted:"
  },
  {
    "path": "ast/testdata/TestLeftNewLines.golden",
    "chars": 6,
    "preview": "1\n0\n0\n"
  },
  {
    "path": "ast/testdata/TestListRewriteRules.golden",
    "chars": 9237,
    "preview": "# 重写规则\n\n[toc]\n\n## dml2select\n* **Description**:将数据库更新请求转换为只读查询请求,便于执行EXPLAIN\n\n* **Original**:\n\n```sql\nDELETE FROM film W"
  },
  {
    "path": "ast/testdata/TestMergeAlterTables.golden",
    "chars": 805,
    "preview": "`customer` : ALTER TABLE `customer` ADD INDEX part_of_name (name(10)) ;\n\n`db`.`old_table` : ALTER TABLE `db`.`old_table`"
  },
  {
    "path": "ast/testdata/TestNewLines.golden",
    "chars": 6,
    "preview": "1\n2\n0\n"
  },
  {
    "path": "ast/testdata/TestPretty.golden",
    "chars": 32906,
    "preview": "select sourcetable, if(f.lastcontent = ?, f.lastupdate, f.lastcontent) as lastactivity, f.totalcount as activity, type.c"
  },
  {
    "path": "ast/testdata/TestPrintPrettyStmtNode.golden",
    "chars": 2817,
    "preview": "[]ast.StmtNode{\n    &ast.SelectStmt{\n        dmlNode: ast.dmlNode{\n            stmtNode: ast.stmtNode{\n                n"
  },
  {
    "path": "ast/testdata/TestPrintPrettyVitessStmtNode.golden",
    "chars": 849,
    "preview": "&sqlparser.Select{\n    Cache:       \"\",\n    Comments:    nil,\n    Distinct:    \"\",\n    Hints:       \"\",\n    SelectExprs:"
  },
  {
    "path": "ast/testdata/TestQueryType.golden",
    "chars": 643,
    "preview": "SELECT\nSELECT\nSELECT\nGRANT\nREVOKE\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSE"
  },
  {
    "path": "ast/testdata/TestSchemaMetaInfo.golden",
    "chars": 10599,
    "preview": "use world_x;\n[`world_x`.`dual`]\nselect 1;\n[]\nsyntax error case\n[]\nselect * from ta join tb using (id)\n[`sakila`.`ta` `sa"
  },
  {
    "path": "ast/testdata/TestSplitStatement.golden",
    "chars": 1006,
    "preview": "0 select * from test;\n1 select 'asd;fas', col from test;\n2 -- select * from test;hello\n3 #select * from test;hello\n4 sel"
  },
  {
    "path": "ast/testdata/TestStmtNode2JSON.golden",
    "chars": 1517,
    "preview": "[\n  {\n    \"text\": \"select 1\",\n    \"offset\": 0,\n    \"SQLBigResult\": false,\n    \"SQLBufferResult\": false,\n    \"SQLCache\": "
  },
  {
    "path": "ast/testdata/TestTokenize.golden",
    "chars": 27429,
    "preview": "SELECT * FROM film WHERE length = 86;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} "
  },
  {
    "path": "ast/testdata/TestTokenizer.golden",
    "chars": 10311,
    "preview": "[]ast.Token{\n    {Type:57348, Val:\"select\", i:0},\n    {Type:57407, Val:\"-- comment 1\", i:0},\n}\n[]ast.Token{\n    {Type:57"
  },
  {
    "path": "ast/testdata/TestVitessStmtNode2JSON.golden",
    "chars": 457,
    "preview": "{\n  \"Cache\": \"\",\n  \"Comments\": null,\n  \"Distinct\": \"\",\n  \"Hints\": \"\",\n  \"SelectExprs\": [\n    {\n      \"Expr\": {\n        \""
  },
  {
    "path": "ast/tidb.go",
    "chars": 5740,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/tidb_test.go",
    "chars": 3323,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/token.go",
    "chars": 36836,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/token_test.go",
    "chars": 8545,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/vitess.go",
    "chars": 1362,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "ast/vitess_test.go",
    "chars": 1520,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "cmd/soar/doc.go",
    "chars": 654,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "cmd/soar/soar.go",
    "chars": 14737,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "cmd/soar/soar_test.go",
    "chars": 5343,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "cmd/soar/testdata/Test_Main.golden",
    "chars": 77,
    "preview": "# Query: 16219655761820A2\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  1\n```\n\n##  ✔️\n\n"
  },
  {
    "path": "cmd/soar/testdata/Test_Main_verboseInfo.golden",
    "chars": 311,
    "preview": "Syntax check OK!\nMySQL environment verbose info\n* test-dsn: 127.0.0.1:3306 is disable, please check log.\n* online-dsn: 1"
  },
  {
    "path": "cmd/soar/tool.go",
    "chars": 6862,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/cases.go",
    "chars": 13647,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/chardet.go",
    "chars": 2392,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/chardet_test.go",
    "chars": 2045,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/config.go",
    "chars": 39002,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/config_test.go",
    "chars": 4807,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/doc.go",
    "chars": 695,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/example_test.go",
    "chars": 2010,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/logger.go",
    "chars": 3358,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/logger_test.go",
    "chars": 1321,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/markdown.go",
    "chars": 57348,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/markdown_test.go",
    "chars": 2694,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/meta.go",
    "chars": 12987,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/meta_test.go",
    "chars": 6698,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/signal.go",
    "chars": 960,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/signal_test.go",
    "chars": 841,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/testdata/TestJSONFind.golden",
    "chars": 333,
    "preview": "[{\n\t\t\t\"Collate\":{\n\t\t\t\t\"Collate\":{\n\t\t\t\t\t\"key\":\"value\"\n\t\t\t\t}\n\t\t\t}\n\t\t} {\n\t\t\t\t\"Collate\":{\n\t\t\t\t\t\"key\":\"value\"\n\t\t\t\t}\n\t\t\t} {\n\t\t"
  },
  {
    "path": "common/testdata/TestListReportTypes.golden",
    "chars": 3372,
    "preview": "# 支持的报告类型\n\n[toc]\n\n## lint\n* **Description**:参考sqlint格式,以插件形式集成到代码编辑器,显示输出更加友好\n\n* **Example**:\n\n```bash\nsoar -report-type"
  },
  {
    "path": "common/testdata/TestMarkdown2Html.golden",
    "chars": 15933,
    "preview": "<h1>Markdown For Typora</h1>\n\n<h2>Overview</h2>\n\n<p><strong>Markdown</strong> is created by <a href=\"http://daringfireba"
  },
  {
    "path": "common/testdata/TestMarkdown2Html.md",
    "chars": 13029,
    "preview": "# Markdown For Typora\n\n## Overview\n\n**Markdown** is created by [Daring Fireball](http://daringfireball.net/), the origin"
  },
  {
    "path": "common/testdata/TestMarkdownHTMLHeader.golden",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "common/testdata/TestParseDSN.golden",
    "chars": 22100,
    "preview": "\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:    "
  },
  {
    "path": "common/testdata/TestPrintConfiguration.golden",
    "chars": 2468,
    "preview": "online-dsn:\n  user: root\n  password: '********'\n  net: tcp\n  addr: 127.0.0.1:3306\n  schema: sakila\n  charset: utf8\n  col"
  },
  {
    "path": "common/testdata/TestRemoveBOM.golden",
    "chars": 41,
    "preview": "select col from tb c = 1;\n [239 187 191]\n"
  },
  {
    "path": "common/testdata/TestStringStorageReq.golden",
    "chars": 9543,
    "preview": "char(10) armscii8 10\nchar(256) armscii8 255\nbinary(10) armscii8 10\nbinary(256) armscii8 255\nvarchar(10) armscii8 11\nvarb"
  },
  {
    "path": "common/testdata/UTF-8.bom.sql",
    "chars": 27,
    "preview": "select col from tb c = 1;\n"
  },
  {
    "path": "common/testdata/chardet_BIG5.txt",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "common/testdata/chardet_GB-18030.txt",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "common/testdata/chardet_UTF-8.txt",
    "chars": 3,
    "preview": "中文\n"
  },
  {
    "path": "common/tricks.go",
    "chars": 3830,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/tricks_test.go",
    "chars": 8802,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "common/version.go",
    "chars": 134,
    "preview": "package common\n\n// -version输出信息\nvar (\n\tVersion  = \"No Version Provided\"\n\tCompile  = \"\"\n\tBranch   = \"\"\n\tGitDirty = \"\"\n\tDe"
  },
  {
    "path": "database/doc.go",
    "chars": 686,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/explain.go",
    "chars": 41212,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/explain_test.go",
    "chars": 81595,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/mysql.go",
    "chars": 11281,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/mysql_test.go",
    "chars": 6340,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/privilege.go",
    "chars": 3604,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/privilege_test.go",
    "chars": 1670,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/profiling.go",
    "chars": 3092,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/profiling_test.go",
    "chars": 1355,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/sampling.go",
    "chars": 5797,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/show.go",
    "chars": 17958,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/show_test.go",
    "chars": 5051,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/testdata/TestExplain.golden",
    "chars": 5575,
    "preview": "&database.ExplainInfo{\n    SQL:           \"select ID,name from (select address from customer_list where SID=1 order by p"
  },
  {
    "path": "database/testdata/TestExplainInfoTranslator.golden",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "database/testdata/TestFindColumn.golden",
    "chars": 456,
    "preview": "[]*common.Column{\n    &common.Column{\n        Name:        \"film_id\",\n        Alias:       nil,\n        Table:       \"fi"
  },
  {
    "path": "database/testdata/TestFormatProfiling.golden",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "database/testdata/TestFormatTrace.golden",
    "chars": 439,
    "preview": "\n```sql\nselect 1\n```\n\n```json\n{\n  \"steps\": [\n    {\n      \"join_preparation\": {\n        \"select#\": 1,\n        \"steps\": [\n"
  },
  {
    "path": "database/testdata/TestMySQLExplainQueryCost.golden",
    "chars": 21,
    "preview": "<nil> \n<nil> \n<nil> \n"
  },
  {
    "path": "database/testdata/TestMySQLExplainWarnings.golden",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "database/testdata/TestPrintMarkdownExplainTable.golden",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "database/testdata/TestRemoveSQLComments.golden",
    "chars": 58,
    "preview": "select 'c#\\'#not comment'\nselect \"c#\\\"#not comment\"\n\n\n\n\n\n\n"
  },
  {
    "path": "database/testdata/TestShowColumns.golden",
    "chars": 1708,
    "preview": "&database.TableDesc{\n    Name:       \"actor_info\",\n    DescValues: {\n        {\n            Field:      \"actor_id\",\n     "
  },
  {
    "path": "database/testdata/TestShowCreateDatabase.golden",
    "chars": 135,
    "preview": "CREATE DATABASE `sakila` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTIO"
  },
  {
    "path": "database/testdata/TestShowCreateTable.golden",
    "chars": 2312,
    "preview": "CREATE TABLE `film` (\n  `film_id` smallint unsigned NOT NULL AUTO_INCREMENT,\n  `title` varchar(255) NOT NULL,\n  `descrip"
  },
  {
    "path": "database/testdata/TestShowIndex.golden",
    "chars": 2522,
    "preview": "&database.TableIndexInfo{\n    TableName: \"film\",\n    Rows:      {\n        {\n            Table:        \"film\",\n          "
  },
  {
    "path": "database/testdata/TestShowReference.golden",
    "chars": 31,
    "preview": "[]database.ReferenceValue(nil)\n"
  },
  {
    "path": "database/testdata/TestShowTables.golden",
    "chars": 242,
    "preview": "actor\nactor_info\naddress\ncategory\ncity\ncountry\ncustomer\ncustomer_list\nfilm\nfilm_actor\nfilm_category\nfilm_list\nfilm_text\n"
  },
  {
    "path": "database/testdata/TestTrace.golden",
    "chars": 758,
    "preview": "select 1 []database.TraceRow{\n    {Query:\"explain select 1\", Trace:\"{\\n  \\\"steps\\\": [\\n    {\\n      \\\"join_preparation\\\""
  },
  {
    "path": "database/trace.go",
    "chars": 3648,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "database/trace_test.go",
    "chars": 1525,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "deps.sh",
    "chars": 551,
    "preview": "#!/bin/bash\n\nNEEDED_COMMANDS=\"docker git go retool bats\"\n\nfor cmd in ${NEEDED_COMMANDS} ; do\n    if ! command -v \"${cmd}"
  },
  {
    "path": "doc/FAQ.md",
    "chars": 3141,
    "preview": "# 常见问题\n\n## 软件依赖\n\n* [git](https://git-scm.co) 项目代码管理工具\n* [go](https://golang.org/) 源码编译依赖\n* [govendor](https://github.com"
  },
  {
    "path": "doc/FAQ_en.md",
    "chars": 2231,
    "preview": "## FAQ\n\n### Dependency Tools\n\n* [git](https://git-scm.co): clone code from git repository\n* [go](https://golang.org/): b"
  },
  {
    "path": "doc/cheatsheet.md",
    "chars": 3346,
    "preview": "# 常用命令\n\n[toc]\n\n## 基本用法\n\n```bash\necho \"select title from sakila.film\" | ./soar -log-output=soar.log\n```\n\n## 指定输入源\n\n```bas"
  },
  {
    "path": "doc/cheatsheet_en.md",
    "chars": 2313,
    "preview": "\n# Useful Commands\n\n[toc]\n\n## Basic suggest\n\n```bash\necho \"select title from sakila.film\" | ./soar -log-output=soar.log\n"
  },
  {
    "path": "doc/comparison.md",
    "chars": 912,
    "preview": "## 业内其他优秀产品对比\n\n|              | SOAR | sqlcheck | pt-query-advisor | SQL Advisor | Inception | sqlautoreview |\n| ---    "
  },
  {
    "path": "doc/comparison_en.md",
    "chars": 1041,
    "preview": "## Compare with other wonderful product\n\n|                    | SOAR | sqlcheck | pt-query-advisor | SQL Advisor | Incep"
  },
  {
    "path": "doc/config.md",
    "chars": 2860,
    "preview": "## 配置文件说明\n\n配置文件为[yaml](https://en.wikipedia.org/wiki/YAML)格式。一般情况下只需要配置online-dsn, test-dsn, log-output等少数几个参数。即使不创建配置文件"
  },
  {
    "path": "doc/editor_plugin.md",
    "chars": 942,
    "preview": "## Vim插件安装\n\n* 首先安装Syntastic,安装方法参见[官方文档](https://github.com/vim-syntastic/syntastic#installation)\n* 将`soar`二进制文件拷贝到可执行文件"
  },
  {
    "path": "doc/environment.md",
    "chars": 560,
    "preview": "## 集成环境\n\n![集成环境](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/env.png)\n\n| 线上环境 | 测试环境 |   场景         "
  },
  {
    "path": "doc/example/digest_pt.py",
    "chars": 2392,
    "preview": "#!/usr/bin/python -u\n#-*- coding: utf-8 -*-\n\nimport sys, re, subprocess\nimport os.path\nreload(sys)\nsys.setdefaultencodin"
  },
  {
    "path": "doc/example/metalinter.json",
    "chars": 454,
    "preview": "{\n    \"Vendor\": true,\n    \"DisableAll\": true,\n    \"Enable\": [\n        \"gofmt\",\n        \"goimports\",\n        \"interfacer\""
  },
  {
    "path": "doc/example/metalinter.sh",
    "chars": 623,
    "preview": "#!/bin/bash\n\nMETABIN=$(which gometalinter.v1)\nPROJECT_PATH=${GOPATH}/src/github.com/XiaoMi/soar/\n\nif [ \"x$METABIN\" == \"x"
  },
  {
    "path": "doc/example/metalinter.txt",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "doc/example/revive.toml",
    "chars": 1122,
    "preview": "ignoreGeneratedHeader = false\nseverity = \"error\"\nconfidence = 0.8\nerrorCode = 0\nwarningCode = 0\n\n[rule.blank-imports]\n[r"
  },
  {
    "path": "doc/example/slow.log.digest",
    "chars": 3927,
    "preview": "\n# 13.7s user time, 20ms system time, 27.95M rss, 181.32M vsz\n# Current date: Thu May 17 15:24:49 2018\n# Hostname: 127.0"
  },
  {
    "path": "doc/example/soar.vim",
    "chars": 1004,
    "preview": "\"============================================================================\n\"File:        soar.vim\n\"Description: Synta"
  },
  {
    "path": "doc/explain.md",
    "chars": 1540,
    "preview": "\n## EXPLAIN信息解读\n\n* [EXPLAIN语法](https://dev.mysql.com/doc/refman/5.7/en/explain.html)\n* [EXPLAIN输出信息](https://dev.mysql.c"
  },
  {
    "path": "doc/heuristic.md",
    "chars": 32304,
    "preview": "# 启发式规则建议\n\n[toc]\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item**:ALI.001\n* **Severity**:L0\n* **Content**:在列或表别名(如\"tbl AS alias\")中, 明"
  },
  {
    "path": "doc/images/logo.ascii",
    "chars": 220,
    "preview": ",adPPYba,  ,adPPYba,  ,adPPYYba, 8b,dPPYba,\nI8[    \"\" a8\"     \"8a \"\"     `Y8 88P'   \"Y8\n `\"Y8ba,  8b       d8 ,adPPPPP88"
  },
  {
    "path": "doc/indexing.md",
    "chars": 5473,
    "preview": "\n# 索引优化建议\n\n以下优化算法基于个人当前理解,能力有限,如有偏颇还请斧正。\n\n## 简单查询索引优化\n\n### 等值查询优化\n\n* 单列等值查询,为该等值列加索引\n* 多列等值查询,每列求取散粒度,按从大到小排序取前N列添加到索引(N"
  },
  {
    "path": "doc/install.md",
    "chars": 1141,
    "preview": "## 下载二进制安装包\n\n```bash\nwget https://github.com/XiaoMi/soar/releases/download/${tag}/soar.${OS}-amd64 -O soar\nchmod a+x soa"
  },
  {
    "path": "doc/install_en.md",
    "chars": 426,
    "preview": "## Get Released Binary\n\n```bash\nwget https://github.com/XiaoMi/soar/releases/download/${tag}/soar.${OS}-amd64 -O soar\nch"
  },
  {
    "path": "doc/js/pretty.js",
    "chars": 57471,
    "preview": "! function(e, E) {\n    \"object\" == typeof exports && \"object\" == typeof module ? module.exports = E() : \"function\" == ty"
  },
  {
    "path": "doc/report_type.md",
    "chars": 3372,
    "preview": "# 支持的报告类型\n\n[toc]\n\n## lint\n* **Description**:参考sqlint格式,以插件形式集成到代码编辑器,显示输出更加友好\n\n* **Example**:\n\n```bash\nsoar -report-type"
  },
  {
    "path": "doc/rewrite.md",
    "chars": 4309,
    "preview": "# 重写规则\n\n[toc]\n\n## dml2select\n* **Description**:将数据库更新请求转换为只读查询请求,便于执行EXPLAIN\n\n* **Original**:\n\n```sql\nDELETE FROM film W"
  },
  {
    "path": "doc/roadmap.md",
    "chars": 347,
    "preview": "## 路线图\n\n* 语法支持方面,目前主要依赖vitess,TiDB对SQL语法的支持。\n* 目前仅针对MySQL语法族进行开发和测试,其他使用SQL的数据库产品暂不支持。\n* Profiling和Trace功能有待深入挖掘,供经验丰富的D"
  },
  {
    "path": "doc/structure.md",
    "chars": 2735,
    "preview": "\n# 体系架构\n\n![架构图](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/structure.png)\n\nSOAR主要由语法解析器,集成环境,优化建议,重"
  },
  {
    "path": "doc/thanks.md",
    "chars": 2076,
    "preview": "## 鸣谢\n\n以下为SOAR的灵感及代码来源,我们站在伟人的肩膀上,让DBA的工作和生活更美好。\n\n* [vitess](https://github.com/vitessio/vitess)\n* [SQLAdvisor](https://"
  },
  {
    "path": "doc/thanks_en.md",
    "chars": 2095,
    "preview": "## Thanks\n\n以下为SOAR的灵感及代码来源,我们站在伟人的肩膀上,让DBA的工作和生活更美好。\n\n* [vitess](https://github.com/vitessio/vitess)\n* [SQLAdvisor](http"
  },
  {
    "path": "doc/themes/foghorn.css",
    "chars": 2147,
    "preview": "\nhtml, body {\n        padding:1em;\n        margin:auto;\n        max-width:42em;\n        background:#fefefe;\n  }\nbody {\n "
  },
  {
    "path": "doc/themes/ghostwriter.css",
    "chars": 6924,
    "preview": "/* ============================================================ */\n/* Base */\n/* ======================================="
  },
  {
    "path": "doc/themes/github-dark.css",
    "chars": 15594,
    "preview": "@font-face {\n  font-family: octicons-link;\n  src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAA"
  },
  {
    "path": "doc/themes/github.css",
    "chars": 13768,
    "preview": "@font-face {\n  font-family: octicons-link;\n  src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAA"
  },
  {
    "path": "doc/themes/godspeed.css",
    "chars": 12271,
    "preview": "/* Title: Godspeed */\n/* Author: Jocelyn Richard http://jocelynrichard.com/ */\n/* Description: A quirky, low-contrast th"
  },
  {
    "path": "doc/themes/markdown-alt.css",
    "chars": 1026,
    "preview": "body {\n    line-height: 1.4em;\n    color: black;\n        padding:1em;\n        margin:auto;\n        max-width:42em;\n}\n\nli"
  },
  {
    "path": "doc/themes/markdown.css",
    "chars": 3008,
    "preview": "html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }\n\nbody{\ncolor:#"
  },
  {
    "path": "doc/themes/markdown5.css",
    "chars": 2185,
    "preview": "body{\n    margin: 0 auto;\n    background-color:white;\n\n/*\t--------- FONT FAMILY --------\n    following are some optional"
  },
  {
    "path": "doc/themes/markdown6.css",
    "chars": 3358,
    "preview": "/* Extracted and interpreted from adcstyle.css and frameset_styles.css */\n\n/* body */\nbody {\n    margin: 20px auto;\n    "
  },
  {
    "path": "doc/themes/markdown7.css",
    "chars": 5643,
    "preview": "body {\n   font-family: Helvetica, arial, sans-serif;\n   font-size: 14px;\n   line-height: 1.6;\n   padding-top: 10px;\n   p"
  },
  {
    "path": "doc/themes/markdown8.css",
    "chars": 2195,
    "preview": "h1, h2, h3, h4, h5, h6, p, blockquote {\n   margin: 0;\n   padding: 0;\n}\nbody {\n   font-family: \"Helvetica Neue\", Helvetic"
  },
  {
    "path": "doc/themes/markdown9.css",
    "chars": 2221,
    "preview": "h1, h2, h3, h4, h5, h6, p, blockquote {\n   margin: 0;\n   padding: 0;\n}\nbody {\n   font-family: \"Helvetica Neue\", Helvetic"
  },
  {
    "path": "doc/themes/markedapp-byword.css",
    "chars": 6013,
    "preview": "/*\n * This document has been created with Marked.app <http://markedapp.com>.\n * Copyright 2011 Brett Terpstra\n * -------"
  },
  {
    "path": "doc/themes/new-modern.css",
    "chars": 9571,
    "preview": "/* Title: New Modern */\n/* Author: Jocelyn Richard http://jocelynrichard.com/ */\n/* Description: Baseline style, meant t"
  },
  {
    "path": "doc/themes/radar.css",
    "chars": 5391,
    "preview": "\nbody {\n  margin: 0px;\n\n  font-family: 'PT Sans', Helvetica, 'Helvetica Neuve', Arial, Tahoma, sans-serif;\n  font-size: "
  },
  {
    "path": "doc/themes/screen.css",
    "chars": 2202,
    "preview": "html { font-size: 62.5%; }\nhtml, body { height: 100%; }\n\nbody {\n    font-family: Helvetica, Arial, sans-serif;\n    font-"
  },
  {
    "path": "doc/themes/solarized-dark.css",
    "chars": 4179,
    "preview": "article,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\n"
  },
  {
    "path": "doc/themes/solarized-light.css",
    "chars": 4163,
    "preview": "article,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\n"
  },
  {
    "path": "doc/themes/torpedo.css",
    "chars": 18778,
    "preview": "/* Title: Torpedo */\n/* Author: Jocelyn Richard http://jocelynrichard.com/ */\n/* Description: A muted color palette for "
  },
  {
    "path": "doc/themes/vostok.css",
    "chars": 13519,
    "preview": "/* Title: Vostok */\n/* Author: Jocelyn Richard http://jocelynrichard.com/ */\n/* Description: Generous x-height and contr"
  },
  {
    "path": "doc.go",
    "chars": 683,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "env/doc.go",
    "chars": 673,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "env/env.go",
    "chars": 16389,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "env/env_test.go",
    "chars": 9752,
    "preview": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
  },
  {
    "path": "env/testdata/TestNewVirtualEnv.golden",
    "chars": 3313,
    "preview": "use sakila OK\nselect frm syntaxError OK\ncreate table t(id int,c1 varchar(20),PRIMARY KEY (id)); OK\nalter table t add ind"
  },
  {
    "path": "etc/soar.blacklist",
    "chars": 116,
    "preview": "# 这是一个黑名单例子\n## 不评审常见的SET, SHOW, SELECT CONST等完美请求\n^set.*\n^show.*\n^select \\?$\n^\\/\\*.*\\*\\/$\n^drop.*\n^lock.*\n^unlock.*\n"
  },
  {
    "path": "etc/soar.yaml",
    "chars": 450,
    "preview": "# 这是一个配置文件例子\nonline-dsn:\n    addr:     127.0.0.1:3306\n    schema:   sakila\n    user:     root\n    password: \"1tIsB1g3rt\""
  },
  {
    "path": "genver.sh",
    "chars": 799,
    "preview": "#!/bin/bash\n\n## Generate Repository Version\ntag=\"$(git describe --tags --always)\"\nversion=\"$(git log --date=iso --pretty"
  },
  {
    "path": "go.mod",
    "chars": 3732,
    "preview": "module github.com/XiaoMi/soar\n\ngo 1.15\n\nrequire (\n\tgithub.com/Azure/azure-storage-blob-go v0.10.0 // indirect\n\tgithub.co"
  },
  {
    "path": "go.sum",
    "chars": 154684,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
  },
  {
    "path": "retool-install.sh",
    "chars": 1059,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\n# This script generates tools.json\n# It helps record what releases/branches are b"
  },
  {
    "path": "revive.toml",
    "chars": 1118,
    "preview": "ignoreGeneratedHeader = false\nseverity = \"error\"\nconfidence = 0.8\nerrorCode = 0\nwarningCode = 0\n\n[rule.blank-imports]\n[r"
  },
  {
    "path": "test/env.bats",
    "chars": 845,
    "preview": "#!/usr/bin/env bats\n\nload test_helper\n\n@test \"Simple Query Optimizer\" {\n  ${SOAR_BIN_ENV} -query \"select * from film whe"
  },
  {
    "path": "test/fixture/test_Check_Max_Join_Table_Count_Default.golden",
    "chars": 113,
    "preview": "# Query: E813EA038141E9CE\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  a  \nFROM  \n  b  \n  JOIN  c  \n  JOIN  d\n```\n\n## OK\n\n"
  },
  {
    "path": "test/fixture/test_Check_Max_Join_Table_Count_Overflow.golden",
    "chars": 236,
    "preview": "# Query: E813EA038141E9CE\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  a  \nFROM  \n  b  \n  JOIN  c  \n  JOIN  d\n```\n\n## 减少 JOIN 的数量\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_Delimiter.golden",
    "chars": 556,
    "preview": "# Query: 8093354EDF76BFDA\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  b  \nFROM  \n  c\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**"
  },
  {
    "path": "test/fixture/test_Check_Soar_Max_Column_Count.golden",
    "chars": 793,
    "preview": "# Query: 7EC60923DD614DF2\n\n★ ☆ ☆ ☆ ☆ 30分\n\n```sql\ncreate  table  a  (a  int, b  int, c  int, d  int)\n```\n\n## 建议为表添加注释\n\n* "
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Fingerprint.golden",
    "chars": 7475,
    "preview": "select * from film where length = ?\nselect * from film where length is null\nselect * from film having title = ?\nselect *"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Alwaystrue_.golden",
    "chars": 43,
    "preview": "select count(col) from tbl where (a = 'b')\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Countstar_.golden",
    "chars": 36,
    "preview": "select count(*) from tbl group by 1\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Delimiter_.golden",
    "chars": 12,
    "preview": "use sakila;\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Distinctstar_.golden",
    "chars": 19,
    "preview": "SELECT * FROM film\n"
  }
]

// ... and 29 more files (download for full content)

About this extraction

This page contains the full source code of the XiaoMi/soar GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 229 files (1.9 MB), approximately 727.0k tokens, and a symbol index with 812 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!