[
  {
    "path": ".editorconfig",
    "content": "[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\n\n# tab_size = 4 spaces\n[*.go]\nindent_style = tab\nindent_size = 4\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug Report\nabout: You're experiencing an issue with SOAR that is different than the documented behavior.\n\n---\n\nPlease answer these questions before submitting your issue. Thanks!\n\n1. What did you do?\nIf possible, provide a recipe for reproducing the error.\n\n\n2. What did you expect to see?\n\n\n\n3. What did you see instead?\n\n\n\n4. What version of are you using (`soar -version`)?\n\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature Request\nabout: If you have something you think SOAR could improve or add support for.\n\n---\n\nPlease search the existing issues for relevant feature requests,  add upvotes to pre-existing requests.\n\n#### Feature Description\n\nA written overview of the feature.\n\n#### Use Case(s)\n\nAny relevant use-cases that you see.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: If you have a question, please check out our other community resources instead of opening an issue.\n\n---\n\nIssues on GitHub are intended to be related to bugs or feature requests, so we recommend using our other community resources instead of asking here.\n\n- [SOAR Doc](http://github.com/XiaoMi/soar/blob/master/README.md)\n- Any other questions can be asked in the community [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar)\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\nThank you for contributing to SOAR! Please read SOAR's [CONTRIBUTING](https://github.com/XiaoMi/soar/blob/master/CONTRIBUTING.md) document **BEFORE** filing this PR.\n-->\n\n### What problem does this PR solve? <!--add issue link with summary if exists-->\n\n\n### What is changed and how it works?\n\n\n### Check List <!--REMOVE the items that are not applicable-->\n\nTests <!-- At least one of them must be included. -->\n\n - Unit test\n - Integration test\n - Manual test (add detailed scripts or steps below)\n - No code\n\nCode changes\n\n - Has exported function/method change\n - Has exported variable/fields change\n - Has interface methods change\n - Has persistent data change\n\nSide effects\n\n - Possible performance regression\n - Increased code complexity\n - Breaking backward compatibility\n"
  },
  {
    "path": ".gitignore",
    "content": "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\nTestMarkdown2Html.html\n"
  },
  {
    "path": ".travis.yml",
    "content": "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\nservices:\n  - docker\n\nbefore_install:\n  - docker pull mysql\n  - sudo add-apt-repository ppa:duggan/bats --yes\n  - sudo apt-get update -qq\n  - sudo apt-get install -qq bats\n\nscript:\n  - make build\n  - make docker\n  - make cover\n  - make test-cli\n"
  },
  {
    "path": "CHANGES.md",
    "content": "# CHANGELOG\n\n## 2019-08\n- Fix RuleImplicitConversion(ARG.003) with INT and DECIMAL\n- Fix RuleImplicitConversion duplicate suggest when use IN () operator\n\n## 2019-07\n- Fix #213 CLA.001 NO WHERE CONDITION\n- Fix PRIMARY key append to multi column index\n- fingerprint verbose mode add id\n\n## 2019-05\n- Fix issue #208 14c19f4 regression bug\n- Add max_execution_time hint for explain query\n- Fix #205 create index rewrite error\n\n## 2019-04\n- Add test case for STA.004\n- RuleSpaceWithQuote add list range check\n- Fix #199 -report-type=json add score\n- Fix #98 JSON result format\n- Fix index col compare case sensitive bug\n- Fix ARG.008 cases: col = 1 OR col IS NULL\n- Fix tokenize bug with multi type of quote\n\n## 2019-02\n- add go.mod for go1.11\n- add new -report-type query-type\n- add new heuristic rule SEC.004\n- fix #196 wrong ip/password will cause soar -check-config hangup\n\n## 2019-01\n\n- add mysql environment verbose info\n- add JSONFind function, which support JSON iterate\n- add new test database `world_x`\n- SplitStatement support optimizer hint `/*+xxx */`\n- include [bats](https://github.com/bats-core/bats-core) bash auto test framework\n- fix #173 with JSONFind `WHERE col = col = '' and col1 = 'xx'`\n- fix #184 table status field datatype overflow\n- fix explain result with multi rows error\n- fix #178 JSON datatype only support utf8mb4\n\n## 2018-12\n\n- replace mysql database driver mymysql with go-sql-driver\n- add new -report-type [ast-json, tiast-json]\n- command line dsn args support '@', '/', ':' in password\n- add new heuristic rule RES.009, \"SELECT * FROM tbl WHERE col = col = 'abc'\"\n- add new heuristic rule RuleColumnNotAllowType COL.018\n- add string escape function for security\n- fix #122 single table select * don't auto-complete table name\n- fix #171 support socket access type\n- fix #58 sampling not deal with NULL able string\n- fix #172 compatible with mysql 5.1, which explain has no Index_Comment column\n- fix #163 column.Tp may be nil, which may raise panic\n- fix #151 bit type not config as int, when two columns compare will give ARG.003 suggestion.\n- \n## 2018-11\n\n- add all third-party lib into vendor\n- support `-report-type chardet`\n- add more heuristic rules: TBL.008, KEY.010, ARG.012, KWR.004\n- add -cleanup-test-database command-line arg\n- add -check-config parameter\n- fix #146 pretty cause syntax error\n- fix #140 COL.012, COL.015 NULL type about TEXT/BLOB\n- fix #141 empty output when query execute failed on mysql\n- fix #89 index advisor give wrong database name, `optimizer_xx`\n- fix #121 RemoveSQLComment trim space\n- fix #120 trimspace before check single line comment\n- fix mac os stdout print buffer truncate\n- fix -config arg load file error\n- fix #116 SplitStatement check if single comment line is in multi-line sql.\n- fix #112 multi-line comment will cause line counter error, when -report-type=lint\n- fix #110 remove bom before auditing\n- fix #104 case insensitive regex @ CLA.009\n- fix #87 RuleImplicitConversion value type mismatch check bug\n- fix #38 always true where condition check\n- abandon stdin terminal interactive mod, which may seems like hangup\n\n## 2018-10\n\n- Fix SplitStatement multistatement eof bug #66\n- Fix pretty func hangup issue #47\n- Fix some foolish code spell error\n- Use travis for CI\n- Fix Go 1.8 default GOPATH compatible issue BUG #5\n- 2018-10-20 开源先锋日(OSCAR)对外正式开源发布代码\n\n## 2018-09\n\n- 修复多个启发式建议不准确BUG，优化部分建议文案使得建议更清晰\n- 基于 TiDB Parser 完善多个 DDL 类型语句的建议\n- 新增lint report-type类型，支持Vim Plugin优化建议输出\n- 更新整理项目文档，开源准备\n- 2018-09-21 Gdevops SOAR首次对外进行技术分享宣传\n\n## 2018-08\n\n- 利用 docker 临时容器进行 daily 测试\n- 添加main_test全功能回归测试\n- 修复在测试中发现的问题\n- mymysql 合并 MySQL8.0 相关PR，修改vendor依赖\n- 改善HeuristicRule中的文案\n- 持续集成Vitess Parser的改进\n- NewQuery4Audit 结构体中引入 TiDB Parser\n- 通过TiAST完成大量与 DDL 相关的TODO\n- 修改heuristic rules检查的返回值，提升拓展性\n- 建议中引入Position，用于表示建议产生于SQL的位置\n- 新增多个HeuristicRule\n- Makefile中添加依赖检查，优化Makefile中逻辑，添加新功能\n- 优化gometalinter性能，引入新的代码质量检测工具，提升代码质量\n- 引入 retool 用于管理依赖的工具\n- 优化 doc 文档\n\n## 2018-07\n\n- 补充文档，添加项目LOGO\n- 改善代码质量提升测试覆盖度\n- mymysql升级，支持MySQL 8.0\n- 提供remove-comment小工具\n- 提供索引重复检查小工具\n- HeuristicRule 新增 RuleSpaceAfterDot\n- 支持字符集和Collation不相同时的隐式数据类型转换的检查\n\n## 2018-06\n\n- 支持更多的SQL Rewrite规则\n- 添加SQL执行超时限制\n- 索引优化建议支持对约束的检查\n- 修复数据采样中 NULL 值处理不正确的问题\n- Explain 支持 last_query_cost\n\n## 2018-05\n\n- 添加数据采样功能\n- 添加语句执行安全检查\n- 支持DDL语法检查\n- 支持DDL在测试环境的执行\n- 支持隐式数据类型转换检查\n- 支持索引去重\n- 索引优化建议支持前缀索引\n- 支持SQL Pretty输出\n\n## 2018-04\n\n- 支持语法检查\n- 支持测试环境\n- 支持MySQL原数据的获取\n- 支持基于数据库环境信息给予索引优化建议\n- 支持不依赖数据库原信息的简单索引优化建议\n- 添加日志模块\n- 引入配置文件\n\n## 2018-03\n\n- 基本架构设计\n- 添加大量底层函数用于处理AST\n- 添加Insert、Delete、Update 转写成 Select 的基本函数\n- 支持MySQL Explain信息输出\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn 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.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject 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.\n\nProject 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.\n\n## Scope\n\nThis 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.\n\n## Enforcement\n\nInstances 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.\n\nProject 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.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Ask questions at [Gitter](https://gitter.im/xiaomi-dba/soar).\n\n[Open an issue](https://github.com/xiaomi/soar/issues/new) to discuss your plans before doing any work on SOAR.\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "# This how we want to name the binary output\n#\n# use checkmake linter https://github.com/mrtazz/checkmake\n# $ checkmake Makefile\n#\nBINARY=soar\nGOPATH ?= $(shell go env GOPATH)\nGO111MODULE:=auto\nexport GO111MODULE\n# Ensure GOPATH is set before running build process.\nifeq \"$(GOPATH)\" \"\"\n  $(error Please set the environment variable GOPATH before running `make`)\nendif\nPATH := ${GOPATH}/bin:$(PATH)\nGCFLAGS=-gcflags \"all=-trimpath=${GOPATH}\"\nVERSION_TAG := $(shell git describe --tags --always)\nVERSION_VERSION := $(shell git log --date=iso --pretty=format:\"%cd\" -1) $(VERSION_TAG)\nVERSION_COMPILE := $(shell date +\"%F %T %z\") by $(shell go version)\nVERSION_BRANCH  := $(shell git rev-parse --abbrev-ref HEAD)\nVERSION_GIT_DIRTY := $(shell git diff --no-ext-diff 2>/dev/null | wc -l | awk '{print $1}')\nVERSION_DEV_PATH:= $(shell pwd)\nLDFLAGS=-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)'\"\n\n# These are the values we want to pass for VERSION  and BUILD\nBUILD_TIME=`date +%Y%m%d%H%M`\nCOMMIT_VERSION=`git rev-parse HEAD`\n\n# colors compatible setting\nCRED:=$(shell tput setaf 1 2>/dev/null)\nCGREEN:=$(shell tput setaf 2 2>/dev/null)\nCYELLOW:=$(shell tput setaf 3 2>/dev/null)\nCEND:=$(shell tput sgr0 2>/dev/null)\n\n# Add mysql version for testing `MYSQL_RELEASE=percona MYSQL_VERSION=5.7 make docker`\n# MySQL 5.1 `MYSQL_RELEASE=vsamov/mysql-5.1.73 make docker`\n# MYSQL_RELEASE: mysql, percona, mariadb ...\n# MYSQL_VERSION: latest, 8.0, 5.7, 5.6, 5.5 ...\n# use mysql:latest as default\nMYSQL_RELEASE := $(or ${MYSQL_RELEASE}, ${MYSQL_RELEASE}, mysql)\nMYSQL_VERSION := $(or ${MYSQL_VERSION}, ${MYSQL_VERSION}, latest)\n\n.PHONY: all\nall: | fmt build\n\n.PHONY: go_version_check\nGO_VERSION_MIN=1.12\n# 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)\n# that allows the three components to be checked in a single comparison.\nVER_TO_INT:=awk '{split(substr($$0, match ($$0, /[0-9\\.]+/)), a, \".\"); print a[1]*10000+a[2]*100+a[3]}'\ngo_version_check:\n\t@echo \"$(CGREEN)Go version check ...$(CEND)\"\n\t@if test $(shell go version | $(VER_TO_INT) ) -lt \\\n  \t$(shell echo \"$(GO_VERSION_MIN)\" | $(VER_TO_INT)); \\\n  \tthen printf \"go version $(GO_VERSION_MIN)+ required, found: \"; go version; exit 1; \\\n\t\telse echo \"go version check pass\";\tfi\n\n# Dependency check\n.PHONY: deps\ndeps:\n\t@echo \"$(CGREEN)Dependency check ...$(CEND)\"\n\t@bash ./deps.sh\n\t# The retool tools.json is setup from retool-install.sh\n\t# some packages download need more open internet access\n\tretool sync\n\t#retool do gometalinter.v2 --install\n\n# Code format\n.PHONY: fmt\nfmt: go_version_check\n\t@echo \"$(CGREEN)Run gofmt on all source files ...$(CEND)\"\n\t@echo \"gofmt -l -s -w ...\"\n\t@ret=0 && for d in $$(go list -f '{{.Dir}}' ./... | grep -v /vendor/); do \\\n\t\tgofmt -l -s -w $$d/*.go || ret=$$? ; \\\n\tdone ; exit $$ret\n\n# Run golang test cases\n.PHONY: test\ntest:\n\t@echo \"$(CGREEN)Run all test cases ...$(CEND)\"\n\t@go test $(LDFLAGS) -timeout 10m -race ./...\n\t@echo \"test Success!\"\n\n# Rule golang test cases with `-update` flag\n.PHONY: test-update\ntest-update:\n\t@echo \"$(CGREEN)Run all test cases with -update flag ...$(CEND)\"\n\t@go test $(LDFLAGS) ./... -update\n\t@echo \"test-update Success!\"\n\n# Using bats test framework run all cli test cases\n# https://github.com/sstephenson/bats\n.PHONY: test-cli\ntest-cli: build\n\t@echo \"$(CGREEN)Run all cli test cases ...$(CEND)\"\n\tbats ./test\n\t@echo \"test-cli Success!\"\n\n# Code Coverage\n# colorful coverage numerical >=90% GREEN, <80% RED, Other YELLOW\n.PHONY: cover\ncover: test\n\t@echo \"$(CGREEN)Run test cover check ...$(CEND)\"\n\t@go test $(LDFLAGS) -coverpkg=./... -coverprofile=coverage.data ./... | column -t\n\t@go tool cover -html=coverage.data -o coverage.html\n\t@go tool cover -func=coverage.data -o coverage.txt\n\t@tail -n 1 coverage.txt | awk '{sub(/%/, \"\", $$NF); \\\n\t\tif($$NF < 80) \\\n\t\t\t{print \"$(CRED)\"$$0\"%$(CEND)\"} \\\n\t\telse if ($$NF >= 90) \\\n\t\t\t{print \"$(CGREEN)\"$$0\"%$(CEND)\"} \\\n\t\telse \\\n\t\t\t{print \"$(CYELLOW)\"$$0\"%$(CEND)\"}}'\n\n# Builds the project\nbuild: fmt\n\t@echo \"$(CGREEN)Building ...$(CEND)\"\n\t@mkdir -p bin\n\t@ret=0 && for d in $$(go list -f '{{if (eq .Name \"main\")}}{{.ImportPath}}{{end}}' ./...); do \\\n\t\tb=$$(basename $${d}) ; \\\n\t\tgo build ${LDFLAGS} ${GCFLAGS} -o bin/$${b} $$d || ret=$$? ; \\\n\tdone ; exit $$ret\n\t@echo \"build Success!\"\n\n# Installs our project: copies binaries\ninstall: build\n\t@echo \"$(CGREEN)Install ...$(CEND)\"\n\tgo install ./...\n\t@echo \"install Success!\"\n\n# Generate doc use -list* command\n.PHONY: doc\ndoc: build\n\t@echo \"$(CGREEN)Auto generate doc ...$(CEND)\"\n\t./bin/soar -list-heuristic-rules > doc/heuristic.md\n\t./bin/soar -list-rewrite-rules > doc/rewrite.md\n\t./bin/soar -list-report-types > doc/report_type.md\n\n# Add or change a heuristic rule\n.PHONY: heuristic\nheuristic: doc\n\t@echo \"$(CGREEN)Update Heuristic rule golden files ...$(CEND)\"\n\tgo test github.com/XiaoMi/soar/advisor -v -update -run TestListHeuristicRules\n\tgo test github.com/XiaoMi/soar/advisor -v -update -run TestMergeConflictHeuristicRules\n\tdocker stop soar-mysql 2>/dev/null || true\n\n# Update all vendor\n.PHONY: vendor\nvendor: vitess pingcap-parser\n# gometalinter\n.PHONY: lint\nlint: build\n\t@echo \"$(CGREEN)Run linter check ...$(CEND)\"\n\tCGO_ENABLED=0 GOMODULE111=off retool do gometalinter.v2 -j 1 --config doc/example/metalinter.json ./...\n\tGOMODULE111=off retool do revive -formatter friendly --exclude vendor/... -config doc/example/revive.toml ./...\n\tGOMODULE111=off retool do golangci-lint --tests=false run\n\t@echo \"gometalinter check your code is pretty good\"\n\n.PHONY: release\nrelease: build\n\t@echo \"$(CGREEN)Cross platform building for release ...$(CEND)\"\n\t@mkdir -p release\n\t@for GOOS in linux windows; do \\\n\t\tfor GOARCH in amd64; do \\\n\t\t\tfor d in $$(go list -f '{{if (eq .Name \"main\")}}{{.ImportPath}}{{end}}' ./...); do \\\n\t\t\t\tb=$$(basename $${d}) ; \\\n\t\t\t\techo \"Building $${b}.$${GOOS}-$${GOARCH} ...\"; \\\n\t\t\t\tCGO_ENABLED=0 GOOS=$${GOOS} GOARCH=$${GOARCH} go build ${GCFLAGS} ${LDFLAGS} -v -o release/$${b}.$${GOOS}-$${GOARCH} $$d 2>/dev/null ; \\\n\t\t\tdone ; \\\n\t\tdone ;\\\n\tdone\n\t@for GOOS in darwin; do \\\n\t\tfor GOARCH in arm64 amd64; do \\\n\t\t\tfor d in $$(go list -f '{{if (eq .Name \"main\")}}{{.ImportPath}}{{end}}' ./...); do \\\n\t\t\t\tb=$$(basename $${d}) ; \\\n\t\t\t\techo \"Building $${b}.$${GOOS}-$${GOARCH} ...\"; \\\n\t\t\t\tCGO_ENABLED=0 GOOS=$${GOOS} GOARCH=$${GOARCH} go build ${GCFLAGS} ${LDFLAGS} -v -o release/$${b}.$${GOOS}-$${GOARCH} $$d 2>/dev/null ; \\\n\t\t\tdone ; \\\n\t\tdone ;\\\n\tdone\n\n.PHONY: docker\ndocker:\n\t@echo \"$(CGREEN)Build mysql test environment ...$(CEND)\"\n\t@docker stop soar-mysql 2>/dev/null || true\n\t@docker wait soar-mysql 2>/dev/null >/dev/null || true\n\t@echo \"docker run --name soar-mysql $(MYSQL_RELEASE):$(MYSQL_VERSION)\"\n\t@docker run --name soar-mysql --rm -d \\\n\t-e MYSQL_ROOT_PASSWORD=1tIsB1g3rt \\\n\t-e MYSQL_DATABASE=sakila \\\n\t-p 3306:3306 \\\n\t-v `pwd`/test/sql/init.sql.gz:/docker-entrypoint-initdb.d/init.sql.gz \\\n\t$(MYSQL_RELEASE):$(MYSQL_VERSION) \\\n\t--sql-mode \"\"\n\n\t@echo \"waiting for sakila database initializing \"\n\t@timeout=180; while [ $${timeout} -gt 0 ] ; do \\\n\t\tif ! docker exec soar-mysql mysql --user=root --password=1tIsB1g3rt --host \"127.0.0.1\" --silent -NBe \"do 1\" >/dev/null 2>&1 ; then \\\n\t\t\ttimeout=`expr $$timeout - 1`; \\\n\t\t\tprintf '.' ;  sleep 1 ; \\\n\t\telse \\\n\t\t\techo \".\" ; echo \"mysql test environment is ready!\" ; break ; \\\n\t\tfi ; \\\n\t\tif [ $$timeout = 0 ] ; then \\\n\t\t\techo \".\" ; echo \"$(CRED)docker soar-mysql start timeout(180 s)!$(CEND)\" ; exit 1 ; \\\n\t\tfi ; \\\n\tdone\n\n.PHONY: docker-connect\ndocker-connect:\n\t@docker exec -it soar-mysql mysql --user=root --password=1tIsB1g3rt --host \"127.0.0.1\" sakila\n\n# attach docker container with bash interactive mode\n.PHONY: docker-it\ndocker-it:\n\tdocker exec -it soar-mysql /bin/bash\n\n.PHONY: daily\ndaily: | deps fmt vendor docker cover doc lint release install test-cli clean logo\n\t@echo \"$(CGREEN)daily build finished ...$(CEND)\"\n\n# vendor, docker will cost long time, if all those are ready, daily-quick will much more fast.\n.PHONY: daily-quick\ndaily-quick: | deps fmt cover test-cli doc lint logo\n\t@echo \"$(CGREEN)daily-quick build finished ...$(CEND)\"\n\n.PHONY: logo\nlogo:\n\t@echo \"$(CYELLOW)\"\n\t@cat doc/images/logo.ascii\n\t@echo \"$(CEND)\"\n\n# Cleans our projects: deletes binaries\n.PHONY: clean\nclean:\n\t@echo \"$(CGREEN)Cleanup ...$(CEND)\"\n\tgo clean\n\t@for GOOS in darwin linux windows; do \\\n\t    for GOARCH in 386 amd64; do \\\n\t\t\trm -f ${BINARY}.$${GOOS}-$${GOARCH} ;\\\n\t\tdone ;\\\n\tdone\n\trm -f ${BINARY} coverage.* test/tmp/*\n\tfind . -name \"*.log\" -delete\n\tgit clean -fi\n\tdocker stop soar-mysql 2>/dev/null || true\n"
  },
  {
    "path": "NOTICE.txt",
    "content": "\nCopyright 2018 Xiaomi, Inc.  All Rights Reserved.\nThis product includes software developed by Xiaomi, Inc.\n(http://www.mi.com/).\nThis product is licensed to you under the Apache License, Version 2.0\n(the \"License\").  You may not use this product except in compliance with\nthe License.\n\n"
  },
  {
    "path": "README.md",
    "content": "# ![SOAR](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/logo.png)\n\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://github.com/XiaoMi/soar/blob/master/LICENSE)\n[![Go Report Card](https://goreportcard.com/badge/github.com/XiaoMi/soar)](https://goreportcard.com/report/github.com/XiaoMi/soar)\n[![Build Status](https://travis-ci.org/XiaoMi/soar.svg?branch=master)](https://travis-ci.org/XiaoMi/soar)\n[![GoDoc](https://godoc.org/github.com/XiaoMi/soar?status.svg)](https://godoc.org/github.com/XiaoMi/soar)\n\n[文档](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)\n\n## SOAR\n\nSOAR(SQL Optimizer And Rewriter) 是一个对 SQL 进行优化和改写的自动化工具。 由小米人工智能与云平台的数据库团队开发与维护。\n\n## 功能特点\n\n* 跨平台支持（支持 Linux, Mac 环境，Windows 环境理论上也支持，不过未全面测试）\n* 目前只支持 MySQL 语法族协议的 SQL 优化\n* 支持基于启发式算法的语句优化\n* 支持复杂查询的多列索引优化（UPDATE, INSERT, DELETE, SELECT）\n* 支持 EXPLAIN 信息丰富解读\n* 支持 SQL 指纹、压缩和美化\n* 支持同一张表多条 ALTER 请求合并\n* 支持自定义规则的 SQL 改写\n\n## 快速入门\n\n* [安装使用](http://github.com/XiaoMi/soar/blob/master/doc/install.md)\n* [体系架构](http://github.com/XiaoMi/soar/blob/master/doc/structure.md)\n* [配置文件](http://github.com/XiaoMi/soar/blob/master/doc/config.md)\n* [常用命令](http://github.com/XiaoMi/soar/blob/master/doc/cheatsheet.md)\n* [产品对比](http://github.com/XiaoMi/soar/blob/master/doc/comparison.md)\n* [路线图](http://github.com/XiaoMi/soar/blob/master/doc/roadmap.md)\n\n## 交流与反馈\n\n* 欢迎通过 Github Issues 提交问题报告与建议\n* QQ 群：779359816（未满） 758940447（已满）\n* [Gitter](https://gitter.im/xiaomi-dba/soar) 推荐\n\n ![xiaomi_sa](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/xiaomi_sa.png)\n\n## License\n\n[Apache License 2.0](https://github.com/XiaoMi/soar/blob/master/LICENSE).\n"
  },
  {
    "path": "README_EN.md",
    "content": "# ![SOAR](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/logo.png)\n\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://github.com/XiaoMi/soar/blob/master/LICENSE)\n[![Go Report Card](https://goreportcard.com/badge/github.com/XiaoMi/soar)](https://goreportcard.com/report/github.com/XiaoMi/soar)\n[![Build Status](https://travis-ci.org/XiaoMi/soar.svg?branch=master)](https://travis-ci.org/XiaoMi/soar)\n[![GoDoc](https://godoc.org/github.com/XiaoMi/soar?status.svg)](https://godoc.org/github.com/XiaoMi/soar)\n\n[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)\n\n## SOAR\n\nSOAR (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.\n\n## Features\n\n* Cross-platform support, such as Linux, Mac, and Windows\n* Support Heuristic Rules Suggestion\n* Support Complicate SQL Indexing Optimize\n* Support EXPLAIN analyze for query plan\n* Support SQL fingerprint, compress and built-in pretty print\n* Support merge multi ALTER query into one SQL\n* Support self-config rewrite rules from SQL Rewrite\n* Suggestions were written in Chinese. But SOAR also gives many tools, which can be used without understanding Chinese.\n\n## QuickStart\n\n* [Install](http://github.com/XiaoMi/soar/blob/master/doc/install_en.md)\n* [CheatSheet](http://github.com/XiaoMi/soar/blob/master/doc/cheatsheet_en.md)\n* [Related works](http://github.com/XiaoMi/soar/blob/master/doc/comparison_en.md)\n\n## Communication\n\n* GitHub issues: bug reports, usage issues, feature requests\n* [Gitter](https://gitter.im/xiaomi-dba/soar)\n* IM QQ Group: 779359816\n\n## License\n\n[Apache License 2.0](https://github.com/XiaoMi/soar/blob/master/LICENSE).\n"
  },
  {
    "path": "VERSION",
    "content": "0.11.0\n"
  },
  {
    "path": "advisor/doc.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package advisor contain heuristic rules, index rules and explain translator.\npackage advisor\n"
  },
  {
    "path": "advisor/explainer.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage advisor\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\t\"github.com/XiaoMi/soar/database\"\n)\n\nvar explainRuleID int\n\n// [EXP.XXX]Rule\nvar explainRules map[string]Rule\n\n// [table_name]\"suggest text\"\nvar tablesSuggests map[string][]string\n\n// explain建议的形式\n// Item: EXP.XXX\n// Severity: L[0-8]\n// Summary: full table scan, not use index, full index scan...\n// Content: XX TABLE xxx\n\n// checkExplainSelectType\nfunc checkExplainSelectType(exp *database.ExplainInfo) {\n\t// 判断是否跳过不检查\n\tif len(common.Config.ExplainWarnSelectType) == 1 {\n\t\tif common.Config.ExplainWarnSelectType[0] == \"\" {\n\t\t\treturn\n\t\t}\n\t} else if len(common.Config.ExplainWarnSelectType) < 1 {\n\t\treturn\n\t}\n\n\tif exp.ExplainFormat == database.JSONFormatExplain {\n\t\t// TODO\n\t\t// JSON 形式遍历分析不方便，转成 Row 格式也没有 SelectType 暂不处理\n\t\treturn\n\t}\n\tfor _, v := range common.Config.ExplainWarnSelectType {\n\t\tfor _, row := range exp.ExplainRows {\n\t\t\tif row.SelectType == v && v != \"\" {\n\t\t\t\ttablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf(\"SelectType:%s\", row.SelectType))\n\t\t\t}\n\t\t}\n\t}\n}\n\n// checkExplainAccessType 用户可以设置AccessType的建议级别，匹配到的查询会给出建议\nfunc checkExplainAccessType(exp *database.ExplainInfo) {\n\t// 判断是否跳过不检查\n\tif len(common.Config.ExplainWarnAccessType) == 1 {\n\t\tif common.Config.ExplainWarnAccessType[0] == \"\" {\n\t\t\treturn\n\t\t}\n\t} else if len(common.Config.ExplainWarnAccessType) < 1 {\n\t\treturn\n\t}\n\n\trows := exp.ExplainRows\n\tif exp.ExplainFormat == database.JSONFormatExplain {\n\t\t// JSON形式遍历分析不方便，转成Row格式统一处理\n\t\trows = database.ConvertExplainJSON2Row(exp.ExplainJSON)\n\t}\n\tfor _, v := range common.Config.ExplainWarnAccessType {\n\t\tfor _, row := range rows {\n\t\t\tif row.AccessType == v && v != \"\" {\n\t\t\t\ttablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf(\"Scalability:%s\", row.Scalability))\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n// TODO:\nfunc checkExplainPossibleKeys(exp *database.ExplainInfo) {\n}\n\nfunc checkExplainKeyLen(exp *database.ExplainInfo) {\n}\n\nfunc checkExplainKey(exp *database.ExplainInfo) {\n\t// 小于最小使用试用key数量\n\t//return intval($explainResult) < intval($userCond);\n\t//explain-min-keys int\n}\n\nfunc checkExplainExtra(exp *database.ExplainInfo) {\n\t// 包含用户配置的逗号分隔关键词之一则提醒\n\t// return self::contains($explainResult, $userCond);\n\t// explain-warn-extra []string\n}\n*/\n\n// checkExplainRef ...\nfunc checkExplainRef(exp *database.ExplainInfo) {\n\trows := exp.ExplainRows\n\tif exp.ExplainFormat == database.JSONFormatExplain {\n\t\t// JSON形式遍历分析不方便，转成Row格式统一处理\n\t\trows = database.ConvertExplainJSON2Row(exp.ExplainJSON)\n\t}\n\tfor i, row := range rows {\n\t\tif strings.Join(row.Ref, \"\") == \"NULL\" || strings.Join(row.Ref, \"\") == \"\" {\n\t\t\tif i == 0 && len(rows) > 1 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf(\"Ref:null\"))\n\t\t}\n\t}\n}\n\n// checkExplainRows ...\nfunc checkExplainRows(exp *database.ExplainInfo) {\n\t// 判断是否跳过不检查\n\tif common.Config.ExplainMaxRows <= 0 {\n\t\treturn\n\t}\n\n\trows := exp.ExplainRows\n\tif exp.ExplainFormat == database.JSONFormatExplain {\n\t\t// JSON形式遍历分析不方便，转成Row格式统一处理\n\t\trows = database.ConvertExplainJSON2Row(exp.ExplainJSON)\n\t}\n\n\tfor _, row := range rows {\n\t\tif row.Rows >= common.Config.ExplainMaxRows {\n\t\t\ttablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf(\"Rows:%d\", row.Rows))\n\t\t}\n\t}\n}\n\n// checkExplainFiltered ...\nfunc checkExplainFiltered(exp *database.ExplainInfo) {\n\t// 判断是否跳过不检查\n\tif common.Config.ExplainMaxFiltered <= 0.001 {\n\t\treturn\n\t}\n\n\trows := exp.ExplainRows\n\tif exp.ExplainFormat == database.JSONFormatExplain {\n\t\t// JSON形式遍历分析不方便，转成Row格式统一处理\n\t\trows = database.ConvertExplainJSON2Row(exp.ExplainJSON)\n\t}\n\tfor i, row := range rows {\n\t\tif i == 0 && len(rows) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tif row.Filtered >= common.Config.ExplainMaxFiltered {\n\t\t\ttablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf(\"Filtered:%.2f%s\", row.Filtered, \"%\"))\n\t\t}\n\t}\n}\n\n// ExplainAdvisor 基于explain信息给出建议\nfunc ExplainAdvisor(exp *database.ExplainInfo) map[string]Rule {\n\tcommon.Log.Debug(\"ExplainAdvisor SQL: %v\", exp.SQL)\n\texplainRuleID = 0\n\texplainRules = make(map[string]Rule)\n\ttablesSuggests = make(map[string][]string)\n\n\tcheckExplainSelectType(exp)\n\tcheckExplainAccessType(exp)\n\tcheckExplainFiltered(exp)\n\tcheckExplainRef(exp)\n\tcheckExplainRows(exp)\n\n\t// 打印explain table\n\tcontent := database.PrintMarkdownExplainTable(exp)\n\n\tif common.Config.ShowWarnings {\n\t\tcontent += \"\\n\" + database.MySQLExplainWarnings(exp)\n\t}\n\n\t// 对explain table中各项难于理解的值做解释\n\tcases := database.ExplainInfoTranslator(exp)\n\n\t// 添加last_query_cost\n\tif common.Config.ShowLastQueryCost {\n\t\tcontent += \"\\n\" + database.MySQLExplainQueryCost(exp)\n\t}\n\n\tif content != \"\" {\n\t\texplainRules[\"EXP.000\"] = Rule{\n\t\t\tItem:     \"EXP.000\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"Explain信息\",\n\t\t\tContent:  content,\n\t\t\tCase:     cases,\n\t\t\tFunc:     (*Query4Audit).RuleOK,\n\t\t}\n\t}\n\t// TODO: 检查explain对应的表是否需要跳过，如dual,空表等\n\treturn explainRules\n}\n\n// DigestExplainText 分析用户输入的EXPLAIN信息\nfunc DigestExplainText(text string) {\n\t// explain信息就不要显示完美了，美不美自己看吧。\n\tcommon.Config.IgnoreRules = append(common.Config.IgnoreRules, \"OK\")\n\n\tif !IsIgnoreRule(\"EXP.\") {\n\t\texplainInfo, err := database.ParseExplainText(text)\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"main ParseExplainText Error: %v\", err)\n\t\t\treturn\n\t\t}\n\t\texpSuggest := ExplainAdvisor(explainInfo)\n\t\t_, output := FormatSuggest(\"\", \"\", common.Config.ReportType, expSuggest)\n\t\tif common.Config.ReportType == \"html\" {\n\t\t\tfmt.Println(common.MarkdownHTMLHeader())\n\t\t\tfmt.Println(common.Markdown2HTML(output))\n\t\t} else {\n\t\t\tfmt.Println(output)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "advisor/explainer_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage advisor\n\nimport (\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\nfunc TestDigestExplainText(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tvar text = `+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+\n| id | select_type | table   | type  | possible_keys                                           | key               | key_len | ref                       | rows | Extra       |\n+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+\n|  1 | SIMPLE      | country | index | PRIMARY,country_id                                      | country           | 152     | NULL                      |  109 | Using index |\n|  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 |\n+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+`\n\tcommon.Config.ReportType = \"explain-digest\"\n\terr := common.GoldenDiff(func() {\n\t\tDigestExplainText(text)\n\t\torgReportType := common.Config.ReportType\n\t\tcommon.Config.ReportType = \"html\"\n\t\tDigestExplainText(text)\n\t\tcommon.Config.ReportType = orgReportType\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "advisor/heuristic.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage advisor\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"github.com/XiaoMi/soar/ast\"\n\t\"github.com/XiaoMi/soar/common\"\n\t\"github.com/XiaoMi/soar/database\"\n\n\t\"github.com/gedex/inflector\"\n\t\"github.com/percona/go-mysql/query\"\n\ttidb \"github.com/pingcap/parser/ast\"\n\t\"github.com/pingcap/parser/format\"\n\t\"github.com/pingcap/parser/mysql\"\n\t\"github.com/tidwall/gjson\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// RuleOK OK\nfunc (q *Query4Audit) RuleOK() Rule {\n\treturn HeuristicRules[\"OK\"]\n}\n\n// RuleImplicitAlias ALI.001\nfunc (q *Query4Audit) RuleImplicitAlias() Rule {\n\tvar rule = q.RuleOK()\n\ttkns := ast.Tokenizer(q.Query)\n\tif len(tkns) == 0 {\n\t\treturn rule\n\t}\n\tif tkns[0].Type != sqlparser.SELECT {\n\t\treturn rule\n\t}\n\tfor i, tkn := range tkns {\n\t\tif tkn.Type == sqlparser.ID && i+1 < len(tkns) && tkn.Type == tkns[i+1].Type {\n\t\t\trule = HeuristicRules[\"ALI.001\"]\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleStarAlias ALI.002\nfunc (q *Query4Audit) RuleStarAlias() Rule {\n\tvar rule = q.RuleOK()\n\ttkns := ast.Tokenizer(q.Query)\n\tfor i, tkn := range tkns {\n\t\tif strings.HasSuffix(tkn.Val, \"*\") && i+1 < len(tkns) && strings.ToLower(tkns[i+1].Val) == \"as\" {\n\t\t\trule = HeuristicRules[\"ALI.002\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleSameAlias ALI.003\nfunc (q *Query4Audit) RuleSameAlias() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.AliasedExpr:\n\t\t\tswitch n := expr.Expr.(type) {\n\t\t\tcase *sqlparser.ColName:\n\t\t\t\tif n.Name.String() == expr.As.String() {\n\t\t\t\t\trule = HeuristicRules[\"ALI.003\"]\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t}\n\t\tcase *sqlparser.AliasedTableExpr:\n\t\t\tswitch n := expr.Expr.(type) {\n\t\t\tcase sqlparser.TableName:\n\t\t\t\tif n.Name.String() == expr.As.String() {\n\t\t\t\t\trule = HeuristicRules[\"ALI.003\"]\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RulePrefixLike ARG.001\nfunc (q *Query4Audit) RulePrefixLike() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tif strings.ToLower(expr.Operator) == \"like\" {\n\t\t\t\tswitch sqlval := expr.Right.(type) {\n\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\t// prefix like with '%', '_'\n\t\t\t\t\tif sqlval.Type == 0 && (sqlval.Val[0] == 0x25 || sqlval.Val[0] == 0x5f) {\n\t\t\t\t\t\trule = HeuristicRules[\"ARG.001\"]\n\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleEqualLike ARG.002\nfunc (q *Query4Audit) RuleEqualLike() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tif strings.ToLower(expr.Operator) == \"like\" {\n\t\t\t\tswitch sqlval := expr.Right.(type) {\n\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\t// 1. string that not contain '%', '_'\n\t\t\t\t\t// 2. int, bit, float without wildcard\n\t\t\t\t\tvar hasWildCard bool\n\t\t\t\t\tif sqlval.Type == 0 {\n\t\t\t\t\t\tfor _, sqlElem := range sqlval.Val {\n\t\t\t\t\t\t\tif sqlElem == 0x25 || sqlElem == 0x5f {\n\t\t\t\t\t\t\t\thasWildCard = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !hasWildCard {\n\t\t\t\t\t\trule = HeuristicRules[\"ARG.002\"]\n\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleImplicitConversion ARG.003\n// 隐式类型转换检查：该项检查一定是在开启测试环境或线上环境情境下下进行的\nfunc (idxAdv *IndexAdvisor) RuleImplicitConversion() Rule {\n\t/*\n\t* 两个参数至少有一个是 NULL 时，比较的结果也是 NULL，例外是使用 <=> 对两个 NULL 做比较时会返回 1，这两种情况都不需要做类型转换\n\t* 两个参数都是字符串，会按照字符串来比较，不做类型转换\n\t* 两个参数都是整数，按照整数来比较，不做类型转换\n\t* 十六进制的值和非数字做比较时，会被当做二进制串\n\t* 有一个参数是 TIMESTAMP 或 DATETIME，并且另外一个参数是常量，常量会被转换为 timestamp\n\t* 有一个参数是 decimal 类型，如果另外一个参数是 decimal 或者整数，会将整数转换为 decimal 后进行比较，如果另外一个参数是浮点数，则会把 decimal 转换为浮点数进行比较\n\t* 所有其他情况下，两个参数都会被转换为浮点数再进行比较\n\t */\n\trule := HeuristicRules[\"OK\"]\n\t// 未开启测试环境不进行检查\n\tif common.Config.TestDSN.Disable {\n\t\treturn rule\n\t}\n\n\tvar content []string\n\tconditions := ast.FindAllCondition(idxAdv.Ast)\n\tfor _, cond := range conditions {\n\t\tvar colList []*common.Column\n\t\tvar values []*sqlparser.SQLVal\n\n\t\t// condition 左右两侧有且只有如下几种可能：\n\t\t// 1. 列与列比较，如： col1 = col2\n\t\t// 2. 列与值比较，如： col = val\n\t\t// 3. 值与值比较，如： val1 = val2 暂不处理\n\t\t// 如果列包含在一个函数中，认为这个条件为值，如： col = func(col) 认定为 列与值比较\n\t\tswitch node := cond.(type) {\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t// 获取 condition 左侧的信息\n\t\t\tswitch nLeft := node.Left.(type) {\n\t\t\tcase *sqlparser.SQLVal, sqlparser.ValTuple:\n\t\t\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\t\t\tswitch val := node.(type) {\n\t\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\t\tvalues = append(values, val)\n\t\t\t\t\t}\n\t\t\t\t\treturn true, nil\n\t\t\t\t}, nLeft)\n\t\t\t\tcommon.LogIfError(err, \"\")\n\n\t\t\tcase *sqlparser.ColName:\n\t\t\t\tleft := &common.Column{Name: nLeft.Name.String()}\n\t\t\t\tif !nLeft.Qualifier.Name.IsEmpty() {\n\t\t\t\t\tleft.Table = nLeft.Qualifier.Name.String()\n\t\t\t\t}\n\t\t\t\tcolList = append(colList, left)\n\t\t\t}\n\n\t\t\t// 获取 condition 右侧的信息\n\t\t\tswitch nRight := node.Right.(type) {\n\t\t\tcase *sqlparser.SQLVal, sqlparser.ValTuple:\n\t\t\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\t\t\tswitch val := node.(type) {\n\t\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\t\tvalues = append(values, val)\n\t\t\t\t\t}\n\t\t\t\t\treturn true, nil\n\t\t\t\t}, nRight)\n\t\t\t\tcommon.LogIfError(err, \"\")\n\n\t\t\tcase *sqlparser.ColName:\n\t\t\t\tright := &common.Column{Name: nRight.Name.String()}\n\t\t\t\tif !nRight.Qualifier.Name.IsEmpty() {\n\t\t\t\t\tright.Table = nRight.Qualifier.Name.String()\n\t\t\t\t}\n\t\t\t\tcolList = append(colList, right)\n\t\t\t}\n\n\t\t\tif len(colList) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// 补全列信息\n\t\t\tcolList = CompleteColumnsInfo(idxAdv.Ast, colList, idxAdv.vEnv)\n\n\t\t\t// 列与列比较\n\t\t\tif len(colList) == 2 {\n\t\t\t\t// 列信息补全后如果依然没有表信息，说明在该数据库中不存在该列\n\t\t\t\t// 如果列信息获取异常，可能会存在无法获取到数据类型的情况，对于这种情况将不会给予建议。\n\t\t\t\tneedBreak := false\n\t\t\t\tfor _, col := range colList {\n\t\t\t\t\tif col.Table == \"\" {\n\t\t\t\t\t\tcommon.Log.Warning(\"Column %s not exists\", col.Name)\n\t\t\t\t\t\tneedBreak = true\n\t\t\t\t\t}\n\n\t\t\t\t\tif col.DataType == \"\" {\n\t\t\t\t\t\tcommon.Log.Warning(\"Can't get column %s data type\", col.Name)\n\t\t\t\t\t\tneedBreak = true\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\tif needBreak {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// 检查数据类型不一致导致的隐式数据转换\n\t\t\t\ttype1 := common.GetDataTypeBase(colList[0].DataType)\n\t\t\t\ttype2 := common.GetDataTypeBase(colList[1].DataType)\n\t\t\t\tcommon.Log.Debug(\"DataType: `%s`.`%s` (%s) VS `%s`.`%s` (%s)\",\n\t\t\t\t\tcolList[0].Table, colList[0].Name, type1,\n\t\t\t\t\tcolList[1].Table, colList[1].Name, type2)\n\t\t\t\t// case-insensitive check type1, type2\n\t\t\t\tif !strings.EqualFold(type1, type2) {\n\t\t\t\t\tcontent = append(content, fmt.Sprintf(\"`%s`.`%s` (%s) VS `%s`.`%s` (%s) datatype not match\",\n\t\t\t\t\t\tcolList[0].Table, colList[0].Name, type1,\n\t\t\t\t\t\tcolList[1].Table, colList[1].Name, type2))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// 检查字符集不一致导致的隐式数据转换\n\t\t\t\tcommon.Log.Debug(\"Charset: `%s`.`%s` (%s) VS `%s`.`%s` (%s)\",\n\t\t\t\t\tcolList[0].Table, colList[0].Name, colList[0].Character,\n\t\t\t\t\tcolList[1].Table, colList[1].Name, colList[1].Character)\n\t\t\t\tif colList[0].Character != colList[1].Character {\n\t\t\t\t\tcontent = append(content, fmt.Sprintf(\"`%s`.`%s` (%s) VS `%s`.`%s` (%s) charset not match\",\n\t\t\t\t\t\tcolList[0].Table, colList[0].Name, colList[0].Character,\n\t\t\t\t\t\tcolList[1].Table, colList[1].Name, colList[1].Character))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// 检查 collation 排序不一致导致的隐式数据转换\n\t\t\t\tcommon.Log.Debug(\"Collation: `%s`.`%s` (%s) VS `%s`.`%s` (%s)\",\n\t\t\t\t\tcolList[0].Table, colList[0].Name, colList[0].Collation,\n\t\t\t\t\tcolList[1].Table, colList[1].Name, colList[1].Collation)\n\t\t\t\tif colList[0].Collation != colList[1].Collation {\n\t\t\t\t\tcontent = append(content, fmt.Sprintf(\"`%s`.`%s` (%s) VS `%s`.`%s` (%s) collation not match\",\n\t\t\t\t\t\tcolList[0].Table, colList[0].Name, colList[0].Collation,\n\t\t\t\t\t\tcolList[1].Table, colList[1].Name, colList[1].Collation))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttypMap := map[sqlparser.ValType][]string{\n\t\t\t\t// date, time, datetime, timestamp, year\n\t\t\t\tsqlparser.StrVal: {\n\t\t\t\t\t\"char\", \"varchar\", \"tinytext\", \"text\", \"mediumtext\", \"longtext\",\n\t\t\t\t\t\"date\", \"time\", \"datetime\", \"timestamp\", \"year\",\n\t\t\t\t\t\"tinyint\", \"smallint\", \"mediumint\", \"int\", \"integer\", \"bigint\",\n\t\t\t\t\t\"float\", \"double\", \"real\", \"decimal\",\n\t\t\t\t},\n\t\t\t\tsqlparser.IntVal: {\n\t\t\t\t\t\"tinyint\", \"smallint\", \"mediumint\", \"int\", \"integer\", \"bigint\",\n\t\t\t\t\t\"timestamp\", \"year\", \"bit\", \"decimal\",\n\t\t\t\t},\n\t\t\t\tsqlparser.FloatVal: {\n\t\t\t\t\t\"float\", \"double\", \"real\", \"decimal\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\ttypNameMap := map[sqlparser.ValType]string{\n\t\t\t\tsqlparser.StrVal:   \"string\",\n\t\t\t\tsqlparser.IntVal:   \"int\",\n\t\t\t\tsqlparser.FloatVal: \"float\",\n\t\t\t}\n\n\t\t\t// 列与值比较\n\t\t\tfor _, val := range values {\n\t\t\t\tif colList[0].DataType == \"\" {\n\t\t\t\t\tcommon.Log.Warn(\"Can't get %s data type\", colList[0].Name)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tisCovered := true\n\t\t\t\tif tps, ok := typMap[val.Type]; ok {\n\t\t\t\t\tfor _, tp := range tps {\n\t\t\t\t\t\t// colList[0].DataType, eg. year(4)\n\t\t\t\t\t\tif strings.HasPrefix(colList[0].DataType, tp) {\n\t\t\t\t\t\t\tisCovered = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif isCovered {\n\t\t\t\t\tif colList[0].Table == \"\" {\n\t\t\t\t\t\tcommon.Log.Warning(\"Column %s not exists\", colList[0].Name)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tc := fmt.Sprintf(\"%s表中列%s的定义是 %s 而不是 %s。\",\n\t\t\t\t\t\tcolList[0].Table, colList[0].Name, colList[0].DataType, typNameMap[val.Type])\n\n\t\t\t\t\tcommon.Log.Debug(\"Implicit data type conversion: %s\", c)\n\t\t\t\t\tcontent = append(content, c)\n\t\t\t\t} else {\n\t\t\t\t\t// 检查时间格式，如：\"\", \"2020-0a-01\"\n\t\t\t\t\tswitch strings.Split(colList[0].DataType, \"(\")[0] {\n\t\t\t\t\tcase \"date\", \"time\", \"datetime\", \"timestamp\", \"year\":\n\t\t\t\t\t\tif !timeFormatCheck(string(val.Val)) {\n\t\t\t\t\t\t\tc := fmt.Sprintf(\"%s 表中列 %s 的时间格式错误，%s。\", colList[0].Table, colList[0].Name, string(val.Val))\n\t\t\t\t\t\t\tcommon.Log.Debug(\"Implicit data type conversion: %s\", c)\n\t\t\t\t\t\t\tcontent = append(content, c)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// TODO: 各种数据类型格式检查\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase *sqlparser.RangeCond:\n\t\t\t// TODO\n\t\tcase *sqlparser.IsExpr:\n\t\t\t// TODO\n\t\t}\n\t}\n\tif len(content) > 0 {\n\t\trule = HeuristicRules[\"ARG.003\"]\n\t\trule.Content = strings.Join(common.RemoveDuplicatesItem(content), \" \")\n\t}\n\treturn rule\n}\n\n// timeFormatCheck 时间格式检查，格式正确返回 true，格式错误返回 false\nfunc timeFormatCheck(t string) bool {\n\t// 不允许为空，但允许时间前后有空格\n\tt = strings.TrimSpace(t)\n\t// 仅允许 数字、减号、冒号、空格\n\tallowChars := regexp.MustCompile(`^[\\-0-9:. ]+$`)\n\treturn allowChars.MatchString(t)\n}\n\n// RuleNoWhere CLA.001 & CLA.014 & CLA.015\nfunc (q *Query4Audit) RuleNoWhere() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tfor _, f := range n.From {\n\t\t\t\tswitch f.(type) {\n\t\t\t\tcase *sqlparser.JoinTableExpr:\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tif n.Where == nil && sqlparser.String(n.From) != \"dual\" {\n\t\t\t\trule = HeuristicRules[\"CLA.001\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\tcase *sqlparser.Delete:\n\t\t\tif n.Where == nil {\n\t\t\t\trule = HeuristicRules[\"CLA.014\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\tcase *sqlparser.Update:\n\t\t\tif n.Where == nil {\n\t\t\t\trule = HeuristicRules[\"CLA.015\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleOrderByRand CLA.002\nfunc (q *Query4Audit) RuleOrderByRand() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase sqlparser.OrderBy:\n\t\t\tfor _, order := range n {\n\t\t\t\tswitch expr := order.Expr.(type) {\n\t\t\t\tcase *sqlparser.FuncExpr:\n\t\t\t\t\tif strings.ToLower(expr.Name.String()) == \"rand\" {\n\t\t\t\t\t\trule = HeuristicRules[\"CLA.002\"]\n\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleOffsetLimit CLA.003\nfunc (q *Query4Audit) RuleOffsetLimit() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Limit:\n\t\t\tif n != nil && n.Offset != nil {\n\t\t\t\tswitch v := n.Offset.(type) {\n\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\toffset, err := strconv.Atoi(string(v.Val))\n\t\t\t\t\t// TODO: 检查一下Offset阈值，太小了给这个建议也没什么用，阈值写死了没加配置\n\t\t\t\t\tif err == nil && offset > 1000 {\n\t\t\t\t\t\trule = HeuristicRules[\"CLA.003\"]\n\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleGroupByConst CLA.004\nfunc (q *Query4Audit) RuleGroupByConst() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase sqlparser.GroupBy:\n\t\t\tfor _, group := range n {\n\t\t\t\tswitch group.(type) {\n\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\trule = HeuristicRules[\"CLA.004\"]\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleGroupByConst GRP.001\nfunc (idxAdv *IndexAdvisor) RuleGroupByConst() Rule {\n\trule := HeuristicRules[\"OK\"]\n\n\t// 非GroupBy语句\n\tif len(idxAdv.groupBy) == 0 || len(idxAdv.whereEQ) == 0 {\n\t\treturn rule\n\t}\n\n\tfor _, groupByCols := range idxAdv.groupBy {\n\t\tfor _, whereEQCols := range idxAdv.whereEQ {\n\t\t\tif (groupByCols.Name == whereEQCols.Name) &&\n\t\t\t\t(groupByCols.DB == whereEQCols.DB) &&\n\t\t\t\t(groupByCols.Table == whereEQCols.Table) {\n\t\t\t\trule = HeuristicRules[\"GRP.001\"]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleOrderByConst CLA.005\nfunc (q *Query4Audit) RuleOrderByConst() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase sqlparser.OrderBy:\n\t\t\tfor _, order := range n {\n\t\t\t\tswitch order.Expr.(type) {\n\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\trule = HeuristicRules[\"CLA.005\"]\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleOrderByConst CLA.005\n// TODO: SELECT col FROM tbl WHERE col IN('NEWS') ORDER BY col;\nfunc (idxAdv *IndexAdvisor) RuleOrderByConst() Rule {\n\trule := HeuristicRules[\"OK\"]\n\n\t// 非GroupBy语句\n\tif len(idxAdv.orderBy) == 0 || len(idxAdv.whereEQ) == 0 {\n\t\treturn rule\n\t}\n\n\tfor _, groupbyCols := range idxAdv.orderBy {\n\t\tfor _, whereEQCols := range idxAdv.whereEQ {\n\t\t\tif (groupbyCols.Name == whereEQCols.Name) &&\n\t\t\t\t(groupbyCols.DB == whereEQCols.DB) &&\n\t\t\t\t(groupbyCols.Table == whereEQCols.Table) {\n\t\t\t\trule = HeuristicRules[\"CLA.005\"]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleDiffGroupByOrderBy CLA.006\nfunc (q *Query4Audit) RuleDiffGroupByOrderBy() Rule {\n\tvar rule = q.RuleOK()\n\tvar groupbyTbls []sqlparser.TableIdent\n\tvar orderbyTbls []sqlparser.TableIdent\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase sqlparser.GroupBy:\n\t\t\t// 检查group by涉及到表的个数\n\t\t\tfor _, group := range n {\n\t\t\t\tswitch g := group.(type) {\n\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\ttblExist := false\n\t\t\t\t\tfor _, t := range groupbyTbls {\n\t\t\t\t\t\tif t.String() == g.Qualifier.Name.String() {\n\t\t\t\t\t\t\ttblExist = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !tblExist {\n\t\t\t\t\t\tgroupbyTbls = append(groupbyTbls, g.Qualifier.Name)\n\t\t\t\t\t\tif len(groupbyTbls) > 1 {\n\t\t\t\t\t\t\trule = HeuristicRules[\"CLA.006\"]\n\t\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase sqlparser.OrderBy:\n\t\t\t// 检查order by涉及到表的个数\n\t\t\tfor _, order := range n {\n\t\t\t\tswitch o := order.Expr.(type) {\n\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\ttblExist := false\n\t\t\t\t\tfor _, t := range orderbyTbls {\n\t\t\t\t\t\tif t.String() == o.Qualifier.Name.String() {\n\t\t\t\t\t\t\ttblExist = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !tblExist {\n\t\t\t\t\t\torderbyTbls = append(orderbyTbls, o.Qualifier.Name)\n\t\t\t\t\t\tif len(orderbyTbls) > 1 {\n\t\t\t\t\t\t\trule = HeuristicRules[\"CLA.006\"]\n\t\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\n\tif rule.Item == \"OK\" {\n\t\t// 检查group by, order by涉及到表的个数\n\t\tfor _, g := range groupbyTbls {\n\t\t\ttblExist := false\n\t\t\tfor _, o := range orderbyTbls {\n\t\t\t\tif g.String() == o.String() {\n\t\t\t\t\ttblExist = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !tblExist && len(orderbyTbls) > 0 {\n\t\t\t\trule = HeuristicRules[\"CLA.006\"]\n\t\t\t\treturn rule\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RuleExplicitOrderBy CLA.008\nfunc (q *Query4Audit) RuleExplicitOrderBy() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\t// 有group by，但没有order by\n\t\t\tif n.GroupBy != nil && n.OrderBy == nil {\n\t\t\t\trule = HeuristicRules[\"CLA.008\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleOrderByExpr CLA.009\nfunc (q *Query4Audit) RuleOrderByExpr() Rule {\n\tvar rule = q.RuleOK()\n\tvar orderByCols []string\n\tvar selectCols []string\n\tfuncExp := regexp.MustCompile(`(?i)[a-z0-9]\\(`)\n\tallowExp := regexp.MustCompile(\"(?i)[a-z0-9_,.` ()]\")\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase sqlparser.OrderBy:\n\t\t\torderBy := sqlparser.String(n)\n\t\t\t// 函数名方式，如：from_unixtime(col)\n\t\t\tif funcExp.MatchString(orderBy) {\n\t\t\t\trule = HeuristicRules[\"CLA.009\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\t// 运算符方式，如：colA - colB\n\t\t\ttrim := allowExp.ReplaceAllFunc([]byte(orderBy), func(s []byte) []byte {\n\t\t\t\treturn []byte(\"\")\n\t\t\t})\n\t\t\tif string(trim) != \"\" {\n\t\t\t\trule = HeuristicRules[\"CLA.009\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\tfor _, o := range strings.Split(strings.TrimPrefix(orderBy, \" order by \"), \",\") {\n\t\t\t\torderByCols = append(orderByCols, strings.TrimSpace(strings.Split(o, \" \")[0]))\n\t\t\t}\n\t\tcase *sqlparser.Select:\n\t\t\tfor _, s := range n.SelectExprs {\n\t\t\t\tselectCols = append(selectCols, sqlparser.String(s))\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\n\t// AS情况，如：SELECT colA-colB a FROM tbl ORDER BY a;\n\tfor _, o := range orderByCols {\n\t\tif o == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, s := range selectCols {\n\t\t\tif strings.HasSuffix(s, \" as \"+o) {\n\t\t\t\tbuf := strings.TrimSuffix(s, \" as \"+o)\n\t\t\t\t// 运算符\n\t\t\t\ttrim := allowExp.ReplaceAllFunc([]byte(buf), func(s []byte) []byte {\n\t\t\t\t\treturn []byte(\"\")\n\t\t\t\t})\n\t\t\t\tif string(trim) != \"\" {\n\t\t\t\t\trule = HeuristicRules[\"CLA.009\"]\n\t\t\t\t}\n\t\t\t\t// 函数\n\t\t\t\tif funcExp.MatchString(s) {\n\t\t\t\t\trule = HeuristicRules[\"CLA.009\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleGroupByExpr CLA.010\nfunc (q *Query4Audit) RuleGroupByExpr() Rule {\n\tvar rule = q.RuleOK()\n\tvar groupByCols []string\n\tvar selectCols []string\n\tfuncExp := regexp.MustCompile(`(?i)[a-z0-9]\\(`)\n\tallowExp := regexp.MustCompile(\"(?i)[a-z0-9_,.` ()]\")\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase sqlparser.GroupBy:\n\t\t\tgroupBy := sqlparser.String(n)\n\t\t\t// 函数名方式，如：from_unixtime(col)\n\t\t\tif funcExp.MatchString(groupBy) {\n\t\t\t\trule = HeuristicRules[\"CLA.010\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\t// 运算符方式，如：colA - colB\n\t\t\ttrim := allowExp.ReplaceAllFunc([]byte(groupBy), func(s []byte) []byte {\n\t\t\t\treturn []byte(\"\")\n\t\t\t})\n\t\t\tif string(trim) != \"\" {\n\t\t\t\trule = HeuristicRules[\"CLA.010\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\tfor _, o := range strings.Split(strings.TrimPrefix(groupBy, \" group by \"), \",\") {\n\t\t\t\tgroupByCols = append(groupByCols, strings.TrimSpace(strings.Split(o, \" \")[0]))\n\t\t\t}\n\t\tcase *sqlparser.Select:\n\t\t\tfor _, s := range n.SelectExprs {\n\t\t\t\tselectCols = append(selectCols, sqlparser.String(s))\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\n\t// AS情况，如：SELECT colA-colB a FROM tbl GROUP BY a;\n\tfor _, g := range groupByCols {\n\t\tif g == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, s := range selectCols {\n\t\t\tif strings.HasSuffix(s, \" as \"+g) {\n\t\t\t\tbuf := strings.TrimSuffix(s, \" as \"+g)\n\t\t\t\t// 运算符\n\t\t\t\ttrim := allowExp.ReplaceAllFunc([]byte(buf), func(s []byte) []byte {\n\t\t\t\t\treturn []byte(\"\")\n\t\t\t\t})\n\t\t\t\tif string(trim) != \"\" {\n\t\t\t\t\trule = HeuristicRules[\"CLA.010\"]\n\t\t\t\t}\n\t\t\t\t// 函数\n\t\t\t\tif funcExp.MatchString(s) {\n\t\t\t\t\trule = HeuristicRules[\"CLA.010\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleTblCommentCheck CLA.011\nfunc (q *Query4Audit) RuleTblCommentCheck() Rule {\n\tvar rule = q.RuleOK()\n\tswitch node := q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif strings.ToLower(node.Action) != \"create\" {\n\t\t\treturn rule\n\t\t}\n\t\tif node.TableSpec == nil {\n\t\t\treturn rule\n\t\t}\n\t\tif options := node.TableSpec.Options; options == \"\" {\n\t\t\trule = HeuristicRules[\"CLA.011\"]\n\n\t\t} else {\n\t\t\treg := regexp.MustCompile(\"(?i)comment\")\n\t\t\tif !reg.MatchString(options) {\n\t\t\t\trule = HeuristicRules[\"CLA.011\"]\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleSelectStar COL.001\nfunc (q *Query4Audit) RuleSelectStar() Rule {\n\tvar rule = q.RuleOK()\n\t// 先把count(*)替换为count(1)\n\tre := regexp.MustCompile(`(?i)count\\s*\\(\\s*\\*\\s*\\)`)\n\tsql := re.ReplaceAllString(q.Query, \"count(1)\")\n\tstmt, err := sqlparser.Parse(sql)\n\tif err != nil {\n\t\tcommon.Log.Debug(\"RuleSelectStar sqlparser.Parse Error: %v\", err)\n\t\treturn rule\n\t}\n\terr = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node.(type) {\n\t\tcase *sqlparser.StarExpr:\n\t\t\trule = HeuristicRules[\"COL.001\"]\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\t}, stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleInsertColDef COL.002\nfunc (q *Query4Audit) RuleInsertColDef() Rule {\n\tvar rule = q.RuleOK()\n\tswitch node := q.Stmt.(type) {\n\tcase *sqlparser.Insert:\n\t\tif node.Columns == nil {\n\t\t\trule = HeuristicRules[\"COL.002\"]\n\t\t\treturn rule\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleAddDefaultValue COL.004\nfunc (q *Query4Audit) RuleAddDefaultValue() Rule {\n\tvar rule = q.RuleOK()\n\tfor _, node := range q.TiStmt {\n\t\tswitch n := node.(type) {\n\t\tcase *tidb.CreateTableStmt:\n\t\t\tfor _, c := range n.Cols {\n\t\t\t\tcolDefault := false\n\t\t\t\tfor _, o := range c.Options {\n\t\t\t\t\t// 忽略AutoIncrement类型的默认值检查\n\t\t\t\t\tif o.Tp == tidb.ColumnOptionDefaultValue || o.Tp == tidb.ColumnOptionAutoIncrement {\n\t\t\t\t\t\tcolDefault = true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch c.Tp.Tp {\n\t\t\t\tcase mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON:\n\t\t\t\t\tcolDefault = true\n\t\t\t\t}\n\n\t\t\t\tif !colDefault {\n\t\t\t\t\trule = HeuristicRules[\"COL.004\"]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase *tidb.AlterTableStmt:\n\t\t\tfor _, s := range n.Specs {\n\t\t\t\tswitch s.Tp {\n\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:\n\t\t\t\t\tfor _, c := range s.NewColumns {\n\t\t\t\t\t\tcolDefault := false\n\t\t\t\t\t\tfor _, o := range c.Options {\n\t\t\t\t\t\t\t// 忽略AutoIncrement类型的默认值检查\n\t\t\t\t\t\t\tif o.Tp == tidb.ColumnOptionDefaultValue || o.Tp == tidb.ColumnOptionAutoIncrement {\n\t\t\t\t\t\t\t\tcolDefault = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tswitch c.Tp.Tp {\n\t\t\t\t\t\tcase mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON:\n\t\t\t\t\t\t\tcolDefault = true\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif !colDefault {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.004\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleColCommentCheck COL.005\nfunc (q *Query4Audit) RuleColCommentCheck() Rule {\n\tvar rule = q.RuleOK()\n\tfor _, node := range q.TiStmt {\n\t\tswitch n := node.(type) {\n\t\tcase *tidb.CreateTableStmt:\n\t\t\tfor _, c := range n.Cols {\n\t\t\t\tcolComment := false\n\t\t\t\tfor _, o := range c.Options {\n\t\t\t\t\tif o.Tp == tidb.ColumnOptionComment {\n\t\t\t\t\t\tcolComment = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !colComment {\n\t\t\t\t\trule = HeuristicRules[\"COL.005\"]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase *tidb.AlterTableStmt:\n\t\t\tfor _, s := range n.Specs {\n\t\t\t\tswitch s.Tp {\n\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:\n\t\t\t\t\tfor _, c := range s.NewColumns {\n\t\t\t\t\t\tcolComment := false\n\t\t\t\t\t\tfor _, o := range c.Options {\n\t\t\t\t\t\t\tif o.Tp == tidb.ColumnOptionComment {\n\t\t\t\t\t\t\t\tcolComment = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !colComment {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.005\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleIPString LIT.001\nfunc (q *Query4Audit) RuleIPString() Rule {\n\tvar rule = q.RuleOK()\n\n\tfor _, stmt := range q.TiStmt {\n\t\tswitch stmt.(type) {\n\t\tcase *tidb.AlterUserStmt, *tidb.CreateUserStmt, *tidb.GrantStmt, *tidb.GrantRoleStmt,\n\t\t\t*tidb.RevokeRoleStmt, *tidb.RevokeStmt, *tidb.DropUserStmt:\n\t\t\treturn rule\n\t\t}\n\t}\n\n\tre := regexp.MustCompile(`['\"]\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}`)\n\tif re.FindString(q.Query) != \"\" {\n\t\trule = HeuristicRules[\"LIT.001\"]\n\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\trule.Position = position[0]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleDateNotQuote LIT.002\nfunc (q *Query4Audit) RuleDateNotQuote() Rule {\n\tvar rule = q.RuleOK()\n\n\t// by pass insert except, insert select\n\tswitch n := q.Stmt.(type) {\n\tcase *sqlparser.Insert:\n\t\tvar insertSelect bool\n\t\tswitch n.Rows.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tinsertSelect = true\n\t\t}\n\t\tif !insertSelect {\n\t\t\treturn rule\n\t\t}\n\t}\n\n\t// 2010-01-01\n\tre := regexp.MustCompile(`.\\d{4}\\s*-\\s*\\d{1,2}\\s*-\\s*\\d{1,2}\\b`)\n\tsqls := re.FindAllString(q.Query, -1)\n\tfor _, sql := range sqls {\n\t\tre = regexp.MustCompile(`^['\"\\w-].*`)\n\t\tif re.FindString(sql) == \"\" {\n\t\t\trule = HeuristicRules[\"LIT.002\"]\n\t\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\trule.Position = position[0]\n\t\t\t}\n\t\t\treturn rule\n\t\t}\n\t}\n\n\t// 10-01-01\n\tre = regexp.MustCompile(`.\\d{2}\\s*-\\s*\\d{1,2}\\s*-\\s*\\d{1,2}\\b`)\n\tsqls = re.FindAllString(q.Query, -1)\n\tfor _, sql := range sqls {\n\t\tre = regexp.MustCompile(`^['\"\\w-].*`)\n\t\tif re.FindString(sql) == \"\" {\n\t\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\trule.Position = position[0]\n\t\t\t}\n\t\t\trule = HeuristicRules[\"LIT.002\"]\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RuleSQLCalcFoundRows KWR.001\nfunc (q *Query4Audit) RuleSQLCalcFoundRows() Rule {\n\tvar rule = q.RuleOK()\n\ttkns := ast.Tokenizer(q.Query)\n\tfor _, tkn := range tkns {\n\t\tif strings.ToLower(tkn.Val) == \"sql_calc_found_rows\" {\n\t\t\trule = HeuristicRules[\"KWR.001\"]\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleCommaAnsiJoin JOI.001\nfunc (q *Query4Audit) RuleCommaAnsiJoin() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tansiJoin := false\n\t\t\tcommaJoin := false\n\t\t\tfor _, f := range n.From {\n\t\t\t\tswitch f.(type) {\n\t\t\t\tcase *sqlparser.JoinTableExpr:\n\t\t\t\t\tansiJoin = true\n\t\t\t\tcase *sqlparser.AliasedTableExpr:\n\t\t\t\t\tcommaJoin = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ansiJoin && commaJoin {\n\t\t\t\trule = HeuristicRules[\"JOI.001\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleDupJoin JOI.002\nfunc (q *Query4Audit) RuleDupJoin() Rule {\n\tvar rule = q.RuleOK()\n\tvar tables []string\n\tswitch q.Stmt.(type) {\n\t// TODO: 这里未检查UNION SELECT\n\tcase *sqlparser.Union:\n\t\treturn rule\n\tdefault:\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch n := node.(type) {\n\t\t\tcase *sqlparser.AliasedTableExpr:\n\t\t\t\tswitch table := n.Expr.(type) {\n\t\t\t\tcase sqlparser.TableName:\n\t\t\t\t\tfor _, t := range tables {\n\t\t\t\t\t\tif t == table.Name.String() {\n\t\t\t\t\t\t\trule = HeuristicRules[\"JOI.002\"]\n\t\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\ttables = append(tables, table.Name.String())\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, q.Stmt)\n\t\tcommon.LogIfError(err, \"\")\n\t}\n\treturn rule\n}\n\n// RuleImpossibleOuterJoin JOI.003\n// TODO: 未实现完\nfunc (idxAdv *IndexAdvisor) RuleImpossibleOuterJoin() Rule {\n\trule := HeuristicRules[\"OK\"]\n\n\tvar joinTables []string         // JOIN相关表名\n\tvar whereEQTables []string      // WHERE等值判断条件表名\n\tvar joinNotWhereTables []string // 是JOIN相关表，但未出现在WHERE等值判断条件中的表名\n\n\t// 非JOIN语句\n\tif len(idxAdv.joinCond) == 0 || len(idxAdv.whereEQ) == 0 {\n\t\treturn rule\n\t}\n\n\tfor _, l1 := range idxAdv.joinCond {\n\t\tfor _, l2 := range l1 {\n\t\t\tif l2.Table != \"\" && strings.ToLower(l2.Table) != \"dual\" {\n\t\t\t\tjoinTables = append(joinTables, l2.Table)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, w := range idxAdv.whereEQ {\n\t\twhereEQTables = append(whereEQTables, w.Table)\n\t}\n\n\tfor _, j := range joinTables {\n\t\tfound := false\n\t\tfor _, w := range whereEQTables {\n\t\t\tif j == w {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tjoinNotWhereTables = append(joinNotWhereTables, j)\n\t\t}\n\t}\n\n\t// TODO:\n\tfmt.Println(joinNotWhereTables)\n\t/*\n\t\tif len(joinNotWhereTables) == 0 {\n\t\t\trule = HeuristicRules[\"JOI.003\"]\n\t\t}\n\t*/\n\trule = HeuristicRules[\"JOI.003\"]\n\treturn rule\n}\n\n// TODO: JOI.004\n\n// RuleNoDeterministicGroupby RES.001\nfunc (q *Query4Audit) RuleNoDeterministicGroupby() Rule {\n\tvar rule = q.RuleOK()\n\tvar groupbyCols []*common.Column\n\tvar selectCols []*common.Column\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\t// 过滤select列\n\t\t\tselectCols = ast.FindColumn(n.SelectExprs)\n\t\t\t// 过滤group by列\n\t\t\tgroupbyCols = ast.FindColumn(n.GroupBy)\n\t\t\t// `select *`, but not `select count(*)`\n\t\t\tif strings.Contains(sqlparser.String(n), \" * \") && len(groupbyCols) > 0 {\n\t\t\t\trule = HeuristicRules[\"RES.001\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\n\t// TODO：暂时只检查了列名，未对库表名进行检查，也未处理AS\n\tfor _, s := range selectCols {\n\t\t// 无group by退出\n\t\tif len(groupbyCols) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tfound := false\n\t\tfor _, g := range groupbyCols {\n\t\t\tif g.Name == s.Name {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\trule = HeuristicRules[\"RES.001\"]\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleNoDeterministicLimit RES.002\nfunc (q *Query4Audit) RuleNoDeterministicLimit() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tif n.Limit != nil && n.OrderBy == nil {\n\t\t\t\trule = HeuristicRules[\"RES.002\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleUpdateDeleteWithLimit RES.003\nfunc (q *Query4Audit) RuleUpdateDeleteWithLimit() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.Update:\n\t\tif s.Limit != nil {\n\t\t\trule = HeuristicRules[\"RES.003\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleUpdateDeleteWithOrderby RES.004\nfunc (q *Query4Audit) RuleUpdateDeleteWithOrderby() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.Update:\n\t\tif s.OrderBy != nil {\n\t\t\trule = HeuristicRules[\"RES.004\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleUpdateSetAnd RES.005\nfunc (q *Query4Audit) RuleUpdateSetAnd() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.Update:\n\t\tfor _, c := range s.Exprs {\n\t\t\tswitch c.Expr.(type) {\n\t\t\tcase *sqlparser.Subquery:\n\t\t\tdefault:\n\t\t\t\tif strings.Contains(sqlparser.String(c), \" and \") {\n\t\t\t\t\trule = HeuristicRules[\"RES.005\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleImpossibleWhere RES.006\nfunc (q *Query4Audit) RuleImpossibleWhere() Rule {\n\tvar rule = q.RuleOK()\n\t// BETWEEN 10 AND 5\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.RangeCond:\n\t\t\tif strings.ToLower(n.Operator) == \"between\" {\n\t\t\t\tfrom := 0\n\t\t\t\tto := 0\n\t\t\t\tswitch s := n.From.(type) {\n\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\tfrom, _ = strconv.Atoi(string(s.Val))\n\t\t\t\t}\n\t\t\t\tswitch s := n.To.(type) {\n\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\tto, _ = strconv.Atoi(string(s.Val))\n\t\t\t\t}\n\t\t\t\tif from > to {\n\t\t\t\t\trule = HeuristicRules[\"RES.006\"]\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t}\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tfactor := false\n\t\t\tswitch n.Operator {\n\t\t\tcase \"!=\", \"<>\":\n\t\t\tcase \"=\", \"<=>\":\n\t\t\t\tfactor = true\n\t\t\tdefault:\n\t\t\t\treturn true, nil\n\t\t\t}\n\n\t\t\tvar left []byte\n\t\t\tvar right []byte\n\n\t\t\t// left\n\t\t\tswitch l := n.Left.(type) {\n\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\tleft = l.Val\n\t\t\tdefault:\n\t\t\t\treturn true, nil\n\t\t\t}\n\n\t\t\t// right\n\t\t\tswitch r := n.Right.(type) {\n\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\tright = r.Val\n\t\t\tdefault:\n\t\t\t\treturn true, nil\n\t\t\t}\n\n\t\t\t// compare\n\t\t\tif (!bytes.Equal(left, right) && factor) || (bytes.Equal(left, right) && !factor) {\n\t\t\t\trule = HeuristicRules[\"RES.006\"]\n\t\t\t}\n\t\t\treturn false, nil\n\t\t}\n\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleMeaninglessWhere RES.007\nfunc (q *Query4Audit) RuleMeaninglessWhere() Rule {\n\tvar rule = q.RuleOK()\n\n\tvar where *sqlparser.Where\n\tswitch n := q.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\twhere = n.Where\n\tcase *sqlparser.Update:\n\t\twhere = n.Where\n\tcase *sqlparser.Delete:\n\t\twhere = n.Where\n\t}\n\tif where != nil {\n\t\tswitch v := where.Expr.(type) {\n\t\t// WHERE 1\n\t\tcase *sqlparser.SQLVal:\n\t\t\tswitch string(v.Val) {\n\t\t\tcase \"0\", \"false\":\n\t\t\tdefault:\n\t\t\t\trule = HeuristicRules[\"RES.007\"]\n\t\t\t\treturn rule\n\t\t\t}\n\t\t// WHERE true\n\t\tcase sqlparser.BoolVal:\n\t\t\tif v {\n\t\t\t\trule = HeuristicRules[\"RES.007\"]\n\t\t\t\treturn rule\n\t\t\t}\n\t\t}\n\t}\n\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\t// WHERE id = 1 or 2\n\t\tcase *sqlparser.OrExpr:\n\t\t\t// right always true\n\t\t\tswitch v := n.Right.(type) {\n\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\tswitch string(v.Val) {\n\t\t\t\tcase \"0\", \"false\":\n\t\t\t\tdefault:\n\t\t\t\t\trule = HeuristicRules[\"RES.007\"]\n\t\t\t\t}\n\t\t\tcase sqlparser.BoolVal:\n\t\t\t\tif v {\n\t\t\t\t\trule = HeuristicRules[\"RES.007\"]\n\t\t\t\t}\n\t\t\t}\n\t\t\t// left always true\n\t\t\tswitch v := n.Left.(type) {\n\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\tswitch string(v.Val) {\n\t\t\t\tcase \"0\", \"false\":\n\t\t\t\tdefault:\n\t\t\t\t\trule = HeuristicRules[\"RES.007\"]\n\t\t\t\t}\n\t\t\tcase sqlparser.BoolVal:\n\t\t\t\tif v {\n\t\t\t\t\trule = HeuristicRules[\"RES.007\"]\n\t\t\t\t}\n\t\t\t}\n\t\t// 1=1, 0=0\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tfactor := false\n\t\t\tswitch n.Operator {\n\t\t\tcase \"!=\", \"<>\":\n\t\t\t\tfactor = true\n\t\t\tcase \"=\", \"<=>\":\n\t\t\tdefault:\n\t\t\t\treturn true, nil\n\t\t\t}\n\n\t\t\tvar left []byte\n\t\t\tvar right []byte\n\n\t\t\t// left\n\t\t\tswitch l := n.Left.(type) {\n\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\tleft = l.Val\n\t\t\tdefault:\n\t\t\t\treturn true, nil\n\t\t\t}\n\n\t\t\t// right\n\t\t\tswitch r := n.Right.(type) {\n\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\tright = r.Val\n\t\t\tdefault:\n\t\t\t\treturn true, nil\n\t\t\t}\n\n\t\t\t// compare\n\t\t\tif (bytes.Equal(left, right) && !factor) || (!bytes.Equal(left, right) && factor) {\n\t\t\t\trule = HeuristicRules[\"RES.007\"]\n\t\t\t}\n\n\t\t\t// TODO:\n\t\t\t// 2 > 1\n\t\t\t// true = 1\n\t\t\t// false != 1\n\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleLoadFile RES.008\nfunc (q *Query4Audit) RuleLoadFile() Rule {\n\tvar rule = q.RuleOK()\n\t// 去除注释\n\tsql := database.RemoveSQLComments(q.Query)\n\t// 去除多余的空格和回车\n\tsql = strings.Join(strings.Fields(sql), \" \")\n\ttks := ast.Tokenize(sql)\n\tfor i, tk := range tks {\n\t\t// 注意：每个关键字token的结尾是带空格的，这里偷懒没trimspace直接加空格比较\n\t\t// LOAD DATA...\n\t\tif strings.ToLower(tk.Val) == \"load \" && i+1 < len(tks) &&\n\t\t\tstrings.ToLower(tks[i+1].Val) == \"data \" {\n\t\t\trule = HeuristicRules[\"RES.008\"]\n\t\t\tbreak\n\t\t}\n\n\t\t// SELECT ... INTO OUTFILE\n\t\tif strings.ToLower(tk.Val) == \"into \" && i+1 < len(tks) &&\n\t\t\t(strings.ToLower(tks[i+1].Val) == \"outfile \" || strings.ToLower(tks[i+1].Val) == \"dumpfile \") {\n\t\t\trule = HeuristicRules[\"RES.008\"]\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleMultiCompare RES.009\nfunc (q *Query4Audit) RuleMultiCompare() Rule {\n\tvar rule = q.RuleOK()\n\tif q.TiStmt != nil {\n\t\tjson := ast.StmtNode2JSON(q.Query, \"\", \"\")\n\t\twhereJSON := common.JSONFind(json, \"Where\")\n\t\tfor _, where := range whereJSON {\n\t\t\tconds := []string{where}\n\t\t\tconds = append(conds, common.JSONFind(where, \"L\")...)\n\t\t\tconds = append(conds, common.JSONFind(where, \"R\")...)\n\t\t\tfor _, cond := range conds {\n\t\t\t\tif gjson.Get(cond, \"Op\").Int() == 7 && gjson.Get(cond, \"L.Op\").Int() == 7 {\n\t\t\t\t\trule = HeuristicRules[\"RES.009\"]\n\t\t\t\t\treturn rule\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleCreateOnUpdate RES.010\nfunc (q *Query4Audit) RuleCreateOnUpdate() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tfor _, op := range col.Options {\n\t\t\t\t\t\tif op.Tp == tidb.ColumnOptionOnUpdate {\n\t\t\t\t\t\t\trule = HeuristicRules[\"RES.010\"]\n\t\t\t\t\t\t\treturn rule\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableModifyColumn, tidb.AlterTableChangeColumn:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor _, op := range col.Options {\n\t\t\t\t\t\t\t\tif op.Tp == tidb.ColumnOptionOnUpdate {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"RES.010\"]\n\t\t\t\t\t\t\t\t\treturn rule\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleUpdateOnUpdate RES.011\nfunc (idxAdv *IndexAdvisor) RuleUpdateOnUpdate() Rule {\n\trule := HeuristicRules[\"OK\"]\n\t// 未开启测试环境不进行检查\n\tif common.Config.TestDSN.Disable {\n\t\treturn rule\n\t}\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch stmt := node.(type) {\n\t\tcase *sqlparser.Update:\n\t\t\tfor _, tbExpr := range stmt.TableExprs {\n\t\t\t\tddl, err := idxAdv.vEnv.ShowCreateTable(sqlparser.String(tbExpr))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcommon.Log.Error(\"RuleMaxTextColsCount create statement got failed: %s\", err.Error())\n\t\t\t\t\treturn false, err\n\t\t\t\t}\n\t\t\t\tif strings.Contains(ddl, \"ON UPDATE\") {\n\t\t\t\t\trule = HeuristicRules[\"RES.011\"]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, setExpr := range stmt.Exprs {\n\t\t\t\ttup := strings.Split(sqlparser.String(setExpr), \" = \")\n\t\t\t\tif len(tup) == 2 && tup[0] == tup[1] {\n\t\t\t\t\trule = HeuristicRules[\"OK\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, idxAdv.Ast)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleStandardINEQ STA.001\nfunc (q *Query4Audit) RuleStandardINEQ() Rule {\n\tvar rule = q.RuleOK()\n\tre := regexp.MustCompile(`(!=)`)\n\tif re.FindString(q.Query) != \"\" {\n\t\trule = HeuristicRules[\"STA.001\"]\n\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\trule.Position = position[0]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleUseKeyWord KWR.002\nfunc (q *Query4Audit) RuleUseKeyWord() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif q.TiStmt == nil {\n\t\t\tcommon.Log.Error(\"TiStmt is nil, SQL: %s\", q.Query)\n\t\t\treturn rule\n\t\t}\n\n\t\tfor _, tiStmtNode := range q.TiStmt {\n\t\t\tswitch stmt := tiStmtNode.(type) {\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\t// alter\n\t\t\t\tfor _, spec := range stmt.Specs {\n\t\t\t\t\tfor _, column := range spec.NewColumns {\n\t\t\t\t\t\tif ast.IsMysqlKeyword(column.Name.String()) {\n\t\t\t\t\t\t\treturn HeuristicRules[\"KWR.002\"]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\t// create\n\t\t\t\tif ast.IsMysqlKeyword(stmt.Table.Name.String()) {\n\t\t\t\t\treturn HeuristicRules[\"KWR.002\"]\n\t\t\t\t}\n\n\t\t\t\tfor _, col := range stmt.Cols {\n\t\t\t\t\tif ast.IsMysqlKeyword(col.Name.String()) {\n\t\t\t\t\t\treturn HeuristicRules[\"KWR.002\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RulePluralWord KWR.003\n// Reference: https://en.wikipedia.org/wiki/English_plurals\nfunc (q *Query4Audit) RulePluralWord() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif q.TiStmt == nil {\n\t\t\tcommon.Log.Error(\"TiStmt is nil, SQL: %s\", q.Query)\n\t\t\treturn rule\n\t\t}\n\n\t\tfor _, tiStmtNode := range q.TiStmt {\n\t\t\tswitch stmt := tiStmtNode.(type) {\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\t// alter\n\t\t\t\tfor _, spec := range stmt.Specs {\n\t\t\t\t\tfor _, column := range spec.NewColumns {\n\t\t\t\t\t\tif inflector.Singularize(column.Name.String()) != column.Name.String() {\n\t\t\t\t\t\t\treturn HeuristicRules[\"KWR.003\"]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\t// create\n\t\t\t\tif inflector.Singularize(stmt.Table.Name.String()) != stmt.Table.Name.String() {\n\t\t\t\t\treturn HeuristicRules[\"KWR.003\"]\n\t\t\t\t}\n\n\t\t\t\tfor _, col := range stmt.Cols {\n\t\t\t\t\tif inflector.Singularize(col.Name.String()) != col.Name.String() {\n\t\t\t\t\t\treturn HeuristicRules[\"KWR.003\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t}\n\treturn rule\n}\n\n// RuleMultiBytesWord KWR.004\nfunc (q *Query4Audit) RuleMultiBytesWord() Rule {\n\t// TODO: 目前使用 utf8 字符集检查，其他字符集输入可能会有问题\n\tvar rule = q.RuleOK()\n\tfor _, tk := range ast.Tokenize(q.Query) {\n\t\tswitch tk.Type {\n\t\tcase ast.TokenTypeBacktickQuote, ast.TokenTypeWord:\n\t\t\tif utf8.RuneCountInString(tk.Val) != len(tk.Val) {\n\t\t\t\trule = HeuristicRules[\"KWR.004\"]\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleInvisibleUnicode KWR.005\nfunc (q *Query4Audit) RuleInvisibleUnicode() Rule {\n\tvar rule = q.RuleOK()\n\tfor _, tk := range ast.Tokenizer(q.Query) {\n\t\t// 多字节的肉眼不可见字符经过 Tokenizer 后被切成了单字节字符。\n\t\t// strings.Contains 中的内容也肉眼不可见，需要使用 cat -A 查看代码\n\t\tswitch tk.Val {\n\t\tcase string([]byte{194}), string([]byte{160}): // non-broken-space C2 A0\n\t\t\tif strings.Contains(q.Query, ` `) {\n\t\t\t\trule = HeuristicRules[\"KWR.005\"]\n\t\t\t\treturn rule\n\t\t\t}\n\t\tcase string([]byte{226}), string([]byte{128}), string([]byte{139}): // zero-width space E2 80 8B\n\t\t\tif strings.Contains(q.Query, `​`) {\n\t\t\t\trule = HeuristicRules[\"KWR.005\"]\n\t\t\t\treturn rule\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleInsertSelect LCK.001\nfunc (q *Query4Audit) RuleInsertSelect() Rule {\n\tvar rule = q.RuleOK()\n\tswitch n := q.Stmt.(type) {\n\tcase *sqlparser.Insert:\n\t\tswitch n.Rows.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\trule = HeuristicRules[\"LCK.001\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleInsertOnDup LCK.002\nfunc (q *Query4Audit) RuleInsertOnDup() Rule {\n\tvar rule = q.RuleOK()\n\tswitch n := q.Stmt.(type) {\n\tcase *sqlparser.Insert:\n\t\tif n.OnDup != nil {\n\t\t\trule = HeuristicRules[\"LCK.002\"]\n\t\t\treturn rule\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleInSubquery SUB.001\nfunc (q *Query4Audit) RuleInSubquery() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node.(type) {\n\t\tcase *sqlparser.Subquery:\n\t\t\trule = HeuristicRules[\"SUB.001\"]\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleSubqueryDepth SUB.004\nfunc (q *Query4Audit) RuleSubqueryDepth() Rule {\n\tvar rule = q.RuleOK()\n\tif depth := ast.GetSubqueryDepth(q.Stmt); depth > common.Config.MaxSubqueryDepth {\n\t\trule = HeuristicRules[\"SUB.004\"]\n\t}\n\treturn rule\n}\n\n// RuleSubQueryLimit SUB.005\n// 只有 IN 的 SUBQUERY 限制了 LIMIT, FROM 子句中的 SUBQUERY 并未限制 LIMIT\nfunc (q *Query4Audit) RuleSubQueryLimit() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tif n.Operator == \"in\" {\n\t\t\t\tswitch r := n.Right.(type) {\n\t\t\t\tcase *sqlparser.Subquery:\n\t\t\t\t\tswitch s := r.Select.(type) {\n\t\t\t\t\tcase *sqlparser.Select:\n\t\t\t\t\t\tif s.Limit != nil {\n\t\t\t\t\t\t\trule = HeuristicRules[\"SUB.005\"]\n\t\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleSubQueryFunctions SUB.006\nfunc (q *Query4Audit) RuleSubQueryFunctions() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node.(type) {\n\t\tcase *sqlparser.Subquery:\n\t\t\terr = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\t\tswitch node.(type) {\n\t\t\t\tcase *sqlparser.FuncExpr:\n\t\t\t\t\trule = HeuristicRules[\"SUB.006\"]\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t\treturn true, nil\n\t\t\t}, node)\n\t\t\tcommon.LogIfError(err, \"\")\n\t\t}\n\n\t\tif rule.Item == \"OK\" {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleUNIONLimit SUB.007\nfunc (q *Query4Audit) RuleUNIONLimit() Rule {\n\tvar rule = q.RuleOK()\n\tfor _, tiStmtNode := range q.TiStmt {\n\t\tswitch stmt := tiStmtNode.(type) {\n\t\t// SetOprStmt represents \"union/except/intersect statement\"\n\t\tcase *tidb.SetOprStmt:\n\t\t\tif stmt.Limit != nil {\n\t\t\t\tfor _, sel := range stmt.SelectList.Selects {\n\t\t\t\t\tswitch n := sel.(type) {\n\t\t\t\t\tcase *tidb.SelectStmt:\n\t\t\t\t\t\tif n.Limit == nil {\n\t\t\t\t\t\t\trule = HeuristicRules[\"SUB.007\"]\n\t\t\t\t\t\t}\n\t\t\t\t\tcase *tidb.SetOprSelectList:\n\t\t\t\t\t\tfor _, s := range n.Selects {\n\t\t\t\t\t\t\tswitch s1 := s.(type) {\n\t\t\t\t\t\t\tcase *tidb.SelectStmt:\n\t\t\t\t\t\t\t\tif s1.Limit == nil {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"SUB.007\"]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleMultiValueAttribute LIT.003\nfunc (q *Query4Audit) RuleMultiValueAttribute() Rule {\n\tvar rule = q.RuleOK()\n\tre := regexp.MustCompile(`(?i)(id\\s+regexp)`)\n\tif re.FindString(q.Query) != \"\" {\n\t\trule = HeuristicRules[\"LIT.003\"]\n\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\trule.Position = position[0]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleAddDelimiter LIT.004\nfunc (q *Query4Audit) RuleAddDelimiter() Rule {\n\tvar rule = q.RuleOK()\n\tre := regexp.MustCompile(`(?i)(^use\\s+[0-9a-z_-]*)|(^show\\s+databases)`)\n\tif re.FindString(q.Query) != \"\" && !strings.HasSuffix(q.Query, common.Config.Delimiter) {\n\t\trule = HeuristicRules[\"LIT.004\"]\n\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\trule.Position = position[0]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleRecursiveDependency KEY.003\nfunc (q *Query4Audit) RuleRecursiveDependency() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\t// create statement\n\t\t\t\tfor _, ref := range node.Constraints {\n\t\t\t\t\tif ref != nil && ref.Tp == tidb.ConstraintForeignKey {\n\t\t\t\t\t\trule = HeuristicRules[\"KEY.003\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\t// alter table statement\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tif spec.Constraint != nil && spec.Constraint.Tp == tidb.ConstraintForeignKey {\n\t\t\t\t\t\trule = HeuristicRules[\"KEY.003\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif rule.Item == \"KEY.003\" {\n\t\tre := regexp.MustCompile(`(?i)(\\s+references\\s+)`)\n\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\trule.Position = position[0]\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RuleImpreciseDataType COL.009\nfunc (q *Query4Audit) RuleImpreciseDataType() Rule {\n\tvar rule = q.RuleOK()\n\tif q.TiStmt != nil {\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\t// Create table statement\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeFloat, mysql.TypeDouble, mysql.TypeNewDecimal:\n\t\t\t\t\t\trule = HeuristicRules[\"COL.009\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\t// Alter table statement\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeFloat, mysql.TypeDouble, mysql.TypeNewDecimal:\n\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.009\"]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.InsertStmt:\n\t\t\t\t// Insert statement\n\t\t\t\tfor _, values := range node.Lists {\n\t\t\t\t\tfor _, value := range values {\n\t\t\t\t\t\tswitch value.GetType().Tp {\n\t\t\t\t\t\tcase mysql.TypeNewDecimal, mysql.TypeFloat:\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.009\"]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.SelectStmt:\n\t\t\t\t// Select statement\n\t\t\t\tswitch where := node.Where.(type) {\n\t\t\t\tcase *tidb.BinaryOperationExpr:\n\t\t\t\t\tswitch where.R.GetType().Tp {\n\t\t\t\t\tcase mysql.TypeNewDecimal, mysql.TypeFloat:\n\t\t\t\t\t\trule = HeuristicRules[\"COL.009\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RuleValuesInDefinition COL.010\nfunc (q *Query4Audit) RuleValuesInDefinition() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeSet, mysql.TypeEnum, mysql.TypeBit:\n\t\t\t\t\t\trule = HeuristicRules[\"COL.010\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeSet, mysql.TypeEnum, mysql.TypeBit:\n\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.010\"]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleIndexAttributeOrder KEY.004\nfunc (q *Query4Audit) RuleIndexAttributeOrder() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateIndexStmt:\n\t\t\t\tif len(node.IndexPartSpecifications) > 1 {\n\t\t\t\t\trule = HeuristicRules[\"KEY.004\"]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, constraint := range node.Constraints {\n\t\t\t\t\t// 当一条索引中包含多个列的时候给予建议\n\t\t\t\t\tif len(constraint.Keys) > 1 {\n\t\t\t\t\t\trule = HeuristicRules[\"KEY.004\"]\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tif spec.Tp == tidb.AlterTableAddConstraint && len(spec.Constraint.Keys) > 1 {\n\t\t\t\t\t\trule = HeuristicRules[\"KEY.004\"]\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleNullUsage COL.011\nfunc (q *Query4Audit) RuleNullUsage() Rule {\n\tvar rule = q.RuleOK()\n\tre := regexp.MustCompile(`(?i)(\\s+null\\s+)`)\n\tif re.FindString(q.Query) != \"\" {\n\t\trule = HeuristicRules[\"COL.011\"]\n\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\trule.Position = position[0]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleStringConcatenation FUN.003\nfunc (q *Query4Audit) RuleStringConcatenation() Rule {\n\tvar rule = q.RuleOK()\n\t// 匹配 or 关键字\n\tmatchF := func(s string) bool {\n\t\t// 状态：进入下一状态要匹配的字符\n\t\tpath := map[int]rune{\n\t\t\t-1: '\\'', // 特殊状态，左侧有单引号，等待匹配单引号\n\t\t\t0:  ' ',  // 初始态\n\t\t\t1:  'o',\n\t\t\t2:  'r',\n\t\t\t3:  ' ',\n\t\t}\n\t\tstatus := 0\n\t\tskip := false\n\t\tfor _, c := range s {\n\t\t\tif skip {\n\t\t\t\tskip = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tswitch c {\n\t\t\tcase '\\\\':\n\t\t\t\tskip = true\n\t\t\tcase path[status]:\n\t\t\t\tstatus += 1 // next status\n\t\t\tcase '\\'':\n\t\t\t\tstatus = -1\n\t\t\tdefault:\n\t\t\t\t// 碰到其他字符，如果status = -1，说明在引号中间，保持不变\n\t\t\t\tif status != -1 {\n\t\t\t\t\tstatus = 0\n\t\t\t\t}\n\t\t\t}\n\t\t\tif status == 4 {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tfor _, expr := range n.SelectExprs {\n\t\t\t\tif matchF(sqlparser.String(expr)) {\n\t\t\t\t\trule = HeuristicRules[\"FUN.003\"]\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\n\treturn rule\n}\n\n// RuleSysdate FUN.004\nfunc (q *Query4Audit) RuleSysdate() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.FuncExpr:\n\t\t\tif strings.ToLower(n.Name.String()) == \"sysdate\" {\n\t\t\t\trule = HeuristicRules[\"FUN.004\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleCountConst FUN.005\nfunc (q *Query4Audit) RuleCountConst() Rule {\n\tvar rule = q.RuleOK()\n\tfingerprint := query.Fingerprint(q.Query)\n\tcountReg := regexp.MustCompile(`(?i)count\\(\\s*[0-9a-z?]*\\s*\\)`)\n\tif countReg.MatchString(fingerprint) {\n\t\trule = HeuristicRules[\"FUN.005\"]\n\t\tif position := countReg.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\trule.Position = position[0]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleSumNPE FUN.006\nfunc (q *Query4Audit) RuleSumNPE() Rule {\n\tvar rule = q.RuleOK()\n\tfingerprint := query.Fingerprint(q.Query)\n\t// TODO: https://github.com/XiaoMi/soar/issues/143\n\t// https://dev.mysql.com/doc/refman/8.0/en/group-by-functions.html\n\tsumReg := regexp.MustCompile(`(?i)sum\\(\\s*[0-9a-z?]*\\s*\\)`)\n\tisnullReg := regexp.MustCompile(`(?i)isnull\\(sum\\(\\s*[0-9a-z?]*\\s*\\)\\)`)\n\tif sumReg.MatchString(fingerprint) && !isnullReg.MatchString(fingerprint) {\n\t\t// TODO: check wether column define with not null flag\n\t\trule = HeuristicRules[\"FUN.006\"]\n\t\tif position := isnullReg.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\trule.Position = position[0]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleForbiddenTrigger FUN.007\nfunc (q *Query4Audit) RuleForbiddenTrigger() Rule {\n\tvar rule = q.RuleOK()\n\n\t// 由于vitess对某些语法的支持不完善，使得如创建临时表等语句无法通过语法检查\n\t// 所以这里使用正则对触发器、临时表、存储过程等进行匹配\n\t// 但是目前支持的也不是非常全面，有待完善匹配规则\n\t// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键\n\n\tforbidden := []*regexp.Regexp{\n\t\tregexp.MustCompile(`(?i)CREATE\\s+TRIGGER\\s+`),\n\t}\n\n\tfor _, reg := range forbidden {\n\t\tif reg.MatchString(q.Query) {\n\t\t\trule = HeuristicRules[\"FUN.007\"]\n\t\t\tif position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\trule.Position = position[0]\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleForbiddenProcedure FUN.008\nfunc (q *Query4Audit) RuleForbiddenProcedure() Rule {\n\tvar rule = q.RuleOK()\n\n\t// 由于vitess对某些语法的支持不完善，使得如创建临时表等语句无法通过语法检查\n\t// 所以这里使用正则对触发器、临时表、存储过程等进行匹配\n\t// 但是目前支持的也不是非常全面，有待完善匹配规则\n\t// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键\n\n\tforbidden := []*regexp.Regexp{\n\t\tregexp.MustCompile(`(?i)CREATE\\s+PROCEDURE\\s+`),\n\t}\n\n\tfor _, reg := range forbidden {\n\t\tif reg.MatchString(q.Query) {\n\t\t\trule = HeuristicRules[\"FUN.008\"]\n\t\t\tif position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\trule.Position = position[0]\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleForbiddenFunction FUN.009\nfunc (q *Query4Audit) RuleForbiddenFunction() Rule {\n\tvar rule = q.RuleOK()\n\n\t// 由于vitess对某些语法的支持不完善，使得如创建临时表等语句无法通过语法检查\n\t// 所以这里使用正则对触发器、临时表、存储过程等进行匹配\n\t// 但是目前支持的也不是非常全面，有待完善匹配规则\n\t// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键\n\n\tforbidden := []*regexp.Regexp{\n\t\tregexp.MustCompile(`(?i)CREATE\\s+FUNCTION\\s+`),\n\t}\n\n\tfor _, reg := range forbidden {\n\t\tif reg.MatchString(q.Query) {\n\t\t\trule = HeuristicRules[\"FUN.009\"]\n\t\t\tif position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\trule.Position = position[0]\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RulePatternMatchingUsage ARG.007\nfunc (q *Query4Audit) RulePatternMatchingUsage() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\tre := regexp.MustCompile(`(?i)(\\bregexp\\b)|(\\bsimilar to\\b)`)\n\t\tif re.FindString(q.Query) != \"\" {\n\t\t\trule = HeuristicRules[\"ARG.007\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleSpaghettiQueryAlert CLA.012\nfunc (q *Query4Audit) RuleSpaghettiQueryAlert() Rule {\n\tvar rule = q.RuleOK()\n\tif len(query.Fingerprint(q.Query)) > common.Config.SpaghettiQueryLength {\n\t\trule = HeuristicRules[\"CLA.012\"]\n\t}\n\treturn rule\n}\n\n// RuleReduceNumberOfJoin JOI.005\nfunc (q *Query4Audit) RuleReduceNumberOfJoin() Rule {\n\tvar rule = q.RuleOK()\n\tvar tables []string\n\tswitch q.Stmt.(type) {\n\t// TODO: UNION有可能有多张表，这里未检查UNION SELECT\n\tcase *sqlparser.Union:\n\t\treturn rule\n\tdefault:\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch n := node.(type) {\n\t\t\tcase *sqlparser.AliasedTableExpr:\n\t\t\t\tswitch table := n.Expr.(type) {\n\t\t\t\tcase sqlparser.TableName:\n\t\t\t\t\texist := false\n\t\t\t\t\tfor _, t := range tables {\n\t\t\t\t\t\tif t == table.Name.String() {\n\t\t\t\t\t\t\texist = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !exist {\n\t\t\t\t\t\ttables = append(tables, table.Name.String())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, q.Stmt)\n\t\tcommon.LogIfError(err, \"\")\n\t}\n\tif len(tables) > common.Config.MaxJoinTableCount {\n\t\trule = HeuristicRules[\"JOI.005\"]\n\t}\n\treturn rule\n}\n\n// RuleDistinctUsage DIS.001\nfunc (q *Query4Audit) RuleDistinctUsage() Rule {\n\t// Distinct\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\tre := regexp.MustCompile(`(?i)(\\bdistinct\\b)`)\n\t\tif len(re.FindAllString(q.Query, -1)) > common.Config.MaxDistinctCount {\n\t\t\trule = HeuristicRules[\"DIS.001\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleCountDistinctMultiCol DIS.002\nfunc (q *Query4Audit) RuleCountDistinctMultiCol() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.FuncExpr:\n\t\t\tstr := strings.ToLower(sqlparser.String(n))\n\t\t\tif strings.HasPrefix(str, \"count\") && strings.Contains(str, \",\") {\n\t\t\t\trule = HeuristicRules[\"DIS.002\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleDistinctStar DIS.003\nfunc (q *Query4Audit) RuleDistinctStar() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\tmeta := ast.GetMeta(q.Stmt, nil)\n\t\tfor _, m := range meta {\n\t\t\tif len(m.Table) == 1 {\n\t\t\t\t// distinct tbl.* from tbl和 distinct *\n\t\t\t\tre := regexp.MustCompile(`(?i)((\\s+distinct\\s*\\*)|(\\s+distinct\\s+[0-9a-z_` + \"`\" + `]*\\.\\*))`)\n\t\t\t\tif re.MatchString(q.Query) {\n\t\t\t\t\trule = HeuristicRules[\"DIS.003\"]\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleHavingClause CLA.013\nfunc (q *Query4Audit) RuleHavingClause() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tif expr.Having != nil {\n\t\t\t\trule = HeuristicRules[\"CLA.013\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleUpdatePrimaryKey CLA.016\nfunc (idxAdv *IndexAdvisor) RuleUpdatePrimaryKey() Rule {\n\trule := HeuristicRules[\"OK\"]\n\tswitch node := idxAdv.Ast.(type) {\n\tcase *sqlparser.Update:\n\t\tvar setColumns []*common.Column\n\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch node.(type) {\n\t\t\tcase *sqlparser.UpdateExpr:\n\t\t\t\t// 获取 set 操作的全部 column\n\t\t\t\tsetColumns = append(setColumns, ast.FindAllCols(node)...)\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, node)\n\t\tcommon.LogIfError(err, \"\")\n\t\tsetColumns = idxAdv.calcCardinality(CompleteColumnsInfo(idxAdv.Ast, setColumns, idxAdv.vEnv))\n\t\tfor _, col := range setColumns {\n\t\t\tidxMeta := idxAdv.IndexMeta[idxAdv.vEnv.DBHash(col.DB)][col.Table]\n\t\t\tif idxMeta == nil {\n\t\t\t\treturn rule\n\t\t\t}\n\t\t\tfor _, idx := range idxMeta.Rows {\n\t\t\t\tif strings.ToLower(idx.KeyName) == \"primary\" {\n\t\t\t\t\tif col.Name == idx.ColumnName {\n\t\t\t\t\t\trule = HeuristicRules[\"CLA.016\"]\n\t\t\t\t\t\treturn rule\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RuleNestedSubQueries JOI.006\nfunc (q *Query4Audit) RuleNestedSubQueries() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node.(type) {\n\t\tcase *sqlparser.Subquery:\n\t\t\trule = HeuristicRules[\"JOI.006\"]\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleMultiDeleteUpdate JOI.007\nfunc (q *Query4Audit) RuleMultiDeleteUpdate() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.Delete, *sqlparser.Update:\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch node.(type) {\n\t\t\tcase *sqlparser.JoinTableExpr:\n\t\t\t\trule = HeuristicRules[\"JOI.007\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, q.Stmt)\n\t\tcommon.LogIfError(err, \"\")\n\t}\n\treturn rule\n}\n\n// RuleMultiDBJoin JOI.008\nfunc (q *Query4Audit) RuleMultiDBJoin() Rule {\n\tvar rule = q.RuleOK()\n\tmeta := ast.GetMeta(q.Stmt, nil)\n\tdbCount := 0\n\tfor range meta {\n\t\tdbCount++\n\t}\n\n\tif dbCount > 1 {\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch node.(type) {\n\t\t\tcase *sqlparser.JoinTableExpr:\n\t\t\t\trule = HeuristicRules[\"JOI.008\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, q.Stmt)\n\t\tcommon.LogIfError(err, \"\")\n\t}\n\treturn rule\n}\n\n// RuleORUsage ARG.008\nfunc (q *Query4Audit) RuleORUsage() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch n := node.(type) {\n\t\t\tcase *sqlparser.OrExpr:\n\t\t\t\tswitch n.Left.(type) {\n\t\t\t\tcase *sqlparser.IsExpr:\n\t\t\t\t\t// IS TRUE|FALSE|NULL eg. a = 1 or a IS NULL 这种情况也需要考虑\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\t\t\t\tswitch n.Right.(type) {\n\t\t\t\tcase *sqlparser.IsExpr:\n\t\t\t\t\t// IS TRUE|FALSE|NULL eg. a = 1 or a IS NULL 这种情况也需要考虑\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\n\t\t\t\tif strings.Fields(sqlparser.String(n.Left))[0] != strings.Fields(sqlparser.String(n.Right))[0] {\n\t\t\t\t\t// 不同字段需要区分开，不同字段的 OR 不能改写为 IN\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\n\t\t\t\trule = HeuristicRules[\"ARG.008\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, q.Stmt)\n\t\tcommon.LogIfError(err, \"\")\n\t}\n\treturn rule\n}\n\n// RuleSpaceWithQuote ARG.009\nfunc (q *Query4Audit) RuleSpaceWithQuote() Rule {\n\tvar rule = q.RuleOK()\n\tfor _, tk := range ast.Tokenize(q.Query) {\n\t\tif tk.Type == ast.TokenTypeQuote {\n\t\t\tif len(tk.Val) >= 2 {\n\t\t\t\t// 序列化的Val是带引号，所以要取第2个和倒数第二个，这样也就不用担心len<2了。\n\t\t\t\tswitch tk.Val[1] {\n\t\t\t\tcase ' ':\n\t\t\t\t\trule = HeuristicRules[\"ARG.009\"]\n\t\t\t\t}\n\t\t\t\tswitch tk.Val[len(tk.Val)-2] {\n\t\t\t\tcase ' ':\n\t\t\t\t\trule = HeuristicRules[\"ARG.009\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleHint ARG.010\n// TODO: sql_no_cache, straight join\nfunc (q *Query4Audit) RuleHint() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.IndexHints:\n\t\t\tif n != nil {\n\t\t\t\trule = HeuristicRules[\"ARG.010\"]\n\t\t\t}\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleNot ARG.011\nfunc (q *Query4Audit) RuleNot() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tif strings.HasPrefix(strings.ToLower(n.Operator), \"not\") {\n\t\t\t\trule = HeuristicRules[\"ARG.011\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleInsertValues ARG.012\nfunc (q *Query4Audit) RuleInsertValues() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.Insert:\n\t\tswitch val := s.Rows.(type) {\n\t\tcase sqlparser.Values:\n\t\t\tif len(val) > common.Config.MaxValueCount {\n\t\t\t\trule = HeuristicRules[\"ARG.012\"]\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleFullWidthQuote ARG.013\nfunc (q *Query4Audit) RuleFullWidthQuote() Rule {\n\tvar rule = q.RuleOK()\n\tfor _, node := range q.TiStmt {\n\t\tswitch n := node.(type) {\n\t\tcase *tidb.CreateTableStmt, *tidb.AlterTableStmt:\n\t\t\tvar sb strings.Builder\n\t\t\tctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)\n\t\t\tif err := n.Restore(ctx); err == nil {\n\t\t\t\tif strings.Contains(sb.String(), `“”`) || strings.Contains(sb.String(), `‘’`) {\n\t\t\t\t\trule = HeuristicRules[\"ARG.013\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleUNIONUsage SUB.002\nfunc (q *Query4Audit) RuleUNIONUsage() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.Union:\n\t\tif strings.ToLower(s.Type) == \"union\" {\n\t\t\trule = HeuristicRules[\"SUB.002\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleDistinctJoinUsage SUB.003\nfunc (q *Query4Audit) RuleDistinctJoinUsage() Rule {\n\tvar rule = q.RuleOK()\n\tswitch expr := q.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\tif expr.Distinct != \"\" {\n\t\t\tif expr.From != nil {\n\t\t\t\tif len(expr.From) > 1 {\n\t\t\t\t\trule = HeuristicRules[\"SUB.003\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleReadablePasswords SEC.002\nfunc (q *Query4Audit) RuleReadablePasswords() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tre := regexp.MustCompile(`(?i)(password)|(password)|(pwd)`)\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\t// create table stmt\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString,\n\t\t\t\t\t\tmysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob:\n\t\t\t\t\t\tif re.FindString(q.Query) != \"\" {\n\t\t\t\t\t\t\treturn HeuristicRules[\"SEC.002\"]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\t// alter table stmt\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableModifyColumn, tidb.AlterTableChangeColumn, tidb.AlterTableAddColumns:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString,\n\t\t\t\t\t\t\t\tmysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob:\n\t\t\t\t\t\t\t\tif re.FindString(q.Query) != \"\" {\n\t\t\t\t\t\t\t\t\treturn HeuristicRules[\"SEC.002\"]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleDataDrop SEC.003\nfunc (q *Query4Audit) RuleDataDrop() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.DBDDL:\n\t\tif strings.ToLower(s.Action) == \"drop\" {\n\t\t\trule = HeuristicRules[\"SEC.003\"]\n\t\t}\n\tcase *sqlparser.DDL:\n\t\tif strings.ToLower(s.Action) == \"drop\" || strings.ToLower(s.Action) == \"truncate\" {\n\t\t\trule = HeuristicRules[\"SEC.003\"]\n\t\t}\n\tcase *sqlparser.Delete:\n\t\trule = HeuristicRules[\"SEC.003\"]\n\t}\n\treturn rule\n}\n\n// RuleInjection SEC.004\nfunc (q *Query4Audit) RuleInjection() Rule {\n\tvar rule = q.RuleOK()\n\tif q.TiStmt != nil {\n\t\tjson := ast.StmtNode2JSON(q.Query, \"\", \"\")\n\t\tfs := common.JSONFind(json, \"FnName\")\n\t\tfor _, f := range fs {\n\t\t\tfunctionName := gjson.Get(f, \"L\")\n\t\t\tswitch functionName.String() {\n\t\t\tcase \"sleep\", \"benchmark\", \"get_lock\", \"release_lock\":\n\t\t\t\t// Ref: https://www.k0rz3n.com/2019/02/01/一篇文章带你深入理解%20SQL%20盲注/\n\t\t\t\trule = HeuristicRules[\"SEC.004\"]\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleCompareWithFunction FUN.001\nfunc (q *Query4Audit) RuleCompareWithFunction() Rule {\n\tvar rule = q.RuleOK()\n\n\t// `select id from t where num/2 = 100`,\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t// Vitess 中有些函数进行了单独定义不在 FuncExpr 中，如: substring。所以不能直接用 FuncExpr 判断。\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tswitch n.Left.(type) {\n\t\t\tcase *sqlparser.SQLVal, *sqlparser.ColName:\n\t\t\tdefault:\n\t\t\t\trule = HeuristicRules[\"FUN.001\"]\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\n\t// select id from t where substring(name,1,3)='abc';\n\tfor _, tiStmt := range q.TiStmt {\n\t\tswitch tiStmt.(type) {\n\t\tcase *tidb.SelectStmt, *tidb.UpdateStmt, *tidb.DeleteStmt:\n\t\t\tjson := ast.StmtNode2JSON(q.Query, \"\", \"\")\n\t\t\twhereJSON := common.JSONFind(json, \"Where\")\n\t\t\tfor _, where := range whereJSON {\n\t\t\t\tif len(common.JSONFind(where, \"FnName\")) > 0 {\n\t\t\t\t\trule = HeuristicRules[\"FUN.001\"]\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RuleCountStar FUN.002\nfunc (q *Query4Audit) RuleCountStar() Rule {\n\tvar rule = q.RuleOK()\n\tswitch n := q.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\t// count(N), count(col), count(*)\n\t\tre := regexp.MustCompile(`(?i)(count\\(\\s*[*0-9a-z_` + \"`\" + `]*\\s*\\))`)\n\t\tif re.FindString(q.Query) != \"\" && n.Where != nil {\n\t\t\trule = HeuristicRules[\"FUN.002\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleTruncateTable SEC.001\nfunc (q *Query4Audit) RuleTruncateTable() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif strings.ToLower(s.Action) == \"truncate\" {\n\t\t\trule = HeuristicRules[\"SEC.001\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleIn ARG.005 && ARG.004 && ARG.014\nfunc (q *Query4Audit) RuleIn() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tswitch strings.ToLower(n.Operator) {\n\t\t\tcase \"in\":\n\t\t\t\tswitch r := n.Right.(type) {\n\t\t\t\tcase *sqlparser.Subquery:\n\t\t\t\t\t// by pass sub query\n\t\t\t\t\t// id in (select id from tb where xxx)\n\t\t\t\t\tbreak\n\t\t\t\tcase sqlparser.ValTuple:\n\t\t\t\t\t// IN (NULL)\n\t\t\t\t\tfor _, v := range r {\n\t\t\t\t\t\tswitch v.(type) {\n\t\t\t\t\t\tcase *sqlparser.NullVal:\n\t\t\t\t\t\t\trule = HeuristicRules[\"ARG.004\"]\n\t\t\t\t\t\t\treturn false, nil\n\n\t\t\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\t\t\t// id in (1, 2, id), always true.\n\t\t\t\t\t\t\trule = HeuristicRules[\"ARG.014\"]\n\t\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif len(r) > common.Config.MaxInCount {\n\t\t\t\t\t\trule = HeuristicRules[\"ARG.005\"]\n\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t}\n\t\t\t\t\t//default: // debug\n\t\t\t\t\t//\tfmt.Println(\"Type: \", reflect.TypeOf(n.Right).String())\n\t\t\t\t}\n\t\t\tcase \"not in\":\n\t\t\t\tswitch r := n.Right.(type) {\n\t\t\t\tcase sqlparser.ValTuple:\n\t\t\t\t\t// NOT IN (NULL)\n\t\t\t\t\tfor _, v := range r {\n\t\t\t\t\t\tswitch v.(type) {\n\t\t\t\t\t\tcase *sqlparser.NullVal:\n\t\t\t\t\t\t\trule = HeuristicRules[\"ARG.004\"]\n\t\t\t\t\t\t\treturn false, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleIsNullIsNotNull ARG.006\nfunc (q *Query4Audit) RuleIsNullIsNotNull() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\tre := regexp.MustCompile(`(?i)is\\s*(not)?\\s+null\\b`)\n\t\tif re.FindString(q.Query) != \"\" {\n\t\t\trule = HeuristicRules[\"ARG.006\"]\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleVarcharVSChar COL.008\nfunc (q *Query4Audit) RuleVarcharVSChar() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t// 在 TiDB 的 AST 中，char 和 binary 的 type 都是 mysql.TypeString\n\t\t\t\t\t// 只是 binary 数据类型的 character 和 collate 是 binary\n\t\t\t\t\tcase mysql.TypeString:\n\t\t\t\t\t\trule = HeuristicRules[\"COL.008\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn, tidb.AlterTableModifyColumn:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeString:\n\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.008\"]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleCreateDualTable TBL.003\nfunc (q *Query4Audit) RuleCreateDualTable() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif s.Table.Name.String() == \"dual\" {\n\t\t\trule = HeuristicRules[\"TBL.003\"]\n\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleAlterCharset ALT.001\nfunc (q *Query4Audit) RuleAlterCharset() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableOption:\n\t\t\t\t\t\tfor _, option := range spec.Options {\n\t\t\t\t\t\t\tif option.Tp == tidb.TableOptionCharset ||\n\t\t\t\t\t\t\t\toption.Tp == tidb.TableOptionCollate {\n\t\t\t\t\t\t\t\t//增加CONVERT TO的判断\n\t\t\t\t\t\t\t\tconvertReg, _ := regexp.Compile(\"convert\\\\b\\\\s+to\")\n\t\t\t\t\t\t\t\tif convertReg.Match([]byte(strings.ToLower(q.Query))) {\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"ALT.001\"]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif rule.Item == \"ALT.001\" {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleAlterDropColumn ALT.003\nfunc (q *Query4Audit) RuleAlterDropColumn() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableDropColumn:\n\t\t\t\t\t\trule = HeuristicRules[\"ALT.003\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif rule.Item == \"ALT.003\" {\n\t\t\tre := regexp.MustCompile(`(?i)(drop\\s+column)`)\n\t\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\trule.Position = position[0]\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleAlterDropKey ALT.004\nfunc (q *Query4Audit) RuleAlterDropKey() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableDropPrimaryKey,\n\t\t\t\t\t\ttidb.AlterTableDropIndex,\n\t\t\t\t\t\ttidb.AlterTableDropForeignKey:\n\t\t\t\t\t\trule = HeuristicRules[\"ALT.004\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleBLOBNotNull COL.012\nfunc (q *Query4Audit) RuleBLOBNotNull() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON:\n\t\t\t\t\t\tfor _, opt := range col.Options {\n\t\t\t\t\t\t\tif opt.Tp == tidb.ColumnOptionNotNull {\n\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.012\"]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif mysql.HasNotNullFlag(col.Tp.Flag) {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.012\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableModifyColumn, tidb.AlterTableChangeColumn:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON:\n\t\t\t\t\t\t\t\tfor _, opt := range col.Options {\n\t\t\t\t\t\t\t\t\tif opt.Tp == tidb.ColumnOptionNotNull {\n\t\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.012\"]\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif mysql.HasNotNullFlag(col.Tp.Flag) {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.012\"]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RuleTooManyKeys KEY.005\nfunc (q *Query4Audit) RuleTooManyKeys() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tif len(node.Constraints) > common.Config.MaxIdxCount {\n\t\t\t\t\trule = HeuristicRules[\"KEY.005\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleTooManyKeyParts KEY.006\nfunc (q *Query4Audit) RuleTooManyKeyParts() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, constraint := range node.Constraints {\n\t\t\t\t\tif len(constraint.Keys) > common.Config.MaxIdxColsCount {\n\t\t\t\t\t\treturn HeuristicRules[\"KEY.006\"]\n\t\t\t\t\t}\n\n\t\t\t\t\tif constraint.Refer != nil && len(constraint.Refer.IndexPartSpecifications) > common.Config.MaxIdxColsCount {\n\t\t\t\t\t\treturn HeuristicRules[\"KEY.006\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddConstraint:\n\t\t\t\t\t\tif spec.Constraint != nil {\n\t\t\t\t\t\t\tif len(spec.Constraint.Keys) > common.Config.MaxIdxColsCount {\n\t\t\t\t\t\t\t\treturn HeuristicRules[\"KEY.006\"]\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif spec.Constraint.Refer != nil {\n\t\t\t\t\t\t\t\tif len(spec.Constraint.Refer.IndexPartSpecifications) > common.Config.MaxIdxColsCount {\n\t\t\t\t\t\t\t\t\treturn HeuristicRules[\"KEY.006\"]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RulePKNotInt KEY.007 && KEY.001\nfunc (q *Query4Audit) RulePKNotInt() Rule {\n\tvar rule = q.RuleOK()\n\tvar pk sqlparser.ColIdent\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif strings.ToLower(s.Action) == \"create\" {\n\t\t\tif s.TableSpec == nil {\n\t\t\t\treturn rule\n\t\t\t}\n\t\t\tfor _, idx := range s.TableSpec.Indexes {\n\t\t\t\tif strings.ToLower(idx.Info.Type) == \"primary key\" {\n\t\t\t\t\tif len(idx.Columns) == 1 {\n\t\t\t\t\t\tpk = idx.Columns[0].Column\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 未指定主键\n\t\t\tif pk.String() == \"\" {\n\t\t\t\trule = HeuristicRules[\"KEY.007\"]\n\t\t\t\treturn rule\n\t\t\t}\n\n\t\t\t// 主键非int, bigint类型\n\t\t\tfor _, col := range s.TableSpec.Columns {\n\t\t\t\tif pk.String() == col.Name.String() {\n\t\t\t\t\tswitch strings.ToLower(col.Type.Type) {\n\t\t\t\t\tcase \"int\", \"bigint\", \"integer\":\n\t\t\t\t\t\tif !col.Type.Unsigned {\n\t\t\t\t\t\t\trule = HeuristicRules[\"KEY.007\"]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !col.Type.Autoincrement {\n\t\t\t\t\t\t\trule = HeuristicRules[\"KEY.001\"]\n\t\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\trule = HeuristicRules[\"KEY.007\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleOrderByMultiDirection KEY.008\nfunc (q *Query4Audit) RuleOrderByMultiDirection() Rule {\n\tvar rule = q.RuleOK()\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase sqlparser.OrderBy:\n\t\t\torder := \"\"\n\t\t\tfor _, col := range strings.Split(sqlparser.String(n), \",\") {\n\t\t\t\torders := strings.Split(col, \" \")\n\t\t\t\tif order != \"\" && order != orders[len(orders)-1] {\n\t\t\t\t\trule = HeuristicRules[\"KEY.008\"]\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t\torder = orders[len(orders)-1]\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, q.Stmt)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleUniqueKeyDup KEY.009\n// TODO: 目前只是给建议，期望能够实现自动检查\nfunc (q *Query4Audit) RuleUniqueKeyDup() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateIndexStmt:\n\t\t\t\t// create index\n\t\t\t\tif node.KeyType == tidb.IndexKeyTypeUnique {\n\t\t\t\t\tre := regexp.MustCompile(`(?i)(create\\s+(unique)\\s)`)\n\t\t\t\t\trule = HeuristicRules[\"KEY.009\"]\n\t\t\t\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\t\t\trule.Position = position[0]\n\t\t\t\t\t}\n\t\t\t\t\treturn rule\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\t// alter table add constraint\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddConstraint:\n\t\t\t\t\t\tif spec.Constraint == nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tswitch spec.Constraint.Tp {\n\t\t\t\t\t\tcase tidb.ConstraintPrimaryKey, tidb.ConstraintUniq, tidb.ConstraintUniqKey, tidb.ConstraintUniqIndex:\n\t\t\t\t\t\t\tre := regexp.MustCompile(`(?i)(add\\s+(unique)\\s)`)\n\t\t\t\t\t\t\trule = HeuristicRules[\"KEY.009\"]\n\t\t\t\t\t\t\tif position := re.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\t\t\t\t\trule.Position = position[0]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn rule\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleFulltextIndex KEY.010\nfunc (q *Query4Audit) RuleFulltextIndex() Rule {\n\tvar rule = q.RuleOK()\n\n\t/* // TiDB parser\n\tfor _, tiStmt := range q.TiStmt {\n\t\tswitch tiStmt.(type) {\n\t\tcase *tidb.CreateTableStmt, *tidb.AlterTableStmt:\n\t\tdefault:\n\t\t\treturn rule\n\t\t}\n\t}\n\t*/\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\tdefault:\n\t\treturn rule\n\t}\n\n\ttks := ast.Tokenize(q.Query)\n\tfor _, tk := range tks {\n\t\tswitch tk.Type {\n\t\tcase ast.TokenTypeWord:\n\t\t\tif strings.TrimSpace(strings.ToLower(tk.Val)) == \"fulltext\" {\n\t\t\t\trule = HeuristicRules[\"KEY.010\"]\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleTimestampDefault COL.013\nfunc (q *Query4Audit) RuleTimestampDefault() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeTimestamp, mysql.TypeDate, mysql.TypeDatetime, mysql.TypeNewDate:\n\t\t\t\t\t\thasDefault := false\n\t\t\t\t\t\tvar sb strings.Builder\n\t\t\t\t\t\tctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)\n\t\t\t\t\t\tfor _, option := range col.Options {\n\t\t\t\t\t\t\tif option.Tp == tidb.ColumnOptionDefaultValue {\n\t\t\t\t\t\t\t\thasDefault = true\n\t\t\t\t\t\t\t\tif err := option.Restore(ctx); err == nil {\n\t\t\t\t\t\t\t\t\tif strings.HasPrefix(strings.ToLower(sb.String()), `default '0`) ||\n\t\t\t\t\t\t\t\t\t\tstrings.HasPrefix(strings.ToLower(sb.String()), `default 0`) {\n\t\t\t\t\t\t\t\t\t\thasDefault = false\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !hasDefault {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.013\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddColumns,\n\t\t\t\t\t\ttidb.AlterTableModifyColumn,\n\t\t\t\t\t\ttidb.AlterTableChangeColumn,\n\t\t\t\t\t\ttidb.AlterTableAlterColumn:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvar sb strings.Builder\n\t\t\t\t\t\t\tctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeTimestamp, mysql.TypeDate, mysql.TypeDatetime, mysql.TypeNewDate:\n\t\t\t\t\t\t\t\thasDefault := false\n\t\t\t\t\t\t\t\tfor _, option := range col.Options {\n\t\t\t\t\t\t\t\t\tif option.Tp == tidb.ColumnOptionDefaultValue {\n\t\t\t\t\t\t\t\t\t\thasDefault = true\n\t\t\t\t\t\t\t\t\t\tif err := option.Restore(ctx); err == nil {\n\t\t\t\t\t\t\t\t\t\t\tif strings.HasPrefix(strings.ToLower(sb.String()), `default '0`) ||\n\t\t\t\t\t\t\t\t\t\t\t\tstrings.HasPrefix(strings.ToLower(sb.String()), `default 0`) ||\n\t\t\t\t\t\t\t\t\t\t\t\tstrings.HasPrefix(strings.ToLower(sb.String()), `default _utf8mb4'0`) ||\n\t\t\t\t\t\t\t\t\t\t\t\tstrings.HasPrefix(strings.ToLower(sb.String()), `default _utf8'0`) {\n\t\t\t\t\t\t\t\t\t\t\t\thasDefault = false\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !hasDefault {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.013\"]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleAutoIncrementInitNotZero TBL.004\nfunc (q *Query4Audit) RuleAutoIncrementInitNotZero() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, opt := range node.Options {\n\t\t\t\t\tif opt.Tp == tidb.TableOptionAutoIncrement && opt.UintValue > 1 {\n\t\t\t\t\t\trule = HeuristicRules[\"TBL.004\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleColumnWithCharset COL.014\nfunc (q *Query4Audit) RuleColumnWithCharset() Rule {\n\tvar rule = q.RuleOK()\n\ttks := ast.Tokenize(q.Query)\n\tfor _, tk := range tks {\n\t\tif tk.Type == ast.TokenTypeWord {\n\t\t\tswitch strings.TrimSpace(strings.ToLower(tk.Val)) {\n\t\t\t//character移到后面检查\n\t\t\tcase \"national\", \"nvarchar\", \"nchar\", \"nvarchar(\", \"nchar(\":\n\t\t\t\trule = HeuristicRules[\"COL.014\"]\n\t\t\t\treturn rule\n\t\t\t}\n\t\t}\n\t}\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif col.Tp.Charset != \"\" || col.Tp.Collate != \"\" {\n\t\t\t\t\t\tif col.Tp.Charset == \"binary\" || col.Tp.Collate == \"binary\" {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.014\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t//在这里检查character\n\t\t\t\t\tcharacterReg, _ := regexp.Compile(\"character set\")\n\t\t\t\t\tif characterReg.Match([]byte(strings.ToLower(q.Query))) {\n\t\t\t\t\t\trule = HeuristicRules[\"COL.014\"]\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAlterColumn, tidb.AlterTableChangeColumn,\n\t\t\t\t\t\ttidb.AlterTableModifyColumn, tidb.AlterTableAddColumns:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif col.Tp.Charset != \"\" || col.Tp.Collate != \"\" {\n\t\t\t\t\t\t\t\tif col.Tp.Charset == \"binary\" || col.Tp.Collate == \"binary\" {\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.014\"]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcharacterReg, _ := regexp.Compile(\"character set\")\n\t\t\t\t\t\t\tif characterReg.Match([]byte(strings.ToLower(q.Query))) {\n\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.014\"]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleTableCharsetCheck TBL.005\nfunc (q *Query4Audit) RuleTableCharsetCheck() Rule {\n\tvar rule = q.RuleOK()\n\tvar allow bool\n\tvar hasCharset bool\n\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL, *sqlparser.DBDDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, opt := range node.Options {\n\t\t\t\t\tif opt.Tp == tidb.TableOptionCharset {\n\t\t\t\t\t\thasCharset = true\n\t\t\t\t\t\tfor _, ch := range common.Config.AllowCharsets {\n\t\t\t\t\t\t\tif strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.StrValue)) {\n\t\t\t\t\t\t\t\tallow = true\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.CreateDatabaseStmt:\n\t\t\t\tfor _, opt := range node.Options {\n\t\t\t\t\tif opt.Tp == tidb.DatabaseOptionCharset {\n\t\t\t\t\t\thasCharset = true\n\t\t\t\t\t\tfor _, ch := range common.Config.AllowCharsets {\n\t\t\t\t\t\t\tif strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.Value)) {\n\t\t\t\t\t\t\t\tallow = true\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableOption:\n\t\t\t\t\t\tfor _, opt := range spec.Options {\n\t\t\t\t\t\t\tif opt.Tp == tidb.TableOptionCharset {\n\t\t\t\t\t\t\t\thasCharset = true\n\t\t\t\t\t\t\t\tfor _, ch := range common.Config.AllowCharsets {\n\t\t\t\t\t\t\t\t\tif strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.StrValue)) {\n\t\t\t\t\t\t\t\t\t\tallow = true\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 未指定字符集使用MySQL默认配置字符集，我们认为MySQL的配置是被优化过的。\n\tif hasCharset && !allow {\n\t\trule = HeuristicRules[\"TBL.005\"]\n\t}\n\treturn rule\n}\n\n// RuleForbiddenView TBL.006\nfunc (q *Query4Audit) RuleForbiddenView() Rule {\n\tvar rule = q.RuleOK()\n\n\t// 由于vitess对某些语法的支持不完善，使得如创建临时表等语句无法通过语法检查\n\t// 所以这里使用正则对触发器、临时表、存储过程等进行匹配\n\t// 但是目前支持的也不是非常全面，有待完善匹配规则\n\t// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键\n\n\tforbidden := []*regexp.Regexp{\n\t\tregexp.MustCompile(`(?i)CREATE\\s+VIEW\\s+`),\n\t\tregexp.MustCompile(`(?i)REPLACE\\s+VIEW\\s+`),\n\t}\n\n\tfor _, reg := range forbidden {\n\t\tif reg.MatchString(q.Query) {\n\t\t\trule = HeuristicRules[\"TBL.006\"]\n\t\t\tif position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\trule.Position = position[0]\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleForbiddenTempTable TBL.007\nfunc (q *Query4Audit) RuleForbiddenTempTable() Rule {\n\tvar rule = q.RuleOK()\n\n\t// 由于vitess对某些语法的支持不完善，使得如创建临时表等语句无法通过语法检查\n\t// 所以这里使用正则对触发器、临时表、存储过程等进行匹配\n\t// 但是目前支持的也不是非常全面，有待完善匹配规则\n\t// TODO TiDB 目前还不支持触发器、存储过程、自定义函数、外键\n\n\tforbidden := []*regexp.Regexp{\n\t\tregexp.MustCompile(`(?i)CREATE\\s+TEMPORARY\\s+TABLE\\s+`),\n\t}\n\n\tfor _, reg := range forbidden {\n\t\tif reg.MatchString(q.Query) {\n\t\t\trule = HeuristicRules[\"TBL.007\"]\n\t\t\tif position := reg.FindIndex([]byte(q.Query)); len(position) > 0 {\n\t\t\t\trule.Position = position[0]\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleTableCollateCheck TBL.008\nfunc (q *Query4Audit) RuleTableCollateCheck() Rule {\n\tvar rule = q.RuleOK()\n\tvar allow bool\n\tvar hasCollate bool\n\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL, *sqlparser.DBDDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, opt := range node.Options {\n\t\t\t\t\tif opt.Tp == tidb.TableOptionCollate {\n\t\t\t\t\t\thasCollate = true\n\t\t\t\t\t\tfor _, ch := range common.Config.AllowCollates {\n\t\t\t\t\t\t\tif strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.StrValue)) {\n\t\t\t\t\t\t\t\tallow = true\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.CreateDatabaseStmt:\n\t\t\t\tfor _, opt := range node.Options {\n\t\t\t\t\tif opt.Tp == tidb.DatabaseOptionCollate {\n\t\t\t\t\t\thasCollate = true\n\t\t\t\t\t\tfor _, ch := range common.Config.AllowCollates {\n\t\t\t\t\t\t\tif strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.Value)) {\n\t\t\t\t\t\t\t\tallow = true\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableOption:\n\t\t\t\t\t\tfor _, opt := range spec.Options {\n\t\t\t\t\t\t\tif opt.Tp == tidb.TableOptionCollate {\n\t\t\t\t\t\t\t\thasCollate = true\n\t\t\t\t\t\t\t\tfor _, ch := range common.Config.AllowCollates {\n\t\t\t\t\t\t\t\t\tif strings.TrimSpace(strings.ToLower(ch)) == strings.TrimSpace(strings.ToLower(opt.StrValue)) {\n\t\t\t\t\t\t\t\t\t\tallow = true\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 未指定字符集使用MySQL默认配置COLLATE，我们认为MySQL的配置是被优化过的。\n\tif hasCollate && !allow {\n\t\trule = HeuristicRules[\"TBL.008\"]\n\t}\n\treturn rule\n}\n\n// RuleBlobDefaultValue COL.015\nfunc (q *Query4Audit) RuleBlobDefaultValue() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob, mysql.TypeLongBlob, mysql.TypeJSON:\n\t\t\t\t\t\tfor _, opt := range col.Options {\n\t\t\t\t\t\t\tif opt.Tp == tidb.ColumnOptionDefaultValue && opt.Expr.GetType().Tp != mysql.TypeNull {\n\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.015\"]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableModifyColumn, tidb.AlterTableAlterColumn,\n\t\t\t\t\t\ttidb.AlterTableChangeColumn, tidb.AlterTableAddColumns:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob, mysql.TypeLongBlob, mysql.TypeJSON:\n\t\t\t\t\t\t\t\tfor _, opt := range col.Options {\n\t\t\t\t\t\t\t\t\tif opt.Tp == tidb.ColumnOptionDefaultValue && opt.Expr.GetType().Tp != mysql.TypeNull {\n\t\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.015\"]\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleIntPrecision COL.016\nfunc (q *Query4Audit) RuleIntPrecision() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeLong:\n\t\t\t\t\t\tif (col.Tp.Flen < 10 || col.Tp.Flen > 11) && col.Tp.Flen > 0 {\n\t\t\t\t\t\t\t// 有些语言 ORM 框架会生成 int(11)，有些语言的框架生成 int(10)\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.016\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\tcase mysql.TypeLonglong:\n\t\t\t\t\t\tif (col.Tp.Flen != 20) && col.Tp.Flen > 0 {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.016\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn,\n\t\t\t\t\t\ttidb.AlterTableAlterColumn, tidb.AlterTableModifyColumn:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeLong:\n\t\t\t\t\t\t\t\tif (col.Tp.Flen < 10 || col.Tp.Flen > 11) && col.Tp.Flen > 0 {\n\t\t\t\t\t\t\t\t\t// 有些语言 ORM 框架会生成 int(11)，有些语言的框架生成 int(10)\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.016\"]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase mysql.TypeLonglong:\n\t\t\t\t\t\t\t\tif col.Tp.Flen != 20 && col.Tp.Flen > 0 {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.016\"]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleVarcharLength COL.017\nfunc (q *Query4Audit) RuleVarcharLength() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeVarchar, mysql.TypeVarString:\n\t\t\t\t\t\tif col.Tp.Flen > common.Config.MaxVarcharLength {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.017\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableAddColumns, tidb.AlterTableChangeColumn,\n\t\t\t\t\t\ttidb.AlterTableAlterColumn, tidb.AlterTableModifyColumn:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeVarchar, mysql.TypeVarString:\n\t\t\t\t\t\t\t\tif col.Tp.Flen > common.Config.MaxVarcharLength {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.017\"]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleColumnNotAllowType COL.018\nfunc (q *Query4Audit) RuleColumnNotAllowType() Rule {\n\tvar rule = q.RuleOK()\n\n\tif len(common.Config.ColumnNotAllowType) == 0 {\n\t\treturn rule\n\t}\n\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tswitch strings.ToLower(s.Action) {\n\t\tcase \"create\", \"alter\":\n\t\t\ttks := ast.Tokenize(q.Query)\n\t\t\tfor _, tk := range tks {\n\t\t\t\tif tk.Type == ast.TokenTypeWord {\n\t\t\t\t\tfor _, tp := range common.Config.ColumnNotAllowType {\n\t\t\t\t\t\tif len(tk.Val) <= len(tp)+1 &&\n\t\t\t\t\t\t\tstrings.HasPrefix(strings.ToLower(tk.Val), strings.ToLower(tp)) {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.018\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif rule.Item != \"OK\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleTimePrecision COL.019\nfunc (q *Query4Audit) RuleTimePrecision() Rule {\n\tvar rule = q.RuleOK()\n\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeDatetime, mysql.TypeTimestamp, mysql.TypeDuration:\n\t\t\t\t\t\tif col.Tp.Decimal > 0 {\n\t\t\t\t\t\t\trule = HeuristicRules[\"COL.019\"]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableChangeColumn, tidb.AlterTableAlterColumn,\n\t\t\t\t\t\ttidb.AlterTableModifyColumn, tidb.AlterTableAddColumns:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\t\t\tcase mysql.TypeDatetime, mysql.TypeTimestamp, mysql.TypeDuration:\n\t\t\t\t\t\t\t\tif col.Tp.Decimal > 0 {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.019\"]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rule\n}\n\n// RuleNoOSCKey KEY.002\nfunc (q *Query4Audit) RuleNoOSCKey() Rule {\n\tvar rule = q.RuleOK()\n\tswitch s := q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif strings.ToLower(s.Action) == \"create\" {\n\t\t\tpkReg := regexp.MustCompile(`(?i)(primary\\s+key)`)\n\t\t\tif !pkReg.MatchString(q.Query) {\n\t\t\t\tukReg := regexp.MustCompile(`(?i)(unique\\s+((key)|(index)))`)\n\t\t\t\tif !ukReg.MatchString(q.Query) {\n\t\t\t\t\trule = HeuristicRules[\"KEY.002\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleTooManyFields COL.006\nfunc (q *Query4Audit) RuleTooManyFields() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tif len(node.Cols) > common.Config.MaxColCount {\n\t\t\t\t\trule = HeuristicRules[\"COL.006\"]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleMaxTextColsCount COL.007\nfunc (q *Query4Audit) RuleMaxTextColsCount() Rule {\n\tvar textColsCount int\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tswitch col.Tp.Tp {\n\t\t\t\t\tcase mysql.TypeBlob, mysql.TypeLongBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob:\n\t\t\t\t\t\ttextColsCount++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif textColsCount > common.Config.MaxTextColsCount {\n\t\trule = HeuristicRules[\"COL.007\"]\n\t}\n\n\treturn rule\n}\n\n// RuleMaxTextColsCount COL.007 checking for existed table\nfunc (idxAdv *IndexAdvisor) RuleMaxTextColsCount() Rule {\n\trule := HeuristicRules[\"OK\"]\n\t// 未开启测试环境不进行检查\n\tif common.Config.TestDSN.Disable {\n\t\treturn rule\n\t}\n\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch stmt := node.(type) {\n\t\tcase *sqlparser.DDL:\n\t\t\tif strings.ToLower(stmt.Action) != \"alter\" {\n\t\t\t\treturn true, nil\n\t\t\t}\n\n\t\t\t// 添加字段的语句会在初始化环境的时候被执行\n\t\t\t// 只需要获取该标的 CREATE 语句，后再对该语句进行检查即可\n\t\t\tddl, err := idxAdv.vEnv.ShowCreateTable(stmt.Table.Name.String())\n\t\t\tif err != nil {\n\t\t\t\tcommon.Log.Error(\"RuleMaxTextColsCount create statement got failed: %s\", err.Error())\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\tq, err := NewQuery4Audit(ddl)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\tr := q.RuleMaxTextColsCount()\n\t\t\tif r.Item != \"OK\" {\n\t\t\t\trule = r\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, idxAdv.Ast)\n\tcommon.LogIfError(err, \"\")\n\treturn rule\n}\n\n// RuleAllowEngine TBL.002\nfunc (q *Query4Audit) RuleAllowEngine() Rule {\n\tvar rule = q.RuleOK()\n\tvar hasDefaultEngine bool\n\tvar allowedEngine bool\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, opt := range node.Options {\n\t\t\t\t\tif opt.Tp == tidb.TableOptionEngine {\n\t\t\t\t\t\thasDefaultEngine = true\n\t\t\t\t\t\t// 使用了非推荐的存储引擎\n\t\t\t\t\t\tfor _, engine := range common.Config.AllowEngines {\n\t\t\t\t\t\t\tif strings.EqualFold(opt.StrValue, engine) {\n\t\t\t\t\t\t\t\tallowedEngine = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// common.Config.AllowEngines 为空时不给予建议\n\t\t\t\t\t\tif !allowedEngine && len(common.Config.AllowEngines) > 0 {\n\t\t\t\t\t\t\trule = HeuristicRules[\"TBL.002\"]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// 建表语句未指定表的存储引擎\n\t\t\t\tif !hasDefaultEngine {\n\t\t\t\t\trule = HeuristicRules[\"TBL.002\"]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableOption:\n\t\t\t\t\t\tfor _, opt := range spec.Options {\n\t\t\t\t\t\t\tif opt.Tp == tidb.TableOptionEngine {\n\t\t\t\t\t\t\t\t// 使用了非推荐的存储引擎\n\t\t\t\t\t\t\t\tfor _, engine := range common.Config.AllowEngines {\n\t\t\t\t\t\t\t\t\tif strings.EqualFold(opt.StrValue, engine) {\n\t\t\t\t\t\t\t\t\t\tallowedEngine = true\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// common.Config.AllowEngines 为空时不给予建议\n\t\t\t\t\t\t\t\tif !allowedEngine && len(common.Config.AllowEngines) > 0 {\n\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"TBL.002\"]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RulePartitionNotAllowed TBL.001\nfunc (q *Query4Audit) RulePartitionNotAllowed() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tif node.Partition != nil {\n\t\t\t\t\trule = HeuristicRules[\"TBL.001\"]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tif len(spec.PartDefinitions) > 0 {\n\t\t\t\t\t\trule = HeuristicRules[\"TBL.001\"]\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleAutoIncUnsigned COL.003:\nfunc (q *Query4Audit) RuleAutoIncUnsigned() Rule {\n\tvar rule = q.RuleOK()\n\tswitch q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tfor _, tiStmt := range q.TiStmt {\n\t\t\tswitch node := tiStmt.(type) {\n\t\t\tcase *tidb.CreateTableStmt:\n\t\t\t\tfor _, col := range node.Cols {\n\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tfor _, opt := range col.Options {\n\t\t\t\t\t\tif opt.Tp == tidb.ColumnOptionAutoIncrement {\n\t\t\t\t\t\t\tif !mysql.HasUnsignedFlag(col.Tp.Flag) {\n\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.003\"]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif rule.Item == \"COL.003\" {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase *tidb.AlterTableStmt:\n\t\t\t\tfor _, spec := range node.Specs {\n\t\t\t\t\tswitch spec.Tp {\n\t\t\t\t\tcase tidb.AlterTableChangeColumn, tidb.AlterTableAlterColumn,\n\t\t\t\t\t\ttidb.AlterTableModifyColumn, tidb.AlterTableAddColumns:\n\t\t\t\t\t\tfor _, col := range spec.NewColumns {\n\t\t\t\t\t\t\tif col.Tp == nil {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor _, opt := range col.Options {\n\t\t\t\t\t\t\t\tif opt.Tp == tidb.ColumnOptionAutoIncrement {\n\t\t\t\t\t\t\t\t\tif !mysql.HasUnsignedFlag(col.Tp.Flag) {\n\t\t\t\t\t\t\t\t\t\trule = HeuristicRules[\"COL.003\"]\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif rule.Item == \"COL.003\" {\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleSpaceAfterDot STA.002\nfunc (q *Query4Audit) RuleSpaceAfterDot() Rule {\n\tvar rule = q.RuleOK()\n\ttks := ast.Tokenize(q.Query)\n\tfor i, tk := range tks {\n\t\tswitch tk.Type {\n\n\t\t// SELECT * FROM db. tbl\n\t\t// SELECT tbl. col FROM tbl\n\t\tcase ast.TokenTypeWord:\n\t\t\tif len(tks) > i+1 &&\n\t\t\t\ttks[i+1].Type == ast.TokenTypeWhitespace &&\n\t\t\t\tstrings.HasSuffix(tk.Val, \".\") {\n\t\t\t\tcommon.Log.Debug(\"RuleSpaceAfterDot: \", tk.Val, tks[i+1].Val)\n\t\t\t\trule = HeuristicRules[\"STA.002\"]\n\t\t\t\treturn rule\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleIdxPrefix STA.003\nfunc (q *Query4Audit) RuleIdxPrefix() Rule {\n\tvar rule = q.RuleOK()\n\tfor _, node := range q.TiStmt {\n\t\tswitch n := node.(type) {\n\t\tcase *tidb.CreateTableStmt:\n\t\t\tfor _, c := range n.Constraints {\n\t\t\t\tswitch c.Tp {\n\t\t\t\tcase tidb.ConstraintIndex, tidb.ConstraintKey:\n\t\t\t\t\tif !strings.HasPrefix(c.Name, common.Config.IdxPrefix) {\n\t\t\t\t\t\trule = HeuristicRules[\"STA.003\"]\n\t\t\t\t\t}\n\t\t\t\tcase tidb.ConstraintUniq, tidb.ConstraintUniqKey, tidb.ConstraintUniqIndex:\n\t\t\t\t\tif !strings.HasPrefix(c.Name, common.Config.UkPrefix) {\n\t\t\t\t\t\trule = HeuristicRules[\"STA.003\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase *tidb.AlterTableStmt:\n\t\t\tfor _, s := range n.Specs {\n\t\t\t\tswitch s.Tp {\n\t\t\t\tcase tidb.AlterTableAddConstraint:\n\t\t\t\t\tswitch s.Constraint.Tp {\n\t\t\t\t\tcase tidb.ConstraintIndex, tidb.ConstraintKey:\n\t\t\t\t\t\tif !strings.HasPrefix(s.Constraint.Name, common.Config.IdxPrefix) {\n\t\t\t\t\t\t\trule = HeuristicRules[\"STA.003\"]\n\t\t\t\t\t\t}\n\t\t\t\t\tcase tidb.ConstraintUniq, tidb.ConstraintUniqKey, tidb.ConstraintUniqIndex:\n\t\t\t\t\t\tif !strings.HasPrefix(s.Constraint.Name, common.Config.UkPrefix) {\n\t\t\t\t\t\t\trule = HeuristicRules[\"STA.003\"]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rule\n}\n\n// RuleStandardName STA.004\nfunc (q *Query4Audit) RuleStandardName() Rule {\n\tvar rule = q.RuleOK()\n\tallowReg := regexp.MustCompile(`(?i)[a-z0-9_` + \"`\" + `]`)\n\tfor _, tk := range ast.Tokenize(q.Query) {\n\t\tif tk.Val == \"``\" {\n\t\t\trule = HeuristicRules[\"STA.004\"]\n\t\t}\n\n\t\tswitch tk.Type {\n\t\t// 反引号中可能有乱七八糟的东西\n\t\tcase ast.TokenTypeBacktickQuote:\n\t\t\t// 特殊字符，连续下划线\n\t\t\tif allowReg.ReplaceAllString(tk.Val, \"\") != \"\" || strings.Contains(tk.Val, \"__\") {\n\t\t\t\trule = HeuristicRules[\"STA.004\"]\n\t\t\t}\n\t\t\t// 统一大小写\n\t\t\tif !(strings.ToLower(tk.Val) == tk.Val || strings.ToUpper(tk.Val) == tk.Val) {\n\t\t\t\trule = HeuristicRules[\"STA.004\"]\n\t\t\t}\n\t\tcase ast.TokenTypeWord:\n\t\t\t// TOKEN_TYPE_WORD 中处理连续下划线的情况，其他情况容易误伤\n\t\t\tif strings.Contains(tk.Val, \"__\") {\n\t\t\t\trule = HeuristicRules[\"STA.004\"]\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t}\n\treturn rule\n}\n\n// MergeConflictHeuristicRules merge conflict rules\nfunc MergeConflictHeuristicRules(rules map[string]Rule) map[string]Rule {\n\t// KWR.001 VS ERR.000\n\t// select sql_calc_found_rows * from film\n\tif _, ok := rules[\"KWR.001\"]; ok {\n\t\tdelete(rules, \"ERR.000\")\n\t}\n\n\t// SUB.001 VS OWN.004 VS JOI.006\n\tif _, ok := rules[\"SUB.001\"]; ok {\n\t\tdelete(rules, \"ARG.005\")\n\t\tdelete(rules, \"JOI.006\")\n\t}\n\n\t// SUB.004 VS SUB.001\n\tif _, ok := rules[\"SUB.004\"]; ok {\n\t\tdelete(rules, \"SUB.001\")\n\t}\n\n\t// KEY.007 VS KEY.002\n\tif _, ok := rules[\"KEY.007\"]; ok {\n\t\tdelete(rules, \"KEY.002\")\n\t}\n\n\t// JOI.002 VS JOI.006\n\tif _, ok := rules[\"JOI.002\"]; ok {\n\t\tdelete(rules, \"JOI.006\")\n\t}\n\n\t// JOI.008 VS JOI.007\n\tif _, ok := rules[\"JOI.008\"]; ok {\n\t\tdelete(rules, \"JOI.007\")\n\t}\n\treturn rules\n}\n\n// RuleMySQLError ERR.XXX\nfunc RuleMySQLError(item string, err error) Rule {\n\n\ttype MySQLError struct {\n\t\tErrCode   string\n\t\tErrString string\n\t}\n\n\t// tidb parser 语法检查出错返回的是ERR.000\n\tswitch item {\n\tcase \"ERR.000\":\n\t\treturn Rule{\n\t\t\tItem:     item,\n\t\t\tSummary:  \"No available MySQL environment, build-in sql parse failed: \" + err.Error(),\n\t\t\tSeverity: \"L8\",\n\t\t\tContent:  err.Error(),\n\t\t}\n\t}\n\n\terrStr := err.Error()\n\t// Error 1071: Specified key was too long; max key length is 3072 bytes\n\terrReg := regexp.MustCompile(`(?i)Error ([0-9]+): (.*)`)\n\tif strings.HasPrefix(errStr, \"Received\") {\n\t\t// Received #1146 error from MySQL server: \"table xxx doesn't exist\"\n\t\terrReg = regexp.MustCompile(`(?i)Received #([0-9]+) error from MySQL server: ['\"](.*)['\"]`)\n\t}\n\n\tmsg := errReg.FindStringSubmatch(errStr)\n\tvar mysqlError MySQLError\n\n\tif len(msg) == 3 {\n\t\tif msg[1] != \"\" && msg[2] != \"\" {\n\t\t\tmysqlError = MySQLError{\n\t\t\t\tErrCode:   msg[1],\n\t\t\t\tErrString: msg[2],\n\t\t\t}\n\t\t}\n\t} else {\n\t\tvar errcode string\n\t\tif strings.HasPrefix(err.Error(), \"syntax error at position\") {\n\t\t\terrcode = \"1064\"\n\t\t}\n\t\tmysqlError = MySQLError{\n\t\t\tErrCode:   errcode,\n\t\t\tErrString: err.Error(),\n\t\t}\n\t}\n\tswitch mysqlError.ErrCode {\n\t// 1146 ER_NO_SUCH_TABLE\n\tcase \"\", \"1146\":\n\t\treturn Rule{\n\t\t\tItem:     item,\n\t\t\tSummary:  \"MySQL execute failed: \",\n\t\t\tSeverity: \"L0\",\n\t\t\tContent:  \"\",\n\t\t}\n\tdefault:\n\t\treturn Rule{\n\t\t\tItem:     item,\n\t\t\tSummary:  \"MySQL execute failed\",\n\t\t\tSeverity: \"L8\",\n\t\t\tContent:  mysqlError.ErrString,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "advisor/heuristic_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage advisor\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/XiaoMi/soar/env\"\n\t\"github.com/kr/pretty\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// ALI.001\nfunc TestRuleImplicitAlias(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"select col c from tbl where id < 1000\",\n\t\t\t\"select col from tbl tb where id < 1000\",\n\t\t},\n\t\t{\n\t\t\t\"select 1\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleImplicitAlias()\n\t\tif rule.Item != \"ALI.001\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ALI.001\")\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleImplicitAlias()\n\t\tif rule.Item != \"OK\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ALI.002\nfunc TestRuleStarAlias(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"select tbl.* AS c1,c2,c3 from tbl where id < 1000\",\n\t\t\t\"SELECT * as\",\n\t\t},\n\t\t{\n\t\t\t`SELECT c1, c2, c3, FROM tb WHERE id < 1000 AND content=\"mytest* as test\"`,\n\t\t\t`select *`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleStarAlias()\n\t\tif rule.Item != \"ALI.002\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ALI.002\")\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleStarAlias()\n\t\tif rule.Item != \"OK\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ALI.003\nfunc TestRuleSameAlias(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select col as col from tbl where id < 1000\",\n\t\t\"select col from tbl as tbl where id < 1000\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSameAlias()\n\t\t\tif rule.Item != \"ALI.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ALI.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.001\nfunc TestRulePrefixLike(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select col from tbl where id like '%abc'\",\n\t\t\"select col from tbl where id like '_abc'\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RulePrefixLike()\n\t\t\tif rule.Item != \"ARG.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.002\nfunc TestRuleEqualLike(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"select col from tbl where id like 'abc'\",\n\t\t\t\"select col from tbl where id like 1\",\n\t\t},\n\t\t{\n\t\t\t\"select col from tbl where id like 'abc%'\",\n\t\t\t\"select col from tbl where id like '%abc'\",\n\t\t\t\"select col from tbl where id like 'a%c'\", // issue #273\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleEqualLike()\n\t\t\tif rule.Item != \"ARG.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleEqualLike()\n\t\t\tif rule.Item == \"ARG.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.003\n// TODO:\n\nfunc TestTimeFormatError(t *testing.T) {\n\trightTimes := []string{\n\t\t`2020-01-01`,\n\t\t`2020-01-01 23:59:59`,\n\t\t`2020-01-01 23:59:59.0`,   // 0ms\n\t\t`2020-01-01 23:59:59.123`, // 123ms\n\t}\n\tfor _, rt := range rightTimes {\n\t\tif !timeFormatCheck(rt) {\n\t\t\tt.Error(\"wrong time format\")\n\t\t}\n\t}\n\n\twrongTimes := []string{\n\t\t``,                    // 空时间\n\t\t`2020-01-01 abc`,      // 含英文字符\n\t\t`2020–02-15 23:59:59`, // 2020 后面的不是减号，是个 连接符\n\t}\n\tfor _, wt := range wrongTimes {\n\t\tif timeFormatCheck(wt) {\n\t\t\tt.Error(\"wrong time format\")\n\t\t}\n\t}\n}\n\n// CLA.001\nfunc TestRuleNoWhere(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\"select col from tbl\",\n\t\t\t\"delete from tbl\",\n\t\t\t\"update tbl set col=1\",\n\t\t\t\"insert into city (country_id) select country_id from country\",\n\t\t},\n\t\t{\n\t\t\t`select 1;`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNoWhere()\n\t\t\tif rule.Item != \"CLA.001\" && rule.Item != \"CLA.014\" && rule.Item != \"CLA.015\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.001/CLA.014/CLA.015\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNoWhere()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.002\nfunc TestRuleOrderByRand(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select col from tbl where id = 1 order by rand()\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleOrderByRand()\n\t\t\tif rule.Item != \"CLA.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.003\nfunc TestRuleOffsetLimit(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select c1,c2 from tbl where name=xx order by number limit 1 offset 2000\",\n\t\t\"select c1,c2 from tbl where name=xx order by number limit 2000,1\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleOffsetLimit()\n\t\t\tif rule.Item != \"CLA.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.004\nfunc TestRuleGroupByConst(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select col1,col2 from tbl where col1='abc' group by 1\",\n\t\t\"select col1,col2 from tbl group by 1\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleGroupByConst()\n\t\t\tif rule.Item != \"CLA.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.005\nfunc TestRuleOrderByConst(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t// \"select id from test where id=1 order by id\",\n\t\t\"select id from test where id=1 order by 1\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleOrderByConst()\n\t\t\tif rule.Item != \"CLA.005\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.005\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.006\nfunc TestRuleDiffGroupByOrderBy(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col\",\n\t\t\"select tb1.col, tb2.col from tb1, tb2 where id=1 order by tb1.col, tb2.col\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDiffGroupByOrderBy()\n\t\t\tif rule.Item != \"CLA.006\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.006\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.008\nfunc TestRuleExplicitOrderBy(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select c1,c2,c3 from t1 where c1='foo' group by c2\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleExplicitOrderBy()\n\t\t\tif rule.Item != \"CLA.008\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.008\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.009\nfunc TestRuleOrderByExpr(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"SELECT col FROM tbl order by cola - cl;\",                // order by 列运算\n\t\t\t\"SELECT cola - cl col FROM tbl order by col;\",            // 别名为列运算\n\t\t\t\"SELECT cola FROM tbl order by from_unixtime(col);\",      // order by 函数运算\n\t\t\t\"SELECT from_unixtime(col) cola FROM tbl order by cola;\", // 别名为函数运算\n\t\t},\n\t\t{\n\t\t\t`SELECT tbl.col FROM tbl ORDER BY col`,\n\t\t\t\"SELECT sum(col) AS col FROM tbl ORDER BY dt\",\n\t\t\t\"SELECT tbl.col FROM tb, tbl WHERE tbl.tag_id = tb.id ORDER BY tbl.col\",\n\t\t\t\"SELECT col FROM tbl order by `timestamp`;\",           // 列名为关键字\n\t\t\t\"select col from tb where cl = 1 order by APPLY_TIME\", // issue #104 case sensitive\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleOrderByExpr()\n\t\t\tif rule.Item != \"CLA.009\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.009\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleOrderByExpr()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.010\nfunc TestRuleGroupByExpr(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"SELECT col FROM tbl GROUP by cola - col;\",\n\t\t\"SELECT cola - col col FROM tbl GROUP by col;\",\n\t\t\"SELECT cola FROM tbl GROUP by from_unixtime(col);\",\n\t\t\"SELECT from_unixtime(col) cola FROM tbl GROUP by cola;\",\n\n\t\t// 反面例子\n\t\t// `SELECT tbl.col FROM tbl GROUP BY col`,\n\t\t// \"SELECT dt, sum(col) AS col FROM tbl GROUP BY dt\",\n\t\t// \"SELECT tbl.col FROM tb, tbl WHERE tbl.tag_id = tb.id GROUP BY tbl.col\",\n\t\t// \"SELECT col FROM tbl GROUP by `timestamp`;\", // 列名为关键字\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleGroupByExpr()\n\t\t\tif rule.Item != \"CLA.010\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.010\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.011\nfunc TestRuleTblCommentCheck(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"CREATE TABLE `test1`( `ID` bigint(20) NOT NULL AUTO_INCREMENT,\" +\n\t\t\t\" `c1` varchar(128) DEFAULT NULL, `c2` varchar(300) DEFAULT NULL,\" +\n\t\t\t\" `c3` varchar(32) DEFAULT NULL, `c4` int(11) NOT NULL, `c5` double NOT NULL,\" +\n\t\t\t\" `c6` text NOT NULL, PRIMARY KEY (`ID`), KEY `idx_c3_c2_c4_c5_c6` \" +\n\t\t\t\"(`c3`,`c2`(255),`c4`,`c5`,`c6`(255)), KEY `idx_c3_c2_c4` (`c3`,`c2`,`c4`)) \" +\n\t\t\t\"ENGINE = InnoDB DEFAULT CHARSET=utf8\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTblCommentCheck()\n\t\t\tif rule.Item != \"CLA.011\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.011\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.001\nfunc TestRuleSelectStar(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select * from tbl where id=1\",\n\t\t\"select col, * from tbl where id=1\",\n\t\t// 反面例子\n\t\t// \"select count(*) from film where id=1\",\n\t\t// `select count(* ) from film where id=1`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSelectStar()\n\t\t\tif rule.Item != \"COL.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.002\nfunc TestRuleInsertColDef(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"insert into tbl values(1,'name')\",\n\t\t\t\"replace into tbl values(1,'name')\",\n\t\t},\n\t\t{\n\t\t\t\"insert into tb (col) values ('hello world')\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInsertColDef()\n\t\t\tif rule.Item != \"COL.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInsertColDef()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.004\nfunc TestRuleAddDefaultValue(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"create table test(id int)\",\n\t\t\t`ALTER TABLE test change id id varchar(10);`,\n\t\t\t`ALTER TABLE test modify id varchar(10);`,\n\t\t},\n\t\t{\n\t\t\t`ALTER TABLE test modify id varchar(10) DEFAULT '';`,\n\t\t\t`ALTER TABLE test CHANGE id id varchar(10) DEFAULT '';`,\n\t\t\t\"create table test(id int not null default 0 comment '用户id')\",\n\t\t\t`create table tb (a text)`,\n\t\t\t`alter table tb add a text`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAddDefaultValue()\n\t\t\tif rule.Item != \"COL.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAddDefaultValue()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.005\nfunc TestRuleColCommentCheck(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"create table test(id int not null default 0)\",\n\t\t\t`alter table test add column a int`,\n\t\t\t`ALTER TABLE t1 CHANGE b b INT NOT NULL;`,\n\t\t},\n\t\t{\n\t\t\t\"create table test(id int not null default 0 comment '用户id')\",\n\t\t\t`alter table test add column a int comment 'test'`,\n\t\t\t`ALTER TABLE t1 AUTO_INCREMENT = 13;`,\n\t\t\t`ALTER TABLE t1 CHANGE b b INT NOT NULL COMMENT 'test';`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleColCommentCheck()\n\t\t\tif rule.Item != \"COL.005\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.005\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleColCommentCheck()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// LIT.001\nfunc TestRuleIPString(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"insert into tbl (IP,name) values('10.20.306.122','test')\",\n\t\t},\n\t\t{\n\t\t\t`CREATE USER IF NOT EXISTS 'test'@'1.1.1.1';`,\n\t\t\t\"ALTER USER 'test'@'1.1.1.1' IDENTIFIED WITH 'mysql_native_password' AS '*xxxxx' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK;\",\n\t\t\t\"GRANT SELECT ON `test`.* TO 'test'@'1.1.1.1';\",\n\t\t\t`GRANT USAGE ON *.* TO 'test'@'1.1.1.1';`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIPString()\n\t\t\tif rule.Item != \"LIT.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : LIT.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIPString()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// LIT.002\nfunc TestRuleDateNotQuote(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"select col1,col2 from tbl where time < 2018-01-10\",\n\t\t\t\"select col1,col2 from tbl where time < 18-01-10\",\n\t\t\t\"INSERT INTO tb1 SELECT * FROM tb2 WHERE time < 2020-01-10\",\n\t\t\t`select * from tb where col < ' 2022-01-10'`,\n\t\t},\n\t\t{\n\t\t\t\"select col1,col2 from tbl where time < '2018-01-10'\",\n\t\t\t\"INSERT INTO `tb` (`col`) VALUES ('timestamp=2019-12-16')\",\n\t\t\t\"insert into tb (col) values (' 2020-09-15 ')\",\n\t\t\t\"replace into tb (col) values (' 2020-09-15 ')\",\n\t\t\t\"INSERT INTO tb1 SELECT * FROM tb2 WHERE time < '2020-01-10'\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDateNotQuote()\n\t\t\tif rule.Item != \"LIT.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : LIT.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDateNotQuote()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KWR.001\nfunc TestRuleSQLCalcFoundRows(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select SQL_CALC_FOUND_ROWS col from tbl where id>1000\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSQLCalcFoundRows()\n\t\t\tif rule.Item != \"KWR.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KWR.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// JOI.001\nfunc TestRuleCommaAnsiJoin(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1 and t1.c3=t3.c1 where id>1000;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCommaAnsiJoin()\n\t\t\tif rule.Item != \"JOI.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : JOI.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// JOI.002\nfunc TestRuleDupJoin(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDupJoin()\n\t\t\tif rule.Item != \"JOI.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : JOI.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.001\nfunc TestRuleNoDeterministicGroupby(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t// 正面CASE\n\t\t{\n\t\t\t\"select c1,c2,c3 from t1 where c2='foo' group by c2\",\n\t\t\t\"select col, col2, sum(col1) from tb group by col\",\n\t\t\t\"select col, col1 from tb group by col,sum(col1)\",\n\t\t\t\"select * from tb group by col\",\n\t\t},\n\n\t\t// 反面CASE\n\t\t{\n\t\t\t\"select id from film\",\n\t\t\t\"select col, sum(col1) from tb group by col\",\n\t\t\t\"select * from file\",\n\t\t\t\"SELECT COUNT(*) AS cnt, language_id FROM film GROUP BY language_id;\",\n\t\t\t\"SELECT COUNT(*) AS cnt FROM film GROUP BY language_id;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNoDeterministicGroupby()\n\t\t\tif rule.Item != \"RES.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNoDeterministicGroupby()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.002\nfunc TestRuleNoDeterministicLimit(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select col1,col2 from tbl where name='tony' limit 10\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNoDeterministicLimit()\n\t\t\tif rule.Item != \"RES.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.003\nfunc TestRuleUpdateDeleteWithLimit(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;\",\n\t\t},\n\t\t{\n\t\t\t\"UPDATE film SET length = 120 WHERE title = 'abc';\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUpdateDeleteWithLimit()\n\t\t\tif rule.Item != \"RES.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUpdateDeleteWithLimit()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.004\nfunc TestRuleUpdateDeleteWithOrderby(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title;\",\n\t\t},\n\t\t{\n\t\t\t\"UPDATE film SET length = 120 WHERE title = 'abc';\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUpdateDeleteWithOrderby()\n\t\t\tif rule.Item != \"RES.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUpdateDeleteWithOrderby()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.005\nfunc TestRuleUpdateSetAnd(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"update tbl set col = 1 AND cl = 2 where col=3;\",\n\t\t\t\"update table1 set a = ( select a from table2 where b=1 and c=2) and b=1 where d=2\",\n\t\t},\n\t\t{\n\t\t\t\"update tbl set col = 1 ,cl = 2 where col=3;\",\n\t\t\t// https://github.com/XiaoMi/soar/issues/226\n\t\t\t\"update table1 set a = ( select a from table2 where b=1 and c=2), b=1, c=2 where d=2\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUpdateSetAnd()\n\t\t\tif rule.Item != \"RES.005\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.005\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUpdateSetAnd()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.006\nfunc TestRuleImpossibleWhere(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"select * from tbl where 1 != 1;\",\n\t\t\t\"select * from tbl where 'a' != 'a';\",\n\t\t\t\"select * from tbl where col between 10 AND 5;\",\n\t\t},\n\t\t{\n\t\t\t\"select * from tbl where 1 = 1;\",\n\t\t\t\"select * from tbl where 'a' != 1;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleImpossibleWhere()\n\t\t\tif rule.Item != \"RES.006\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.006, SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleImpossibleWhere()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK, SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.007\nfunc TestRuleMeaninglessWhere(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"select * from tbl where 1 = 1;\",\n\t\t\t\"select * from tbl where 'a' = 'a';\",\n\t\t\t\"select * from tbl where 'a' != 1;\",\n\t\t\t\"select * from tbl where 'a';\",\n\t\t\t\"select * from tbl where 'a' limit 1;\",\n\t\t\t\"select * from tbl where 1;\",\n\t\t\t\"select * from tbl where 1 limit 1;\",\n\t\t\t\"select * from tbl where id = 1 or 2;\",\n\t\t\t\"select * from tbl where true;\",\n\t\t\t\"select * from tbl where 'true';\",\n\t\t},\n\t\t{\n\t\t\t\"select * from tbl where false;\",\n\t\t\t\"select * from tbl where 'false';\",\n\t\t\t\"select * from tbl where 0;\",\n\t\t\t\"select * from tbl where '0';\",\n\t\t\t\"select * from tbl where 2 = 1;\",\n\t\t\t\"select * from tbl where 'b' = 'a';\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMeaninglessWhere()\n\t\t\tif rule.Item != \"RES.007\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.007, SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMeaninglessWhere()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK, SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.008\nfunc TestRuleLoadFile(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;\",\n\t\t\t\"LOAD    DATA INFILE 'data.txt' INTO TABLE db2.my_table;\",\n\t\t\t\"LOAD /*COMMENT*/DATA INFILE 'data.txt' INTO TABLE db2.my_table;\",\n\t\t\t`SELECT a,b,a+b INTO OUTFILE '/tmp/result.txt' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n' FROM test_table;`,\n\t\t},\n\t\t{\n\t\t\t\"SELECT id, data INTO @x, @y FROM test.t1 LIMIT 1;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq := &Query4Audit{Query: sql}\n\t\trule := q.RuleLoadFile()\n\t\tif rule.Item != \"RES.008\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.008, SQL: \", sql)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq := &Query4Audit{Query: sql}\n\t\trule := q.RuleLoadFile()\n\t\tif rule.Item != \"OK\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK, SQL: \", sql)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.009\nfunc TestRuleMultiCompare(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"SELECT * FROM tbl WHERE col = col = 'abc'\",\n\t\t\t\"SELECT * FROM tbl WHERE col = 'def' and col = col = 'abc'\",\n\t\t\t\"SELECT * FROM tbl WHERE col = 'def' or col = col = 'abc'\",\n\t\t\t\"SELECT * FROM tbl WHERE col = col = 'abc' and col = 'def'\",\n\t\t\t\"UPDATE tbl set col = 1 WHERE col = col = 'abc'\",\n\t\t\t\"DELETE FROM tbl WHERE col = col = 'abc'\",\n\t\t},\n\t\t{\n\t\t\t\"SELECT * FROM tbl WHERE col = 'abc'\",\n\t\t\t// https://github.com/XiaoMi/soar/issues/169\n\t\t\t\"SELECT * FROM tbl WHERE col = 'abc' and c = 1\",\n\t\t\t\"update tb set c = 1 where a = 2 and b = 3\",\n\t\t\t\"delete from tb where a = 2 and b = 3\",\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMultiCompare()\n\t\t\tif rule.Item != \"RES.009\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.009, SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMultiCompare()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK, SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.010\nfunc TestRuleCreateOnUpdate(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`CREATE TABLE category (\n  category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,\n  name VARCHAR(25) NOT NULL,\n  last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  PRIMARY KEY  (category_id)\n)`,\n\t\t},\n\t\t{\n\t\t\t`CREATE TABLE category (\n  category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,\n  name VARCHAR(25) NOT NULL,\n  last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  PRIMARY KEY  (category_id)\n)`,\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCreateOnUpdate()\n\t\t\tif rule.Item != \"RES.010\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.010, SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCreateOnUpdate()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK, SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// RES.011\nfunc TestRuleUpdateOnUpdate(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`UPDATE category SET name='ActioN' WHERE category_id=1`,\n\t\t},\n\t\t{\n\t\t\t`select * from film limit 1`,\n\t\t\t\"UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1\",\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tvEnv.BuildVirtualEnv(rEnv, sql)\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tt.Error(syntaxErr)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\tif err != nil {\n\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t}\n\n\t\tif idxAdvisor != nil {\n\t\t\trule := idxAdvisor.RuleUpdateOnUpdate()\n\t\t\tif rule.Item != \"RES.011\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : RES.011, SQL:\", sql)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tvEnv.BuildVirtualEnv(rEnv, sql)\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tt.Error(syntaxErr)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\tif err != nil {\n\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t}\n\n\t\tif idxAdvisor != nil {\n\t\t\trule := idxAdvisor.RuleUpdateOnUpdate()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK, SQL:\", sql)\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// STA.001\nfunc TestRuleStandardINEQ(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select col1,col2 from tbl where type!=0\",\n\t\t// \"select col1,col2 from tbl where type<>0\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleStandardINEQ()\n\t\t\tif rule.Item != \"STA.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : STA.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KWR.002\nfunc TestRuleUseKeyWord(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE tbl (`select` int)\",\n\t\t\t\"CREATE TABLE `select` (a int)\",\n\t\t\t\"ALTER TABLE tbl ADD COLUMN `select` varchar(10)\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE tbl (a int)\",\n\t\t\t\"ALTER TABLE tbl ADD COLUMN col varchar(10)\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUseKeyWord()\n\t\t\tif rule.Item != \"KWR.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KWR.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUseKeyWord()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KWR.003\nfunc TestRulePluralWord(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE tbl (`people` int)\",\n\t\t\t\"CREATE TABLE people (a int)\",\n\t\t\t\"ALTER TABLE tbl ADD COLUMN people varchar(10)\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE tbl (`person` int)\",\n\t\t\t\"ALTER TABLE tbl ADD COLUMN person varchar(10)\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RulePluralWord()\n\t\t\tif rule.Item != \"KWR.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KWR.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RulePluralWord()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KWR.004\nfunc TestRuleMultiBytesWord(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"select col as 列 from tb\",\n\t\t\t\"select col as `列` from tb\",\n\t\t},\n\t\t{\n\t\t\t\"select col as c from tb\",\n\t\t\t\"select '列'\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMultiBytesWord()\n\t\t\tif rule.Item != \"KWR.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KWR.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMultiBytesWord()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KWR.005\nfunc TestRuleInvisibleUnicode(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\t// 不可见的 unicode 可以通过 https://unicode-table.com 复制得到\n\tsqls := [][]string{\n\t\t{\n\t\t\t`select 1`,   // SQL 中包含 non-broken-space\n\t\t\t`select​ 1;`, // SQL 中包含 zero-width space\n\t\t},\n\t\t{\n\t\t\t\"select 1\",    // 正常 SQL\n\t\t\t`select \"1 \"`, // 值中包含 non-broken-space\n\t\t\t`select \"1​\"`, // 值中包含 zero-width space\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\t// 含有特殊 unicode 字符的 SQL 语法肯定是不通过的\n\t\trule := q.RuleInvisibleUnicode()\n\t\tif rule.Item != \"KWR.005\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KWR.005\")\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInvisibleUnicode()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// LCK.001\nfunc TestRuleInsertSelect(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`INSERT INTO tbl SELECT * FROM tbl2;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInsertSelect()\n\t\t\tif rule.Item != \"LCK.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : LCK.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// LCK.002\nfunc TestRuleInsertOnDup(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInsertOnDup()\n\t\t\tif rule.Item != \"LCK.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : LCK.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SUB.001\nfunc TestRuleInSubquery(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select col1,col2,col3 from table1 where col2 in(select col from table2)\",\n\t\t\"SELECT col1,col2,col3 from table1 where col2 =(SELECT col2 FROM `table1` limit 1)\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInSubquery()\n\t\t\tif rule.Item != \"SUB.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SUB.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// LIT.003\nfunc TestRuleMultiValueAttribute(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMultiValueAttribute()\n\t\t\tif rule.Item != \"LIT.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : LIT.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// LIT.003\nfunc TestRuleAddDelimiter(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`use sakila\n\t\tselect * from film`,\n\t\t\t`use sakila`,\n\t\t\t`show databases`,\n\t\t},\n\t\t{\n\t\t\t`use sakila;`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, _ := NewQuery4Audit(sql)\n\n\t\trule := q.RuleAddDelimiter()\n\t\tif rule.Item != \"LIT.004\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : LIT.004\")\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, _ := NewQuery4Audit(sql)\n\n\t\trule := q.RuleAddDelimiter()\n\t\tif rule.Item != \"OK\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.003\nfunc TestRuleRecursiveDependency(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`CREATE TABLE tab2 (\n          p_id BIGINT UNSIGNED NOT NULL,\n          a_id BIGINT UNSIGNED NOT NULL,\n          PRIMARY KEY (p_id, a_id),\n          FOREIGN KEY (p_id) REFERENCES tab1(p_id),\n          FOREIGN KEY (a_id) REFERENCES tab3(a_id)\n         );`,\n\t\t\t`ALTER TABLE tbl2 add FOREIGN KEY (p_id) REFERENCES tab1(p_id);`,\n\t\t},\n\t\t{\n\t\t\t`ALTER TABLE tbl2 ADD KEY p_id (p_id);`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleRecursiveDependency()\n\t\t\tif rule.Item != \"KEY.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleRecursiveDependency()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.009\nfunc TestRuleImpreciseDataType(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`CREATE TABLE tab2 (\n          p_id BIGINT UNSIGNED NOT NULL,\n          a_id BIGINT UNSIGNED NOT NULL,\n          hours float NOT null,\n          PRIMARY KEY (p_id, a_id)\n         );`,\n\t\t\t`alter table tbl add column c float not null;`,\n\t\t\t`insert into tb (col) values (0.00001);`,\n\t\t\t`select * from tb where col = 0.00001;`,\n\t\t},\n\t\t{\n\t\t\t\"REPLACE INTO `storage` (`hostname`,`storagehost`, `filename`, `starttime`, `binlogstarttime`, `uploadname`, `binlogsize`, `filesize`, `md5`, `status`) VALUES (1, 1, 1, 1, 1, 1, ?, ?);\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleImpreciseDataType()\n\t\t\tif rule.Item != \"COL.009\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.009\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleImpreciseDataType()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.010\nfunc TestRuleValuesInDefinition(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`create table tab1(status ENUM('new', 'in progress', 'fixed'))`,\n\t\t`alter table tab1 add column status ENUM('new', 'in progress', 'fixed')`,\n\t}\n\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleValuesInDefinition()\n\t\t\tif rule.Item != \"COL.010\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.010\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.004\nfunc TestRuleIndexAttributeOrder(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`create index idx1 on tab(last_name,first_name);`,\n\t\t`alter table tab add index idx1 (last_name,first_name);`,\n\t\t`CREATE TABLE test (id int,blob_col BLOB, INDEX(blob_col(10),id));`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIndexAttributeOrder()\n\t\t\tif rule.Item != \"KEY.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.011\nfunc TestRuleNullUsage(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select c1,c2,c3 from tab where c4 is null or c4 <> 1;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNullUsage()\n\t\t\tif rule.Item != \"COL.011\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.011\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// FUN.003\nfunc TestRuleStringConcatenation(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tab;`,\n\t\t`update t1 set c1 = v1 where id in (select id || 1 from t2)`,\n\t\t`update t1 set c1 = v1 where id in (select id \n\t\t\t|| 1 from t2)`,\n\t\t`update t1 set c1 = v1 where id in (select id \n\t\t\t\t|| \n\t\t1 from t2)`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleStringConcatenation()\n\t\t\tif rule.Item != \"FUN.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : FUN.003\", \"SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tokSqls := []string{\n\t\t`select \"a || b\" from tab;`,\n\t\t`update t1 set c1 = \"{\\\"key\\\":\\\"text or text\\\"}\" where id = 1`,\n\t\t`update t1 set c1 = \"{\\\"key\\\":\\\"text||text\\\"}\" where id = 1`,\n\t\t`select \" ' or ' \" from tab;`,\n\t\t`select \" or ' \" from tab;`,\n\t\t`select \"a or b\" from tab;`,\n\t\t\"INSERT INTO `order` VALUES (8, \\\"test\\\", \\\"order.salesType==200||order.salesType==201||order.salesType==202\\\")\",\n\t}\n\tfor _, sql := range okSqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleStringConcatenation()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\", \"SQL: \", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n// FUN.004\nfunc TestRuleSysdate(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select sysdate();`,\n\t\t`select Sysdate();`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSysdate()\n\t\t\tif rule.Item != \"FUN.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : FUN.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// FUN.005\nfunc TestRuleCountConst(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`select count(1) from tbl;`,\n\t\t\t`select count(col) from tbl;`,\n\t\t},\n\t\t{\n\t\t\t`select count(*) from tbl`,\n\t\t\t`select count(DISTINCT col) from tbl`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCountConst()\n\t\t\tif rule.Item != \"FUN.005\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : FUN.005\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCountConst()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// FUN.006\nfunc TestRuleSumNPE(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`select sum(1) from tbl;`,\n\t\t\t`select sum(col) from tbl;`,\n\t\t},\n\t\t{\n\t\t\t`SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSumNPE()\n\t\t\tif rule.Item != \"FUN.006\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : FUN.006\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSumNPE()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.007\nfunc TestRulePatternMatchingUsage(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]';`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RulePatternMatchingUsage()\n\t\t\tif rule.Item != \"ARG.007\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.007\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.012\nfunc TestRuleSpaghettiQueryAlert(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select 1`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\tcommon.Config.SpaghettiQueryLength = 1\n\t\t\trule := q.RuleSpaghettiQueryAlert()\n\t\t\tif rule.Item != \"CLA.012\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.012\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// JOI.005\nfunc TestRuleReduceNumberOfJoin(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`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; `,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleReduceNumberOfJoin()\n\t\t\tif rule.Item != \"JOI.005\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : JOI.005\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// DIS.001\nfunc TestRuleDistinctUsage(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`SELECT DISTINCT c.c_id,count(DISTINCT c.c_name),count(DISTINCT c.c_e),count(DISTINCT c.c_n),count(DISTINCT c.c_me),c.c_d FROM (select distinct id, name from B) as e WHERE e.country_id = c.country_id;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDistinctUsage()\n\t\t\tif rule.Item != \"DIS.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : DIS.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// DIS.002\nfunc TestRuleCountDistinctMultiCol(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"SELECT COUNT(DISTINCT col, col2) FROM tbl;\",\n\t\t},\n\t\t{\n\t\t\t\"SELECT COUNT(DISTINCT col) FROM tbl;\",\n\t\t\t`SELECT JSON_OBJECT( \"key\", p.id, \"title\", p.name, \"manufacturer\", p.manufacturer, \"price\", p.price, \"specifications\", JSON_OBJECTAGG(a.name, v.value)) as product FROM product as p JOIN value as v ON p.id = v.prod_id JOIN attribute as a ON a.id = v.attribute_id GROUP BY v.prod_id`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCountDistinctMultiCol()\n\t\t\tif rule.Item != \"DIS.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : DIS.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCountDistinctMultiCol()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// DIS.003\n// RuleDistinctStar\nfunc TestRuleDistinctStar(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"SELECT DISTINCT * FROM film;\",\n\t\t\t\"SELECT DISTINCT film.* FROM film;\",\n\t\t},\n\t\t{\n\t\t\t\"SELECT DISTINCT col FROM film;\",\n\t\t\t\"SELECT DISTINCT film.* FROM film, tbl;\",\n\t\t\t\"SELECT DISTINCT * FROM film, tbl;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDistinctStar()\n\t\t\tif rule.Item != \"DIS.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : DIS.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDistinctStar()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.013\nfunc TestRuleHavingClause(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleHavingClause()\n\t\t\tif rule.Item != \"CLA.013\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.013\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// FUN.007\nfunc TestRuleForbiddenTrigger(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`CREATE TRIGGER t1 AFTER INSERT ON work FOR EACH ROW INSERT INTO time VALUES(NOW());`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleForbiddenTrigger()\n\t\tif rule.Item != \"FUN.007\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : FUN.007\")\n\t\t}\n\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// FUN.008\nfunc TestRuleForbiddenProcedure(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`CREATE PROCEDURE simpleproc (OUT param1 INT)`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleForbiddenProcedure()\n\t\tif rule.Item != \"FUN.008\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : FUN.008\")\n\t\t}\n\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// FUN.009\nfunc TestRuleForbiddenFunction(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`CREATE FUNCTION hello (s CHAR(20));`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleForbiddenFunction()\n\t\tif rule.Item != \"FUN.009\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : FUN.009\")\n\t\t}\n\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TBL.006\nfunc TestRuleForbiddenView(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`create view v_today (today) AS SELECT CURRENT_DATE;`,\n\t\t`CREATE VIEW v (col) AS SELECT 'abc';`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleForbiddenView()\n\t\tif rule.Item != \"TBL.006\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : TBL.006\")\n\t\t}\n\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TBL.007\nfunc TestRuleForbiddenTempTable(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"CREATE TEMPORARY TABLE `work` (`time` time DEFAULT NULL) ENGINE=InnoDB;\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, _ := NewQuery4Audit(sql)\n\t\trule := q.RuleForbiddenTempTable()\n\t\tif rule.Item != \"TBL.007\" {\n\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : TBL.007\")\n\t\t}\n\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// JOI.006\nfunc TestRuleNestedSubQueries(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`SELECT s,p,d FROM tab WHERE p.p_id = (SELECT s.p_id FROM tab WHERE s.c_id = 100996 AND s.q = 1 );`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNestedSubQueries()\n\t\t\tif rule.Item != \"JOI.006\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : JOI.006\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// JOI.007\nfunc TestRuleMultiDeleteUpdate(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`DELETE u FROM users u LEFT JOIN hobby tna ON u.id = tna.uid WHERE tna.hobby = 'piano'; `,\n\t\t`UPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano';`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMultiDeleteUpdate()\n\t\t\tif rule.Item != \"JOI.007\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : JOI.007\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// JOI.008\nfunc TestRuleMultiDBJoin(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`SELECT s,p,d FROM db1.tb1 join db2.tb2 on db1.tb1.a = db2.tb2.a where db1.tb1.a > 10;`,\n\t\t`SELECT s,p,d FROM db1.tb1 join tb2 on db1.tb1.a = tb2.a where db1.tb1.a > 10;`,\n\t\t// `SELECT s,p,d FROM db1.tb1 join db1.tb2 on db1.tb1.a = db1.tb2.a where db1.tb1.a > 10;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMultiDBJoin()\n\t\t\tif rule.Item != \"JOI.008\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : JOI.008\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.008\nfunc TestRuleORUsage(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`SELECT c1,c2,c3 FROM tab WHERE c1 = 14 OR c1 = 14;`,\n\t\t},\n\t\t{\n\t\t\t`SELECT c1,c2,c3 FROM tab WHERE c1 = 14 OR c2 = 17;`,\n\t\t\t`SELECT c1,c2,c3 FROM tab WHERE c1 = 14 OR c1 IS NULL;`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleORUsage()\n\t\t\tif rule.Item != \"ARG.008\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.008\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleORUsage()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.009\nfunc TestRuleSpaceWithQuote(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`SELECT 'a ';`,\n\t\t\t`SELECT ' a';`,\n\t\t\t`SELECT \"a \";`,\n\t\t\t`SELECT \" a\";`,\n\t\t\t`create table tb ( a varchar(10) default ' ');`,\n\t\t},\n\t\t{\n\t\t\t`select ''`,\n\t\t\t`select 'a'`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSpaceWithQuote()\n\t\t\tif rule.Item != \"ARG.009\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.009\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSpaceWithQuote()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.010\nfunc TestRuleHint(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`SELECT * FROM t1 USE INDEX (i1) ORDER BY a;`,\n\t\t\t`SELECT * FROM t1 IGNORE INDEX (i1) ORDER BY (i2);`,\n\t\t\t// TODO: vitess syntax not support now\n\t\t\t// `SELECT * FROM t1 USE INDEX (i1,i2) IGNORE INDEX (i2);`,\n\t\t\t// `SELECT * FROM t1 USE INDEX (i1) IGNORE INDEX (i2) USE INDEX (i2);`,\n\t\t},\n\t\t{\n\t\t\t`select ''`,\n\t\t\t`select 'a'`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleHint()\n\t\t\tif rule.Item != \"ARG.010\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.010\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleHint()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.011\nfunc TestRuleNot(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`select id from t where num not in(1,2,3);`,\n\t\t\t`select id from t where num not like \"a%\"`,\n\t\t},\n\t\t{\n\t\t\t`select id from t where num in(1,2,3);`,\n\t\t\t`select id from t where num like \"a%\"`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNot()\n\t\t\tif rule.Item != \"ARG.011\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.011\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNot()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.012\nfunc TestRuleInsertValues(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`INSERT INTO tb VALUES (1), (2)`,\n\t\t\t`REPLACE INTO tb VALUES (1), (2)`,\n\t\t},\n\t\t{\n\t\t\t`INSERT INTO tb VALUES (1)`,\n\t\t},\n\t}\n\toldMaxValueCount := common.Config.MaxValueCount\n\tcommon.Config.MaxValueCount = 1\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInsertValues()\n\t\t\tif rule.Item != \"ARG.012\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.012\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInsertValues()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Config.MaxValueCount = oldMaxValueCount\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.013\nfunc TestRuleFullWidthQuote(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`CREATE TABLE tb (a varchar(10) default '“”')`,\n\t\t\t`CREATE TABLE tb (a varchar(10) default '‘’')`,\n\t\t\t`ALTER TABLE tb ADD COLUMN a VARCHAR(10) DEFAULT \"“”\"`,\n\t\t},\n\t\t{\n\t\t\t`CREATE TABLE tb (a varchar(10) default '\"\"')`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleFullWidthQuote()\n\t\t\tif rule.Item != \"ARG.013\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.013\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleFullWidthQuote()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SUB.002\nfunc TestRuleUNIONUsage(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUNIONUsage()\n\t\t\tif rule.Item != \"SUB.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SUB.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SUB.003\nfunc TestRuleDistinctJoinUsage(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`SELECT DISTINCT c.c_id, c.c_name FROM c,e WHERE e.c_id = c.c_id;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDistinctJoinUsage()\n\t\t\tif rule.Item != \"SUB.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SUB.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SUB.005\nfunc TestRuleSubQueryLimit(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`SELECT * FROM staff WHERE name IN (SELECT NAME FROM customer ORDER BY name LIMIT 1)`,\n\t\t},\n\t\t{\n\t\t\t`select * from (select id from tbl limit 3) as foo`,\n\t\t\t`select * from tbl where id in (select t.id from (select * from tbl limit 3)as t)`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSubQueryLimit()\n\t\t\tif rule.Item != \"SUB.005\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SUB.005\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSubQueryLimit()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SUB.006\nfunc TestRuleSubQueryFunctions(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`SELECT * FROM staff WHERE name IN (SELECT max(NAME) FROM customer)`,\n\t\t},\n\t\t{\n\t\t\t`select * from (select id from tbl limit 3) as foo`,\n\t\t\t`select * from tbl where id in (select t.id from (select * from tbl limit 3)as t)`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSubQueryFunctions()\n\t\t\tif rule.Item != \"SUB.006\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SUB.006\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSubQueryFunctions()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SUB.007\nfunc TestRuleUNIONLimit(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`(SELECT * FROM tb1 ORDER BY name) UNION ALL (SELECT * FROM tb2 ORDER BY name) LIMIT 20;`,\n\t\t\t`(SELECT * FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT * FROM tb2 ORDER BY name) LIMIT 20;`,\n\t\t\t`(SELECT * FROM tb1 ORDER BY name) UNION ALL (SELECT * FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;`,\n\t\t},\n\t\t{\n\t\t\t`(SELECT * FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT * FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;`,\n\t\t\t`SELECT * FROM tb1 ORDER BY name LIMIT 20`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUNIONLimit()\n\t\t\tif rule.Item != \"SUB.007\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SUB.007\", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUNIONLimit()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SEC.002\nfunc TestRuleReadablePasswords(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`create table test(id int,name varchar(20) not null,password varchar(200)not null);`,\n\t\t`alter table test add column password varchar(200) not null;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleReadablePasswords()\n\t\t\tif rule.Item != \"SEC.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SEC.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SEC.003\nfunc TestRuleDataDrop(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`delete from tb where a = b;`,\n\t\t`truncate table tb;`,\n\t\t`drop table tb;`,\n\t\t`drop database db;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleDataDrop()\n\t\t\tif rule.Item != \"SEC.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SEC.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SEC.004\nfunc TestRuleInjection(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`select benchmark(10, rand())`,\n\t\t\t`select sleep(1)`,\n\t\t\t`select Sleep(1)`,\n\t\t\t`select get_lock('lock_name', 1)`,\n\t\t\t`select release_lock('lock_name')`,\n\t\t},\n\t\t{\n\t\t\t\"select * from `sleep`\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInjection()\n\t\t\tif rule.Item != \"SEC.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SEC.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleInjection()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// FUN.001\nfunc TestCompareWithFunction(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`select id from t where substring(name,1,3)='abc';`,\n\t\t\t`SELECT * FROM tbl WHERE UNIX_TIMESTAMP(loginTime) BETWEEN UNIX_TIMESTAMP('2018-11-16 09:46:00 +0800 CST') AND UNIX_TIMESTAMP('2018-11-22 00:00:00 +0800 CST')`,\n\t\t\t`select id from t where num/2 = 100`,\n\t\t\t`select id from t where num/2 < 100`,\n\t\t\t// 时间 builtin 函数\n\t\t\t`SELECT * FROM tb WHERE DATE '2020-01-01'`,\n\t\t\t`DELETE FROM tb WHERE DATE '2020-01-01'`,\n\t\t\t`UPDATE tb SET col = 1 WHERE DATE '2020-01-01'`,\n\t\t\t`SELECT * FROM tb WHERE TIME '10:01:01'`,\n\t\t\t`SELECT * FROM tb WHERE TIMESTAMP '1587181360'`,\n\t\t\t`select * from mysql.user where user  = \"root\" and date '2020-02-01'`,\n\t\t\t// 右侧使用函数比较\n\t\t\t`select id from t where 'abc'=substring(name,1,3);`,\n\t\t},\n\t\t// 正常 SQL\n\t\t{\n\t\t\t`select id from t where col = (select 1)`,\n\t\t\t`select id from t where col = 1`,\n\t\t},\n\t}\n\tfor i, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCompareWithFunction()\n\t\t\tif rule.Item != \"FUN.001\" {\n\t\t\t\tt.Errorf(\"SQL: %d,  Rule not match: %s Expect : FUN.001\", i, rule.Item)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCompareWithFunction()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// FUN.002\nfunc TestRuleCountStar(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY num;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCountStar()\n\t\t\tif rule.Item != \"FUN.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : FUN.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// SEC.001\nfunc TestRuleTruncateTable(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`TRUNCATE TABLE tbl_name;`,\n\t\t`truncate TABLE tbl_name;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTruncateTable()\n\t\t\tif rule.Item != \"SEC.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : SEC.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.005\nfunc TestRuleIn(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select id from t where num in(1,2,3);`,\n\t\t`SELECT * FROM tbl WHERE col IN (NULL)`,\n\t\t`SELECT * FROM tbl WHERE col NOT IN (NULL)`,\n\t}\n\tcommon.Config.MaxInCount = 0\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIn()\n\t\t\tif rule.Item != \"ARG.005\" && rule.Item != \"ARG.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.005 OR ARG.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ARG.006\nfunc TestRuleIsNullIsNotNull(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select id from t where num is null;`,\n\t\t`select id from t where num is not null;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIsNullIsNotNull()\n\t\t\tif rule.Item != \"ARG.006\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.006\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.008\nfunc TestRuleVarcharVSChar(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`create table t1(id int,name char(20),last_time date);`,\n\t\t`create table t1(id int,name binary(20),last_time date);`,\n\t\t`alter table t1 add column id int, add column name binary(20), add column last_time date;`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleVarcharVSChar()\n\t\t\tif rule.Item != \"COL.008\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.008\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TBL.003\nfunc TestRuleCreateDualTable(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"create table `dual`(id int, primary key (id));\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleCreateDualTable()\n\t\t\tif rule.Item != \"TBL.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : TBL.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ALT.001\nfunc TestRuleAlterCharset(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`alter table tbl default character set 'utf8';`,\n\t\t\t`alter table tbl default character set='utf8';`,\n\t\t\t`ALTER TABLE t1 CHANGE a b BIGINT NOT NULL, default character set utf8`,\n\t\t\t`ALTER TABLE t1 CHANGE a b BIGINT NOT NULL,default character set utf8`,\n\t\t\t`ALTER TABLE t1 CHANGE a b BIGINT NOT NULL, character set utf8`,\n\t\t\t`ALTER TABLE t1 CHANGE a b BIGINT NOT NULL,character set utf8`,\n\t\t\t`alter table t1 default collate = utf8_unicode_ci;`,\n\t\t\t`ALTER TABLE tbl_name CHARACTER SET 'utf8';`,\n\t\t\t// `ALTER TABLE tbl_name CHARACTER SET charset_name;`, // FIXME: unknown CHARACTER SET\n\t\t\t// `alter table t1 convert to character set utf8 collate utf8_unicode_ci;`, // FIXME: syntax not compatible\n\t\t},\n\t\t{\n\t\t\t// 反面的例子\n\t\t\t`ALTER TABLE t MODIFY latin1_text_col TEXT CHARACTER SET utf8`,\n\t\t\t`ALTER TABLE t1 CHANGE c1 c1 TEXT CHARACTER SET utf8;`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAlterCharset()\n\t\t\tif rule.Item != \"ALT.001\" {\n\t\t\t\tt.Error(sql, \" Rule not match:\", rule.Item, \"Expect : ALT.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAlterCharset()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(sql, \" Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ALT.003\nfunc TestRuleAlterDropColumn(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`alter table film drop column title;`,\n\t\t},\n\t\t{\n\t\t\t// 反面的例子\n\t\t\t`ALTER TABLE t1 CHANGE c1 c1 TEXT CHARACTER SET utf8;`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAlterDropColumn()\n\t\t\tif rule.Item != \"ALT.003\" {\n\t\t\t\tt.Error(sql, \" Rule not match:\", rule.Item, \"Expect : ALT.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAlterDropColumn()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(sql, \" Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// ALT.004\nfunc TestRuleAlterDropKey(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`alter table film drop primary key`,\n\t\t\t`alter table film drop foreign key fk_film_language`,\n\t\t},\n\t\t{\n\t\t\t// 反面的例子\n\t\t\t`ALTER TABLE t1 CHANGE c1 c1 TEXT CHARACTER SET utf8;`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAlterDropKey()\n\t\t\tif rule.Item != \"ALT.004\" {\n\t\t\t\tt.Error(sql, \" Rule not match:\", rule.Item, \"Expect : ALT.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAlterDropKey()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(sql, \" Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.012\nfunc TestRuleCantBeNull(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE `tb`(`c` longblob NOT NULL);\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE `tbl` (`c` longblob);\",\n\t\t\t\"alter TABLE `tbl` add column `c` longblob;\",\n\t\t\t\"alter TABLE `tbl` add column `c` text;\",\n\t\t\t\"alter TABLE `tbl` add column `c` blob;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleBLOBNotNull()\n\t\t\tif rule.Item != \"COL.012\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.012\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleBLOBNotNull()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.006\nfunc TestRuleTooManyKeyParts(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` longblob NOT NULL DEFAULT '', PRIMARY KEY (`id`));\",\n\t\t\"alter TABLE `tb` add index idx_idx (`id`);\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\tcommon.Config.MaxIdxColsCount = 0\n\t\t\trule := q.RuleTooManyKeyParts()\n\t\t\tif rule.Item != \"KEY.006\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.006\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.005\nfunc TestRuleTooManyKeys(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"create table tbl ( a char(10), b int, primary key (`a`)) engine=InnoDB;\",\n\t\t\"create table tbl ( a varchar(64) not null, b int, PRIMARY KEY (`a`), key `idx_a_b` (`a`,`b`)) engine=InnoDB\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\tcommon.Config.MaxIdxCount = 0\n\t\t\trule := q.RuleTooManyKeys()\n\t\t\tif rule.Item != \"KEY.005\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.005\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.007\nfunc TestRulePKNotInt(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"create table tbl ( a char(10), b int, primary key (`a`)) engine=InnoDB;\",\n\t\t\t\"create table tbl ( a int, b int, primary key (`a`)) engine=InnoDB;\",\n\t\t\t\"create table tbl ( a bigint, b int, primary key (`a`)) engine=InnoDB;\",\n\t\t\t\"create table tbl ( a int unsigned, b int, primary key (`a`)) engine=InnoDB;\",\n\t\t\t\"create table tbl ( a bigint unsigned, b int, primary key (`a`)) engine=InnoDB;\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE tbl (a int unsigned auto_increment, b int, primary key(`a`)) engine=InnoDB;\",\n\t\t\t\"CREATE TABLE `tb` ( `id` Bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'auto id', Primary  key (`id`) ) ENGINE = InnoDB COMMENT 'comment'\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RulePKNotInt()\n\t\t\tif rule.Item != \"KEY.007\" && rule.Item != \"KEY.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.007 OR KEY.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RulePKNotInt()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.008\nfunc TestRuleOrderByMultiDirection(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`SELECT col FROM tbl order by col desc, col2 asc`,\n\t\t\t`SELECT col FROM tbl order by col desc, col2`,\n\t\t\t`SELECT col FROM tbl order by col, col2 desc`,\n\t\t},\n\t\t{\n\t\t\t`SELECT col FROM tbl order by col, col2`,\n\t\t\t`SELECT col FROM tbl order by col desc, col2 desc`,\n\t\t\t`SELECT col FROM tbl order by col asc, col2 asc`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleOrderByMultiDirection()\n\t\t\tif rule.Item != \"KEY.008\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.008\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleOrderByMultiDirection()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.009\nfunc TestRuleUniqueKeyDup(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`ALTER TABLE customer ADD UNIQUE INDEX part_of_name (name(10));`,\n\t\t\t`CREATE UNIQUE INDEX part_of_name ON customer (name(10));`,\n\t\t},\n\t\t{\n\t\t\t`ALTER TABLE tbl add INDEX idx_col (col);`,\n\t\t\t`CREATE INDEX part_of_name ON customer (name(10));`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUniqueKeyDup()\n\t\t\tif rule.Item != \"KEY.009\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.009\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleUniqueKeyDup()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.010\nfunc TestRuleFulltextIndex(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`ALTER TABLE tb ADD FULLTEXT INDEX ip (ip);`,\n\t\t\t// `CREATE FULLTEXT INDEX ft_ip ON tb (ip);`, // TODO: tidb not support yet\n\t\t\t`CREATE TABLE tb ( id int(10) unsigned NOT NULL AUTO_INCREMENT, ip varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (id), FULLTEXT KEY ip (ip) ) ENGINE=InnoDB;`,\n\t\t},\n\t\t{\n\t\t\t`ALTER TABLE tbl add INDEX idx_col (col);`,\n\t\t\t`CREATE INDEX part_of_name ON customer (name(10));`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleFulltextIndex()\n\t\t\tif rule.Item != \"KEY.010\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.010\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleFulltextIndex()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.013\nfunc TestRuleTimestampDefault(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp) ENGINE=InnoDB DEFAULT CHARSET=utf8;\",\n\t\t\t\"ALTER TABLE t1 MODIFY b timestamp NOT NULL;\",\n\t\t\t`ALTER TABLE t1 ADD c_time timestamp NOT NULL default \"0000-00-00\"`,\n\t\t\t`ALTER TABLE t1 ADD c_time timestamp NOT NULL default '0'`,\n\t\t\t`ALTER TABLE t1 ADD c_time timestamp NOT NULL default 0`,\n\t\t\t`ALTER TABLE t1 ADD c_time datetime NOT NULL default 0`,\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE tbl (`id` bigint not null, `update_time` timestamp default current_timestamp)\",\n\t\t\t\"ALTER TABLE t1 MODIFY b timestamp NOT NULL default current_timestamp;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTimestampDefault()\n\t\t\tif rule.Item != \"COL.013\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.013\", sql)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTimestampDefault()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TBL.004\nfunc TestRuleAutoIncrementInitNotZero(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t// 正面的例子\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=13\",\n\t\t},\n\t\t// 反面的例子\n\t\t{\n\t\t\t\"CREATE TABLE `test1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`))\",\n\t\t\t\"CREATE TABLE `test1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`)) auto_increment = 1\",\n\t\t\t\"CREATE TABLE `test1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`)) auto_increment = 1 DEFAULT CHARSET=latin1\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAutoIncrementInitNotZero()\n\t\t\tif rule.Item != \"TBL.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : TBL.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAutoIncrementInitNotZero()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.014\nfunc TestRuleColumnWithCharset(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t// 正面的例子\n\t\t{\n\t\t\t\"CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)\",\n\t\t\t\"alter table tb2 change col col char(10) CHARACTER SET utf8 DEFAULT NULL;\",\n\t\t\t\"CREATE TABLE tb (a nvarchar(10))\",\n\t\t\t\"CREATE TABLE tb (a nchar(10))\",\n\t\t},\n\t\t// 反面的例子\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` char(120) NOT NULL DEFAULT '', PRIMARY KEY (`id`))\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleColumnWithCharset()\n\t\t\tif rule.Item != \"COL.014\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.014\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleColumnWithCharset()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TBL.005\nfunc TestRuleTableCharsetCheck(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t//\"CREATE DATABASE sbtest /*!40100 DEFAULT CHARACTER SET latin1 */;\", // FIXME:\n\t\t\t\"create table tbl (a int) DEFAULT CHARSET=latin1;\",\n\t\t\t\"ALTER TABLE tbl CONVERT TO CHARACTER SET latin1;\",\n\t\t},\n\t\t{\n\t\t\t\"create table tlb (a int);\",\n\t\t\t\"ALTER TABLE `tbl` add column a int, add column b int ;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTableCharsetCheck()\n\t\t\tif rule.Item != \"TBL.005\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : TBL.005\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTableCharsetCheck()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TBL.008\nfunc TestRuleTableCollateCheck(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE DATABASE sbtest /*!40100 DEFAULT COLLATE latin1_bin */;\",\n\t\t\t\"create table tbl (a int) DEFAULT COLLATE=latin1_bin;\",\n\t\t},\n\t\t{\n\t\t\t\"create table tlb (a int);\",\n\t\t\t\"ALTER TABLE `tbl` add column a int, add column b int ;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTableCollateCheck()\n\t\t\tif rule.Item != \"TBL.008\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : TBL.008\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTableCollateCheck()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.015\nfunc TestRuleBlobDefaultValue(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));\",\n\t\t\t\"CREATE TABLE `tb` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` json NOT NULL DEFAULT '', PRIMARY KEY (`id`));\",\n\t\t\t\"alter table `tb` add column `c` blob NOT NULL DEFAULT '';\",\n\t\t\t\"alter table `tb` add column `c` json NOT NULL DEFAULT '';\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL, PRIMARY KEY (`id`));\",\n\t\t\t\"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` json NOT NULL, PRIMARY KEY (`id`));\",\n\t\t\t\"CREATE TABLE `tb` (`col` text NOT NULL);\",\n\t\t\t\"alter table `tb` add column `c` blob NOT NULL;\",\n\t\t\t\"alter table `tb` add column `c` json NOT NULL;\",\n\t\t\t\"ALTER TABLE tb ADD COLUMN a BLOB DEFAULT NULL\",\n\t\t\t\"ALTER TABLE tb ADD COLUMN a JSON DEFAULT NULL\",\n\t\t\t\"CREATE TABLE tb ( a BLOB DEFAULT NULL)\",\n\t\t\t\"CREATE TABLE tb ( a JSON DEFAULT NULL)\",\n\t\t\t\"alter TABLE `tbl` add column `c` longblob;\",\n\t\t\t\"alter TABLE `tbl` add column `c` text;\",\n\t\t\t\"alter TABLE `tbl` add column `c` blob;\",\n\t\t\t\"alter TABLE `tbl` add column `c` json;\",\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleBlobDefaultValue()\n\t\t\tif rule.Item != \"COL.015\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.015\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleBlobDefaultValue()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.016\nfunc TestRuleIntPrecision(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` int(1) );\",\n\t\t\t\"CREATE TABLE `tb` ( `id` bigint(1) );\",\n\t\t\t\"alter TABLE `tb` add column `id` bigint(1);\",\n\t\t\t\"alter TABLE `tb` add column `id` int(1);\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` int(10));\",\n\t\t\t\"CREATE TABLE `tb` ( `id` bigint(20));\",\n\t\t\t\"alter TABLE `tb` add column `id` bigint(20);\",\n\t\t\t\"alter TABLE `tb` add column `id` int(10);\",\n\t\t\t\"CREATE TABLE `tb` ( `id` int);\",\n\t\t\t\"alter TABLE `tb` add column `id` bigint;\",\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIntPrecision()\n\t\t\tif rule.Item != \"COL.016\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.016\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIntPrecision()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.017\nfunc TestRuleVarcharLength(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` varchar(4000) );\",\n\t\t\t\"CREATE TABLE `tb` ( `id` varchar(3500) );\",\n\t\t\t\"alter TABLE `tb` add column `id` varchar(3500);\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` varchar(1024));\",\n\t\t\t\"CREATE TABLE `tb` ( `id` varchar(20));\",\n\t\t\t\"alter TABLE `tb` add column `id` varchar(35);\",\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleVarcharLength()\n\t\t\tif rule.Item != \"COL.017\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.017\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleVarcharLength()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.018\nfunc TestRuleColumnNotAllowType(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE tab (a BOOLEAN);\",\n\t\t\t\"CREATE TABLE tab (a BOOLEAN );\",\n\t\t\t\"ALTER TABLE `tb` add column `a` BOOLEAN;\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE `tb` ( `id` varchar(1024));\",\n\t\t\t\"ALTER TABLE `tb` add column `id` varchar(35);\",\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleColumnNotAllowType()\n\t\t\tif rule.Item != \"COL.018\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.018\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleColumnNotAllowType()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.019\nfunc TestRuleTimePrecision(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t// 正面的例子\n\t\t{\n\t\t\t\"CREATE TABLE t1 (t TIME(3), dt DATETIME(6));\",\n\t\t\t\"ALTER TABLE t1 add t TIME(3);\",\n\t\t},\n\t\t// 反面的例子\n\t\t{\n\t\t\t\"CREATE TABLE t1 (t TIME, dt DATETIME);\",\n\t\t\t\"ALTER TABLE t1 add t TIME;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTimePrecision()\n\t\t\tif rule.Item != \"COL.019\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.019\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTimePrecision()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// KEY.002\nfunc TestRuleNoOSCKey(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t// 正面的例子\n\t\t{\n\t\t\t\"CREATE TABLE tbl (a int, b int)\",\n\t\t},\n\t\t// 反面的例子\n\t\t{\n\t\t\t\"CREATE TABLE tbl (a int, primary key(`a`))\",\n\t\t\t\"CREATE TABLE tbl (a int, unique key(`a`))\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNoOSCKey()\n\t\t\tif rule.Item != \"KEY.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : KEY.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleNoOSCKey()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.006\nfunc TestRuleTooManyFields(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"create table tbl (a int);\",\n\t}\n\n\tcommon.Config.MaxColCount = 0\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleTooManyFields()\n\t\t\tif rule.Item != \"COL.006\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.006\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.007\nfunc TestRuleMaxTextColsCount(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"create table tbl (a int, b text, c blob, d text);\",\n\t}\n\n\tcommon.Config.MaxColCount = 0\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleMaxTextColsCount()\n\t\t\tif rule.Item != \"COL.007\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.007\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.007\nfunc TestRuleMaxTextColsCountWithEnv(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgMaxTextColsCount := common.Config.MaxTextColsCount\n\tcommon.Config.MaxTextColsCount = 1\n\n\tvEnv, rEnv := env.BuildEnv()\n\tdefer vEnv.CleanUp()\n\tinitSQLs := []string{\n\t\t`CREATE TABLE t1 (id int, title text);`,\n\t\t`CREATE TABLE t2 (id int, title text);`,\n\t}\n\n\tfor _, sql := range initSQLs {\n\t\tvEnv.BuildVirtualEnv(rEnv, sql)\n\t}\n\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"alter table t1 add column other text;\",\n\t\t},\n\t\t{\n\t\t\t\"alter table t2 add column col varchar(10);\",\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tvEnv.BuildVirtualEnv(rEnv, sql)\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tt.Error(syntaxErr)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\tif err != nil {\n\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t}\n\n\t\tif idxAdvisor != nil {\n\t\t\trule := idxAdvisor.RuleMaxTextColsCount()\n\t\t\tif rule.Item != \"COL.007\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule, \"Expect : COL.007, SQL:\", sql)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tvEnv.BuildVirtualEnv(rEnv, sql)\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tt.Error(syntaxErr)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\tif err != nil {\n\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t}\n\n\t\tif idxAdvisor != nil {\n\t\t\trule := idxAdvisor.RuleMaxTextColsCount()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule, \"Expect : OK, SQL:\", sql)\n\t\t\t}\n\t\t}\n\t}\n\n\tcommon.Config.MaxTextColsCount = orgMaxTextColsCount\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TBL.002\nfunc TestRuleAllowEngine(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE tbl (a int) engine=MyISAM;\",\n\t\t\t\"ALTER TABLE tbl engine=MyISAM;\",\n\t\t\t\"CREATE TABLE tbl (a int);\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE tbl (a int) engine = InnoDB;\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAllowEngine()\n\t\t\tif rule.Item != \"TBL.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : TBL.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAllowEngine()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TBL.001\nfunc TestRulePartitionNotAllowed(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`CREATE TABLE trb3 (id INT, name VARCHAR(50), purchased DATE) PARTITION BY RANGE( YEAR(purchased) )\n\t(\n        PARTITION p0 VALUES LESS THAN (1990),\n        PARTITION p1 VALUES LESS THAN (1995),\n        PARTITION p2 VALUES LESS THAN (2000),\n        PARTITION p3 VALUES LESS THAN (2005)\n    );`,\n\t\t`ALTER TABLE t1 ADD PARTITION (PARTITION p3 VALUES LESS THAN (2002));`,\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RulePartitionNotAllowed()\n\t\t\tif rule.Item != \"TBL.001\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : TBL.001\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// COL.003\nfunc TestRuleAutoIncUnsigned(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"CREATE TABLE `tb` ( `id` int(10) NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));\",\n\t\t\"ALTER TABLE `tbl` ADD COLUMN `id` int(10) NOT NULL AUTO_INCREMENT;\",\n\t}\n\tfor _, sql := range sqls {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleAutoIncUnsigned()\n\t\t\tif rule.Item != \"COL.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : COL.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// STA.003\nfunc TestRuleIdxPrefix(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE tbl (a int, unique key `xx_a` (`a`));\",\n\t\t\t\"CREATE TABLE tbl (a int, key `xx_a` (`a`));\",\n\t\t\t`ALTER TABLE tbl ADD INDEX xx_a (a)`,\n\t\t\t`ALTER TABLE tbl ADD UNIQUE INDEX xx_a (a)`,\n\t\t},\n\t\t{\n\t\t\t`ALTER TABLE tbl ADD INDEX idx_a (a)`,\n\t\t\t`ALTER TABLE tbl ADD UNIQUE INDEX uk_a (a)`,\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIdxPrefix()\n\t\t\tif rule.Item != \"STA.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : STA.003\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleIdxPrefix()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// STA.004\nfunc TestRuleStandardName(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"CREATE TABLE `tbl-name` (a int);\",\n\t\t\t\"CREATE TABLE `tbl `(a int)\",\n\t\t\t\"CREATE TABLE t__bl (a int);\",\n\t\t\t\"SELECT `dataType` FROM tb;\",\n\t\t},\n\t\t{\n\t\t\t\"CREATE TABLE tbl (a int)\",\n\t\t\t\"CREATE TABLE `tbl`(a int)\",\n\t\t\t\"CREATE TABLE `tbl` (a int) ENGINE=InnoDB DEFAULT CHARSET=utf8\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleStandardName()\n\t\t\tif rule.Item != \"STA.004\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : STA.004\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleStandardName()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// STA.002\nfunc TestRuleSpaceAfterDot(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t\"SELECT * FROM sakila. film\",\n\t\t\t\"SELECT film. length FROM film\",\n\t\t},\n\t\t{\n\t\t\t\"SELECT * FROM sakila.film\",\n\t\t\t\"SELECT film.length FROM film\",\n\t\t\t\"SELECT * FROM t1, t2 WHERE t1.title = t2.title\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSpaceAfterDot()\n\t\t\tif rule.Item != \"STA.002\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : STA.002\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tq, err := NewQuery4Audit(sql)\n\t\tif err == nil {\n\t\t\trule := q.RuleSpaceAfterDot()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRuleMySQLError(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := errors.New(`received #1146 error from MySQL server: \"can't xxxx\"`)\n\tif RuleMySQLError(\"ERR.002\", err).Content != \"\" {\n\t\tt.Error(\"Want: '', Bug get: \", err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestMergeConflictHeuristicRules(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttmpRules := make(map[string]Rule)\n\tfor item, val := range HeuristicRules {\n\t\ttmpRules[item] = val\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tsuggest := MergeConflictHeuristicRules(tmpRules)\n\t\tvar sortedSuggest []string\n\t\tfor item := range suggest {\n\t\t\tsortedSuggest = append(sortedSuggest, item)\n\t\t}\n\t\tsort.Strings(sortedSuggest)\n\t\tfor _, item := range sortedSuggest {\n\t\t\tpretty.Println(suggest[item])\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "advisor/index.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage advisor\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/ast\"\n\t\"github.com/XiaoMi/soar/common\"\n\t\"github.com/XiaoMi/soar/database\"\n\t\"github.com/XiaoMi/soar/env\"\n\n\t\"github.com/dchest/uniuri\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\nconst (\n\t// IndexNameMaxLength Ref. https://dev.mysql.com/doc/refman/8.0/en/identifiers.html\n\tIndexNameMaxLength = 64\n)\n\n// IndexAdvisor 索引建议需要使用到的所有信息\ntype IndexAdvisor struct {\n\tvEnv      *env.VirtualEnv     // 线下虚拟测试环境（测试环境）\n\trEnv      database.Connector  // 线上真实环境\n\tAst       sqlparser.Statement // Vitess Parser生成的抽象语法树\n\twhere     []*common.Column    // 所有where条件中用到的列\n\twhereEQ   []*common.Column    // where条件中可以加索引的等值条件列\n\twhereINEQ []*common.Column    // where条件中可以加索引的非等值条件列\n\tgroupBy   []*common.Column    // group by可以加索引列\n\torderBy   []*common.Column    // order by可以加索引列\n\tjoinCond  [][]*common.Column  // 由于join condition跨层级间索引不可共用，需要多一个维度用来维护层级关系\n\tIndexMeta map[string]map[string]*database.TableIndexInfo\n}\n\n// IndexInfo 创建一条索引需要的信息\ntype IndexInfo struct {\n\tName          string           `json:\"name\"`           // 索引名称\n\tDatabase      string           `json:\"database\"`       // 数据库名\n\tTable         string           `json:\"table\"`          // 表名\n\tDDL           string           `json:\"ddl\"`            // ALTER, CREATE 等类型的 DDL 语句\n\tColumnDetails []*common.Column `json:\"column_details\"` // 列详情\n}\n\n// IndexAdvises IndexAdvises列表\ntype IndexAdvises []IndexInfo\n\n// mergeAdvices 合并索引建议\nfunc mergeAdvices(dst []IndexInfo, src ...IndexInfo) IndexAdvises {\n\tif len(src) == 0 {\n\t\treturn dst\n\t}\n\n\tfor _, newIdx := range src {\n\t\thas := false\n\t\tfor _, idx := range dst {\n\t\t\tif newIdx.DDL == idx.DDL {\n\t\t\t\tcommon.Log.Debug(\"merge index %s and %s\", idx.Name, newIdx.Name)\n\t\t\t\thas = true\n\t\t\t}\n\t\t}\n\n\t\tif !has {\n\t\t\tdst = append(dst, newIdx)\n\t\t}\n\t}\n\n\treturn dst\n}\n\n// NewAdvisor 构造一个 IndexAdvisor 的时候就会对其本身结构初始化\n// 获取 condition 中的等值条件、非等值条件，以及group by 、 order by信息\nfunc NewAdvisor(env *env.VirtualEnv, rEnv database.Connector, q Query4Audit) (*IndexAdvisor, error) {\n\tcommon.Log.Debug(\"Enter: NewAdvisor(), Caller: %s\", common.Caller())\n\tif common.Config.TestDSN.Disable {\n\t\treturn nil, fmt.Errorf(\"TestDSN is Disabled: %s\", common.Config.TestDSN.Addr)\n\t}\n\t// DDL 检测\n\tswitch stmt := q.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\t// 获取ast中用到的库表\n\t\tsqlMeta := ast.GetMeta(q.Stmt, nil)\n\t\tfor db := range sqlMeta {\n\t\t\tdbRef := db\n\t\t\tif db == \"\" {\n\t\t\t\tdbRef = rEnv.Database\n\t\t\t}\n\n\t\t\t// DDL 在 Env 初始化的时候已经执行过了\n\t\t\tif _, ok := env.TableMap[dbRef]; !ok {\n\t\t\t\tenv.TableMap[dbRef] = make(map[string]string)\n\t\t\t}\n\n\t\t\tfor _, tb := range sqlMeta[db].Table {\n\t\t\t\tenv.TableMap[dbRef][tb.TableName] = tb.TableName\n\t\t\t}\n\t\t}\n\n\t\treturn &IndexAdvisor{\n\t\t\tvEnv: env,\n\t\t\trEnv: rEnv,\n\t\t\tAst:  q.Stmt,\n\t\t}, nil\n\n\tcase *sqlparser.DBDDL:\n\t\t// 忽略建库语句\n\t\treturn nil, nil\n\n\tcase *sqlparser.Use:\n\t\t// 如果是use，切基础环境\n\t\tenv.Database = env.DBHash(stmt.DBName.String())\n\t\treturn nil, nil\n\t}\n\n\treturn &IndexAdvisor{\n\t\tvEnv: env,\n\t\trEnv: rEnv,\n\t\tAst:  q.Stmt,\n\n\t\t// 所有的FindXXXXCols尽最大可能先排除不需要加索引的列，但由于元数据在此阶段尚未补齐，给出的列有可能也无法添加索引\n\t\t// 后续需要通过CompleteColumnsInfo + calcCardinality补全后再进一步判断\n\t\tjoinCond:  ast.FindJoinCols(q.Stmt),\n\t\twhereEQ:   ast.FindWhereEQ(q.Stmt),\n\t\twhereINEQ: ast.FindWhereINEQ(q.Stmt),\n\t\tgroupBy:   ast.FindGroupByCols(q.Stmt),\n\t\torderBy:   ast.FindOrderByCols(q.Stmt),\n\t\twhere:     ast.FindAllCols(q.Stmt, ast.WhereExpression),\n\t\tIndexMeta: make(map[string]map[string]*database.TableIndexInfo),\n\t}, nil\n}\n\n/*\n\n关于如何添加索引：\n在《Relational Database Index Design and the Optimizers》一书中，作者提出著名的的三星索引理论（Three-Star Index）\n\nTo Qualify for the First Star:\nPick the columns from all equal predicates (WHERE COL = . . .).\nMake these the first columns of the index—in any order. For CURSOR41, the three-star index will begin with\ncolumns LNAME, CITY or CITY, LNAME. In both cases the index slice that must be scanned will be as thin as possible.\n\nTo Qualify for the Second Star:\nAdd the ORDER BY columns. Do not change the order of these columns, but ignore columns that were already\npicked in step 1. For example, if CURSOR41 had redundant columns in the ORDER BY, say ORDER BY LNAME,\nFNAME or ORDER BY FNAME, CITY, only FNAME would be added in this step. When FNAME is the third index column,\nthe result table will be in the right order without sorting. The first FETCH call will return the row with\nthe smallest FNAME value.\n\nTo Qualify for the Third Star:\nAdd all the remaining columns from the SELECT statement. The order of the columns added in this step\nhas no impact on the performance of the SELECT, but the cost of updates should be reduced by placing volatile\ncolumns at the end. Now the index contains all the columns required for an index-only access path.\n\n索引添加算法正是以这个理想化索策略添为基础，尽可能的给予\"三星\"索引建议。\n\n但又如《High Performance MySQL》一书中所说，索引并不总是最好的工具。只有当索引帮助存储引擎快速查找到记录带来的好处大于其\n带来的额外工作时，索引才是有效的。\n\n因此，在三星索引理论的基础上引入启发式索引算法，在第二颗星的实现上做了部分改进，对于非等值条件只会添加散粒度最高的一列到索引中，\n并基于总体列的使用情况作出判断，按需对order by、group by添加索引，由此来想`增强索引建议的通用性。\n\n*/\n\n// IndexAdvise 索引优化建议算法入口主函数\n// TODO 索引顺序该如何确定\nfunc (idxAdv *IndexAdvisor) IndexAdvise() IndexAdvises {\n\t// 支持不依赖DB的索引建议分析\n\tif common.Config.TestDSN.Disable {\n\t\t// 未开启Env原数据依赖，信息不全的情况下可能会给予错误的索引建议，请人工进行核查。\n\t\tcommon.Log.Warn(\"TestDSN.Disable = true\")\n\t}\n\n\t// 检查否是否含有子查询\n\tsubQueries := ast.FindSubquery(0, idxAdv.Ast)\n\tvar subQueryAdvises []IndexInfo\n\t// 含有子查询对子查询进行单独评审，子查询评审建议报错忽略\n\tif len(subQueries) > 0 {\n\t\tfor _, subSQL := range subQueries {\n\t\t\tstmt, err := sqlparser.Parse(subSQL)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tq := Query4Audit{\n\t\t\t\tQuery: subSQL,\n\t\t\t\tStmt:  stmt,\n\t\t\t}\n\t\t\tsubIdxAdv, _ := NewAdvisor(idxAdv.vEnv, idxAdv.rEnv, q)\n\t\t\tsubQueryAdvises = append(subQueryAdvises, subIdxAdv.IndexAdvise()...)\n\t\t}\n\t}\n\n\t// 变量初始化，用于存放索引信息，按照db.tb.[cols]组织\n\tindexList := make(map[string]map[string][]*common.Column)\n\n\t// 为用到的每一列填充库名，表名等信息\n\tvar joinCond [][]*common.Column\n\tfor _, joinCols := range idxAdv.joinCond {\n\t\tjoinCond = append(joinCond, CompleteColumnsInfo(idxAdv.Ast, joinCols, idxAdv.vEnv))\n\t}\n\tidxAdv.joinCond = joinCond\n\n\tidxAdv.where = CompleteColumnsInfo(idxAdv.Ast, idxAdv.where, idxAdv.vEnv)\n\tidxAdv.whereEQ = CompleteColumnsInfo(idxAdv.Ast, idxAdv.whereEQ, idxAdv.vEnv)\n\tidxAdv.whereINEQ = CompleteColumnsInfo(idxAdv.Ast, idxAdv.whereINEQ, idxAdv.vEnv)\n\tidxAdv.groupBy = CompleteColumnsInfo(idxAdv.Ast, idxAdv.groupBy, idxAdv.vEnv)\n\tidxAdv.orderBy = CompleteColumnsInfo(idxAdv.Ast, idxAdv.orderBy, idxAdv.vEnv)\n\n\t// 只要在开启使用env元数据的时候才会计算散粒度\n\tif !common.Config.TestDSN.Disable {\n\t\t// 计算joinCond, whereEQ, whereINEQ用到的每一列的散粒度，并排序，方便后续添加复合索引\n\t\t// groupBy, orderBy列按书写顺序给索引建议，不需要按散粒度排序\n\t\tidxAdv.calcCardinality(idxAdv.whereEQ)\n\t\tidxAdv.calcCardinality(idxAdv.whereINEQ)\n\t\tidxAdv.calcCardinality(idxAdv.orderBy)\n\t\tidxAdv.calcCardinality(idxAdv.groupBy)\n\n\t\tfor i, joinCols := range idxAdv.joinCond {\n\t\t\tidxAdv.calcCardinality(joinCols)\n\t\t\tjoinCols = common.ColumnSort(joinCols)\n\t\t\tidxAdv.joinCond[i] = joinCols\n\t\t}\n\n\t\t// 根据散粒度进行排序\n\t\t// 对所有列进行排序，按散粒度由大到小排序\n\t\tidxAdv.whereEQ = common.ColumnSort(idxAdv.whereEQ)\n\t\tidxAdv.whereINEQ = common.ColumnSort(idxAdv.whereINEQ)\n\t\tidxAdv.orderBy = common.ColumnSort(idxAdv.orderBy)\n\t\tidxAdv.groupBy = common.ColumnSort(idxAdv.groupBy)\n\n\t}\n\n\t// 是否指定Where条件，打标签\n\thasWhere := false\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch where := node.(type) {\n\t\tcase *sqlparser.Subquery:\n\t\t\treturn false, nil\n\t\tcase *sqlparser.Where:\n\t\t\tif where != nil {\n\t\t\t\thasWhere = true\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, idxAdv.Ast)\n\tcommon.LogIfError(err, \"\")\n\t// 获取哪些列被忽略\n\tvar ignore []*common.Column\n\tusedCols := append(idxAdv.whereINEQ, idxAdv.whereEQ...)\n\n\tfor _, whereCol := range idxAdv.where {\n\t\tisUsed := false\n\t\tfor _, used := range usedCols {\n\t\t\tif whereCol.Equal(used) {\n\t\t\t\tisUsed = true\n\t\t\t}\n\t\t}\n\n\t\tif !isUsed {\n\t\t\tcommon.Log.Debug(\"column %s in `%s`.`%s` will ignore when adding index\", whereCol.DB, whereCol.Table, whereCol.Name)\n\t\t\tignore = append(ignore, whereCol)\n\t\t}\n\n\t}\n\n\t// 索引优化算法入口，从这里开始放大招\n\tif hasWhere {\n\t\t// 有Where条件的先分析 等值条件\n\t\tfor _, index := range idxAdv.whereEQ {\n\t\t\t// 对应列在前面已经按散粒度由大到小排序好了\n\t\t\tidxAdv.mergeIndex(indexList, index)\n\t\t}\n\t\t// 若存在非等值查询条件，可以给第一个非等值条件添加索引\n\t\tif len(idxAdv.whereINEQ) > 0 {\n\t\t\tidxAdv.mergeIndex(indexList, idxAdv.whereINEQ[0])\n\t\t}\n\t\t// 有WHERE条件，但 WHERE 条件未能给出索引建议就不能再加 GROUP BY 和 ORDER BY 建议了\n\t\tif len(ignore) == 0 {\n\t\t\t// 没有非等值查询条件时可以再为 GroupBy 和 OrderBy 添加索引\n\t\t\tfor _, index := range idxAdv.groupBy {\n\t\t\t\tidxAdv.mergeIndex(indexList, index)\n\t\t\t}\n\n\t\t\t// OrderBy\n\t\t\t// 没有 GroupBy 时可以为 OrderBy 加索引\n\t\t\tif len(idxAdv.groupBy) == 0 {\n\t\t\t\tfor _, index := range idxAdv.orderBy {\n\t\t\t\t\tidxAdv.mergeIndex(indexList, index)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// 未指定 Where 条件的，只需要 GroupBy 和 OrderBy 的索引建议\n\t\tfor _, index := range idxAdv.groupBy {\n\t\t\tidxAdv.mergeIndex(indexList, index)\n\t\t}\n\n\t\t// OrderBy\n\t\t// 没有GroupBy 时可以为 OrderBy 加索引\n\t\t// 没有 where 条件时 OrderBy 的索引仅能够在索引覆盖的情况下被使用\n\n\t\t// if len(idxAdv.groupBy) == 0 {\n\t\t// \tfor _, index := range idxAdv.orderBy {\n\t\t// \t\tmergeIndex(indexList, index)\n\t\t// \t}\n\t\t// }\n\t}\n\n\t// 开始整合索引信息，添加索引\n\tvar indexes []IndexInfo\n\n\t// 为join添加索引\n\t// 获取 join condition 中需要加索引的表有哪些\n\tdefaultDB := \"\"\n\tif !common.Config.TestDSN.Disable {\n\t\tdefaultDB = idxAdv.vEnv.RealDB(idxAdv.vEnv.Database)\n\t}\n\tif !common.Config.OnlineDSN.Disable {\n\t\tdefaultDB = idxAdv.rEnv.Database\n\t}\n\n\t// 根据join table的信息给予优化建议\n\tjoinTableMeta := ast.FindJoinTable(idxAdv.Ast, nil).SetDefault(idxAdv.rEnv.Database).SetDefault(defaultDB)\n\tindexes = mergeAdvices(indexes, idxAdv.buildJoinIndex(joinTableMeta)...)\n\n\tif common.Config.TestDSN.Disable || common.Config.OnlineDSN.Disable {\n\t\t// 无 env 环境下只提供单列索引，无法确定 table 时不给予优化建议\n\t\t// 仅有 table 信息时给出的建议不包含 DB 信息\n\t\tindexes = mergeAdvices(indexes, idxAdv.buildIndexWithNoEnv(indexList)...)\n\t} else {\n\t\t// 给出尽可能详细的索引建议\n\t\tindexes = mergeAdvices(indexes, idxAdv.buildIndex(indexList)...)\n\t}\n\n\tindexes = mergeAdvices(indexes, subQueryAdvises...)\n\n\t// 在开启 env 的情况下，检查数据库版本，字段类型，索引总长度\n\tindexes = idxAdv.idxColsTypeCheck(indexes)\n\n\t// 在开启 env 的情况下，会对索引进行检查，对全索引进行过滤\n\t// 在前几步都不会对 idx 生成 DDL 语句，DDL语句在这里生成\n\treturn idxAdv.mergeIndexes(indexes)\n}\n\n// idxColsTypeCheck 对超长的字段添加前缀索引，剔除无法添索引字段的列\n// TODO: 暂不支持 fulltext 索引，\nfunc (idxAdv *IndexAdvisor) idxColsTypeCheck(idxList []IndexInfo) []IndexInfo {\n\tif common.Config.TestDSN.Disable {\n\t\treturn rmSelfDupIndex(idxList)\n\t}\n\n\tvar indexes []IndexInfo\n\n\tfor _, idx := range idxList {\n\t\tvar newCols []*common.Column\n\t\tvar newColInfo []string\n\t\t// 索引总长度\n\t\tidxBytesTotal := 0\n\t\tisOverFlow := false\n\t\tfor _, col := range idx.ColumnDetails {\n\t\t\t// 获取字段 bytes\n\t\t\tbytes := col.GetDataBytes(common.Config.OnlineDSN.Version)\n\t\t\ttmpCol := col.Name\n\t\t\toverFlow := 0\n\t\t\t// 加上该列后是否索引长度过长\n\t\t\tif bytes < 0 {\n\t\t\t\t// bytes < 0 说明字段的长度是无法计算的\n\t\t\t\tcommon.Log.Warning(\"%s.%s data type not support %s, can't add index\",\n\t\t\t\t\tcol.Table, col.Name, col.DataType)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// idx bytes over flow\n\t\t\tif total := idxBytesTotal + bytes; total > common.Config.MaxIdxBytes {\n\n\t\t\t\tcommon.Log.Debug(\"bytes: %d, idxBytesTotal: %d, total: %d, common.Config.MaxIdxBytes: %d\",\n\t\t\t\t\tbytes, idxBytesTotal, total, common.Config.MaxIdxBytes)\n\n\t\t\t\toverFlow = total - common.Config.MaxIdxBytes\n\t\t\t\tisOverFlow = true\n\n\t\t\t} else {\n\t\t\t\tidxBytesTotal = total\n\t\t\t}\n\n\t\t\t// common.Config.MaxIdxColBytes 默认大小 767\n\t\t\tif bytes > common.Config.MaxIdxBytesPerColumn || isOverFlow {\n\t\t\t\t// In 5.6, you may not include a column that equates to\n\t\t\t\t// bigger than 767 bytes: VARCHAR(255) CHARACTER SET utf8 or VARCHAR(191) CHARACTER SET utf8mb4.\n\t\t\t\t// In 5.7  you may not include a column that equates to\n\t\t\t\t// bigger than 3072 bytes.\n\n\t\t\t\t// v : 在 col.Character 字符集下每个字符占用 v bytes\n\t\t\t\tv, ok := common.CharSets[strings.ToLower(col.Character)]\n\t\t\t\tif !ok {\n\t\t\t\t\t// 找不到对应字符集，不添加索引\n\t\t\t\t\t// 如果出现不认识的字符集，认为每个字符占用4个字节\n\t\t\t\t\tcommon.Log.Warning(\"%s.%s(%s) charset not support yet %s, use default 4 bytes length\",\n\t\t\t\t\t\tcol.Table, col.Name, col.DataType, col.Character)\n\t\t\t\t\tv = 4\n\t\t\t\t}\n\n\t\t\t\t// 保留两个字节的安全余量\n\t\t\t\tlength := (common.Config.MaxIdxBytesPerColumn - 2) / v\n\t\t\t\tif isOverFlow {\n\t\t\t\t\t// 在索引中添加该列会导致索引长度过长，建议根据需求转换为合理的前缀索引\n\t\t\t\t\t// _OPR_SPLIT_ 是自定的用于后续处理的特殊分隔符\n\t\t\t\t\tcommon.Log.Warning(\"adding index '%s(%s)' to table '%s' causes the index to be too long, overflow is %d\",\n\t\t\t\t\t\tcol.Name, col.DataType, col.Table, overFlow)\n\t\t\t\t\ttmpCol += fmt.Sprintf(\"_OPR_SPLIT_(N)\")\n\t\t\t\t} else {\n\t\t\t\t\t// 索引没有过长，可以加一个最长的前缀索引\n\t\t\t\t\tcommon.Log.Warning(\"index column too large: %s.%s --> %s.%s(%d), data type: %s\",\n\t\t\t\t\t\tcol.Table, col.Name, col.Table, tmpCol, length, col.DataType)\n\t\t\t\t\ttmpCol += fmt.Sprintf(\"_OPR_SPLIT_(%d)\", length)\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tnewCols = append(newCols, col)\n\t\t\tnewColInfo = append(newColInfo, tmpCol)\n\t\t}\n\n\t\t// 为新索引重建索引语句\n\t\tidxName := \"idx_\"\n\t\tidxCols := \"\"\n\t\tfor i, newCol := range newColInfo {\n\t\t\t// 对名称和可能存在的长度进行拼接\n\t\t\t// 用等号进行分割\n\t\t\ttmp := strings.Split(newCol, \"_OPR_SPLIT_\")\n\t\t\tidxName += tmp[0]\n\t\t\tif len(tmp) > 1 {\n\t\t\t\tidxCols += tmp[0] + \"`\" + tmp[1]\n\t\t\t} else {\n\t\t\t\tidxCols += tmp[0] + \"`\"\n\t\t\t}\n\n\t\t\tif i+1 < len(newColInfo) {\n\t\t\t\tidxName += \"_\"\n\t\t\t\tidxCols += \",`\"\n\t\t\t}\n\t\t}\n\n\t\t// 索引名称最大长度64\n\t\tif len(idxName) > IndexNameMaxLength {\n\t\t\tcommon.Log.Warn(\"index '%s' name large than IndexNameMaxLength\", idxName)\n\t\t\tidxName = strings.TrimRight(idxName[:IndexNameMaxLength], \"_\")\n\t\t}\n\n\t\t// 新的alter语句\n\t\tnewDDL := fmt.Sprintf(\"alter table `%s`.`%s` add index `%s` (`%s)\", idxAdv.vEnv.RealDB(idx.Database),\n\t\t\tidx.Table, idxName, idxCols)\n\n\t\t// 将筛选改造后的索引信息信息加入到新的索引列表中\n\t\tidx.ColumnDetails = newCols\n\t\tidx.DDL = newDDL\n\t\tindexes = append(indexes, idx)\n\t}\n\n\treturn indexes\n}\n\n// mergeIndexes 与线上环境对比，将给出的索引建议进行去重\nfunc (idxAdv *IndexAdvisor) mergeIndexes(idxList []IndexInfo) []IndexInfo {\n\t// TODO 暂不支持前缀索引去重\n\tif common.Config.TestDSN.Disable {\n\t\treturn rmSelfDupIndex(idxList)\n\t}\n\n\tvar indexes []IndexInfo\n\tfor _, idx := range idxList {\n\t\t// 将 DB 替换成 vEnv 中的数据库名称\n\t\tdbInVEnv := idx.Database\n\t\tif _, ok := idxAdv.vEnv.DBRef[idx.Database]; ok {\n\t\t\tdbInVEnv = idxAdv.vEnv.DBRef[idx.Database]\n\t\t}\n\n\t\t// 检测索引添加的表是否是视图\n\t\tif idxAdv.vEnv.IsView(idx.Table) {\n\t\t\tcommon.Log.Info(\"%s.%s is a view. no need indexed\", idx.Database, idx.Table)\n\t\t\tcontinue\n\t\t}\n\n\t\t// 检测是否存在重复索引\n\t\tindexMeta := idxAdv.IndexMeta[dbInVEnv][idx.Table]\n\t\tisExisted := false\n\n\t\t// 检测无索引列的情况\n\t\tif len(idx.ColumnDetails) < 1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif existedIndexes := indexMeta.FindIndex(database.IndexColumnName, idx.ColumnDetails[0].Name); len(existedIndexes) > 0 {\n\t\t\tfor _, existedIdx := range existedIndexes {\n\t\t\t\t// flag: 用于标记已存在的索引是否是约束条件\n\t\t\t\tisConstraint := false\n\n\t\t\t\tvar cols []string\n\t\t\t\tvar colsDetail []*common.Column\n\n\t\t\t\t// 把已经存在的 key 摘出来遍历一遍对比是否是包含关系\n\t\t\t\tfor _, col := range indexMeta.FindIndex(database.IndexKeyName, existedIdx.KeyName) {\n\t\t\t\t\tcols = append(cols, col.ColumnName)\n\t\t\t\t\tcolsDetail = append(colsDetail, &common.Column{\n\t\t\t\t\t\tName:  col.ColumnName,\n\t\t\t\t\t\tTable: idx.Table,\n\t\t\t\t\t\tDB:    idx.ColumnDetails[0].DB,\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\t// 判断已存在的索引是否属于约束条件(唯一索引、主键)\n\t\t\t\t// 这里可以忽略是否含有外键的情况，因为索引已经重复了，添加了新索引后原先重复的索引是可以删除的。\n\t\t\t\tif existedIdx.NonUnique == 0 {\n\t\t\t\t\tcommon.Log.Debug(\"%s.%s表%s为约束条件\", dbInVEnv, idx.Table, existedIdx.KeyName)\n\t\t\t\t\tisConstraint = true\n\t\t\t\t}\n\n\t\t\t\t// 如果已存在的索引与索引建议存在重叠，则说明无需添加新索引或可能需要给出删除索引的建议\n\t\t\t\tif common.IsColsPart(colsDetail, idx.ColumnDetails) {\n\t\t\t\t\tidxName := existedIdx.KeyName\n\t\t\t\t\t// 如果已经存在的索引包含需要添加的索引，则无需添加\n\t\t\t\t\tif len(colsDetail) >= len(idx.ColumnDetails) {\n\t\t\t\t\t\tcommon.Log.Info(\" `%s`.`%s` %s already had a index `%s`\",\n\t\t\t\t\t\t\tidx.Database, idx.Table, strings.Join(cols, \",\"), idxName)\n\t\t\t\t\t\tisExisted = true\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\t// 库、表、列名需要用反撇转义\n\t\t\t\t\t// TODO: 关于外键索引去重的优雅解决方案\n\t\t\t\t\tif !isConstraint {\n\t\t\t\t\t\tif common.Config.AllowDropIndex {\n\t\t\t\t\t\t\talterSQL := fmt.Sprintf(\"alter table `%s`.`%s` drop index `%s`\", idx.Database, idx.Table, idxName)\n\t\t\t\t\t\t\tindexes = append(indexes, IndexInfo{\n\t\t\t\t\t\t\t\tName:          idxName,\n\t\t\t\t\t\t\t\tDatabase:      idx.Database,\n\t\t\t\t\t\t\t\tTable:         idx.Table,\n\t\t\t\t\t\t\t\tDDL:           alterSQL,\n\t\t\t\t\t\t\t\tColumnDetails: colsDetail,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcommon.Log.Warning(\"In table `%s`, the new index of column `%s` contains index `%s`,\"+\n\t\t\t\t\t\t\t\t\" maybe you could drop one of them.\", existedIdx.Table,\n\t\t\t\t\t\t\t\tstrings.Join(cols, \",\"), idxName)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !isExisted {\n\t\t\t// 检测索引名称是否重复?\n\t\t\tif existedIndexes := indexMeta.FindIndex(database.IndexKeyName, idx.Name); len(existedIndexes) > 0 {\n\t\t\t\tvar newName string\n\t\t\t\tidxSuffix := getRandomIndexSuffix()\n\t\t\t\tif len(idx.Name) < IndexNameMaxLength-len(idxSuffix) {\n\t\t\t\t\tnewName = idx.Name + idxSuffix\n\t\t\t\t} else {\n\t\t\t\t\tnewName = idx.Name[:IndexNameMaxLength-len(idxSuffix)] + idxSuffix\n\t\t\t\t}\n\n\t\t\t\tcommon.Log.Warning(\"duplicate index name '%s', new name is '%s'\", idx.Name, newName)\n\t\t\t\tidx.DDL = strings.Replace(idx.DDL, idx.Name, newName, -1)\n\t\t\t\tidx.Name = newName\n\t\t\t}\n\n\t\t\t// 添加合并\n\t\t\tindexes = mergeAdvices(indexes, idx)\n\t\t}\n\n\t}\n\n\t// 对索引进行去重\n\treturn rmSelfDupIndex(indexes)\n}\n\n// getRandomIndexSuffix format: _xxxx, length: 5\nfunc getRandomIndexSuffix() string {\n\treturn fmt.Sprintf(\"_%s\", uniuri.New()[:4])\n}\n\n// rmSelfDupIndex 去重传入的[]IndexInfo中重复的索引\nfunc rmSelfDupIndex(indexes []IndexInfo) []IndexInfo {\n\tvar resultIndex []IndexInfo\n\ttmpIndexList := indexes\n\tfor _, a := range indexes {\n\t\ttmp := a\n\t\tfor i, b := range tmpIndexList {\n\t\t\tif common.IsColsPart(tmp.ColumnDetails, b.ColumnDetails) && tmp.Name != b.Name {\n\t\t\t\tif len(b.ColumnDetails) > len(tmp.ColumnDetails) {\n\t\t\t\t\tcommon.Log.Debug(\"remove duplicate index: %s\", tmp.Name)\n\t\t\t\t\ttmp = b\n\t\t\t\t}\n\n\t\t\t\tif i < len(tmpIndexList) {\n\t\t\t\t\ttmpIndexList = append(tmpIndexList[:i], tmpIndexList[i+1:]...)\n\t\t\t\t} else {\n\t\t\t\t\ttmpIndexList = tmpIndexList[:i]\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t\tresultIndex = mergeAdvices(resultIndex, tmp)\n\t}\n\n\treturn resultIndex\n}\n\n// buildJoinIndex 检查Join中使用的库表是否需要添加索引并给予索引建议\nfunc (idxAdv *IndexAdvisor) buildJoinIndex(meta common.Meta) []IndexInfo {\n\tvar indexes []IndexInfo\n\tfor _, IndexCols := range idxAdv.joinCond {\n\t\t// 如果该列的库表为join condition中需要添加索引的库表\n\t\tindexColsList := make(map[string]map[string][]*common.Column)\n\t\tfor _, col := range IndexCols {\n\t\t\tidxAdv.mergeIndex(indexColsList, col)\n\t\t}\n\n\t\tif common.Config.TestDSN.Disable || common.Config.OnlineDSN.Disable {\n\t\t\tindexes = mergeAdvices(indexes, idxAdv.buildIndexWithNoEnv(indexColsList)...)\n\t\t\tcontinue\n\t\t}\n\n\t\tindexes = mergeAdvices(indexes, idxAdv.buildIndex(indexColsList)...)\n\t}\n\treturn indexes\n}\n\n// buildIndex 尽可能的将 map[string]map[string][]*common.Column 转换成 []IndexInfo\n// 此处不判断索引是否重复\nfunc (idxAdv *IndexAdvisor) buildIndex(idxList map[string]map[string][]*common.Column) []IndexInfo {\n\tvar indexes []IndexInfo\n\tfor db, tbs := range idxList {\n\t\tfor tb, cols := range tbs {\n\n\t\t\t// 单个索引中含有的列收 config 中参数限制\n\t\t\tif len(cols) > common.Config.MaxIdxColsCount {\n\t\t\t\tcols = cols[:common.Config.MaxIdxColsCount]\n\t\t\t}\n\n\t\t\tvar colNames []string\n\t\t\tfor _, col := range cols {\n\t\t\t\tif col.DB == \"\" || col.Table == \"\" {\n\t\t\t\t\tcommon.Log.Warn(\"can not get the meta info of column '%s'\", col.Name)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcolNames = append(colNames, col.Name)\n\t\t\t}\n\n\t\t\tif len(colNames) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tidxName := common.Config.IdxPrefix + strings.Join(colNames, \"_\")\n\n\t\t\t// 索引名称最大长度64\n\t\t\tif len(idxName) > IndexNameMaxLength {\n\t\t\t\tcommon.Log.Warn(\"index '%s' name large than IndexNameMaxLength\", idxName)\n\t\t\t\tidxName = strings.TrimRight(idxName[:IndexNameMaxLength], \"_\")\n\t\t\t}\n\n\t\t\talterSQL := fmt.Sprintf(\"alter table `%s`.`%s` add index `%s` (`%s`)\", idxAdv.vEnv.RealDB(db), tb,\n\t\t\t\tidxName, strings.Join(colNames, \"`,`\"))\n\n\t\t\tindexes = append(indexes, IndexInfo{\n\t\t\t\tName:          idxName,\n\t\t\t\tDatabase:      idxAdv.vEnv.RealDB(db),\n\t\t\t\tTable:         tb,\n\t\t\t\tDDL:           alterSQL,\n\t\t\t\tColumnDetails: cols,\n\t\t\t})\n\t\t}\n\t}\n\treturn indexes\n}\n\n// buildIndexWithNoEnv 忽略原数据，给予最基础的索引\nfunc (idxAdv *IndexAdvisor) buildIndexWithNoEnv(indexList map[string]map[string][]*common.Column) []IndexInfo {\n\t// 如果不获取数据库原信息，则不去判断索引是否重复，且只给单列加索引\n\tvar indexes []IndexInfo\n\tfor _, tableIndex := range indexList {\n\t\tfor _, indexCols := range tableIndex {\n\t\t\tfor _, col := range indexCols {\n\t\t\t\tif col.Table == \"\" {\n\t\t\t\t\tcommon.Log.Warn(\"can not get the meta info of column '%s'\", col.Name)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tidxName := common.Config.IdxPrefix + col.Name\n\t\t\t\t// 库、表、列名需要用反撇转义\n\t\t\t\talterSQL := fmt.Sprintf(\"alter table `%s`.`%s` add index `%s` (`%s`)\", idxAdv.vEnv.RealDB(col.DB), col.Table, idxName, col.Name)\n\t\t\t\tif col.DB == \"\" {\n\t\t\t\t\talterSQL = fmt.Sprintf(\"alter table `%s` add index `%s` (`%s`)\", col.Table, idxName, col.Name)\n\t\t\t\t}\n\n\t\t\t\tindexes = append(indexes, IndexInfo{\n\t\t\t\t\tName:          idxName,\n\t\t\t\t\tDatabase:      idxAdv.vEnv.RealDB(col.DB),\n\t\t\t\t\tTable:         col.Table,\n\t\t\t\t\tDDL:           alterSQL,\n\t\t\t\t\tColumnDetails: []*common.Column{col},\n\t\t\t\t})\n\t\t\t}\n\n\t\t}\n\t}\n\treturn indexes\n}\n\n// mergeIndex 将索引用到的列去重后合并到一起\nfunc (idxAdv *IndexAdvisor) mergeIndex(idxList map[string]map[string][]*common.Column, column *common.Column) {\n\t// 散粒度低于阈值将不会添加索引\n\tif common.Config.MinCardinality/100 > column.Cardinality {\n\t\treturn\n\t}\n\n\tdb := column.DB\n\ttb := column.Table\n\tif idxList[db] == nil {\n\t\tidxList[db] = make(map[string][]*common.Column)\n\t}\n\tif idxList[db][tb] == nil {\n\t\tidxList[db][tb] = make([]*common.Column, 0)\n\t}\n\n\t// 去除重复列Append\n\texist := false\n\tfor _, cl := range idxList[db][tb] {\n\t\tif cl.Name == column.Name {\n\t\t\texist = true\n\t\t}\n\t}\n\n\t// 将 DB 替换成 vEnv 中的数据库名称\n\tdbInVEnv := db\n\tif _, ok := idxAdv.vEnv.DBRef[db]; ok {\n\t\tdbInVEnv = idxAdv.vEnv.DBRef[db]\n\t}\n\tindexMeta := idxAdv.IndexMeta[dbInVEnv][tb]\n\t// 主键列不需要追加\n\tpr := indexMeta.FindIndex(database.IndexKeyName, \"PRIMARY\")\n\tfor _, c := range pr {\n\t\tif c.ColumnName == column.Name {\n\t\t\texist = true\n\t\t}\n\t}\n\n\tif !exist {\n\t\tidxList[db][tb] = append(idxList[db][tb], column)\n\t}\n}\n\n// CompleteColumnsInfo 补全索引可能会用到列的所属库名、表名等信息\nfunc CompleteColumnsInfo(stmt sqlparser.Statement, cols []*common.Column, env *env.VirtualEnv) []*common.Column {\n\t// 如果传过来的列是空的，没必要跑逻辑\n\tif len(cols) == 0 {\n\t\treturn cols\n\t}\n\n\t// 从 Ast 中拿到 DBStructure，包含所有表的相关信息\n\tdbs := ast.GetMeta(stmt, nil)\n\n\t// 此处生成的 meta 信息中不应该含有\"\"db的信息，若 DB 为空则认为是已传入的 db 为默认 db 并进行信息补全\n\t// BUG Fix:\n\t// 修补 dbs 中空 DB 的导致后续补全列信息时无法获取正确 table 名称的问题\n\tif _, ok := dbs[\"\"]; ok {\n\t\tdbs[env.Database] = dbs[\"\"]\n\t\tdelete(dbs, \"\")\n\t}\n\n\ttableCount := 0\n\tfor db := range dbs {\n\t\tfor tb := range dbs[db].Table {\n\t\t\tif tb != \"\" {\n\t\t\t\ttableCount++\n\t\t\t}\n\t\t}\n\t}\n\n\tvar noEnvTmp []*common.Column\n\tfor _, col := range cols {\n\t\tfor db := range dbs {\n\t\t\t// 对每一列进行比对，将别名转换为正确的名称\n\t\t\tfind := false\n\t\t\tfor _, tb := range dbs[db].Table {\n\t\t\t\tfor _, tbAlias := range tb.TableAliases {\n\t\t\t\t\tif col.Table != \"\" && col.Table == tbAlias {\n\t\t\t\t\t\tcommon.Log.Debug(\"column '%s' prefix change: %s --> %s\", col.Name, col.Table, tb.TableName)\n\t\t\t\t\t\tfind = true\n\t\t\t\t\t\tcol.Table = tb.TableName\n\t\t\t\t\t\tcol.DB = db\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif find {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 如果不依赖env环境，利用ast中包含的信息推理列的库表信息\n\t\t\tif common.Config.TestDSN.Disable {\n\t\t\t\tif tableCount == 1 {\n\t\t\t\t\tfor _, tb := range dbs[db].Table {\n\t\t\t\t\t\tcol.Table = tb.TableName\n\n\t\t\t\t\t\t// 因为tableMeta是按照库表组织的树状结构，db变量贯穿全局\n\t\t\t\t\t\t// 只有在最终赋值前才能根据逻辑变更补全\n\t\t\t\t\t\tif db == \"\" {\n\t\t\t\t\t\t\tdb = env.Database\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcol.DB = db\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// 如果SQL中含有的表大于一个，则使用的列中必须含有前缀，不然无法判断该列属于哪个表\n\t\t\t\t// 如果某一列未含有前缀信息，则认为两张表中都含有该列，需要由人去判断\n\t\t\t\tif tableCount > 1 {\n\t\t\t\t\tif col.Table == \"\" {\n\t\t\t\t\t\tfor _, tb := range dbs[db].Table {\n\t\t\t\t\t\t\tif tb.TableName == \"\" {\n\t\t\t\t\t\t\t\tcommon.Log.Warn(\"can not get the meta info of column '%s'\", col.Name)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif db == \"\" {\n\t\t\t\t\t\t\t\tdb = env.RealDB(env.Database)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcol.Table = tb.TableName\n\t\t\t\t\t\t\tcol.DB = db\n\n\t\t\t\t\t\t\ttmp := *col\n\t\t\t\t\t\t\ttmp.Table = tb.TableName\n\t\t\t\t\t\t\ttmp.DB = db\n\n\t\t\t\t\t\t\tnoEnvTmp = append(noEnvTmp, &tmp)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif col.DB == \"\" {\n\t\t\t\t\t\tif db == \"\" {\n\t\t\t\t\t\t\tdb = env.Database\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcol.DB = db\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// 将已经获取到正确表信息的列信息带入到env中，利用show columns where table 获取库表信息\n\t\t\t// 此出会传入之前从ast中，该 db 下获取的所有表来作为where限定条件，\n\t\t\t// 防止与SQL无关的库表信息干扰准确性\n\t\t\t// 此处传入的是测试环境，DB 是经过变换的，所以在寻找列名的时候需要将 DB 名称转换成测试环境中经过 hash 的 DB 名称\n\t\t\t// 不然会找不到col的信息\n\t\t\trealCols, err := env.FindColumn(col.Name, env.DBHash(db), dbs.Tables(db)...)\n\t\t\tif err != nil {\n\t\t\t\tcommon.Log.Warn(\"%v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// 对比 column 信息中的表名与从 env 中获取的库表名的一致性\n\t\t\tfor _, realCol := range realCols {\n\t\t\t\tif col.Name == realCol.Name {\n\t\t\t\t\t// 如果查询到了列名一致，但从 ast 中获取的列的前缀与 env 中的表信息不符\n\t\t\t\t\t// 1.存在一个同名列，但不同表，该情况下忽略\n\t\t\t\t\t// 2.存在一个未正确转换的别名(如表名为)，该情况下修正，大概率是正确的\n\t\t\t\t\tif col.Table != \"\" && col.Table != realCol.Table {\n\t\t\t\t\t\thas, _ := env.FindColumn(col.Name, env.DBHash(db), col.Table)\n\t\t\t\t\t\tif len(has) > 0 {\n\t\t\t\t\t\t\trealCol = has[0]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tcol.DataType = realCol.DataType\n\t\t\t\t\tcol.Table = realCol.Table\n\t\t\t\t\tcol.DB = env.RealDB(realCol.DB)\n\t\t\t\t\tcol.Character = realCol.Character\n\t\t\t\t\tcol.Collation = realCol.Collation\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\t// 如果不依赖env环境，将可能存在的列也加入到索引预处理列表中\n\tif common.Config.TestDSN.Disable {\n\t\tcols = append(cols, noEnvTmp...)\n\t}\n\n\treturn cols\n}\n\n// calcCardinality 计算每一列的散粒度\n// 这个函数需要在补全列的库表信息之后再调用，否则无法确定要计算列的归属\nfunc (idxAdv *IndexAdvisor) calcCardinality(cols []*common.Column) []*common.Column {\n\tcommon.Log.Debug(\"Enter: calcCardinality(), Caller: %s\", common.Caller())\n\ttmpDB := *idxAdv.vEnv\n\tfor _, col := range cols {\n\t\t// 补全对应列的库->表->索引信息到IndexMeta\n\t\t// 这将在后面用于判断某一列是否为主键或单列唯一索引，快速返回散粒度\n\t\tif col.DB == \"\" {\n\t\t\tcol.DB = idxAdv.vEnv.Database\n\t\t}\n\t\trealDB := idxAdv.vEnv.DBHash(col.DB)\n\t\tif idxAdv.IndexMeta[realDB] == nil {\n\t\t\tidxAdv.IndexMeta[realDB] = make(map[string]*database.TableIndexInfo)\n\t\t}\n\n\t\tif idxAdv.IndexMeta[realDB][col.Table] == nil {\n\t\t\ttmpDB.Database = realDB\n\t\t\tindexInfo, err := tmpDB.ShowIndex(col.Table)\n\t\t\tif err != nil {\n\t\t\t\t// 如果是不存在的表就会报错，报错的可能性有三个：\n\t\t\t\t// 1.数据库错误  2.表不存在  3.临时表\n\t\t\t\t// 而这三种错误都是不需要在这一层关注的，直接跳过\n\t\t\t\tcommon.Log.Warn(\"calcCardinality error: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// 将获取的索引信息以db.tb 维度组织到 IndexMeta 中\n\t\t\tidxAdv.IndexMeta[realDB][col.Table] = indexInfo\n\t\t}\n\n\t\t// 检查对应列是否为主键或单列唯一索引，如果满足直接返回1，不再重复计算，提高效率\n\t\t// 多列复合唯一索引不能跳过计算，单列普通索引不能跳过计算\n\t\tfor _, index := range idxAdv.IndexMeta[realDB][col.Table].Rows {\n\t\t\t// 根据索引的名称判断该索引包含的列数，列数大于1即为复合索引\n\t\t\tcolumnCount := len(idxAdv.IndexMeta[realDB][col.Table].FindIndex(database.IndexKeyName, index.KeyName))\n\t\t\tif col.Name == index.ColumnName {\n\t\t\t\t// 主键、唯一键 无需计算散粒度\n\t\t\t\tif (index.KeyName == \"PRIMARY\" || index.NonUnique == 0) && columnCount == 1 {\n\t\t\t\t\tcommon.Log.Debug(\"column '%s' is PK or UK, no need to calculate cardinality.\", col.Name)\n\t\t\t\t\tcol.Cardinality = 1\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\t// 给非 PRIMARY、UNIQUE 的列计算散粒度\n\t\tif col.Cardinality != 1 {\n\t\t\tcol.Cardinality = idxAdv.vEnv.ColumnCardinality(col.Table, col.Name)\n\t\t}\n\t}\n\n\treturn cols\n}\n\n// Format 用于格式化输出索引建议\nfunc (idxAdvs IndexAdvises) Format() map[string]Rule {\n\trulesMap := make(map[string]Rule)\n\tnumber := 1\n\trules := make(map[string]*Rule)\n\tsqls := make(map[string][]string)\n\n\tfor _, advise := range idxAdvs {\n\t\tadvKey := advise.Database + advise.Table\n\n\t\tif _, ok := sqls[advKey]; !ok {\n\t\t\tsqls[advKey] = make([]string, 0)\n\t\t}\n\n\t\tsqls[advKey] = append(sqls[advKey], advise.DDL)\n\n\t\tif _, ok := rules[advKey]; !ok {\n\t\t\tsummary := fmt.Sprintf(\"为%s库的%s表添加索引\", advise.Database, advise.Table)\n\t\t\tif advise.Database == \"\" {\n\t\t\t\tsummary = fmt.Sprintf(\"为%s表添加索引\", advise.Table)\n\t\t\t}\n\n\t\t\trules[advKey] = &Rule{\n\t\t\t\tSummary:  summary,\n\t\t\t\tContent:  \"\",\n\t\t\t\tSeverity: \"L2\",\n\t\t\t}\n\t\t}\n\n\t\tfor _, col := range advise.ColumnDetails {\n\t\t\t// 为了更好地显示效果\n\t\t\tif common.Config.Sampling {\n\t\t\t\tcardinal := fmt.Sprintf(\"%0.2f\", col.Cardinality*100)\n\t\t\t\tif cardinal != \"0.00\" {\n\t\t\t\t\trules[advKey].Content += fmt.Sprintf(\"为列%s添加索引，散粒度为: %s%%; \",\n\t\t\t\t\t\tcol.Name, cardinal)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trules[advKey].Content += fmt.Sprintf(\"为列%s添加索引;\", col.Name)\n\t\t\t}\n\t\t}\n\t\tif !common.Config.Sampling && len(rules[advKey].Content) > 5 {\n\t\t\trules[advKey].Content += \" 由于未开启数据采样，各列在索引中的顺序需要自行调整。\"\n\t\t}\n\t\t// 清理多余的标点\n\t\trules[advKey].Content = strings.Trim(rules[advKey].Content, common.Config.Delimiter)\n\t}\n\n\tvar sortAdvs []string\n\tfor adv := range rules {\n\t\tsortAdvs = append(sortAdvs, adv)\n\t}\n\tsort.Strings(sortAdvs)\n\n\tfor _, adv := range sortAdvs {\n\t\tkey := fmt.Sprintf(\"IDX.%03d\", number)\n\t\tddl := ast.MergeAlterTables(sqls[adv]...)\n\t\t// 由于传入合并的SQL都是一张表的，所以一定只会输出一条ddl语句\n\t\tfor _, v := range ddl {\n\t\t\trules[adv].Case = v\n\t\t}\n\n\t\t// set item\n\t\trules[adv].Item = key\n\n\t\trulesMap[key] = *rules[adv]\n\n\t\tnumber++\n\t}\n\n\treturn rulesMap\n}\n\n// HeuristicCheck 依赖数据字典的启发式检查\n// IndexAdvisor会构建测试环境和数据字典，所以放在这里实现\nfunc (idxAdv *IndexAdvisor) HeuristicCheck(q Query4Audit) map[string]Rule {\n\tvar rule Rule\n\theuristicSuggest := make(map[string]Rule)\n\tif common.Config.OnlineDSN.Disable && common.Config.TestDSN.Disable {\n\t\treturn heuristicSuggest\n\t}\n\n\truleFuncs := []func(*IndexAdvisor) Rule{\n\t\t(*IndexAdvisor).RuleMaxTextColsCount,   // COL.007\n\t\t(*IndexAdvisor).RuleImplicitConversion, // ARG.003\n\t\t(*IndexAdvisor).RuleGroupByConst,       // CLA.004\n\t\t(*IndexAdvisor).RuleOrderByConst,       // CLA.005\n\t\t(*IndexAdvisor).RuleUpdatePrimaryKey,   // CLA.016\n\t\t// (*IndexAdvisor).RuleImpossibleOuterJoin, // TODO: JOI.003, JOI.004\n\t}\n\n\tfor _, f := range ruleFuncs {\n\t\trule = f(idxAdv)\n\t\tif rule.Item != \"OK\" {\n\t\t\theuristicSuggest[rule.Item] = rule\n\t\t}\n\t}\n\treturn heuristicSuggest\n}\n\n// DuplicateKeyChecker 对所有用到的库表检查是否存在重复索引\nfunc DuplicateKeyChecker(conn *database.Connector, databases ...string) map[string]Rule {\n\tcommon.Log.Debug(\"Enter:  DuplicateKeyChecker, Caller: %s\", common.Caller())\n\t// 复制一份online connector,防止环境切换影响其他功能的使用\n\ttmpOnline := *conn\n\truleMap := make(map[string]Rule)\n\tnumber := 1\n\n\t// 错误处理，用于汇总所有的错误\n\tfuncErrCheck := func(err error) {\n\t\tif err != nil {\n\t\t\tif sug, ok := ruleMap[\"ERR.003\"]; ok {\n\t\t\t\tsug.Content += fmt.Sprintf(\"; %s\", err.Error())\n\t\t\t} else {\n\t\t\t\truleMap[\"ERR.003\"] = Rule{\n\t\t\t\t\tItem:     \"ERR.003\",\n\t\t\t\t\tSeverity: \"L8\",\n\t\t\t\t\tContent:  err.Error(),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 不指定 DB 的时候检查 online dsn 中的 DB\n\tif len(databases) == 0 {\n\t\tdatabases = append(databases, tmpOnline.Database)\n\t}\n\n\tfor _, db := range databases {\n\t\t// 获取所有的表\n\t\ttmpOnline.Database = db\n\t\ttables, err := tmpOnline.ShowTables()\n\n\t\tif err != nil {\n\t\t\tfuncErrCheck(err)\n\t\t\tif !common.Config.DryRun {\n\t\t\t\treturn ruleMap\n\t\t\t}\n\t\t}\n\n\t\tfor _, tb := range tables {\n\t\t\t// 获取表中所有的索引\n\t\t\tidxMap := make(map[string][]*common.Column)\n\t\t\tidxInfo, err := tmpOnline.ShowIndex(tb)\n\t\t\tif err != nil {\n\t\t\t\tfuncErrCheck(err)\n\t\t\t\tif !common.Config.DryRun {\n\t\t\t\t\treturn ruleMap\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 枚举所有的索引信息，提取用到的列\n\t\t\tfor _, idx := range idxInfo.Rows {\n\t\t\t\tif _, ok := idxMap[idx.KeyName]; !ok {\n\t\t\t\t\tidxMap[idx.KeyName] = make([]*common.Column, 0)\n\t\t\t\t\tfor _, col := range idxInfo.FindIndex(database.IndexKeyName, idx.KeyName) {\n\t\t\t\t\t\tidxMap[idx.KeyName] = append(idxMap[idx.KeyName], &common.Column{\n\t\t\t\t\t\t\tName:  col.ColumnName,\n\t\t\t\t\t\t\tTable: tb,\n\t\t\t\t\t\t\tDB:    db,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 对索引进行重复检查\n\t\t\thasDup := false\n\t\t\tcontent := \"\"\n\n\t\t\tfor k1, cl1 := range idxMap {\n\t\t\t\tfor k2, cl2 := range idxMap {\n\t\t\t\t\tif k1 != k2 && common.IsColsPart(cl1, cl2) {\n\t\t\t\t\t\t// by pass primary key\n\t\t\t\t\t\tif k1 == \"PRIMARY\" || k2 == \"PRIMARY\" {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\thasDup = true\n\t\t\t\t\t\tcol1Str := common.JoinColumnsName(cl1, \", \")\n\t\t\t\t\t\tcol2Str := common.JoinColumnsName(cl2, \", \")\n\t\t\t\t\t\tcontent += fmt.Sprintf(\"索引%s(%s)与%s(%s)重复;\", k1, col1Str, k2, col2Str)\n\t\t\t\t\t\tcommon.Log.Debug(\" %s.%s has duplicate index %s(%s) <--> %s(%s)\", db, tb, k1, col1Str, k2, col2Str)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete(idxMap, k1)\n\t\t\t}\n\n\t\t\t// TODO 重复索引检查添加对约束及索引的判断，提供重复索引的删除功能\n\t\t\tif hasDup {\n\t\t\t\ttmpOnline.Database = db\n\t\t\t\tddl, _ := tmpOnline.ShowCreateTable(tb)\n\t\t\t\tkey := fmt.Sprintf(\"IDX.%03d\", number)\n\t\t\t\truleMap[key] = Rule{\n\t\t\t\t\tItem:     key,\n\t\t\t\t\tSeverity: \"L2\",\n\t\t\t\t\tSummary:  fmt.Sprintf(\"%s.%s存在重复的索引\", db, tb),\n\t\t\t\t\tContent:  content,\n\t\t\t\t\tCase:     ddl,\n\t\t\t\t}\n\t\t\t\tnumber++\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ruleMap\n}\n"
  },
  {
    "path": "advisor/index_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage advisor\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\t\"github.com/XiaoMi/soar/database\"\n\t\"github.com/XiaoMi/soar/env\"\n\n\t\"github.com/kr/pretty\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\nvar update = flag.Bool(\"update\", false, \"update .golden files\")\nvar vEnv *env.VirtualEnv\nvar rEnv *database.Connector\n\nfunc TestMain(m *testing.M) {\n\t// 初始化 init\n\tif common.DevPath == \"\" {\n\t\t_, file, _, _ := runtime.Caller(0)\n\t\tcommon.DevPath, _ = filepath.Abs(filepath.Dir(filepath.Join(file, \"..\"+string(filepath.Separator))))\n\t}\n\tcommon.BaseDir = common.DevPath\n\terr := common.ParseConfig(\"\")\n\tcommon.LogIfError(err, \"init ParseConfig\")\n\tcommon.Log.Debug(\"advisor_test init\")\n\tvEnv, rEnv = env.BuildEnv()\n\tif _, err = vEnv.Version(); err != nil {\n\t\tfmt.Println(err.Error(), \", By pass all advisor test cases\")\n\t\tos.Exit(0)\n\t}\n\n\tif _, err := rEnv.Version(); err != nil {\n\t\tfmt.Println(err.Error(), \", By pass all advisor test cases\")\n\t\tos.Exit(0)\n\t}\n\n\t// 分割线\n\tflag.Parse()\n\tm.Run()\n\n\t// 环境清理\n\tvEnv.CleanUp()\n}\n\n// ARG.003\nfunc TestRuleImplicitConversion(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tdsn := common.Config.OnlineDSN\n\tcommon.Config.OnlineDSN = common.Config.TestDSN\n\n\tinitSQLs := []string{\n\t\t`CREATE TABLE t1 (id int, title varchar(255) CHARSET utf8 COLLATE utf8_general_ci);`,\n\t\t`CREATE TABLE t2 (id int, title varchar(255) CHARSET utf8mb4);`,\n\t\t`CREATE TABLE t3 (id int, title varchar(255) CHARSET utf8 COLLATE utf8_bin);`,\n\t\t`CREATE TABLE t4 (id int, col bit(1));`,\n\t}\n\tfor _, sql := range initSQLs {\n\t\tvEnv.BuildVirtualEnv(rEnv, sql)\n\t}\n\n\tsqls := [][]string{\n\t\t// ARG.003\n\t\t{\n\t\t\t\"SELECT * FROM t1 WHERE title >= 60;\",\n\t\t\t\"SELECT * FROM t1, t2 WHERE t1.title = t2.title;\",\n\t\t\t\"SELECT * FROM t1, t3 WHERE t1.title = t3.title;\",\n\t\t\t\"SELECT * FROM t1 WHERE title in (60, '60');\",\n\t\t\t\"SELECT * FROM t1 WHERE title in (60);\",\n\t\t\t\"SELECT * FROM t1 WHERE title in (60, 60);\",\n\t\t\t\"SELECT * FROM t1 WHERE title = 1\",\n\t\t},\n\t\t// OK\n\t\t{\n\t\t\t\"SELECT * FROM t1 WHERE id = '1'\", // string -> int can use index\n\t\t\t\"SELECT * FROM t1 WHERE id = 1\",\n\t\t\t\"SELECT * FROM t4 WHERE col = 1\", // https://github.com/XiaoMi/soar/issues/151\n\t\t\t\"SELECT * FROM sakila.film WHERE rental_rate > 1\",\n\t\t},\n\t}\n\tfor _, sql := range sqls[0] {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\tif err != nil {\n\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t}\n\n\t\tif idxAdvisor != nil {\n\t\t\trule := idxAdvisor.RuleImplicitConversion()\n\t\t\tif rule.Item != \"ARG.003\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : ARG.003, SQL:\", sql)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, sql := range sqls[1] {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\tif err != nil {\n\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t}\n\n\t\tif idxAdvisor != nil {\n\t\t\trule := idxAdvisor.RuleImplicitConversion()\n\t\t\tif rule.Item != \"OK\" {\n\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : OK, SQL:\", sql)\n\t\t\t}\n\t\t}\n\t}\n\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n\tcommon.Config.OnlineDSN = dsn\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// JOI.003 & JOI.004\nfunc TestRuleImpossibleOuterJoin(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select city_id, city, country from city left outer join country using(country_id) WHERE city.city_id=59 and country.country='Algeria'`,\n\t\t`select city_id, city, country from city left outer join country using(country_id) WHERE country.country='Algeria'`,\n\t\t`select city_id, city, country from city left outer join country on city.country_id=country.country_id WHERE city.city_id IS NULL`,\n\t}\n\n\tfor _, sql := range sqls {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.RuleImpossibleOuterJoin()\n\t\t\t\tif rule.Item != \"JOI.003\" && rule.Item != \"JOI.004\" {\n\t\t\t\t\tt.Error(\"Rule not match:\", rule, \"Expect : JOI.003 || JOI.004\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// GRP.001\nfunc TestIndexAdvisorRuleGroupByConst(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`select film_id, title from film where release_year='2006' group by release_year`,\n\t\t\t`select film_id, title from film where release_year in ('2006') group by release_year`,\n\t\t},\n\t\t{\n\t\t\t// 反面的例子\n\t\t\t`select film_id, title from film where release_year in ('2006', '2007') group by release_year`,\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.RuleGroupByConst()\n\t\t\t\tif rule.Item != \"GRP.001\" {\n\t\t\t\t\tt.Error(\"Rule not match:\", rule, \"Expect : GRP.001\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.RuleGroupByConst()\n\t\t\t\tif rule.Item != \"OK\" {\n\t\t\t\t\tt.Error(\"Rule not match:\", rule, \"Expect : OK\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.005\nfunc TestIndexAdvisorRuleOrderByConst(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`select film_id, title from film where release_year='2006' order by release_year;`,\n\t\t\t`select film_id, title from film where release_year in ('2006') order by release_year;`,\n\t\t},\n\t\t{\n\t\t\t// 反面的例子\n\t\t\t`select film_id, title from film where release_year in ('2006', '2007') order by release_year;`,\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.RuleOrderByConst()\n\t\t\t\tif rule.Item != \"CLA.005\" {\n\t\t\t\t\tt.Error(\"Rule not match:\", rule, \"Expect : CLA.005\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.RuleOrderByConst()\n\t\t\t\tif rule.Item != \"OK\" {\n\t\t\t\t\tt.Error(\"Rule not match:\", rule, \"Expect : OK\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// CLA.016\nfunc TestRuleUpdatePrimaryKey(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`update film set film_id = 1 where title='a';`,\n\t\t},\n\t\t{\n\t\t\t// 反面的例子\n\t\t\t`select film_id, title from film where release_year in ('2006', '2007') order by release_year;`,\n\t\t},\n\t}\n\n\tfor _, sql := range sqls[0] {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.RuleUpdatePrimaryKey()\n\t\t\t\tif rule.Item != \"CLA.016\" {\n\t\t\t\t\tt.Error(\"Rule not match:\", rule.Item, \"Expect : CLA.016\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, sql := range sqls[1] {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.RuleUpdatePrimaryKey()\n\t\t\t\tif rule.Item != \"OK\" {\n\t\t\t\t\tt.Error(\"Rule not match:\", rule, \"Expect : OK\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestIndexAdvise(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgMinCardinality := common.Config.MinCardinality\n\tcommon.Config.MinCardinality = 20\n\n\tfor _, sql := range common.TestSQLs {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.IndexAdvise().Format()\n\t\t\t\tif len(rule) > 0 {\n\t\t\t\t\t_, _ = pretty.Println(rule)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Config.MinCardinality = orgMinCardinality\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestIndexAdviseNoEnv(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgOnlineDSNStatus := common.Config.OnlineDSN.Disable\n\tcommon.Config.OnlineDSN.Disable = true\n\n\tfor _, sql := range common.TestSQLs {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.IndexAdvise().Format()\n\t\t\t\tif len(rule) > 0 {\n\t\t\t\t\tpretty.Println(rule)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Config.OnlineDSN.Disable = orgOnlineDSNStatus\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestDuplicateKeyChecker(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\trule := DuplicateKeyChecker(rEnv, \"sakila\")\n\tif len(rule) != 0 {\n\t\tt.Errorf(\"got rules: %s\", pretty.Sprint(rule))\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestMergeAdvices(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tdst := []IndexInfo{\n\t\t{\n\t\t\tName:     \"test\",\n\t\t\tDatabase: \"db\",\n\t\t\tTable:    \"tb\",\n\t\t\tColumnDetails: []*common.Column{\n\t\t\t\t{\n\t\t\t\t\tName: \"test\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tsrc := dst[0]\n\n\tadvise := mergeAdvices(dst, src)\n\tif len(advise) != 1 {\n\t\tt.Error(pretty.Sprint(advise))\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestIdxColsTypeCheck(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select city_id, city, country from city left outer join country using(country_id) WHERE city.city_id=59 and country.country='Algeria'`,\n\t}\n\n\tfor _, sql := range sqls {\n\t\tstmt, syntaxErr := sqlparser.Parse(sql)\n\t\tif syntaxErr != nil {\n\t\t\tcommon.Log.Critical(\"Syntax Error: %v, SQL: %s\", syntaxErr, sql)\n\t\t}\n\n\t\tq := &Query4Audit{Query: sql, Stmt: stmt}\n\n\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\tidxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"NewAdvisor Error: \", err, \"SQL: \", sql)\n\t\t\t}\n\n\t\t\tidxList := []IndexInfo{\n\t\t\t\t{\n\t\t\t\t\tName:     \"idx_fk_country_id\",\n\t\t\t\t\tDatabase: \"sakila\",\n\t\t\t\t\tTable:    \"city\",\n\t\t\t\t\tColumnDetails: []*common.Column{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:      \"country_id\",\n\t\t\t\t\t\t\tCharacter: \"utf8\",\n\t\t\t\t\t\t\tDataType:  \"varchar(3000)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif idxAdvisor != nil {\n\t\t\t\trule := idxAdvisor.idxColsTypeCheck(idxList)\n\t\t\t\tif !(len(rule) > 0 && rule[0].DDL == \"alter table `sakila`.`city` add index `idx_country_id` (`country_id`(N))\") {\n\t\t\t\t\tt.Error(pretty.Sprint(rule))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestGetRandomIndexSuffix(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tfor i := 0; i < 5; i++ {\n\t\tr := getRandomIndexSuffix()\n\t\tif !(strings.HasPrefix(r, \"_\") && len(r) == 5) {\n\t\t\tt.Errorf(\"getRandomIndexSuffix should return a string with prefix `_` and 5 length, but got:%s\", r)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "advisor/rules.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage advisor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/ast\"\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n\t\"github.com/percona/go-mysql/query\"\n\ttidb \"github.com/pingcap/parser/ast\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// Query4Audit 待评审的SQL结构体，由原SQL和其对应的抽象语法树组成\ntype Query4Audit struct {\n\tQuery  string              // 查询语句\n\tStmt   sqlparser.Statement // 通过Vitess解析出的抽象语法树\n\tTiStmt []tidb.StmtNode     // 通过TiDB解析出的抽象语法树\n}\n\n// NewQuery4Audit return a struct for Query4Audit\nfunc NewQuery4Audit(sql string, options ...string) (*Query4Audit, error) {\n\tvar err, vErr error\n\tvar charset string\n\tvar collation string\n\n\tif len(options) > 0 {\n\t\tcharset = options[0]\n\t}\n\n\tif len(options) > 1 {\n\t\tcollation = options[1]\n\t}\n\n\tq := &Query4Audit{Query: sql}\n\t// vitess 语法解析不上报，以 tidb parser 为主\n\tq.Stmt, vErr = sqlparser.Parse(sql)\n\tif vErr != nil {\n\t\tcommon.Log.Warn(\"NewQuery4Audit vitess parse Error: %s, Query: %s\", vErr.Error(), sql)\n\t}\n\n\t// TODO: charset, collation\n\t// tidb parser 语法解析\n\tq.TiStmt, err = ast.TiParse(sql, charset, collation)\n\treturn q, err\n}\n\n// Rule 评审规则元数据结构\ntype Rule struct {\n\tItem     string                  `json:\"Item\"`     // 规则代号\n\tSeverity string                  `json:\"Severity\"` // 危险等级：L[0-8], 数字越大表示级别越高\n\tSummary  string                  `json:\"Summary\"`  // 规则摘要\n\tContent  string                  `json:\"Content\"`  // 规则解释\n\tCase     string                  `json:\"Case\"`     // SQL示例\n\tPosition int                     `json:\"Position\"` // 建议所处SQL字符位置，默认0表示全局建议\n\tFunc     func(*Query4Audit) Rule `json:\"-\"`        // 函数名\n}\n\n/*\n\n## Item单词缩写含义\n\n* ALI   Alias(AS)\n* ALT   Alter\n* ARG   Argument\n* CLA   Classic\n* COL   Column\n* DIS   Distinct\n* ERR   Error, 特指MySQL执行返回的报错信息, ERR.000为vitess语法错误，ERR.001为执行错误，ERR.002为EXPLAIN错误\n* EXP   Explain, 由explain模块给\n* FUN   Function\n* IDX   Index, 由index模块给\n* JOI   Join\n* KEY   Key\n* KWR   Keyword\n* LCK\tLock\n* LIT   Literal\n* PRO   Profiling, 由profiling模块给\n* RES   Result\n* SEC   Security\n* STA   Standard\n* SUB   Subquery\n* TBL   TableName\n* TRA   Trace, 由trace模块给\n\n*/\n\n// HeuristicRules 启发式规则列表\nvar HeuristicRules map[string]Rule\n\nfunc init() {\n\tInitHeuristicRules()\n}\n\n// InitHeuristicRules ...\nfunc InitHeuristicRules() {\n\tHeuristicRules = map[string]Rule{\n\t\t\"OK\": {\n\t\t\tItem:     \"OK\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"OK\",\n\t\t\tContent:  `OK`,\n\t\t\tCase:     \"OK\",\n\t\t\tFunc:     (*Query4Audit).RuleOK,\n\t\t},\n\t\t\"ALI.001\": {\n\t\t\tItem:     \"ALI.001\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"建议使用 AS 关键字显示声明一个别名\",\n\t\t\tContent:  `在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。`,\n\t\t\tCase:     \"select name from tbl t1 where id < 1000\",\n\t\t\tFunc:     (*Query4Audit).RuleImplicitAlias,\n\t\t},\n\t\t\"ALI.002\": {\n\t\t\tItem:     \"ALI.002\",\n\t\t\tSeverity: \"L8\",\n\t\t\tSummary:  \"不建议给列通配符'*'设置别名\",\n\t\t\tContent:  `例: \"SELECT tbl.* col1, col2\"上面这条 SQL 给列通配符设置了别名，这样的SQL可能存在逻辑错误。您可能意在查询 col1, 但是代替它的是重命名的是 tbl 的最后一列。`,\n\t\t\tCase:     \"select tbl.* as c1,c2,c3 from tbl where id < 1000\",\n\t\t\tFunc:     (*Query4Audit).RuleStarAlias,\n\t\t},\n\t\t\"ALI.003\": {\n\t\t\tItem:     \"ALI.003\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"别名不要与表或列的名字相同\",\n\t\t\tContent:  `表或列的别名与其真实名称相同, 这样的别名会使得查询更难去分辨。`,\n\t\t\tCase:     \"select name from tbl as tbl where id < 1000\",\n\t\t\tFunc:     (*Query4Audit).RuleSameAlias,\n\t\t},\n\t\t\"ALT.001\": {\n\t\t\tItem:     \"ALT.001\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"修改表的默认字符集不会改表各个字段的字符集\",\n\t\t\tContent:  `很多初学者会将 ALTER TABLE tbl_name [DEFAULT] CHARACTER SET 'UTF8' 误认为会修改所有字段的字符集，但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用 ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;`,\n\t\t\tCase:     \"ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;\",\n\t\t\tFunc:     (*Query4Audit).RuleAlterCharset,\n\t\t},\n\t\t\"ALT.002\": {\n\t\t\tItem:     \"ALT.002\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"同一张表的多条 ALTER 请求建议合为一条\",\n\t\t\tContent:  `每次表结构变更对线上服务都会产生影响，即使是能够通过在线工具进行调整也请尽量通过合并 ALTER 请求的试减少操作次数。`,\n\t\t\tCase:     \"ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);\",\n\t\t\tFunc:     (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给\n\t\t},\n\t\t\"ALT.003\": {\n\t\t\tItem:     \"ALT.003\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"删除列为高危操作，操作前请注意检查业务逻辑是否还有依赖\",\n\t\t\tContent:  `如业务逻辑依赖未完全消除，列被删除后可能导致数据无法写入或无法查询到已删除列数据导致程序异常的情况。这种情况下即使通过备份数据回滚也会丢失用户请求写入的数据。`,\n\t\t\tCase:     \"ALTER TABLE tbl DROP COLUMN col;\",\n\t\t\tFunc:     (*Query4Audit).RuleAlterDropColumn,\n\t\t},\n\t\t\"ALT.004\": {\n\t\t\tItem:     \"ALT.004\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"删除主键和外键为高危操作，操作前请与 DBA 确认影响\",\n\t\t\tContent:  `主键和外键为关系型数据库中两种重要约束，删除已有约束会打破已有业务逻辑，操作前请业务开发与 DBA 确认影响，三思而行。`,\n\t\t\tCase:     \"ALTER TABLE tbl DROP PRIMARY KEY;\",\n\t\t\tFunc:     (*Query4Audit).RuleAlterDropKey,\n\t\t},\n\t\t\"ARG.001\": {\n\t\t\tItem:     \"ARG.001\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"不建议使用前项通配符查找\",\n\t\t\tContent:  `例如 \"％foo\"，查询参数有一个前项通配符的情况无法使用已有索引。`,\n\t\t\tCase:     \"select c1,c2,c3 from tbl where name like '%foo'\",\n\t\t\tFunc:     (*Query4Audit).RulePrefixLike,\n\t\t},\n\t\t\"ARG.002\": {\n\t\t\tItem:     \"ARG.002\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"没有通配符的 LIKE 查询\",\n\t\t\tContent:  `不包含通配符的 LIKE 查询可能存在逻辑错误，因为逻辑上它与等值查询相同。`,\n\t\t\tCase:     \"select c1,c2,c3 from tbl where name like 'foo'\",\n\t\t\tFunc:     (*Query4Audit).RuleEqualLike,\n\t\t},\n\t\t\"ARG.003\": {\n\t\t\tItem:     \"ARG.003\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"参数比较包含隐式转换，无法使用索引\",\n\t\t\tContent:  \"隐式类型转换有无法命中索引的风险，在高并发、大数据量的情况下，命不中索引带来的后果非常严重。\",\n\t\t\tCase:     \"SELECT * FROM sakila.film WHERE length >= '60';\",\n\t\t\tFunc:     (*Query4Audit).RuleOK, // 该建议在IndexAdvisor中给，RuleImplicitConversion\n\t\t},\n\t\t\"ARG.004\": {\n\t\t\tItem:     \"ARG.004\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"IN (NULL)/NOT IN (NULL) 永远非真\",\n\t\t\tContent:  \"正确的作法是 col IN ('val1', 'val2', 'val3') OR col IS NULL\",\n\t\t\tCase:     \"SELECT * FROM tb WHERE col IN (NULL);\",\n\t\t\tFunc:     (*Query4Audit).RuleIn,\n\t\t},\n\t\t\"ARG.005\": {\n\t\t\tItem:     \"ARG.005\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"IN 要慎用，元素过多会导致全表扫描\",\n\t\t\tContent:  ` 如：select id from t where num in(1,2,3)对于连续的数值，能用 BETWEEN 就不要用 IN 了：select id from t where num between 1 and 3。而当 IN 值过多时 MySQL 也可能会进入全表扫描导致性能急剧下降。`,\n\t\t\tCase:     \"select id from t where num in(1,2,3)\",\n\t\t\tFunc:     (*Query4Audit).RuleIn,\n\t\t},\n\t\t\"ARG.006\": {\n\t\t\tItem:     \"ARG.006\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\",\n\t\t\tContent:  `使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;`,\n\t\t\tCase:     \"select id from t where num is null\",\n\t\t\tFunc:     (*Query4Audit).RuleIsNullIsNotNull,\n\t\t},\n\t\t\"ARG.007\": {\n\t\t\tItem:     \"ARG.007\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"避免使用模式匹配\",\n\t\t\tContent:  `性能问题是使用模式匹配操作符的最大缺点。使用 LIKE 或正则表达式进行模式匹配进行查询的另一个问题，是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代 SQL，比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL，请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说，您不一定要使用SQL来解决所有问题。`,\n\t\t\tCase:     \"select c_id,c2,c3 from tbl where c2 like 'test%'\",\n\t\t\tFunc:     (*Query4Audit).RulePatternMatchingUsage,\n\t\t},\n\t\t\"ARG.008\": {\n\t\t\tItem:     \"ARG.008\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"OR 查询索引列时请尽量使用 IN 谓词\",\n\t\t\tContent:  `IN-list 谓词可以用于索引检索，并且优化器可以对 IN-list 进行排序，以匹配索引的排序序列，从而获得更有效的检索。请注意，IN-list 必须只包含常量，或在查询块执行期间保持常量的值，例如外引用。`,\n\t\t\tCase:     \"SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17\",\n\t\t\tFunc:     (*Query4Audit).RuleORUsage,\n\t\t},\n\t\t\"ARG.009\": {\n\t\t\tItem:     \"ARG.009\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"引号中的字符串开头或结尾包含空格\",\n\t\t\tContent:  `如果 VARCHAR 列的前后存在空格将可能引起逻辑问题，如在 MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。`,\n\t\t\tCase:     \"SELECT 'abc '\",\n\t\t\tFunc:     (*Query4Audit).RuleSpaceWithQuote,\n\t\t},\n\t\t\"ARG.010\": {\n\t\t\tItem:     \"ARG.010\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不要使用 hint，如：sql_no_cache, force index, ignore key, straight join等\",\n\t\t\tContent:  `hint 是用来强制 SQL 按照某个执行计划来执行，但随着数据量变化我们无法保证自己当初的预判是正确的。`,\n\t\t\tCase:     \"SELECT * FROM t1 USE INDEX (i1) ORDER BY a;\",\n\t\t\tFunc:     (*Query4Audit).RuleHint,\n\t\t},\n\t\t\"ARG.011\": {\n\t\t\tItem:     \"ARG.011\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"不要使用负向查询，如：NOT IN/NOT LIKE\",\n\t\t\tContent:  `请尽量不要使用负向查询，这将导致全表扫描，对查询性能影响较大。`,\n\t\t\tCase:     \"select id from t where num not in(1,2,3);\",\n\t\t\tFunc:     (*Query4Audit).RuleNot,\n\t\t},\n\t\t\"ARG.012\": {\n\t\t\tItem:     \"ARG.012\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"一次性 INSERT/REPLACE 的数据过多\",\n\t\t\tContent:  \"单条 INSERT/REPLACE 语句批量插入大量数据性能较差，甚至可能导致从库同步延迟。为了提升性能，减少批量写入数据对从库同步延时的影响，建议采用分批次插入的方法。\",\n\t\t\tCase:     \"INSERT INTO tb (a) VALUES (1), (2)\",\n\t\t\tFunc:     (*Query4Audit).RuleInsertValues,\n\t\t},\n\t\t\"ARG.013\": {\n\t\t\tItem:     \"ARG.013\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"DDL 语句中使用了中文全角引号\",\n\t\t\tContent:  \"DDL 语句中使用了中文全角引号“”或‘’，这可能是书写错误，请确认是否符合预期。\",\n\t\t\tCase:     \"CREATE TABLE tb (a varchar(10) default '“”'\",\n\t\t\tFunc:     (*Query4Audit).RuleFullWidthQuote,\n\t\t},\n\t\t\"ARG.014\": {\n\t\t\tItem:     \"ARG.014\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"IN 条件中存在列名，可能导致数据匹配范围扩大\",\n\t\t\tContent:  `如：delete from t where id in(1, 2, id) 可能会导致全表数据误删除。请仔细检查 IN 条件的正确性。`,\n\t\t\tCase:     \"select id from t where id in(1, 2, id)\",\n\t\t\tFunc:     (*Query4Audit).RuleIn,\n\t\t},\n\t\t\"CLA.001\": {\n\t\t\tItem:     \"CLA.001\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"最外层 SELECT 未指定 WHERE 条件\",\n\t\t\tContent:  `SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。`,\n\t\t\tCase:     \"select id from tbl\",\n\t\t\tFunc:     (*Query4Audit).RuleNoWhere,\n\t\t},\n\t\t\"CLA.002\": {\n\t\t\tItem:     \"CLA.002\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"不建议使用 ORDER BY RAND()\",\n\t\t\tContent:  `ORDER BY RAND() 是从结果集中检索随机行的一种非常低效的方法，因为它会对整个结果进行排序并丢弃其大部分数据。`,\n\t\t\tCase:     \"select name from tbl where id < 1000 order by rand(number)\",\n\t\t\tFunc:     (*Query4Audit).RuleOrderByRand,\n\t\t},\n\t\t\"CLA.003\": {\n\t\t\tItem:     \"CLA.003\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不建议使用带 OFFSET 的LIMIT 查询\",\n\t\t\tContent:  `使用 LIMIT 和 OFFSET 对结果集分页的复杂度是 O(n^2)，并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。`,\n\t\t\tCase:     \"select c1,c2 from tbl where name=xx order by number limit 1 offset 20\",\n\t\t\tFunc:     (*Query4Audit).RuleOffsetLimit,\n\t\t},\n\t\t\"CLA.004\": {\n\t\t\tItem:     \"CLA.004\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不建议对常量进行 GROUP BY\",\n\t\t\tContent:  `GROUP BY 1 表示按第一列进行 GROUP BY。如果在 GROUP BY 子句中使用数字，而不是表达式或列名称，当查询列顺序改变时，可能会导致问题。`,\n\t\t\tCase:     \"select col1,col2 from tbl group by 1\",\n\t\t\tFunc:     (*Query4Audit).RuleGroupByConst,\n\t\t},\n\t\t\"CLA.005\": {\n\t\t\tItem:     \"CLA.005\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"ORDER BY 常数列没有任何意义\",\n\t\t\tContent:  `SQL 逻辑上可能存在错误; 最多只是一个无用的操作，不会更改查询结果。`,\n\t\t\tCase:     \"select id from test where id=1 order by id\",\n\t\t\tFunc:     (*Query4Audit).RuleOrderByConst,\n\t\t},\n\t\t\"CLA.006\": {\n\t\t\tItem:     \"CLA.006\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"在不同的表中 GROUP BY 或 ORDER BY\",\n\t\t\tContent:  `这将强制使用临时表和 filesort，可能产生巨大性能隐患，并且可能消耗大量内存和磁盘上的临时空间。`,\n\t\t\tCase:     \"select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col\",\n\t\t\tFunc:     (*Query4Audit).RuleDiffGroupByOrderBy,\n\t\t},\n\t\t\"CLA.008\": {\n\t\t\tItem:     \"CLA.008\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"请为 GROUP BY 显示添加 ORDER BY 条件\",\n\t\t\tContent:  `默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。`,\n\t\t\tCase:     \"select c1,c2,c3 from t1 where c1='foo' group by c2\",\n\t\t\tFunc:     (*Query4Audit).RuleExplicitOrderBy,\n\t\t},\n\t\t\"CLA.009\": {\n\t\t\tItem:     \"CLA.009\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"ORDER BY 的条件为表达式\",\n\t\t\tContent:  `当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。`,\n\t\t\tCase:     \"select description from film where title ='ACADEMY DINOSAUR' order by length-language_id;\",\n\t\t\tFunc:     (*Query4Audit).RuleOrderByExpr,\n\t\t},\n\t\t\"CLA.010\": {\n\t\t\tItem:     \"CLA.010\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"GROUP BY 的条件为表达式\",\n\t\t\tContent:  `当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。`,\n\t\t\tCase:     \"select description from film where title ='ACADEMY DINOSAUR' GROUP BY length-language_id;\",\n\t\t\tFunc:     (*Query4Audit).RuleGroupByExpr,\n\t\t},\n\t\t\"CLA.011\": {\n\t\t\tItem:     \"CLA.011\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"建议为表添加注释\",\n\t\t\tContent:  `为表添加注释能够使得表的意义更明确，从而为日后的维护带来极大的便利。`,\n\t\t\tCase:     \"CREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128) DEFAULT NULL,PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8\",\n\t\t\tFunc:     (*Query4Audit).RuleTblCommentCheck,\n\t\t},\n\t\t\"CLA.012\": {\n\t\t\tItem:     \"CLA.012\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"将复杂的裹脚布式查询分解成几个简单的查询\",\n\t\t\tContent:  `SQL是一门极具表现力的语言，您可以在单个SQL查询或者单条语句中完成很多事情。但这并不意味着必须强制只使用一行代码，或者认为使用一行代码就搞定每个任务是个好主意。通过一个查询来获得所有结果的常见后果是得到了一个笛卡儿积。当查询中的两张表之间没有条件限制它们的关系时，就会发生这种情况。没有对应的限制而直接使用两张表进行联结查询，就会得到第一张表中的每一行和第二张表中的每一行的一个组合。每一个这样的组合就会成为结果集中的一行，最终您就会得到一个行数很多的结果集。重要的是要考虑这些查询很难编写、难以修改和难以调试。数据库查询请求的日益增加应该是预料之中的事。经理们想要更复杂的报告以及在用户界面上添加更多的字段。如果您的设计很复杂，并且是一个单一查询，要扩展它们就会很费时费力。不论对您还是项目来说，时间花在这些事情上面不值得。将复杂的意大利面条式查询分解成几个简单的查询。当您拆分一个复杂的SQL查询时，得到的结果可能是很多类似的查询，可能仅仅在数据类型上有所不同。编写所有的这些查询是很乏味的，因此，最好能够有个程序自动生成这些代码。SQL代码生成是一个很好的应用。尽管SQL支持用一行代码解决复杂的问题，但也别做不切实际的事情。`,\n\t\t\tCase:     \"这是一条很长很长的 SQL，案例略。\",\n\t\t\tFunc:     (*Query4Audit).RuleSpaghettiQueryAlert,\n\t\t},\n\t\t/*\n\t\t\thttps://www.datacamp.com/community/tutorials/sql-tutorial-query\n\t\t\tThe HAVING Clause\n\t\t\tThe HAVING clause was originally added to SQL because the WHERE keyword could not be used with aggregate functions. HAVING is typically used with the GROUP BY clause to restrict the groups of returned rows to only those that meet certain conditions. However, if you use this clause in your query, the index is not used, which -as you already know- can result in a query that doesn't really perform all that well.\n\n\t\t\tIf you’re looking for an alternative, consider using the WHERE clause. Consider the following queries:\n\n\t\t\tSELECT state, COUNT(*)\n\t\t\t  FROM Drivers\n\t\t\t WHERE state IN ('GA', 'TX')\n\t\t\t GROUP BY state\n\t\t\t ORDER BY state\n\t\t\tSELECT state, COUNT(*)\n\t\t\t  FROM Drivers\n\t\t\t GROUP BY state\n\t\t\tHAVING state IN ('GA', 'TX')\n\t\t\t ORDER BY state\n\t\t\tThe first query uses the WHERE clause to restrict the number of rows that need to be summed, whereas the second query sums up all the rows in the table and then uses HAVING to throw away the sums it calculated. In these types of cases, the alternative with the WHERE clause is obviously the better one, as you don’t waste any resources.\n\n\t\t\tYou see that this is not about limiting the result set, rather about limiting the intermediate number of records within a query.\n\n\t\t\tNote that the difference between these two clauses lies in the fact that the WHERE clause introduces a condition on individual rows, while the HAVING clause introduces a condition on aggregations or results of a selection where a single result, such as MIN, MAX, SUM,… has been produced from multiple rows.\n\t\t*/\n\t\t\"CLA.013\": {\n\t\t\tItem:     \"CLA.013\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"不建议使用 HAVING 子句\",\n\t\t\tContent:  `将查询的 HAVING 子句改写为 WHERE 中的查询条件，可以在查询处理期间使用索引。`,\n\t\t\tCase:     \"SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id\",\n\t\t\tFunc:     (*Query4Audit).RuleHavingClause,\n\t\t},\n\t\t\"CLA.014\": {\n\t\t\tItem:     \"CLA.014\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"删除全表时建议使用 TRUNCATE 替代 DELETE\",\n\t\t\tContent:  `删除全表时建议使用 TRUNCATE 替代 DELETE`,\n\t\t\tCase:     \"delete from tbl\",\n\t\t\tFunc:     (*Query4Audit).RuleNoWhere,\n\t\t},\n\t\t\"CLA.015\": {\n\t\t\tItem:     \"CLA.015\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"UPDATE 未指定 WHERE 条件\",\n\t\t\tContent:  `UPDATE 不指定 WHERE 条件一般是致命的，请您三思后行`,\n\t\t\tCase:     \"update tbl set col=1\",\n\t\t\tFunc:     (*Query4Audit).RuleNoWhere,\n\t\t},\n\t\t\"CLA.016\": {\n\t\t\tItem:     \"CLA.016\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不要 UPDATE 主键\",\n\t\t\tContent:  `主键是数据表中记录的唯一标识符，不建议频繁更新主键列，这将影响元数据统计信息进而影响正常的查询。`,\n\t\t\tCase:     \"update tbl set col=1\",\n\t\t\tFunc:     (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 RuleUpdatePrimaryKey\n\t\t},\n\t\t\"COL.001\": {\n\t\t\tItem:     \"COL.001\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用 SELECT * 类型查询\",\n\t\t\tContent:  `当表结构变更时，使用 * 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。`,\n\t\t\tCase:     \"select * from tbl where id=1\",\n\t\t\tFunc:     (*Query4Audit).RuleSelectStar,\n\t\t},\n\t\t\"COL.002\": {\n\t\t\tItem:     \"COL.002\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"INSERT/REPLACE 未指定列名\",\n\t\t\tContent:  `当表结构发生变更，如果 INSERT 或 REPLACE 请求不明确指定列名，请求的结果将会与预想的不同; 建议使用 “INSERT INTO tbl(col1，col2)VALUES ...” 代替。`,\n\t\t\tCase:     \"insert into tbl values(1,'name')\",\n\t\t\tFunc:     (*Query4Audit).RuleInsertColDef,\n\t\t},\n\t\t\"COL.003\": {\n\t\t\tItem:     \"COL.003\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"建议修改自增 ID 为无符号类型\",\n\t\t\tContent:  `建议修改自增 ID 为无符号类型`,\n\t\t\tCase:     \"create table test(`id` int(11) NOT NULL AUTO_INCREMENT)\",\n\t\t\tFunc:     (*Query4Audit).RuleAutoIncUnsigned,\n\t\t},\n\t\t\"COL.004\": {\n\t\t\tItem:     \"COL.004\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"请为列添加默认值\",\n\t\t\tContent:  `请为列添加默认值，如果是 ALTER 操作，请不要忘记将原字段的默认值写上。字段无默认值，当表较大时无法在线变更表结构。`,\n\t\t\tCase:     \"CREATE TABLE tbl (col int) ENGINE=InnoDB;\",\n\t\t\tFunc:     (*Query4Audit).RuleAddDefaultValue,\n\t\t},\n\t\t\"COL.005\": {\n\t\t\tItem:     \"COL.005\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"列未添加注释\",\n\t\t\tContent:  `建议对表中每个列添加注释，来明确每个列在表中的含义及作用。`,\n\t\t\tCase:     \"CREATE TABLE tbl (col int) ENGINE=InnoDB;\",\n\t\t\tFunc:     (*Query4Audit).RuleColCommentCheck,\n\t\t},\n\t\t\"COL.006\": {\n\t\t\tItem:     \"COL.006\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"表中包含有太多的列\",\n\t\t\tContent:  `表中包含有太多的列`,\n\t\t\tCase:     \"CREATE TABLE tbl ( cols ....);\",\n\t\t\tFunc:     (*Query4Audit).RuleTooManyFields,\n\t\t},\n\t\t\"COL.007\": {\n\t\t\tItem:     \"COL.007\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"表中包含有太多的 text/blob 列\",\n\t\t\tContent:  fmt.Sprintf(`表中包含超过%d个的 text/blob 列`, common.Config.MaxTextColsCount),\n\t\t\tCase:     \"CREATE TABLE tbl ( cols ....);\",\n\t\t\tFunc:     (*Query4Audit).RuleTooManyFields,\n\t\t},\n\t\t\"COL.008\": {\n\t\t\tItem:     \"COL.008\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"可使用 VARCHAR 代替 CHAR， VARBINARY 代替 BINARY\",\n\t\t\tContent:  `为首先变长字段存储空间小，可以节省存储空间。其次对于查询来说，在一个相对较小的字段内搜索效率显然要高些。`,\n\t\t\tCase:     \"create table t1(id int,name char(20),last_time date)\",\n\t\t\tFunc:     (*Query4Audit).RuleVarcharVSChar,\n\t\t},\n\t\t\"COL.009\": {\n\t\t\tItem:     \"COL.009\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"建议使用精确的数据类型\",\n\t\t\tContent:  `实际上，任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时，非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。`,\n\t\t\tCase:     \"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))\",\n\t\t\tFunc:     (*Query4Audit).RuleImpreciseDataType,\n\t\t},\n\t\t\"COL.010\": {\n\t\t\tItem:     \"COL.010\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不建议使用 ENUM/BIT/SET 数据类型\",\n\t\t\tContent:  `ENUM 定义了列中值的类型，使用字符串表示 ENUM 里的值时，实际存储在列中的数据是这些值在定义时的序数。因此，这列的数据是字节对齐的，当您进行一次排序查询时，结果是按照实际存储的序数值排序的，而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值；您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项，您可能会为历史数据而烦恼。作为一种策略，改变元数据——也就是说，改变表和列的定义——应该是不常见的，并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表，每一行包含一个允许在列中出现的候选值；然后在引用新表的旧表上声明一个外键约束。`,\n\t\t\tCase:     \"create table tab1(status ENUM('new','in progress','fixed'))\",\n\t\t\tFunc:     (*Query4Audit).RuleValuesInDefinition,\n\t\t},\n\t\t// 这个建议从sqlcheck迁移来的，实际生产环境每条建表SQL都会给这条建议，看多了会不开心。\n\t\t\"COL.011\": {\n\t\t\tItem:     \"COL.011\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"当需要唯一约束时才使用 NULL，仅当列不能有缺失值时才使用 NOT NULL\",\n\t\t\tContent:  `NULL 和0是不同的，10乘以 NULL 还是 NULL。NULL 和空字符串是不一样的。将一个字符串和标准 SQL 中的 NULL 联合起来的结果还是 NULL。NULL 和 FALSE 也是不同的。AND、OR 和 NOT 这三个布尔操作如果涉及 NULL，其结果也让很多人感到困惑。当您将一列声明为 NOT NULL 时，也就是说这列中的每一个值都必须存在且是有意义的。使用 NULL 来表示任意类型不存在的空值。 当您将一列声明为 NOT NULL 时，也就是说这列中的每一个值都必须存在且是有意义的。`,\n\t\t\tCase:     \"select c1,c2,c3 from tbl where c4 is null or c4 <> 1\",\n\t\t\tFunc:     (*Query4Audit).RuleNullUsage,\n\t\t},\n\t\t\"COL.012\": {\n\t\t\tItem:     \"COL.012\",\n\t\t\tSeverity: \"L5\",\n\t\t\tSummary:  \"TEXT、BLOB 和 JSON 类型的字段不建议设置为 NOT NULL\",\n\t\t\tContent:  `TEXT、BLOB 和 JSON 类型的字段无法指定非 NULL 的默认值，如果添加了 NOT NULL 限制，写入数据时又未对该字段指定值可能导致写入失败。`,\n\t\t\tCase:     \"CREATE TABLE `tb`(`c` longblob NOT NULL);\",\n\t\t\tFunc:     (*Query4Audit).RuleBLOBNotNull,\n\t\t},\n\t\t\"COL.013\": {\n\t\t\tItem:     \"COL.013\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"TIMESTAMP 类型默认值检查异常\",\n\t\t\tContent:  `TIMESTAMP 类型建议设置默认值，且不建议使用 0 或 0000-00-00 00:00:00 作为默认值。可以考虑使用 1970-08-02 01:01:01`,\n\t\t\tCase:     \"CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);\",\n\t\t\tFunc:     (*Query4Audit).RuleTimestampDefault,\n\t\t},\n\t\t\"COL.014\": {\n\t\t\tItem:     \"COL.014\",\n\t\t\tSeverity: \"L5\",\n\t\t\tSummary:  \"为列指定了字符集\",\n\t\t\tContent:  `建议列与表使用同一个字符集，不要单独指定列的字符集。`,\n\t\t\tCase:     \"CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)\",\n\t\t\tFunc:     (*Query4Audit).RuleColumnWithCharset,\n\t\t},\n\t\t// https://stackoverflow.com/questions/3466872/why-cant-a-text-column-have-a-default-value-in-mysql\n\t\t\"COL.015\": {\n\t\t\tItem:     \"COL.015\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值\",\n\t\t\tContent:  `MySQL 数据库中 TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值。TEXT最大长度为2^16-1个字符，MEDIUMTEXT最大长度为2^32-1个字符，LONGTEXT最大长度为2^64-1个字符。`,\n\t\t\tCase:     \"CREATE TABLE `tbl` (`c` blob DEFAULT NULL);\",\n\t\t\tFunc:     (*Query4Audit).RuleBlobDefaultValue,\n\t\t},\n\t\t\"COL.016\": {\n\t\t\tItem:     \"COL.016\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"整型定义建议采用 INT(10) 或 BIGINT(20)\",\n\t\t\tContent:  `INT(M) 在 integer 数据类型中，M 表示最大显示宽度。 在 INT(M) 中，M 的值跟 INT(M) 所占多少存储空间并无任何关系。 INT(3)、INT(4)、INT(8) 在磁盘上都是占用 4 bytes 的存储空间。高版本 MySQL 已经不推荐设置整数显示宽度。`,\n\t\t\tCase:     \"CREATE TABLE tab (a INT(1));\",\n\t\t\tFunc:     (*Query4Audit).RuleIntPrecision,\n\t\t},\n\t\t\"COL.017\": {\n\t\t\tItem:     \"COL.017\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"VARCHAR 定义长度过长\",\n\t\t\tContent:  fmt.Sprintf(`varchar 是可变长字符串，不预先分配存储空间，长度不要超过%d，如果存储长度过长 MySQL 将定义字段类型为 text，独立出来一张表，用主键来对应，避免影响其它字段索引效率。`, common.Config.MaxVarcharLength),\n\t\t\tCase:     \"CREATE TABLE tab (a varchar(3500));\",\n\t\t\tFunc:     (*Query4Audit).RuleVarcharLength,\n\t\t},\n\t\t\"COL.018\": {\n\t\t\tItem:     \"COL.018\",\n\t\t\tSeverity: \"L9\",\n\t\t\tSummary:  \"建表语句中使用了不推荐的字段类型\",\n\t\t\tContent:  \"以下字段类型不被推荐使用：\" + strings.Join(common.Config.ColumnNotAllowType, \", \"),\n\t\t\tCase:     \"CREATE TABLE tab (a BOOLEAN);\",\n\t\t\tFunc:     (*Query4Audit).RuleColumnNotAllowType,\n\t\t},\n\t\t\"COL.019\": {\n\t\t\tItem:     \"COL.019\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用精度在秒级以下的时间数据类型\",\n\t\t\tContent:  \"使用高精度的时间数据类型带来的存储空间消耗相对较大；MySQL 在5.6.4以上才可以支持精确到微秒的时间数据类型，使用时需要考虑版本兼容问题。\",\n\t\t\tCase:     \"CREATE TABLE t1 (t TIME(3), dt DATETIME(6));\",\n\t\t\tFunc:     (*Query4Audit).RuleTimePrecision,\n\t\t},\n\t\t\"DIS.001\": {\n\t\t\tItem:     \"DIS.001\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"消除不必要的 DISTINCT 条件\",\n\t\t\tContent:  `太多DISTINCT条件是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少DISTINCT条件的数量。如果主键列是列的结果集的一部分，则DISTINCT条件可能没有影响。`,\n\t\t\tCase:     \"SELECT DISTINCT c.c_id,count(DISTINCT c.c_name),count(DISTINCT c.c_e),count(DISTINCT c.c_n),count(DISTINCT c.c_me),c.c_d FROM (select distinct id, name from B) as e WHERE e.country_id = c.country_id\",\n\t\t\tFunc:     (*Query4Audit).RuleDistinctUsage,\n\t\t},\n\t\t\"DIS.002\": {\n\t\t\tItem:     \"DIS.002\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"COUNT(DISTINCT) 多列时结果可能和你预想的不同\",\n\t\t\tContent:  `COUNT(DISTINCT col) 计算该列除NULL之外的不重复行数，注意 COUNT(DISTINCT col, col2) 如果其中一列全为 NULL 那么即使另一列有不同的值，也返回0。`,\n\t\t\tCase:     \"SELECT COUNT(DISTINCT col, col2) FROM tbl;\",\n\t\t\tFunc:     (*Query4Audit).RuleCountDistinctMultiCol,\n\t\t},\n\t\t// DIS.003 灵感来源于如下链接\n\t\t// http://www.ijstr.org/final-print/oct2015/Query-Optimization-Techniques-Tips-For-Writing-Efficient-And-Faster-Sql-Queries.pdf\n\t\t\"DIS.003\": {\n\t\t\tItem:     \"DIS.003\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"DISTINCT * 对有主键的表没有意义\",\n\t\t\tContent:  `当表已经有主键时，对所有列进行 DISTINCT 的输出结果与不进行 DISTINCT 操作的结果相同，请不要画蛇添足。`,\n\t\t\tCase:     \"SELECT DISTINCT * FROM film;\",\n\t\t\tFunc:     (*Query4Audit).RuleDistinctStar,\n\t\t},\n\t\t\"FUN.001\": {\n\t\t\tItem:     \"FUN.001\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"避免在 WHERE 条件中使用函数或其他运算符\",\n\t\t\tContent:  `虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。`,\n\t\t\tCase:     \"select id from t where substring(name,1,3)='abc'\",\n\t\t\tFunc:     (*Query4Audit).RuleCompareWithFunction,\n\t\t},\n\t\t\"FUN.002\": {\n\t\t\tItem:     \"FUN.002\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(*) 操作性能不佳\",\n\t\t\tContent:  `COUNT(*) 的作用是统计表行数，COUNT(COL) 的作用是统计指定列非 NULL 的行数。MyISAM 表对于 COUNT(*) 统计全表行数进行了特殊的优化，通常情况下非常快。但对于非 MyISAM 表或指定了某些 WHERE 条件，COUNT(*) 操作需要扫描大量的行才能获取精确的结果，性能也因此不佳。有时候某些业务场景并不需要完全精确的 COUNT 值，此时可以用近似值来代替。EXPLAIN 出来的优化器估算的行数就是一个不错的近似值，执行 EXPLAIN 并不需要真正去执行查询，所以成本很低。`,\n\t\t\tCase:     \"SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY num\",\n\t\t\tFunc:     (*Query4Audit).RuleCountStar,\n\t\t},\n\t\t\"FUN.003\": {\n\t\t\tItem:     \"FUN.003\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"使用了合并为可空列的字符串连接\",\n\t\t\tContent:  `在一些查询请求中，您需要强制让某一列或者某个表达式返回非 NULL 的值，从而让查询逻辑变得更简单，但又不想将这个值存下来。可以使用 COALESCE() 函数来构造连接的表达式，这样即使是空值列也不会使整表达式变为 NULL。`,\n\t\t\tCase:     \"select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl\",\n\t\t\tFunc:     (*Query4Audit).RuleStringConcatenation,\n\t\t},\n\t\t\"FUN.004\": {\n\t\t\tItem:     \"FUN.004\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"不建议使用 SYSDATE() 函数\",\n\t\t\tContent:  `SYSDATE() 函数可能导致主从数据不一致，请使用 NOW() 函数替代 SYSDATE()。`,\n\t\t\tCase:     \"SELECT SYSDATE();\",\n\t\t\tFunc:     (*Query4Audit).RuleSysdate,\n\t\t},\n\t\t\"FUN.005\": {\n\t\t\tItem:     \"FUN.005\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用 COUNT(col) 或 COUNT(常量)\",\n\t\t\tContent:  `不要使用 COUNT(col) 或 COUNT(常量) 来替代 COUNT(*), COUNT(*) 是 SQL92 定义的标准统计行数的方法，跟数据无关，跟 NULL 和非 NULL 也无关。`,\n\t\t\tCase:     \"SELECT COUNT(1) FROM tbl;\",\n\t\t\tFunc:     (*Query4Audit).RuleCountConst,\n\t\t},\n\t\t\"FUN.006\": {\n\t\t\tItem:     \"FUN.006\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"使用 SUM(COL) 时需注意 NPE 问题\",\n\t\t\tContent:  `当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl`,\n\t\t\tCase:     \"SELECT SUM(COL) FROM tbl;\",\n\t\t\tFunc:     (*Query4Audit).RuleSumNPE,\n\t\t},\n\t\t\"FUN.007\": {\n\t\t\tItem:     \"FUN.007\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用触发器\",\n\t\t\tContent:  `触发器的执行没有反馈和日志，隐藏了实际的执行步骤，当数据库出现问题是，不能通过慢日志分析触发器的具体执行情况，不易发现问题。在MySQL中，触发器不能临时关闭或打开，在数据迁移或数据恢复等场景下，需要临时drop触发器，可能影响到生产环境。`,\n\t\t\tCase:     \"CREATE TRIGGER t1 AFTER INSERT ON work FOR EACH ROW INSERT INTO time VALUES(NOW());\",\n\t\t\tFunc:     (*Query4Audit).RuleForbiddenTrigger,\n\t\t},\n\t\t\"FUN.008\": {\n\t\t\tItem:     \"FUN.008\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用存储过程\",\n\t\t\tContent:  `存储过程无版本控制，配合业务的存储过程升级很难做到业务无感知。存储过程在拓展和移植上也存在问题。`,\n\t\t\tCase:     \"CREATE PROCEDURE simpleproc (OUT param1 INT);\",\n\t\t\tFunc:     (*Query4Audit).RuleForbiddenProcedure,\n\t\t},\n\t\t\"FUN.009\": {\n\t\t\tItem:     \"FUN.009\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用自定义函数\",\n\t\t\tContent:  `不建议使用自定义函数`,\n\t\t\tCase:     \"CREATE FUNCTION hello (s CHAR(20));\",\n\t\t\tFunc:     (*Query4Audit).RuleForbiddenFunction,\n\t\t},\n\t\t\"GRP.001\": {\n\t\t\tItem:     \"GRP.001\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不建议对等值查询列使用 GROUP BY\",\n\t\t\tContent:  `GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询，对这样的列进行 GROUP BY 意义不大。`,\n\t\t\tCase:     \"select film_id, title from film where release_year='2006' group by release_year\",\n\t\t\tFunc:     (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 RuleGroupByConst\n\t\t},\n\t\t\"JOI.001\": {\n\t\t\tItem:     \"JOI.001\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"JOIN 语句混用逗号和 ANSI 模式\",\n\t\t\tContent:  `表连接的时候混用逗号和 ANSI JOIN 不便于人类理解，并且MySQL不同版本的表连接行为和优先级均有所不同，当 MySQL 版本变化后可能会引入错误。`,\n\t\t\tCase:     \"select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000\",\n\t\t\tFunc:     (*Query4Audit).RuleCommaAnsiJoin,\n\t\t},\n\t\t\"JOI.002\": {\n\t\t\tItem:     \"JOI.002\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"同一张表被连接两次\",\n\t\t\tContent:  `相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。`,\n\t\t\tCase:     \"select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1\",\n\t\t\tFunc:     (*Query4Audit).RuleDupJoin,\n\t\t},\n\t\t\"JOI.003\": {\n\t\t\tItem:     \"JOI.003\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"OUTER JOIN 失效\",\n\t\t\tContent:  `由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回，这会将查询隐式转换为 INNER JOIN 。如：select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解，因为 LEFT/RIGHT JOIN 是 LEFT/RIGHT OUTER JOIN 的缩写。`,\n\t\t\tCase:     \"select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4\",\n\t\t\tFunc:     (*Query4Audit).RuleOK, // TODO\n\t\t},\n\t\t\"JOI.004\": {\n\t\t\tItem:     \"JOI.004\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"不建议使用排它 JOIN\",\n\t\t\tContent:  `只在右侧表为 NULL 的带 WHERE 子句的 LEFT OUTER JOIN 语句，有可能是在WHERE子句中使用错误的列，如：“... FROM l LEFT OUTER JOIN r ON l.l = r.r WHERE r.z IS NULL”，这个查询正确的逻辑可能是 WHERE r.r IS NULL。`,\n\t\t\tCase:     \"select c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null\",\n\t\t\tFunc:     (*Query4Audit).RuleOK, // TODO\n\t\t},\n\t\t\"JOI.005\": {\n\t\t\tItem:     \"JOI.005\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"减少 JOIN 的数量\",\n\t\t\tContent:  `太多的 JOIN 是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少 JOIN 的数量。`,\n\t\t\tCase:     \"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\",\n\t\t\tFunc:     (*Query4Audit).RuleReduceNumberOfJoin,\n\t\t},\n\t\t\"JOI.006\": {\n\t\t\tItem:     \"JOI.006\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"将嵌套查询重写为 JOIN 通常会导致更高效的执行和更有效的优化\",\n\t\t\tContent:  `一般来说，非嵌套子查询总是用于关联子查询，最多是来自FROM子句中的一个表，这些子查询用于 ANY, ALL 和 EXISTS 的谓词。如果可以根据查询语义决定子查询最多返回一个行，那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。`,\n\t\t\tCase:     \"SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 100996 AND s.q = 1 )\",\n\t\t\tFunc:     (*Query4Audit).RuleNestedSubQueries,\n\t\t},\n\t\t\"JOI.007\": {\n\t\t\tItem:     \"JOI.007\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"不建议使用联表删除或更新\",\n\t\t\tContent:  `当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。`,\n\t\t\tCase:     \"UPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano';\",\n\t\t\tFunc:     (*Query4Audit).RuleMultiDeleteUpdate,\n\t\t},\n\t\t\"JOI.008\": {\n\t\t\tItem:     \"JOI.008\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"不要使用跨数据库的 JOIN 查询\",\n\t\t\tContent:  `一般来说，跨数据库的 JOIN 查询意味着查询语句跨越了两个不同的子系统，这可能意味着系统耦合度过高或库表结构设计不合理。`,\n\t\t\tCase:     \"SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 100996 AND s.q = 1 )\",\n\t\t\tFunc:     (*Query4Audit).RuleMultiDBJoin,\n\t\t},\n\t\t// TODO: 跨库事务的检查，目前SOAR未对事务做处理\n\t\t\"KEY.001\": {\n\t\t\tItem:     \"KEY.001\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"建议使用自增列作为主键，如使用联合自增主键时请将自增键作为第一列\",\n\t\t\tContent:  `建议使用自增列作为主键，如使用联合自增主键时请将自增键作为第一列`,\n\t\t\tCase:     \"create table test(`id` int(11) NOT NULL PRIMARY KEY (`id`))\",\n\t\t\tFunc:     (*Query4Audit).RulePKNotInt,\n\t\t},\n\t\t\"KEY.002\": {\n\t\t\tItem:     \"KEY.002\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"无主键或唯一键，无法在线变更表结构\",\n\t\t\tContent:  `无主键或唯一键，无法在线变更表结构`,\n\t\t\tCase:     \"create table test(col varchar(5000))\",\n\t\t\tFunc:     (*Query4Audit).RuleNoOSCKey,\n\t\t},\n\t\t\"KEY.003\": {\n\t\t\tItem:     \"KEY.003\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"避免外键等递归关系\",\n\t\t\tContent:  `存在递归关系的数据很常见，数据常会像树或者以层级方式组织。然而，创建一个外键约束来强制执行同一表中两列之间的关系，会导致笨拙的查询。树的每一层对应着另一个连接。您将需要发出递归查询，以获得节点的所有后代或所有祖先。解决方案是构造一个附加的闭包表。它记录了树中所有节点间的关系，而不仅仅是那些具有直接的父子关系。您也可以比较不同层次的数据设计：闭包表，路径枚举，嵌套集。然后根据应用程序的需要选择一个。`,\n\t\t\tCase:     \"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))\",\n\t\t\tFunc:     (*Query4Audit).RuleRecursiveDependency,\n\t\t},\n\t\t// TODO: 新增复合索引，字段按散粒度是否由大到小排序，区分度最高的在最左边\n\t\t\"KEY.004\": {\n\t\t\tItem:     \"KEY.004\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"提醒：请将索引属性顺序与查询对齐\",\n\t\t\tContent:  `如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。`,\n\t\t\tCase:     \"create index idx1 on tbl (last_name,first_name)\",\n\t\t\tFunc:     (*Query4Audit).RuleIndexAttributeOrder,\n\t\t},\n\t\t\"KEY.005\": {\n\t\t\tItem:     \"KEY.005\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"表建的索引过多\",\n\t\t\tContent:  `表建的索引过多`,\n\t\t\tCase:     \"CREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c(`c`));\",\n\t\t\tFunc:     (*Query4Audit).RuleTooManyKeys,\n\t\t},\n\t\t\"KEY.006\": {\n\t\t\tItem:     \"KEY.006\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"主键中的列过多\",\n\t\t\tContent:  `主键中的列过多`,\n\t\t\tCase:     \"CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));\",\n\t\t\tFunc:     (*Query4Audit).RuleTooManyKeyParts,\n\t\t},\n\t\t\"KEY.007\": {\n\t\t\tItem:     \"KEY.007\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"未指定主键或主键非 int 或 bigint\",\n\t\t\tContent:  `未指定主键或主键非 int 或 bigint，建议将主键设置为 int unsigned 或 bigint unsigned。`,\n\t\t\tCase:     \"CREATE TABLE tbl (a int);\",\n\t\t\tFunc:     (*Query4Audit).RulePKNotInt,\n\t\t},\n\t\t\"KEY.008\": {\n\t\t\tItem:     \"KEY.008\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"ORDER BY 多个列但排序方向不同时可能无法使用索引\",\n\t\t\tContent:  `在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。`,\n\t\t\tCase:     \"SELECT * FROM tbl ORDER BY a DESC, b ASC;\",\n\t\t\tFunc:     (*Query4Audit).RuleOrderByMultiDirection,\n\t\t},\n\t\t\"KEY.009\": {\n\t\t\tItem:     \"KEY.009\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"添加唯一索引前请注意检查数据唯一性\",\n\t\t\tContent:  `请提前检查添加唯一索引列的数据唯一性，如果数据不唯一在线表结构调整时将有可能自动将重复列删除，这有可能导致数据丢失。`,\n\t\t\tCase:     \"CREATE UNIQUE INDEX part_of_name ON customer (name(10));\",\n\t\t\tFunc:     (*Query4Audit).RuleUniqueKeyDup,\n\t\t},\n\t\t\"KEY.010\": {\n\t\t\tItem:     \"KEY.010\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"全文索引不是银弹\",\n\t\t\tContent:  `全文索引主要用于解决模糊查询的性能问题，但需要控制好查询的频率和并发度。同时注意调整 ft_min_word_len, ft_max_word_len, ngram_token_size 等参数。`,\n\t\t\tCase:     \"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) ) ENGINE=InnoDB;\",\n\t\t\tFunc:     (*Query4Audit).RuleFulltextIndex,\n\t\t},\n\t\t\"KWR.001\": {\n\t\t\tItem:     \"KWR.001\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"SQL_CALC_FOUND_ROWS 效率低下\",\n\t\t\tContent:  `因为 SQL_CALC_FOUND_ROWS 不能很好地扩展，所以可能导致性能问题; 建议业务使用其他策略来替代 SQL_CALC_FOUND_ROWS 提供的计数功能，比如：分页结果展示等。`,\n\t\t\tCase:     \"select SQL_CALC_FOUND_ROWS col from tbl where id>1000\",\n\t\t\tFunc:     (*Query4Audit).RuleSQLCalcFoundRows,\n\t\t},\n\t\t\"KWR.002\": {\n\t\t\tItem:     \"KWR.002\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不建议使用 MySQL 关键字做列名或表名\",\n\t\t\tContent:  `当使用关键字做为列名或表名时程序需要对列名和表名进行转义，如果疏忽被将导致请求无法执行。`,\n\t\t\tCase:     \"CREATE TABLE tbl ( `select` int )\",\n\t\t\tFunc:     (*Query4Audit).RuleUseKeyWord,\n\t\t},\n\t\t\"KWR.003\": {\n\t\t\tItem:     \"KWR.003\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用复数做列名或表名\",\n\t\t\tContent:  `表名应该仅仅表示表里面的实体内容，不应该表示实体数量，对应于 DO 类名也是单数形式，符合表达习惯。`,\n\t\t\tCase:     \"CREATE TABLE tbl ( `books` int )\",\n\t\t\tFunc:     (*Query4Audit).RulePluralWord,\n\t\t},\n\t\t\"KWR.004\": {\n\t\t\tItem:     \"KWR.004\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用使用多字节编码字符(中文)命名\",\n\t\t\tContent:  `为库、表、列、别名命名时建议使用英文，数字，下划线等字符，不建议使用中文或其他多字节编码字符。`,\n\t\t\tCase:     \"select col as 列 from tb\",\n\t\t\tFunc:     (*Query4Audit).RuleMultiBytesWord,\n\t\t},\n\t\t\"KWR.005\": {\n\t\t\tItem:     \"KWR.005\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"SQL 中包含 unicode 特殊字符\",\n\t\t\tContent:  \"部分 IDE 会自动在 SQL 插入肉眼不可见的 unicode 字符。如：non-break space, zero-width space 等。Linux 下可使用 `cat -A file.sql` 命令查看不可见字符。\",\n\t\t\tCase:     \"update tb set status = 1 where id = 1;\",\n\t\t\tFunc:     (*Query4Audit).RuleInvisibleUnicode,\n\t\t},\n\t\t\"LCK.001\": {\n\t\t\tItem:     \"LCK.001\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"INSERT INTO xx SELECT 加锁粒度较大请谨慎\",\n\t\t\tContent:  `INSERT INTO xx SELECT 加锁粒度较大请谨慎`,\n\t\t\tCase:     \"INSERT INTO tbl SELECT * FROM tbl2;\",\n\t\t\tFunc:     (*Query4Audit).RuleInsertSelect,\n\t\t},\n\t\t\"LCK.002\": {\n\t\t\tItem:     \"LCK.002\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"请慎用 INSERT ON DUPLICATE KEY UPDATE\",\n\t\t\tContent:  `当主键为自增键时使用 INSERT ON DUPLICATE KEY UPDATE 可能会导致主键出现大量不连续快速增长，导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。`,\n\t\t\tCase:     \"INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;\",\n\t\t\tFunc:     (*Query4Audit).RuleInsertOnDup,\n\t\t},\n\t\t\"LIT.001\": {\n\t\t\tItem:     \"LIT.001\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"用字符类型存储IP地址\",\n\t\t\tContent:  `字符串字面上看起来像IP地址，但不是 INET_ATON() 的参数，表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。`,\n\t\t\tCase:     \"insert into tbl (IP,name) values('10.20.306.122','test')\",\n\t\t\tFunc:     (*Query4Audit).RuleIPString,\n\t\t},\n\t\t\"LIT.002\": {\n\t\t\tItem:     \"LIT.002\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"日期/时间未使用引号括起\",\n\t\t\tContent:  `诸如“WHERE col <2010-02-12”之类的查询是有效的SQL，但可能是一个错误，因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号，且引号前后不应有空格。`,\n\t\t\tCase:     \"select col1,col2 from tbl where time < 2018-01-10\",\n\t\t\tFunc:     (*Query4Audit).RuleDateNotQuote,\n\t\t},\n\t\t\"LIT.003\": {\n\t\t\tItem:     \"LIT.003\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"一列中存储一系列相关数据的集合\",\n\t\t\tContent:  `将 ID 存储为一个列表，作为 VARCHAR/TEXT 列，这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证 ID 更加困难。考虑一下，列表最多支持存放多少数据呢？将 ID 存储在一张单独的表中，代替使用多值属性，从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询，也更有效地验证ID。`,\n\t\t\tCase:     \"select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'\",\n\t\t\tFunc:     (*Query4Audit).RuleMultiValueAttribute,\n\t\t},\n\t\t\"LIT.004\": {\n\t\t\tItem:     \"LIT.004\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"请使用分号或已设定的 DELIMITER 结尾\",\n\t\t\tContent:  `USE database, SHOW DATABASES 等命令也需要使用使用分号或已设定的 DELIMITER 结尾。`,\n\t\t\tCase:     \"USE db\",\n\t\t\tFunc:     (*Query4Audit).RuleOK, // TODO: RuleAddDelimiter\n\t\t},\n\t\t\"RES.001\": {\n\t\t\tItem:     \"RES.001\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"非确定性的 GROUP BY\",\n\t\t\tContent:  `SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。`,\n\t\t\tCase:     \"select c1,c2,c3 from t1 where c2='foo' group by c2\",\n\t\t\tFunc:     (*Query4Audit).RuleNoDeterministicGroupby,\n\t\t},\n\t\t\"RES.002\": {\n\t\t\tItem:     \"RES.002\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"未使用 ORDER BY 的 LIMIT 查询\",\n\t\t\tContent:  `没有 ORDER BY 的 LIMIT 会导致非确定性的结果，这取决于查询执行计划。`,\n\t\t\tCase:     \"select col1,col2 from tbl where name=xx limit 10\",\n\t\t\tFunc:     (*Query4Audit).RuleNoDeterministicLimit,\n\t\t},\n\t\t\"RES.003\": {\n\t\t\tItem:     \"RES.003\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"UPDATE/DELETE 操作使用了 LIMIT 条件\",\n\t\t\tContent:  `UPDATE/DELETE 操作使用 LIMIT 条件和不添加 WHERE 条件一样危险，它可将会导致主从数据不一致或从库同步中断。`,\n\t\t\tCase:     \"UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;\",\n\t\t\tFunc:     (*Query4Audit).RuleUpdateDeleteWithLimit,\n\t\t},\n\t\t\"RES.004\": {\n\t\t\tItem:     \"RES.004\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"UPDATE/DELETE 操作指定了 ORDER BY 条件\",\n\t\t\tContent:  `UPDATE/DELETE 操作不要指定 ORDER BY 条件。`,\n\t\t\tCase:     \"UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title\",\n\t\t\tFunc:     (*Query4Audit).RuleUpdateDeleteWithOrderby,\n\t\t},\n\t\t\"RES.005\": {\n\t\t\tItem:     \"RES.005\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"UPDATE 语句可能存在逻辑错误，导致数据损坏\",\n\t\t\tContent:  \"在一条 UPDATE 语句中，如果要更新多个字段，字段间不能使用 AND ，而应该用逗号分隔。\",\n\t\t\tCase:     \"update tbl set col = 1 and cl = 2 where col=3;\",\n\t\t\tFunc:     (*Query4Audit).RuleUpdateSetAnd,\n\t\t},\n\t\t\"RES.006\": {\n\t\t\tItem:     \"RES.006\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"永远不真的比较条件\",\n\t\t\tContent:  \"查询条件永远非真，如果该条件出现在 where 中可能导致查询无匹配到的结果。\",\n\t\t\tCase:     \"select * from tbl where 1 != 1;\",\n\t\t\tFunc:     (*Query4Audit).RuleImpossibleWhere,\n\t\t},\n\t\t\"RES.007\": {\n\t\t\tItem:     \"RES.007\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"永远为真的比较条件\",\n\t\t\tContent:  \"查询条件永远为真，可能导致 WHERE 条件失效进行全表查询。\",\n\t\t\tCase:     \"select * from tbl where 1 = 1;\",\n\t\t\tFunc:     (*Query4Audit).RuleMeaninglessWhere,\n\t\t},\n\t\t\"RES.008\": {\n\t\t\tItem:     \"RES.008\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不建议使用LOAD DATA/SELECT ... INTO OUTFILE\",\n\t\t\tContent:  \"SELECT INTO OUTFILE 需要授予 FILE 权限，这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度，但同时也可能导致从库同步延迟过大。\",\n\t\t\tCase:     \"LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;\",\n\t\t\tFunc:     (*Query4Audit).RuleLoadFile,\n\t\t},\n\t\t\"RES.009\": {\n\t\t\tItem:     \"RES.009\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不建议使用连续判断\",\n\t\t\tContent:  \"类似这样的 SELECT * FROM tbl WHERE col = col = 'abc' 语句可能是书写错误，您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。\",\n\t\t\tCase:     \"SELECT * FROM tbl WHERE col = col = 'abc'\",\n\t\t\tFunc:     (*Query4Audit).RuleMultiCompare,\n\t\t},\n\t\t\"RES.010\": {\n\t\t\tItem:     \"RES.010\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"建表语句中定义为 ON UPDATE CURRENT_TIMESTAMP 的字段不建议包含业务逻辑\",\n\t\t\tContent:  \"定义为 ON UPDATE CURRENT_TIMESTAMP 的字段在该表其他字段更新时会联动修改，如果包含业务逻辑用户可见会埋下隐患。后续如有批量修改数据却又不想修改该字段时会导致数据错误。\",\n\t\t\tCase:     `CREATE TABLE category (category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,\tname VARCHAR(25) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY  (category_id)`,\n\t\t\tFunc:     (*Query4Audit).RuleCreateOnUpdate,\n\t\t},\n\t\t\"RES.011\": {\n\t\t\tItem:     \"RES.011\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"更新请求操作的表包含 ON UPDATE CURRENT_TIMESTAMP 字段\",\n\t\t\tContent:  \"定义为 ON UPDATE CURRENT_TIMESTAMP 的字段在该表其他字段更新时会联动修改，请注意检查。如不想修改字段的更新时间可以使用如下方法：UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1\",\n\t\t\tCase:     \"UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1\",\n\t\t\tFunc:     (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 RuleUpdateOnUpdate\n\t\t},\n\t\t\"SEC.001\": {\n\t\t\tItem:     \"SEC.001\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"请谨慎使用TRUNCATE操作\",\n\t\t\tContent:  `一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl_name;语句。但TRUNCATE操作也并非是毫无代价的，TRUNCATE TABLE无法返回被删除的准确行数，如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO_INCREMENT，如果不想重置该值建议使用 DELETE FROM tbl_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL)，当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求，因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。`,\n\t\t\tCase:     \"TRUNCATE TABLE tbl_name\",\n\t\t\tFunc:     (*Query4Audit).RuleTruncateTable,\n\t\t},\n\t\t\"SEC.002\": {\n\t\t\tItem:     \"SEC.002\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"不使用明文存储密码\",\n\t\t\tContent:  `使用明文存储密码或者使用明文在网络上传递密码都是不安全的。如果攻击者能够截获您用来插入密码的SQL语句，他们就能直接读到密码。另外，将用户输入的字符串以明文的形式插入到纯SQL语句中，也会让攻击者发现它。如果您能够读取密码，黑客也可以。解决方案是使用单向哈希函数对原始密码进行加密编码。哈希是指将输入字符串转化成另一个新的、不可识别的字符串的函数。对密码加密表达式加点随机串来防御“字典攻击”。不要将明文密码输入到SQL查询语句中。在应用程序代码中计算哈希串，只在SQL查询中使用哈希串。`,\n\t\t\tCase:     \"create table test(id int,name varchar(20) not null,password varchar(200)not null)\",\n\t\t\tFunc:     (*Query4Audit).RuleReadablePasswords,\n\t\t},\n\t\t\"SEC.003\": {\n\t\t\tItem:     \"SEC.003\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"使用DELETE/DROP/TRUNCATE等操作时注意备份\",\n\t\t\tContent:  `在执行高危操作之前对数据进行备份是十分有必要的。`,\n\t\t\tCase:     \"delete from table where col = 'condition'\",\n\t\t\tFunc:     (*Query4Audit).RuleDataDrop,\n\t\t},\n\t\t\"SEC.004\": {\n\t\t\tItem:     \"SEC.004\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"发现常见 SQL 注入函数\",\n\t\t\tContent:  `SLEEP(), BENCHMARK(), GET_LOCK(), RELEASE_LOCK() 等函数通常出现在 SQL 注入语句中，会严重影响数据库性能。`,\n\t\t\tCase:     \"SELECT BENCHMARK(10, RAND())\",\n\t\t\tFunc:     (*Query4Audit).RuleInjection,\n\t\t},\n\t\t\"STA.001\": {\n\t\t\tItem:     \"STA.001\",\n\t\t\tSeverity: \"L0\",\n\t\t\tSummary:  \"'!=' 运算符是非标准的\",\n\t\t\tContent:  `\"<>\"才是标准SQL中的不等于运算符。`,\n\t\t\tCase:     \"select col1,col2 from tbl where type!=0\",\n\t\t\tFunc:     (*Query4Audit).RuleStandardINEQ,\n\t\t},\n\t\t\"STA.002\": {\n\t\t\tItem:     \"STA.002\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"库名或表名点后建议不要加空格\",\n\t\t\tContent:  `当使用 db.table 或 table.column 格式访问表或字段时，请不要在点号后面添加空格，虽然这样语法正确。`,\n\t\t\tCase:     \"select col from sakila. film\",\n\t\t\tFunc:     (*Query4Audit).RuleSpaceAfterDot,\n\t\t},\n\t\t\"STA.003\": {\n\t\t\tItem:     \"STA.003\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"索引起名不规范\",\n\t\t\tContent:  `建议普通二级索引以` + common.Config.IdxPrefix + `为前缀，唯一索引以` + common.Config.UkPrefix + `为前缀。`,\n\t\t\tCase:     \"select col from now where type!=0\",\n\t\t\tFunc:     (*Query4Audit).RuleIdxPrefix,\n\t\t},\n\t\t\"STA.004\": {\n\t\t\tItem:     \"STA.004\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"起名时请不要使用字母、数字和下划线之外的字符\",\n\t\t\tContent:  `以字母或下划线开头，名字只允许使用字母、数字和下划线。请统一大小写，不要使用驼峰命名法。不要在名字中出现连续下划线'__'，这样很难辨认。`,\n\t\t\tCase:     \"CREATE TABLE ` abc` (a int);\",\n\t\t\tFunc:     (*Query4Audit).RuleStandardName,\n\t\t},\n\t\t\"SUB.001\": {\n\t\t\tItem:     \"SUB.001\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"MySQL 对子查询的优化效果不佳\",\n\t\t\tContent:  `MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。`,\n\t\t\tCase:     \"select col1,col2,col3 from table1 where col2 in(select col from table2)\",\n\t\t\tFunc:     (*Query4Audit).RuleInSubquery,\n\t\t},\n\t\t\"SUB.002\": {\n\t\t\tItem:     \"SUB.002\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\",\n\t\t\tContent:  `与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。`,\n\t\t\tCase:     \"select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id\",\n\t\t\tFunc:     (*Query4Audit).RuleUNIONUsage,\n\t\t},\n\t\t\"SUB.003\": {\n\t\t\tItem:     \"SUB.003\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"考虑使用 EXISTS 而不是 DISTINCT 子查询\",\n\t\t\tContent:  `DISTINCT 关键字在对元组排序后删除重复。相反，考虑使用一个带有 EXISTS 关键字的子查询，您可以避免返回整个表。`,\n\t\t\tCase:     \"SELECT DISTINCT c.c_id, c.c_name FROM c,e WHERE e.c_id = c.c_id\",\n\t\t\tFunc:     (*Query4Audit).RuleDistinctJoinUsage,\n\t\t},\n\t\t// TODO: 5.6有了semi join 还要把 in 转成 exists 么？\n\t\t// Use EXISTS instead of IN to check existence of data.\n\t\t// http://www.winwire.com/25-tips-to-improve-sql-query-performance/\n\t\t\"SUB.004\": {\n\t\t\tItem:     \"SUB.004\",\n\t\t\tSeverity: \"L3\",\n\t\t\tSummary:  \"执行计划中嵌套连接深度过深\",\n\t\t\tContent:  `MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。`,\n\t\t\tCase:     \"SELECT * from tb where id in (select id from (select id from tb))\",\n\t\t\tFunc:     (*Query4Audit).RuleSubqueryDepth,\n\t\t},\n\t\t// SUB.005灵感来自 https://blog.csdn.net/zhuocr/article/details/61192418\n\t\t\"SUB.005\": {\n\t\t\tItem:     \"SUB.005\",\n\t\t\tSeverity: \"L8\",\n\t\t\tSummary:  \"子查询不支持LIMIT\",\n\t\t\tContent:  `当前 MySQL 版本不支持在子查询中进行 'LIMIT & IN/ALL/ANY/SOME'。`,\n\t\t\tCase:     \"SELECT * FROM staff WHERE name IN (SELECT NAME FROM customer ORDER BY name LIMIT 1)\",\n\t\t\tFunc:     (*Query4Audit).RuleSubQueryLimit,\n\t\t},\n\t\t\"SUB.006\": {\n\t\t\tItem:     \"SUB.006\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"不建议在子查询中使用函数\",\n\t\t\tContent:  `MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。`,\n\t\t\tCase:     \"SELECT * FROM staff WHERE name IN (SELECT max(NAME) FROM customer)\",\n\t\t\tFunc:     (*Query4Audit).RuleSubQueryFunctions,\n\t\t},\n\t\t\"SUB.007\": {\n\t\t\tItem:     \"SUB.007\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"外层带有 LIMIT 输出限制的 UNION 联合查询，其内层查询建议也添加 LIMIT 输出限制\",\n\t\t\tContent:  `有时 MySQL 无法将限制条件从外层“下推”到内层，这会使得原本可以限制能够限制部分返回结果的条件无法应用到内层查询的优化上。比如：(SELECT * FROM tb1 ORDER BY name) UNION ALL (SELECT * FROM tb2 ORDER BY name) LIMIT 20;  MySQL 会将两个子查询的结果放在一个临时表中，然后取出 20 条结果，可以通过在两个子查询中添加 LIMIT 20 来减少临时表中的数据。(SELECT * FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT * FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;`,\n\t\t\tCase:     \"(SELECT * FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT * FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;\",\n\t\t\tFunc:     (*Query4Audit).RuleUNIONLimit,\n\t\t},\n\t\t\"TBL.001\": {\n\t\t\tItem:     \"TBL.001\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"不建议使用分区表\",\n\t\t\tContent:  `不建议使用分区表`,\n\t\t\tCase:     \"CREATE TABLE trb3(id INT, name VARCHAR(50), purchased DATE) PARTITION BY RANGE(YEAR(purchased)) (PARTITION p0 VALUES LESS THAN (1990), PARTITION p1 VALUES LESS THAN (1995), PARTITION p2 VALUES LESS THAN (2000), PARTITION p3 VALUES LESS THAN (2005) );\",\n\t\t\tFunc:     (*Query4Audit).RulePartitionNotAllowed,\n\t\t},\n\t\t\"TBL.002\": {\n\t\t\tItem:     \"TBL.002\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"请为表选择合适的存储引擎\",\n\t\t\tContent:  `建表或修改表的存储引擎时建议使用推荐的存储引擎，如：` + strings.Join(common.Config.AllowEngines, \",\"),\n\t\t\tCase:     \"create table test(`id` int(11) NOT NULL AUTO_INCREMENT)\",\n\t\t\tFunc:     (*Query4Audit).RuleAllowEngine,\n\t\t},\n\t\t\"TBL.003\": {\n\t\t\tItem:     \"TBL.003\",\n\t\t\tSeverity: \"L8\",\n\t\t\tSummary:  \"以DUAL命名的表在数据库中有特殊含义\",\n\t\t\tContent:  `DUAL表为虚拟表，不需要创建即可使用，也不建议服务以DUAL命名表。`,\n\t\t\tCase:     \"create table dual(id int, primary key (id));\",\n\t\t\tFunc:     (*Query4Audit).RuleCreateDualTable,\n\t\t},\n\t\t\"TBL.004\": {\n\t\t\tItem:     \"TBL.004\",\n\t\t\tSeverity: \"L2\",\n\t\t\tSummary:  \"表的初始AUTO_INCREMENT值不为0\",\n\t\t\tContent:  `AUTO_INCREMENT不为0会导致数据空洞。`,\n\t\t\tCase:     \"CREATE TABLE tbl (a int) AUTO_INCREMENT = 10;\",\n\t\t\tFunc:     (*Query4Audit).RuleAutoIncrementInitNotZero,\n\t\t},\n\t\t\"TBL.005\": {\n\t\t\tItem:     \"TBL.005\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"请使用推荐的字符集\",\n\t\t\tContent:  `表字符集只允许设置为'` + strings.Join(common.Config.AllowCharsets, \",\") + \"'\",\n\t\t\tCase:     \"CREATE TABLE tbl (a int) DEFAULT CHARSET = latin1;\",\n\t\t\tFunc:     (*Query4Audit).RuleTableCharsetCheck,\n\t\t},\n\t\t\"TBL.006\": {\n\t\t\tItem:     \"TBL.006\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用视图\",\n\t\t\tContent:  `不建议使用视图`,\n\t\t\tCase:     \"create view v_today (today) AS SELECT CURRENT_DATE;\",\n\t\t\tFunc:     (*Query4Audit).RuleForbiddenView,\n\t\t},\n\t\t\"TBL.007\": {\n\t\t\tItem:     \"TBL.007\",\n\t\t\tSeverity: \"L1\",\n\t\t\tSummary:  \"不建议使用临时表\",\n\t\t\tContent:  `不建议使用临时表`,\n\t\t\tCase:     \"CREATE TEMPORARY TABLE `work` (`time` time DEFAULT NULL) ENGINE=InnoDB;\",\n\t\t\tFunc:     (*Query4Audit).RuleForbiddenTempTable,\n\t\t},\n\t\t\"TBL.008\": {\n\t\t\tItem:     \"TBL.008\",\n\t\t\tSeverity: \"L4\",\n\t\t\tSummary:  \"请使用推荐的COLLATE\",\n\t\t\tContent:  `COLLATE 只允许设置为'` + strings.Join(common.Config.AllowCollates, \",\") + \"'\",\n\t\t\tCase:     \"CREATE TABLE tbl (a int) DEFAULT COLLATE = latin1_bin;\",\n\t\t\tFunc:     (*Query4Audit).RuleTableCharsetCheck,\n\t\t},\n\t}\n}\n\n// IsIgnoreRule 判断是否是过滤规则\n// 支持XXX*前缀匹配，OK规则不可设置过滤\nfunc IsIgnoreRule(item string) bool {\n\n\tfor _, ir := range common.Config.IgnoreRules {\n\t\tir = strings.Trim(ir, \"*\")\n\t\tif strings.HasPrefix(item, ir) && ir != \"OK\" && ir != \"\" {\n\t\t\tcommon.Log.Debug(\"IsIgnoreRule: %s\", item)\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// InBlackList 判断一条请求是否在黑名单列表中\n// 如果在返回true，表示不需要评审\n// 注意这里没有做指纹判断，是否用指纹在这个函数的外面处理\nfunc InBlackList(sql string) bool {\n\tin := false\n\tfor _, r := range common.BlackList {\n\t\tif sql == r {\n\t\t\tin = true\n\t\t\tbreak\n\t\t}\n\t\tre, err := regexp.Compile(\"(?i)\" + r)\n\t\tif err == nil {\n\t\t\tif re.FindString(sql) != \"\" {\n\t\t\t\tcommon.Log.Debug(\"InBlackList: true, regexp: %s, sql: %s\", \"(?i)\"+r, sql)\n\t\t\t\tin = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcommon.Log.Debug(\"InBlackList: false, regexp: %s, sql: %s\", \"(?i)\"+r, sql)\n\t\t}\n\t}\n\treturn in\n}\n\n// FormatSuggest 格式化输出优化建议\nfunc FormatSuggest(sql string, currentDB string, format string, suggests ...map[string]Rule) (map[string]Rule, string) {\n\tcommon.Log.Debug(\"FormatSuggest, Query: %s\", sql)\n\tvar fingerprint, id string\n\tvar buf []string\n\tvar score = 100\n\ttype Result struct {\n\t\tID          string\n\t\tFingerprint string\n\t\tSample      string\n\t\tSuggest     map[string]Rule\n\t}\n\n\t// 生成指纹和ID\n\tif sql != \"\" {\n\t\tfingerprint = query.Fingerprint(sql)\n\t\tid = query.Id(fingerprint)\n\t}\n\n\t// 合并重复的建议\n\tsuggest := make(map[string]Rule)\n\tfor _, s := range suggests {\n\t\tfor item, rule := range s {\n\t\t\tsuggest[item] = rule\n\t\t}\n\t}\n\tsuggest = MergeConflictHeuristicRules(suggest)\n\n\t// 是否忽略显示OK建议，测试的时候大家都喜欢看OK，线上跑起来的时候OK太多反而容易看花眼\n\tignoreOK := false\n\tfor _, r := range common.Config.IgnoreRules {\n\t\tif \"OK\" == r {\n\t\t\tignoreOK = true\n\t\t}\n\t}\n\n\t// 先保证suggest中有元素，然后再根据ignore配置删除不需要的项\n\tif len(suggest) < 1 {\n\t\tsuggest = map[string]Rule{\"OK\": HeuristicRules[\"OK\"]}\n\t}\n\tif ignoreOK || len(suggest) > 1 {\n\t\tdelete(suggest, \"OK\")\n\t}\n\tfor k := range suggest {\n\t\tif IsIgnoreRule(k) {\n\t\t\tdelete(suggest, k)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"FormatSuggest, format: %s\", format)\n\tswitch format {\n\tcase \"json\":\n\t\tbuf = append(buf, formatJSON(sql, currentDB, suggest))\n\n\tcase \"text\":\n\t\tfor item, rule := range suggest {\n\t\t\tbuf = append(buf, fmt.Sprintln(\"Query: \", sql))\n\t\t\tbuf = append(buf, fmt.Sprintln(\"ID: \", id))\n\t\t\tbuf = append(buf, fmt.Sprintln(\"Item: \", item))\n\t\t\tbuf = append(buf, fmt.Sprintln(\"Severity: \", rule.Severity))\n\t\t\tbuf = append(buf, fmt.Sprintln(\"Summary: \", rule.Summary))\n\t\t\tbuf = append(buf, fmt.Sprintln(\"Content: \", rule.Content))\n\t\t}\n\tcase \"lint\":\n\t\tfor item, rule := range suggest {\n\t\t\t// lint 中无需关注 OK 和 EXP\n\t\t\tif item != \"OK\" && !strings.HasPrefix(item, \"EXP\") {\n\t\t\t\tbuf = append(buf, fmt.Sprintf(\"%s %s\", item, rule.Summary))\n\t\t\t}\n\t\t}\n\n\tcase \"markdown\", \"html\", \"explain-digest\", \"duplicate-key-checker\":\n\t\tif sql != \"\" && len(suggest) > 0 {\n\t\t\tswitch common.Config.ExplainSQLReportType {\n\t\t\tcase \"fingerprint\":\n\t\t\t\tbuf = append(buf, fmt.Sprintf(\"# Query: %s\\n\", id))\n\t\t\t\tbuf = append(buf, fmt.Sprintf(\"```sql\\n%s\\n```\\n\", fingerprint))\n\t\t\tcase \"sample\":\n\t\t\t\tbuf = append(buf, fmt.Sprintf(\"# Query: %s\\n\", id))\n\t\t\t\tbuf = append(buf, fmt.Sprintf(\"```sql\\n%s\\n```\\n\", sql))\n\t\t\tdefault:\n\t\t\t\tbuf = append(buf, fmt.Sprintf(\"# Query: %s\\n\", id))\n\t\t\t\tbuf = append(buf, fmt.Sprintf(\"```sql\\n%s\\n```\\n\", ast.Pretty(sql, format)))\n\t\t\t}\n\t\t}\n\t\t// MySQL\n\t\tcommon.Log.Debug(\"FormatSuggest, start of sortedMySQLSuggest\")\n\t\tvar sortedMySQLSuggest []string\n\t\tfor item := range suggest {\n\t\t\tif strings.HasPrefix(item, \"ERR\") {\n\t\t\t\tif suggest[item].Content == \"\" {\n\t\t\t\t\tdelete(suggest, item)\n\t\t\t\t} else {\n\t\t\t\t\tsortedMySQLSuggest = append(sortedMySQLSuggest, item)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsort.Strings(sortedMySQLSuggest)\n\t\tif len(sortedMySQLSuggest) > 0 {\n\t\t\tbuf = append(buf, \"## MySQL execute failed\\n\")\n\t\t}\n\t\tfor _, item := range sortedMySQLSuggest {\n\t\t\tbuf = append(buf, fmt.Sprintln(suggest[item].Content))\n\t\t\tscore = 0\n\t\t\tdelete(suggest, item)\n\t\t}\n\n\t\t// Explain\n\t\tcommon.Log.Debug(\"FormatSuggest, start of sortedExplainSuggest\")\n\t\tif suggest[\"EXP.000\"].Item != \"\" {\n\t\t\tbuf = append(buf, fmt.Sprintln(\"## \", suggest[\"EXP.000\"].Summary))\n\t\t\tbuf = append(buf, fmt.Sprintln(suggest[\"EXP.000\"].Content))\n\t\t\tbuf = append(buf, fmt.Sprint(suggest[\"EXP.000\"].Case, \"\\n\"))\n\t\t\tdelete(suggest, \"EXP.000\")\n\t\t}\n\t\tvar sortedExplainSuggest []string\n\t\tfor item := range suggest {\n\t\t\tif strings.HasPrefix(item, \"EXP\") {\n\t\t\t\tsortedExplainSuggest = append(sortedExplainSuggest, item)\n\t\t\t}\n\t\t}\n\t\tsort.Strings(sortedExplainSuggest)\n\t\tfor _, item := range sortedExplainSuggest {\n\t\t\tbuf = append(buf, fmt.Sprintln(\"### \", suggest[item].Summary))\n\t\t\tbuf = append(buf, fmt.Sprintln(suggest[item].Content))\n\t\t\tbuf = append(buf, fmt.Sprint(suggest[item].Case, \"\\n\"))\n\t\t}\n\n\t\t// Profiling\n\t\tcommon.Log.Debug(\"FormatSuggest, start of sortedProfilingSuggest\")\n\t\tvar sortedProfilingSuggest []string\n\t\tfor item := range suggest {\n\t\t\tif strings.HasPrefix(item, \"PRO\") {\n\t\t\t\tsortedProfilingSuggest = append(sortedProfilingSuggest, item)\n\t\t\t}\n\t\t}\n\t\tsort.Strings(sortedProfilingSuggest)\n\t\tif len(sortedProfilingSuggest) > 0 {\n\t\t\tbuf = append(buf, \"## Profiling信息\\n\")\n\t\t}\n\t\tfor _, item := range sortedProfilingSuggest {\n\t\t\tbuf = append(buf, fmt.Sprintln(suggest[item].Content))\n\t\t\tdelete(suggest, item)\n\t\t}\n\n\t\t// Trace\n\t\tcommon.Log.Debug(\"FormatSuggest, start of sortedTraceSuggest\")\n\t\tvar sortedTraceSuggest []string\n\t\tfor item := range suggest {\n\t\t\tif strings.HasPrefix(item, \"TRA\") {\n\t\t\t\tsortedTraceSuggest = append(sortedTraceSuggest, item)\n\t\t\t}\n\t\t}\n\t\tsort.Strings(sortedTraceSuggest)\n\t\tif len(sortedTraceSuggest) > 0 {\n\t\t\tbuf = append(buf, \"## Trace信息\\n\")\n\t\t}\n\t\tfor _, item := range sortedTraceSuggest {\n\t\t\tbuf = append(buf, fmt.Sprintln(suggest[item].Content))\n\t\t\tdelete(suggest, item)\n\t\t}\n\n\t\t// Index\n\t\tcommon.Log.Debug(\"FormatSuggest, start of sortedIdxSuggest\")\n\t\tvar sortedIdxSuggest []string\n\t\tfor item := range suggest {\n\t\t\tif strings.HasPrefix(item, \"IDX\") {\n\t\t\t\tsortedIdxSuggest = append(sortedIdxSuggest, item)\n\t\t\t}\n\t\t}\n\t\tsort.Strings(sortedIdxSuggest)\n\t\tfor _, item := range sortedIdxSuggest {\n\t\t\tbuf = append(buf, fmt.Sprintln(\"## \", common.MarkdownEscape(suggest[item].Summary)))\n\t\t\tbuf = append(buf, fmt.Sprintln(\"* **Item:** \", item))\n\t\t\tbuf = append(buf, fmt.Sprintln(\"* **Severity:** \", suggest[item].Severity))\n\t\t\tminus, err := strconv.Atoi(strings.Trim(suggest[item].Severity, \"L\"))\n\t\t\tif err == nil {\n\t\t\t\tscore = score - minus*5\n\t\t\t} else {\n\t\t\t\tcommon.Log.Debug(\"FormatSuggest, sortedIdxSuggest, strconv.Atoi, Error: \", err)\n\t\t\t\tscore = 0\n\t\t\t}\n\t\t\tbuf = append(buf, fmt.Sprintln(\"* **Content:** \", common.MarkdownEscape(suggest[item].Content)))\n\n\t\t\tif format == \"duplicate-key-checker\" {\n\t\t\t\tbuf = append(buf, fmt.Sprintf(\"* **原建表语句:** \\n```sql\\n%s\\n```\\n\", suggest[item].Case), \"\\n\\n\")\n\t\t\t} else {\n\t\t\t\tbuf = append(buf, fmt.Sprint(\"* **Case:** \", common.MarkdownEscape(suggest[item].Case), \"\\n\\n\"))\n\t\t\t}\n\t\t}\n\n\t\t// Heuristic\n\t\tcommon.Log.Debug(\"FormatSuggest, start of sortedHeuristicSuggest\")\n\t\tvar sortedHeuristicSuggest []string\n\t\tfor item := range suggest {\n\t\t\tif !strings.HasPrefix(item, \"EXP\") &&\n\t\t\t\t!strings.HasPrefix(item, \"IDX\") &&\n\t\t\t\t!strings.HasPrefix(item, \"PRO\") {\n\t\t\t\tsortedHeuristicSuggest = append(sortedHeuristicSuggest, item)\n\t\t\t}\n\t\t}\n\t\tsort.Strings(sortedHeuristicSuggest)\n\t\tfor _, item := range sortedHeuristicSuggest {\n\t\t\tbuf = append(buf, fmt.Sprintln(\"##\", suggest[item].Summary))\n\t\t\tif item == \"OK\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbuf = append(buf, fmt.Sprintln(\"* **Item:** \", item))\n\t\t\tbuf = append(buf, fmt.Sprintln(\"* **Severity:** \", suggest[item].Severity))\n\t\t\tminus, err := strconv.Atoi(strings.Trim(suggest[item].Severity, \"L\"))\n\t\t\tif err == nil {\n\t\t\t\tscore = score - minus*5\n\t\t\t} else {\n\t\t\t\tcommon.Log.Debug(\"FormatSuggest, sortedHeuristicSuggest, strconv.Atoi, Error: \", err)\n\t\t\t\tscore = 0\n\t\t\t}\n\t\t\tbuf = append(buf, fmt.Sprintln(\"* **Content:** \", common.MarkdownEscape(suggest[item].Content)))\n\t\t\t// buf = append(buf, fmt.Sprint(\"* **Case:** \", common.MarkdownEscape(suggest[item].Case), \"\\n\\n\"))\n\t\t}\n\n\tdefault:\n\t\tcommon.Log.Debug(\"report-type: %s\", format)\n\t\tbuf = append(buf, fmt.Sprintln(\"Query: \", sql))\n\t\tfor _, rule := range suggest {\n\t\t\tbuf = append(buf, pretty.Sprint(rule))\n\t\t}\n\t}\n\n\t// 打分\n\tvar str string\n\tswitch common.Config.ReportType {\n\tcase \"markdown\", \"html\":\n\t\tif len(buf) > 1 {\n\t\t\tstr = buf[0] + \"\\n\" + common.Score(score) + \"\\n\\n\" + strings.Join(buf[1:], \"\\n\")\n\t\t}\n\tdefault:\n\t\tstr = strings.Join(buf, \"\\n\")\n\t}\n\n\treturn suggest, str\n}\n\n// JSONSuggest json format suggestion\ntype JSONSuggest struct {\n\tID             string   `json:\"ID\"`\n\tFingerprint    string   `json:\"Fingerprint\"`\n\tScore          int      `json:\"Score\"`\n\tSample         string   `json:\"Sample\"`\n\tExplain        []Rule   `json:\"Explain\"`\n\tHeuristicRules []Rule   `json:\"HeuristicRules\"`\n\tIndexRules     []Rule   `json:\"IndexRules\"`\n\tTables         []string `json:\"Tables\"`\n}\n\nfunc formatJSON(sql string, db string, suggest map[string]Rule) string {\n\tvar id, fingerprint, result string\n\n\tfingerprint = query.Fingerprint(sql)\n\tid = query.Id(fingerprint)\n\n\t// Score\n\tscore := 100\n\tfor item := range suggest {\n\t\tl, err := strconv.Atoi(strings.TrimLeft(suggest[item].Severity, \"L\"))\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"formatJSON strconv.Atoi error: %s, item: %s, serverity: %s\", err.Error(), item, suggest[item].Severity)\n\t\t}\n\t\tscore = score - l*5\n\t\t// ## MySQL execute failed\n\t\tif strings.HasPrefix(item, \"ERR\") && suggest[item].Content != \"\" {\n\t\t\tscore = 0\n\t\t}\n\t}\n\tif score < 0 {\n\t\tscore = 0\n\t}\n\n\tsug := JSONSuggest{\n\t\tID:          id,\n\t\tFingerprint: fingerprint,\n\t\tSample:      sql,\n\t\tTables:      ast.SchemaMetaInfo(sql, db),\n\t\tScore:       score,\n\t}\n\n\t// Explain info\n\tvar sortItem []string\n\tfor item := range suggest {\n\t\tif strings.HasPrefix(item, \"EXP\") {\n\t\t\tsortItem = append(sortItem, item)\n\t\t}\n\t}\n\tsort.Strings(sortItem)\n\tfor _, i := range sortItem {\n\t\tsug.Explain = append(sug.Explain, suggest[i])\n\t}\n\tsortItem = make([]string, 0)\n\n\t// Index advisor\n\tfor item := range suggest {\n\t\tif strings.HasPrefix(item, \"IDX\") {\n\t\t\tsortItem = append(sortItem, item)\n\t\t}\n\t}\n\tsort.Strings(sortItem)\n\tfor _, i := range sortItem {\n\t\tsug.IndexRules = append(sug.IndexRules, suggest[i])\n\t}\n\tsortItem = make([]string, 0)\n\n\t// Heuristic rules\n\tfor item := range suggest {\n\t\tif !strings.HasPrefix(item, \"EXP\") && !strings.HasPrefix(item, \"IDX\") {\n\t\t\tif strings.HasPrefix(item, \"ERR\") && suggest[item].Content == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsortItem = append(sortItem, item)\n\t\t}\n\t}\n\tsort.Strings(sortItem)\n\tfor _, i := range sortItem {\n\t\tsug.HeuristicRules = append(sug.HeuristicRules, suggest[i])\n\t}\n\tsortItem = make([]string, 0)\n\n\tjs, err := json.MarshalIndent(sug, \"\", \"  \")\n\tif err == nil {\n\t\tresult = fmt.Sprint(string(js))\n\t} else {\n\t\tcommon.Log.Error(\"formatJSON json.Marshal Error: %v\", err)\n\t}\n\treturn result\n}\n\n// ListHeuristicRules 打印支持的启发式规则，对应命令行参数-list-heuristic-rules\nfunc ListHeuristicRules(rules ...map[string]Rule) {\n\tswitch common.Config.ReportType {\n\tcase \"json\":\n\t\tjs, err := json.MarshalIndent(rules, \"\", \"  \")\n\t\tif err == nil {\n\t\t\tfmt.Println(string(js))\n\t\t}\n\tdefault:\n\t\tfmt.Print(\"# 启发式规则建议\\n\\n[toc]\\n\\n\")\n\t\tfor _, r := range rules {\n\t\t\tdelete(r, \"OK\")\n\t\t\tfor _, item := range common.SortedKey(r) {\n\t\t\t\tfmt.Print(\"## \", common.MarkdownEscape(r[item].Summary),\n\t\t\t\t\t\"\\n\\n* **Item**:\", r[item].Item,\n\t\t\t\t\t\"\\n* **Severity**:\", r[item].Severity,\n\t\t\t\t\t\"\\n* **Content**:\", common.MarkdownEscape(r[item].Content),\n\t\t\t\t\t\"\\n* **Case**:\\n\\n```sql\\n\", r[item].Case, \"\\n```\\n\")\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ListTestSQLs 打印测试用的SQL，方便测试，对应命令行参数-list-test-sqls\nfunc ListTestSQLs() {\n\tfor _, sql := range common.TestSQLs {\n\t\tfmt.Println(sql)\n\t}\n}\n"
  },
  {
    "path": "advisor/rules_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage advisor\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\nfunc TestListTestSQLs(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tfor _, sql := range common.TestSQLs {\n\t\tif !strings.HasSuffix(sql, \";\") {\n\t\t\tt.Errorf(\"%s should end with ';'\", sql)\n\t\t}\n\t}\n\terr := common.GoldenDiff(func() { ListTestSQLs() }, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestListHeuristicRules(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := common.GoldenDiff(func() { ListHeuristicRules(HeuristicRules) }, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestInBlackList(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select\",\n\t\t\"select 1\",\n\t}\n\tcommon.BlackList = []string{\"select\"}\n\tfor _, sql := range sqls {\n\t\tif !InBlackList(sql) {\n\t\t\tt.Error(\"should be true\")\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestIsIgnoreRule(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tcommon.Config.IgnoreRules = []string{\"test\"}\n\tif !IsIgnoreRule(\"test\") {\n\t\tt.Error(\"should be true\")\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestNewQuery4Audit(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`SELECT CONVERT(\"abc\" using gbk)`,\n\t\t`SET NAMES gbk`,\n\t}\n\tfor _, sql := range sqls {\n\t\t_, err := NewQuery4Audit(sql)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"SQL: %s, Error: %s\", sql, err.Error())\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "advisor/testdata/TestDigestExplainText.golden",
    "content": "##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *country* | NULL | index | PRIMARY,<br>country\\_id | country | 152 | NULL | 0 | 0.00% | ☠️ **O(n)** | Using index |\n| 1  | SIMPLE | *city* | NULL | ref | idx\\_fk\\_country\\_id,<br>idx\\_country\\_id\\_city,<br>idx\\_all,<br>idx\\_other | idx\\_fk\\_country\\_id | 2 | sakila.country.country\\_id | 0 | 0.00% | O(log n) | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n\n<head>\n<meta http-equiv=Content-Type content=\"text/html;charset=utf-8\">\n<title>SQL优化分析报告</title>\n<script>!function(e,E){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=E():\"function\"==typeof define&&define.amd?define([],E):\"object\"==typeof exports?exports.sqlFormatter=E():e.sqlFormatter=E()}(this,function(){return function(e){function E(n){if(t[n])return t[n].exports;var r=t[n]={exports:{},id:n,loaded:!1};return e[n].call(r.exports,r,r.exports,E),r.loaded=!0,r.exports}var t={};return E.m=e,E.c=t,E.p=\"\",E(0)}([function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(18),T=n(r),R=t(19),o=n(R),N=t(20),A=n(N),I=t(21),O=n(I);E[\"default\"]={format:function(e,E){switch(E=E||{},E.language){case\"db2\":return new T[\"default\"](E).format(e);case\"n1ql\":return new o[\"default\"](E).format(e);case\"pl/sql\":return new A[\"default\"](E).format(e);case\"sql\":case void 0:return new O[\"default\"](E).format(e);default:throw Error(\"Unsupported SQL dialect: \"+E.language)}}},e.exports=E[\"default\"]},function(e,E){\"use strict\";E.__esModule=!0,E[\"default\"]=function(e,E){if(!(e instanceof E))throw new TypeError(\"Cannot call a class as a function\")}},function(e,E,t){var n=t(39),r=\"object\"==typeof self&&self&&self.Object===Object&&self,T=n||r||Function(\"return this\")();e.exports=T},function(e,E,t){function n(e,E){var t=T(e,E);return r(t)?t:void 0}var r=t(33),T=t(41);e.exports=n},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(66),o=n(R),N=t(7),A=n(N),I=t(15),O=n(I),i=t(16),S=n(i),u=t(17),L=n(u),C=function(){function e(E,t){(0,T[\"default\"])(this,e),this.cfg=E||{},this.indentation=new O[\"default\"](this.cfg.indent),this.inlineBlock=new S[\"default\"],this.params=new L[\"default\"](this.cfg.params),this.tokenizer=t,this.previousReservedWord={}}return e.prototype.format=function(e){var E=this.tokenizer.tokenize(e),t=this.getFormattedQueryFromTokens(E);return t.trim()},e.prototype.getFormattedQueryFromTokens=function(e){var E=this,t=\"\";return e.forEach(function(n,r){n.type!==A[\"default\"].WHITESPACE&&(n.type===A[\"default\"].LINE_COMMENT?t=E.formatLineComment(n,t):n.type===A[\"default\"].BLOCK_COMMENT?t=E.formatBlockComment(n,t):n.type===A[\"default\"].RESERVED_TOPLEVEL?(t=E.formatToplevelReservedWord(n,t),E.previousReservedWord=n):n.type===A[\"default\"].RESERVED_NEWLINE?(t=E.formatNewlineReservedWord(n,t),E.previousReservedWord=n):n.type===A[\"default\"].RESERVED?(t=E.formatWithSpaces(n,t),E.previousReservedWord=n):t=n.type===A[\"default\"].OPEN_PAREN?E.formatOpeningParentheses(e,r,t):n.type===A[\"default\"].CLOSE_PAREN?E.formatClosingParentheses(n,t):n.type===A[\"default\"].PLACEHOLDER?E.formatPlaceholder(n,t):\",\"===n.value?E.formatComma(n,t):\":\"===n.value?E.formatWithSpaceAfter(n,t):\".\"===n.value||\";\"===n.value?E.formatWithoutSpaces(n,t):E.formatWithSpaces(n,t))}),t},e.prototype.formatLineComment=function(e,E){return this.addNewline(E+e.value)},e.prototype.formatBlockComment=function(e,E){return this.addNewline(this.addNewline(E)+this.indentComment(e.value))},e.prototype.indentComment=function(e){return e.replace(/\\n/g,\"\\n\"+this.indentation.getIndent())},e.prototype.formatToplevelReservedWord=function(e,E){return this.indentation.decreaseTopLevel(),E=this.addNewline(E),this.indentation.increaseToplevel(),E+=this.equalizeWhitespace(e.value),this.addNewline(E)},e.prototype.formatNewlineReservedWord=function(e,E){return this.addNewline(E)+this.equalizeWhitespace(e.value)+\" \"},e.prototype.equalizeWhitespace=function(e){return e.replace(/\\s+/g,\" \")},e.prototype.formatOpeningParentheses=function(e,E,t){var n=e[E-1];return n&&n.type!==A[\"default\"].WHITESPACE&&n.type!==A[\"default\"].OPEN_PAREN&&(t=(0,o[\"default\"])(t)),t+=e[E].value,this.inlineBlock.beginIfPossible(e,E),this.inlineBlock.isActive()||(this.indentation.increaseBlockLevel(),t=this.addNewline(t)),t},e.prototype.formatClosingParentheses=function(e,E){return this.inlineBlock.isActive()?(this.inlineBlock.end(),this.formatWithSpaceAfter(e,E)):(this.indentation.decreaseBlockLevel(),this.formatWithSpaces(e,this.addNewline(E)))},e.prototype.formatPlaceholder=function(e,E){return E+this.params.get(e)+\" \"},e.prototype.formatComma=function(e,E){return E=(0,o[\"default\"])(E)+e.value+\" \",this.inlineBlock.isActive()?E:/^LIMIT$/i.test(this.previousReservedWord.value)?E:this.addNewline(E)},e.prototype.formatWithSpaceAfter=function(e,E){return(0,o[\"default\"])(E)+e.value+\" \"},e.prototype.formatWithoutSpaces=function(e,E){return(0,o[\"default\"])(E)+e.value},e.prototype.formatWithSpaces=function(e,E){return E+e.value+\" \"},e.prototype.addNewline=function(e){return(0,o[\"default\"])(e)+\"\\n\"+this.indentation.getIndent()},e}();E[\"default\"]=C,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(58),o=n(R),N=t(53),A=n(N),I=t(7),O=n(I),i=function(){function e(E){(0,T[\"default\"])(this,e),this.WHITESPACE_REGEX=/^(\\s+)/,this.NUMBER_REGEX=/^((-\\s*)?[0-9]+(\\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)\\b/,this.OPERATOR_REGEX=/^(!=|<>|==|<=|>=|!<|!>|\\|\\||::|->>|->|~~\\*|~~|!~~\\*|!~~|~\\*|!~\\*|!~|.)/,this.BLOCK_COMMENT_REGEX=/^(\\/\\*[^]*?(?:\\*\\/|$))/,this.LINE_COMMENT_REGEX=this.createLineCommentRegex(E.lineCommentTypes),this.RESERVED_TOPLEVEL_REGEX=this.createReservedWordRegex(E.reservedToplevelWords),this.RESERVED_NEWLINE_REGEX=this.createReservedWordRegex(E.reservedNewlineWords),this.RESERVED_PLAIN_REGEX=this.createReservedWordRegex(E.reservedWords),this.WORD_REGEX=this.createWordRegex(E.specialWordChars),this.STRING_REGEX=this.createStringRegex(E.stringTypes),this.OPEN_PAREN_REGEX=this.createParenRegex(E.openParens),this.CLOSE_PAREN_REGEX=this.createParenRegex(E.closeParens),this.INDEXED_PLACEHOLDER_REGEX=this.createPlaceholderRegex(E.indexedPlaceholderTypes,\"[0-9]*\"),this.IDENT_NAMED_PLACEHOLDER_REGEX=this.createPlaceholderRegex(E.namedPlaceholderTypes,\"[a-zA-Z0-9._$]+\"),this.STRING_NAMED_PLACEHOLDER_REGEX=this.createPlaceholderRegex(E.namedPlaceholderTypes,this.createStringPattern(E.stringTypes))}return e.prototype.createLineCommentRegex=function(e){return RegExp(\"^((?:\"+e.map(function(e){return(0,A[\"default\"])(e)}).join(\"|\")+\").*?(?:\\n|$))\")},e.prototype.createReservedWordRegex=function(e){var E=e.join(\"|\").replace(/ /g,\"\\\\s+\");return RegExp(\"^(\"+E+\")\\\\b\",\"i\")},e.prototype.createWordRegex=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return RegExp(\"^([\\\\w\"+e.join(\"\")+\"]+)\")},e.prototype.createStringRegex=function(e){return RegExp(\"^(\"+this.createStringPattern(e)+\")\")},e.prototype.createStringPattern=function(e){var E={\"``\":\"((`[^`]*($|`))+)\",\"[]\":\"((\\\\[[^\\\\]]*($|\\\\]))(\\\\][^\\\\]]*($|\\\\]))*)\",'\"\"':'((\"[^\"\\\\\\\\]*(?:\\\\\\\\.[^\"\\\\\\\\]*)*(\"|$))+)',\"''\":\"(('[^'\\\\\\\\]*(?:\\\\\\\\.[^'\\\\\\\\]*)*('|$))+)\",\"N''\":\"((N'[^N'\\\\\\\\]*(?:\\\\\\\\.[^N'\\\\\\\\]*)*('|$))+)\"};return e.map(function(e){return E[e]}).join(\"|\")},e.prototype.createParenRegex=function(e){var E=this;return RegExp(\"^(\"+e.map(function(e){return E.escapeParen(e)}).join(\"|\")+\")\",\"i\")},e.prototype.escapeParen=function(e){return 1===e.length?(0,A[\"default\"])(e):\"\\\\b\"+e+\"\\\\b\"},e.prototype.createPlaceholderRegex=function(e,E){if((0,o[\"default\"])(e))return!1;var t=e.map(A[\"default\"]).join(\"|\");return RegExp(\"^((?:\"+t+\")(?:\"+E+\"))\")},e.prototype.tokenize=function(e){for(var E=[],t=void 0;e.length;)t=this.getNextToken(e,t),e=e.substring(t.value.length),E.push(t);return E},e.prototype.getNextToken=function(e,E){return this.getWhitespaceToken(e)||this.getCommentToken(e)||this.getStringToken(e)||this.getOpenParenToken(e)||this.getCloseParenToken(e)||this.getPlaceholderToken(e)||this.getNumberToken(e)||this.getReservedWordToken(e,E)||this.getWordToken(e)||this.getOperatorToken(e)},e.prototype.getWhitespaceToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].WHITESPACE,regex:this.WHITESPACE_REGEX})},e.prototype.getCommentToken=function(e){return this.getLineCommentToken(e)||this.getBlockCommentToken(e)},e.prototype.getLineCommentToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].LINE_COMMENT,regex:this.LINE_COMMENT_REGEX})},e.prototype.getBlockCommentToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].BLOCK_COMMENT,regex:this.BLOCK_COMMENT_REGEX})},e.prototype.getStringToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].STRING,regex:this.STRING_REGEX})},e.prototype.getOpenParenToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].OPEN_PAREN,regex:this.OPEN_PAREN_REGEX})},e.prototype.getCloseParenToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].CLOSE_PAREN,regex:this.CLOSE_PAREN_REGEX})},e.prototype.getPlaceholderToken=function(e){return this.getIdentNamedPlaceholderToken(e)||this.getStringNamedPlaceholderToken(e)||this.getIndexedPlaceholderToken(e)},e.prototype.getIdentNamedPlaceholderToken=function(e){return this.getPlaceholderTokenWithKey({input:e,regex:this.IDENT_NAMED_PLACEHOLDER_REGEX,parseKey:function(e){return e.slice(1)}})},e.prototype.getStringNamedPlaceholderToken=function(e){var E=this;return this.getPlaceholderTokenWithKey({input:e,regex:this.STRING_NAMED_PLACEHOLDER_REGEX,parseKey:function(e){return E.getEscapedPlaceholderKey({key:e.slice(2,-1),quoteChar:e.slice(-1)})}})},e.prototype.getIndexedPlaceholderToken=function(e){return this.getPlaceholderTokenWithKey({input:e,regex:this.INDEXED_PLACEHOLDER_REGEX,parseKey:function(e){return e.slice(1)}})},e.prototype.getPlaceholderTokenWithKey=function(e){var E=e.input,t=e.regex,n=e.parseKey,r=this.getTokenOnFirstMatch({input:E,regex:t,type:O[\"default\"].PLACEHOLDER});return r&&(r.key=n(r.value)),r},e.prototype.getEscapedPlaceholderKey=function(e){var E=e.key,t=e.quoteChar;return E.replace(RegExp((0,A[\"default\"])(\"\\\\\")+t,\"g\"),t)},e.prototype.getNumberToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].NUMBER,regex:this.NUMBER_REGEX})},e.prototype.getOperatorToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].OPERATOR,regex:this.OPERATOR_REGEX})},e.prototype.getReservedWordToken=function(e,E){if(!E||!E.value||\".\"!==E.value)return this.getToplevelReservedToken(e)||this.getNewlineReservedToken(e)||this.getPlainReservedToken(e)},e.prototype.getToplevelReservedToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].RESERVED_TOPLEVEL,regex:this.RESERVED_TOPLEVEL_REGEX})},e.prototype.getNewlineReservedToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].RESERVED_NEWLINE,regex:this.RESERVED_NEWLINE_REGEX})},e.prototype.getPlainReservedToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].RESERVED,regex:this.RESERVED_PLAIN_REGEX})},e.prototype.getWordToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].WORD,regex:this.WORD_REGEX})},e.prototype.getTokenOnFirstMatch=function(e){var E=e.input,t=e.type,n=e.regex,r=E.match(n);if(r)return{type:t,value:r[1]}},e}();E[\"default\"]=i,e.exports=E[\"default\"]},function(e,E){function t(e){var E=typeof e;return null!=e&&(\"object\"==E||\"function\"==E)}e.exports=t},function(e,E){\"use strict\";E.__esModule=!0,E[\"default\"]={WHITESPACE:\"whitespace\",WORD:\"word\",STRING:\"string\",RESERVED:\"reserved\",RESERVED_TOPLEVEL:\"reserved-toplevel\",RESERVED_NEWLINE:\"reserved-newline\",OPERATOR:\"operator\",OPEN_PAREN:\"open-paren\",CLOSE_PAREN:\"close-paren\",LINE_COMMENT:\"line-comment\",BLOCK_COMMENT:\"block-comment\",NUMBER:\"number\",PLACEHOLDER:\"placeholder\"},e.exports=E[\"default\"]},function(e,E,t){function n(e){return null!=e&&T(e.length)&&!r(e)}var r=t(12),T=t(59);e.exports=n},function(e,E,t){function n(e){return null==e?\"\":r(e)}var r=t(10);e.exports=n},function(e,E,t){function n(e){if(\"string\"==typeof e)return e;if(T(e))return N?N.call(e):\"\";var E=e+\"\";return\"0\"==E&&1/e==-R?\"-0\":E}var r=t(26),T=t(14),R=1/0,o=r?r.prototype:void 0,N=o?o.toString:void 0;e.exports=n},function(e,E){function t(e){if(null!=e){try{return r.call(e)}catch(E){}try{return e+\"\"}catch(E){}}return\"\"}var n=Function.prototype,r=n.toString;e.exports=t},function(e,E,t){function n(e){var E=r(e)?N.call(e):\"\";return E==T||E==R}var r=t(6),T=\"[object Function]\",R=\"[object GeneratorFunction]\",o=Object.prototype,N=o.toString;e.exports=n},function(e,E){function t(e){return null!=e&&\"object\"==typeof e}e.exports=t},function(e,E,t){function n(e){return\"symbol\"==typeof e||r(e)&&o.call(e)==T}var r=t(13),T=\"[object Symbol]\",R=Object.prototype,o=R.toString;e.exports=n},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(61),o=n(R),N=t(60),A=n(N),I=\"top-level\",O=\"block-level\",i=function(){function e(E){(0,T[\"default\"])(this,e),this.indent=E||\"  \",this.indentTypes=[]}return e.prototype.getIndent=function(){return(0,o[\"default\"])(this.indent,this.indentTypes.length)},e.prototype.increaseToplevel=function(){this.indentTypes.push(I)},e.prototype.increaseBlockLevel=function(){this.indentTypes.push(O)},e.prototype.decreaseTopLevel=function(){(0,A[\"default\"])(this.indentTypes)===I&&this.indentTypes.pop()},e.prototype.decreaseBlockLevel=function(){for(;this.indentTypes.length>0;){var e=this.indentTypes.pop();if(e!==I)break}},e}();E[\"default\"]=i,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(7),o=n(R),N=50,A=function(){function e(){(0,T[\"default\"])(this,e),this.level=0}return e.prototype.beginIfPossible=function(e,E){0===this.level&&this.isInlineBlock(e,E)?this.level=1:this.level>0?this.level++:this.level=0},e.prototype.end=function(){this.level--},e.prototype.isActive=function(){return this.level>0},e.prototype.isInlineBlock=function(e,E){for(var t=0,n=0,r=E;e.length>r;r++){var T=e[r];if(t+=T.value.length,t>N)return!1;if(T.type===o[\"default\"].OPEN_PAREN)n++;else if(T.type===o[\"default\"].CLOSE_PAREN&&(n--,0===n))return!0;if(this.isForbiddenToken(T))return!1}return!1},e.prototype.isForbiddenToken=function(e){var E=e.type,t=e.value;return E===o[\"default\"].RESERVED_TOPLEVEL||E===o[\"default\"].RESERVED_NEWLINE||E===o[\"default\"].COMMENT||E===o[\"default\"].BLOCK_COMMENT||\";\"===t},e}();E[\"default\"]=A,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=function(){function e(E){(0,T[\"default\"])(this,e),this.params=E,this.index=0}return e.prototype.get=function(e){var E=e.key,t=e.value;return this.params?E?this.params[E]:this.params[this.index++]:t},e}();E[\"default\"]=R,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(4),o=n(R),N=t(5),A=n(N),I=[\"ABS\",\"ACTIVATE\",\"ALIAS\",\"ALL\",\"ALLOCATE\",\"ALLOW\",\"ALTER\",\"ANY\",\"ARE\",\"ARRAY\",\"AS\",\"ASC\",\"ASENSITIVE\",\"ASSOCIATE\",\"ASUTIME\",\"ASYMMETRIC\",\"AT\",\"ATOMIC\",\"ATTRIBUTES\",\"AUDIT\",\"AUTHORIZATION\",\"AUX\",\"AUXILIARY\",\"AVG\",\"BEFORE\",\"BEGIN\",\"BETWEEN\",\"BIGINT\",\"BINARY\",\"BLOB\",\"BOOLEAN\",\"BOTH\",\"BUFFERPOOL\",\"BY\",\"CACHE\",\"CALL\",\"CALLED\",\"CAPTURE\",\"CARDINALITY\",\"CASCADED\",\"CASE\",\"CAST\",\"CCSID\",\"CEIL\",\"CEILING\",\"CHAR\",\"CHARACTER\",\"CHARACTER_LENGTH\",\"CHAR_LENGTH\",\"CHECK\",\"CLOB\",\"CLONE\",\"CLOSE\",\"CLUSTER\",\"COALESCE\",\"COLLATE\",\"COLLECT\",\"COLLECTION\",\"COLLID\",\"COLUMN\",\"COMMENT\",\"COMMIT\",\"CONCAT\",\"CONDITION\",\"CONNECT\",\"CONNECTION\",\"CONSTRAINT\",\"CONTAINS\",\"CONTINUE\",\"CONVERT\",\"CORR\",\"CORRESPONDING\",\"COUNT\",\"COUNT_BIG\",\"COVAR_POP\",\"COVAR_SAMP\",\"CREATE\",\"CROSS\",\"CUBE\",\"CUME_DIST\",\"CURRENT\",\"CURRENT_DATE\",\"CURRENT_DEFAULT_TRANSFORM_GROUP\",\"CURRENT_LC_CTYPE\",\"CURRENT_PATH\",\"CURRENT_ROLE\",\"CURRENT_SCHEMA\",\"CURRENT_SERVER\",\"CURRENT_TIME\",\"CURRENT_TIMESTAMP\",\"CURRENT_TIMEZONE\",\"CURRENT_TRANSFORM_GROUP_FOR_TYPE\",\"CURRENT_USER\",\"CURSOR\",\"CYCLE\",\"DATA\",\"DATABASE\",\"DATAPARTITIONNAME\",\"DATAPARTITIONNUM\",\"DATE\",\"DAY\",\"DAYS\",\"DB2GENERAL\",\"DB2GENRL\",\"DB2SQL\",\"DBINFO\",\"DBPARTITIONNAME\",\"DBPARTITIONNUM\",\"DEALLOCATE\",\"DEC\",\"DECIMAL\",\"DECLARE\",\"DEFAULT\",\"DEFAULTS\",\"DEFINITION\",\"DELETE\",\"DENSERANK\",\"DENSE_RANK\",\"DEREF\",\"DESCRIBE\",\"DESCRIPTOR\",\"DETERMINISTIC\",\"DIAGNOSTICS\",\"DISABLE\",\"DISALLOW\",\"DISCONNECT\",\"DISTINCT\",\"DO\",\"DOCUMENT\",\"DOUBLE\",\"DROP\",\"DSSIZE\",\"DYNAMIC\",\"EACH\",\"EDITPROC\",\"ELEMENT\",\"ELSE\",\"ELSEIF\",\"ENABLE\",\"ENCODING\",\"ENCRYPTION\",\"END\",\"END-EXEC\",\"ENDING\",\"ERASE\",\"ESCAPE\",\"EVERY\",\"EXCEPTION\",\"EXCLUDING\",\"EXCLUSIVE\",\"EXEC\",\"EXECUTE\",\"EXISTS\",\"EXIT\",\"EXP\",\"EXPLAIN\",\"EXTENDED\",\"EXTERNAL\",\"EXTRACT\",\"FALSE\",\"FENCED\",\"FETCH\",\"FIELDPROC\",\"FILE\",\"FILTER\",\"FINAL\",\"FIRST\",\"FLOAT\",\"FLOOR\",\"FOR\",\"FOREIGN\",\"FREE\",\"FULL\",\"FUNCTION\",\"FUSION\",\"GENERAL\",\"GENERATED\",\"GET\",\"GLOBAL\",\"GOTO\",\"GRANT\",\"GRAPHIC\",\"GROUP\",\"GROUPING\",\"HANDLER\",\"HASH\",\"HASHED_VALUE\",\"HINT\",\"HOLD\",\"HOUR\",\"HOURS\",\"IDENTITY\",\"IF\",\"IMMEDIATE\",\"IN\",\"INCLUDING\",\"INCLUSIVE\",\"INCREMENT\",\"INDEX\",\"INDICATOR\",\"INDICATORS\",\"INF\",\"INFINITY\",\"INHERIT\",\"INNER\",\"INOUT\",\"INSENSITIVE\",\"INSERT\",\"INT\",\"INTEGER\",\"INTEGRITY\",\"INTERSECTION\",\"INTERVAL\",\"INTO\",\"IS\",\"ISOBID\",\"ISOLATION\",\"ITERATE\",\"JAR\",\"JAVA\",\"KEEP\",\"KEY\",\"LABEL\",\"LANGUAGE\",\"LARGE\",\"LATERAL\",\"LC_CTYPE\",\"LEADING\",\"LEAVE\",\"LEFT\",\"LIKE\",\"LINKTYPE\",\"LN\",\"LOCAL\",\"LOCALDATE\",\"LOCALE\",\"LOCALTIME\",\"LOCALTIMESTAMP\",\"LOCATOR\",\"LOCATORS\",\"LOCK\",\"LOCKMAX\",\"LOCKSIZE\",\"LONG\",\"LOOP\",\"LOWER\",\"MAINTAINED\",\"MATCH\",\"MATERIALIZED\",\"MAX\",\"MAXVALUE\",\"MEMBER\",\"MERGE\",\"METHOD\",\"MICROSECOND\",\"MICROSECONDS\",\"MIN\",\"MINUTE\",\"MINUTES\",\"MINVALUE\",\"MOD\",\"MODE\",\"MODIFIES\",\"MODULE\",\"MONTH\",\"MONTHS\",\"MULTISET\",\"NAN\",\"NATIONAL\",\"NATURAL\",\"NCHAR\",\"NCLOB\",\"NEW\",\"NEW_TABLE\",\"NEXTVAL\",\"NO\",\"NOCACHE\",\"NOCYCLE\",\"NODENAME\",\"NODENUMBER\",\"NOMAXVALUE\",\"NOMINVALUE\",\"NONE\",\"NOORDER\",\"NORMALIZE\",\"NORMALIZED\",\"NOT\",\"NULL\",\"NULLIF\",\"NULLS\",\"NUMERIC\",\"NUMPARTS\",\"OBID\",\"OCTET_LENGTH\",\"OF\",\"OFFSET\",\"OLD\",\"OLD_TABLE\",\"ON\",\"ONLY\",\"OPEN\",\"OPTIMIZATION\",\"OPTIMIZE\",\"OPTION\",\"ORDER\",\"OUT\",\"OUTER\",\"OVER\",\"OVERLAPS\",\"OVERLAY\",\"OVERRIDING\",\"PACKAGE\",\"PADDED\",\"PAGESIZE\",\"PARAMETER\",\"PART\",\"PARTITION\",\"PARTITIONED\",\"PARTITIONING\",\"PARTITIONS\",\"PASSWORD\",\"PATH\",\"PERCENTILE_CONT\",\"PERCENTILE_DISC\",\"PERCENT_RANK\",\"PIECESIZE\",\"PLAN\",\"POSITION\",\"POWER\",\"PRECISION\",\"PREPARE\",\"PREVVAL\",\"PRIMARY\",\"PRIQTY\",\"PRIVILEGES\",\"PROCEDURE\",\"PROGRAM\",\"PSID\",\"PUBLIC\",\"QUERY\",\"QUERYNO\",\"RANGE\",\"RANK\",\"READ\",\"READS\",\"REAL\",\"RECOVERY\",\"RECURSIVE\",\"REF\",\"REFERENCES\",\"REFERENCING\",\"REFRESH\",\"REGR_AVGX\",\"REGR_AVGY\",\"REGR_COUNT\",\"REGR_INTERCEPT\",\"REGR_R2\",\"REGR_SLOPE\",\"REGR_SXX\",\"REGR_SXY\",\"REGR_SYY\",\"RELEASE\",\"RENAME\",\"REPEAT\",\"RESET\",\"RESIGNAL\",\"RESTART\",\"RESTRICT\",\"RESULT\",\"RESULT_SET_LOCATOR\",\"RETURN\",\"RETURNS\",\"REVOKE\",\"RIGHT\",\"ROLE\",\"ROLLBACK\",\"ROLLUP\",\"ROUND_CEILING\",\"ROUND_DOWN\",\"ROUND_FLOOR\",\"ROUND_HALF_DOWN\",\"ROUND_HALF_EVEN\",\"ROUND_HALF_UP\",\"ROUND_UP\",\"ROUTINE\",\"ROW\",\"ROWNUMBER\",\"ROWS\",\"ROWSET\",\"ROW_NUMBER\",\"RRN\",\"RUN\",\"SAVEPOINT\",\"SCHEMA\",\"SCOPE\",\"SCRATCHPAD\",\"SCROLL\",\"SEARCH\",\"SECOND\",\"SECONDS\",\"SECQTY\",\"SECURITY\",\"SENSITIVE\",\"SEQUENCE\",\"SESSION\",\"SESSION_USER\",\"SIGNAL\",\"SIMILAR\",\"SIMPLE\",\"SMALLINT\",\"SNAN\",\"SOME\",\"SOURCE\",\"SPECIFIC\",\"SPECIFICTYPE\",\"SQL\",\"SQLEXCEPTION\",\"SQLID\",\"SQLSTATE\",\"SQLWARNING\",\"SQRT\",\"STACKED\",\"STANDARD\",\"START\",\"STARTING\",\"STATEMENT\",\"STATIC\",\"STATMENT\",\"STAY\",\"STDDEV_POP\",\"STDDEV_SAMP\",\"STOGROUP\",\"STORES\",\"STYLE\",\"SUBMULTISET\",\"SUBSTRING\",\"SUM\",\"SUMMARY\",\"SYMMETRIC\",\"SYNONYM\",\"SYSFUN\",\"SYSIBM\",\"SYSPROC\",\"SYSTEM\",\"SYSTEM_USER\",\"TABLE\",\"TABLESAMPLE\",\"TABLESPACE\",\"THEN\",\"TIME\",\"TIMESTAMP\",\"TIMEZONE_HOUR\",\"TIMEZONE_MINUTE\",\"TO\",\"TRAILING\",\"TRANSACTION\",\"TRANSLATE\",\"TRANSLATION\",\"TREAT\",\"TRIGGER\",\"TRIM\",\"TRUE\",\"TRUNCATE\",\"TYPE\",\"UESCAPE\",\"UNDO\",\"UNIQUE\",\"UNKNOWN\",\"UNNEST\",\"UNTIL\",\"UPPER\",\"USAGE\",\"USER\",\"USING\",\"VALIDPROC\",\"VALUE\",\"VARCHAR\",\"VARIABLE\",\"VARIANT\",\"VARYING\",\"VAR_POP\",\"VAR_SAMP\",\"VCAT\",\"VERSION\",\"VIEW\",\"VOLATILE\",\"VOLUMES\",\"WHEN\",\"WHENEVER\",\"WHILE\",\"WIDTH_BUCKET\",\"WINDOW\",\"WITH\",\"WITHIN\",\"WITHOUT\",\"WLM\",\"WRITE\",\"XMLELEMENT\",\"XMLEXISTS\",\"XMLNAMESPACES\",\"YEAR\",\"YEARS\"],O=[\"ADD\",\"AFTER\",\"ALTER COLUMN\",\"ALTER TABLE\",\"DELETE FROM\",\"EXCEPT\",\"FETCH FIRST\",\"FROM\",\"GROUP BY\",\"GO\",\"HAVING\",\"INSERT INTO\",\"INTERSECT\",\"LIMIT\",\"ORDER BY\",\"SELECT\",\"SET CURRENT SCHEMA\",\"SET SCHEMA\",\"SET\",\"UNION ALL\",\"UPDATE\",\"VALUES\",\"WHERE\"],i=[\"AND\",\"CROSS JOIN\",\"INNER JOIN\",\"JOIN\",\"LEFT JOIN\",\"LEFT OUTER JOIN\",\"OR\",\"OUTER JOIN\",\"RIGHT JOIN\",\"RIGHT OUTER JOIN\"],S=void 0,u=function(){function e(E){(0,T[\"default\"])(this,e),this.cfg=E}return e.prototype.format=function(e){return S||(S=new A[\"default\"]({reservedWords:I,reservedToplevelWords:O,reservedNewlineWords:i,stringTypes:['\"\"',\"''\",\"``\",\"[]\"],openParens:[\"(\"],closeParens:[\")\"],indexedPlaceholderTypes:[\"?\"],namedPlaceholderTypes:[\":\"],lineCommentTypes:[\"--\"],specialWordChars:[\"#\",\"@\"]})),new o[\"default\"](this.cfg,S).format(e)},e}();E[\"default\"]=u,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(4),o=n(R),N=t(5),A=n(N),I=[\"ALL\",\"ALTER\",\"ANALYZE\",\"AND\",\"ANY\",\"ARRAY\",\"AS\",\"ASC\",\"BEGIN\",\"BETWEEN\",\"BINARY\",\"BOOLEAN\",\"BREAK\",\"BUCKET\",\"BUILD\",\"BY\",\"CALL\",\"CASE\",\"CAST\",\"CLUSTER\",\"COLLATE\",\"COLLECTION\",\"COMMIT\",\"CONNECT\",\"CONTINUE\",\"CORRELATE\",\"COVER\",\"CREATE\",\"DATABASE\",\"DATASET\",\"DATASTORE\",\"DECLARE\",\"DECREMENT\",\"DELETE\",\"DERIVED\",\"DESC\",\"DESCRIBE\",\"DISTINCT\",\"DO\",\"DROP\",\"EACH\",\"ELEMENT\",\"ELSE\",\"END\",\"EVERY\",\"EXCEPT\",\"EXCLUDE\",\"EXECUTE\",\"EXISTS\",\"EXPLAIN\",\"FALSE\",\"FETCH\",\"FIRST\",\"FLATTEN\",\"FOR\",\"FORCE\",\"FROM\",\"FUNCTION\",\"GRANT\",\"GROUP\",\"GSI\",\"HAVING\",\"IF\",\"IGNORE\",\"ILIKE\",\"IN\",\"INCLUDE\",\"INCREMENT\",\"INDEX\",\"INFER\",\"INLINE\",\"INNER\",\"INSERT\",\"INTERSECT\",\"INTO\",\"IS\",\"JOIN\",\"KEY\",\"KEYS\",\"KEYSPACE\",\"KNOWN\",\"LAST\",\"LEFT\",\"LET\",\"LETTING\",\"LIKE\",\"LIMIT\",\"LSM\",\"MAP\",\"MAPPING\",\"MATCHED\",\"MATERIALIZED\",\"MERGE\",\"MINUS\",\"MISSING\",\"NAMESPACE\",\"NEST\",\"NOT\",\"NULL\",\"NUMBER\",\"OBJECT\",\"OFFSET\",\"ON\",\"OPTION\",\"OR\",\"ORDER\",\"OUTER\",\"OVER\",\"PARSE\",\"PARTITION\",\"PASSWORD\",\"PATH\",\"POOL\",\"PREPARE\",\"PRIMARY\",\"PRIVATE\",\"PRIVILEGE\",\"PROCEDURE\",\"PUBLIC\",\"RAW\",\"REALM\",\"REDUCE\",\"RENAME\",\"RETURN\",\"RETURNING\",\"REVOKE\",\"RIGHT\",\"ROLE\",\"ROLLBACK\",\"SATISFIES\",\"SCHEMA\",\"SELECT\",\"SELF\",\"SEMI\",\"SET\",\"SHOW\",\"SOME\",\"START\",\"STATISTICS\",\"STRING\",\"SYSTEM\",\"THEN\",\"TO\",\"TRANSACTION\",\"TRIGGER\",\"TRUE\",\"TRUNCATE\",\"UNDER\",\"UNION\",\"UNIQUE\",\"UNKNOWN\",\"UNNEST\",\"UNSET\",\"UPDATE\",\"UPSERT\",\"USE\",\"USER\",\"USING\",\"VALIDATE\",\"VALUE\",\"VALUED\",\"VALUES\",\"VIA\",\"VIEW\",\"WHEN\",\"WHERE\",\"WHILE\",\"WITH\",\"WITHIN\",\"WORK\",\"XOR\"],O=[\"DELETE FROM\",\"EXCEPT ALL\",\"EXCEPT\",\"EXPLAIN DELETE FROM\",\"EXPLAIN UPDATE\",\"EXPLAIN UPSERT\",\"FROM\",\"GROUP BY\",\"HAVING\",\"INFER\",\"INSERT INTO\",\"INTERSECT ALL\",\"INTERSECT\",\"LET\",\"LIMIT\",\"MERGE\",\"NEST\",\"ORDER BY\",\"PREPARE\",\"SELECT\",\"SET CURRENT SCHEMA\",\"SET SCHEMA\",\"SET\",\"UNION ALL\",\"UNION\",\"UNNEST\",\"UPDATE\",\"UPSERT\",\"USE KEYS\",\"VALUES\",\"WHERE\"],i=[\"AND\",\"INNER JOIN\",\"JOIN\",\"LEFT JOIN\",\"LEFT OUTER JOIN\",\"OR\",\"OUTER JOIN\",\"RIGHT JOIN\",\"RIGHT OUTER JOIN\",\"XOR\"],S=void 0,u=function(){function e(E){(0,T[\"default\"])(this,e),this.cfg=E}return e.prototype.format=function(e){return S||(S=new A[\"default\"]({reservedWords:I,reservedToplevelWords:O,reservedNewlineWords:i,stringTypes:['\"\"',\"''\",\"``\"],openParens:[\"(\",\"[\",\"{\"],closeParens:[\")\",\"]\",\"}\"],namedPlaceholderTypes:[\"$\"],lineCommentTypes:[\"#\",\"--\"]})),new o[\"default\"](this.cfg,S).format(e)},e}();E[\"default\"]=u,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(4),o=n(R),N=t(5),A=n(N),I=[\"A\",\"ACCESSIBLE\",\"AGENT\",\"AGGREGATE\",\"ALL\",\"ALTER\",\"ANY\",\"ARRAY\",\"AS\",\"ASC\",\"AT\",\"ATTRIBUTE\",\"AUTHID\",\"AVG\",\"BETWEEN\",\"BFILE_BASE\",\"BINARY_INTEGER\",\"BINARY\",\"BLOB_BASE\",\"BLOCK\",\"BODY\",\"BOOLEAN\",\"BOTH\",\"BOUND\",\"BULK\",\"BY\",\"BYTE\",\"C\",\"CALL\",\"CALLING\",\"CASCADE\",\"CASE\",\"CHAR_BASE\",\"CHAR\",\"CHARACTER\",\"CHARSET\",\"CHARSETFORM\",\"CHARSETID\",\"CHECK\",\"CLOB_BASE\",\"CLONE\",\"CLOSE\",\"CLUSTER\",\"CLUSTERS\",\"COALESCE\",\"COLAUTH\",\"COLLECT\",\"COLUMNS\",\"COMMENT\",\"COMMIT\",\"COMMITTED\",\"COMPILED\",\"COMPRESS\",\"CONNECT\",\"CONSTANT\",\"CONSTRUCTOR\",\"CONTEXT\",\"CONTINUE\",\"CONVERT\",\"COUNT\",\"CRASH\",\"CREATE\",\"CREDENTIAL\",\"CURRENT\",\"CURRVAL\",\"CURSOR\",\"CUSTOMDATUM\",\"DANGLING\",\"DATA\",\"DATE_BASE\",\"DATE\",\"DAY\",\"DECIMAL\",\"DEFAULT\",\"DEFINE\",\"DELETE\",\"DESC\",\"DETERMINISTIC\",\"DIRECTORY\",\"DISTINCT\",\"DO\",\"DOUBLE\",\"DROP\",\"DURATION\",\"ELEMENT\",\"ELSIF\",\"EMPTY\",\"ESCAPE\",\"EXCEPTIONS\",\"EXCLUSIVE\",\"EXECUTE\",\"EXISTS\",\"EXIT\",\"EXTENDS\",\"EXTERNAL\",\"EXTRACT\",\"FALSE\",\"FETCH\",\"FINAL\",\"FIRST\",\"FIXED\",\"FLOAT\",\"FOR\",\"FORALL\",\"FORCE\",\"FROM\",\"FUNCTION\",\"GENERAL\",\"GOTO\",\"GRANT\",\"GROUP\",\"HASH\",\"HEAP\",\"HIDDEN\",\"HOUR\",\"IDENTIFIED\",\"IF\",\"IMMEDIATE\",\"IN\",\"INCLUDING\",\"INDEX\",\"INDEXES\",\"INDICATOR\",\"INDICES\",\"INFINITE\",\"INSTANTIABLE\",\"INT\",\"INTEGER\",\"INTERFACE\",\"INTERVAL\",\"INTO\",\"INVALIDATE\",\"IS\",\"ISOLATION\",\"JAVA\",\"LANGUAGE\",\"LARGE\",\"LEADING\",\"LENGTH\",\"LEVEL\",\"LIBRARY\",\"LIKE\",\"LIKE2\",\"LIKE4\",\"LIKEC\",\"LIMITED\",\"LOCAL\",\"LOCK\",\"LONG\",\"MAP\",\"MAX\",\"MAXLEN\",\"MEMBER\",\"MERGE\",\"MIN\",\"MINUS\",\"MINUTE\",\"MLSLABEL\",\"MOD\",\"MODE\",\"MONTH\",\"MULTISET\",\"NAME\",\"NAN\",\"NATIONAL\",\"NATIVE\",\"NATURAL\",\"NATURALN\",\"NCHAR\",\"NEW\",\"NEXTVAL\",\"NOCOMPRESS\",\"NOCOPY\",\"NOT\",\"NOWAIT\",\"NULL\",\"NULLIF\",\"NUMBER_BASE\",\"NUMBER\",\"OBJECT\",\"OCICOLL\",\"OCIDATE\",\"OCIDATETIME\",\"OCIDURATION\",\"OCIINTERVAL\",\"OCILOBLOCATOR\",\"OCINUMBER\",\"OCIRAW\",\"OCIREF\",\"OCIREFCURSOR\",\"OCIROWID\",\"OCISTRING\",\"OCITYPE\",\"OF\",\"OLD\",\"ON\",\"ONLY\",\"OPAQUE\",\"OPEN\",\"OPERATOR\",\"OPTION\",\"ORACLE\",\"ORADATA\",\"ORDER\",\"ORGANIZATION\",\"ORLANY\",\"ORLVARY\",\"OTHERS\",\"OUT\",\"OVERLAPS\",\"OVERRIDING\",\"PACKAGE\",\"PARALLEL_ENABLE\",\"PARAMETER\",\"PARAMETERS\",\"PARENT\",\"PARTITION\",\"PASCAL\",\"PCTFREE\",\"PIPE\",\"PIPELINED\",\"PLS_INTEGER\",\"PLUGGABLE\",\"POSITIVE\",\"POSITIVEN\",\"PRAGMA\",\"PRECISION\",\"PRIOR\",\"PRIVATE\",\"PROCEDURE\",\"PUBLIC\",\"RAISE\",\"RANGE\",\"RAW\",\"READ\",\"REAL\",\"RECORD\",\"REF\",\"REFERENCE\",\"RELEASE\",\"RELIES_ON\",\"REM\",\"REMAINDER\",\"RENAME\",\"RESOURCE\",\"RESULT_CACHE\",\"RESULT\",\"RETURN\",\"RETURNING\",\"REVERSE\",\"REVOKE\",\"ROLLBACK\",\"ROW\",\"ROWID\",\"ROWNUM\",\"ROWTYPE\",\"SAMPLE\",\"SAVE\",\"SAVEPOINT\",\"SB1\",\"SB2\",\"SB4\",\"SECOND\",\"SEGMENT\",\"SELF\",\"SEPARATE\",\"SEQUENCE\",\"SERIALIZABLE\",\"SHARE\",\"SHORT\",\"SIZE_T\",\"SIZE\",\"SMALLINT\",\"SOME\",\"SPACE\",\"SPARSE\",\"SQL\",\"SQLCODE\",\"SQLDATA\",\"SQLERRM\",\"SQLNAME\",\"SQLSTATE\",\"STANDARD\",\"START\",\"STATIC\",\"STDDEV\",\"STORED\",\"STRING\",\"STRUCT\",\"STYLE\",\"SUBMULTISET\",\"SUBPARTITION\",\"SUBSTITUTABLE\",\"SUBTYPE\",\"SUCCESSFUL\",\"SUM\",\"SYNONYM\",\"SYSDATE\",\"TABAUTH\",\"TABLE\",\"TDO\",\"THE\",\"THEN\",\"TIME\",\"TIMESTAMP\",\"TIMEZONE_ABBR\",\"TIMEZONE_HOUR\",\"TIMEZONE_MINUTE\",\"TIMEZONE_REGION\",\"TO\",\"TRAILING\",\"TRANSACTION\",\"TRANSACTIONAL\",\"TRIGGER\",\"TRUE\",\"TRUSTED\",\"TYPE\",\"UB1\",\"UB2\",\"UB4\",\"UID\",\"UNDER\",\"UNIQUE\",\"UNPLUG\",\"UNSIGNED\",\"UNTRUSTED\",\"USE\",\"USER\",\"USING\",\"VALIDATE\",\"VALIST\",\"VALUE\",\"VARCHAR\",\"VARCHAR2\",\"VARIABLE\",\"VARIANCE\",\"VARRAY\",\"VARYING\",\"VIEW\",\"VIEWS\",\"VOID\",\"WHENEVER\",\"WHILE\",\"WITH\",\"WORK\",\"WRAPPED\",\"WRITE\",\"YEAR\",\"ZONE\"],O=[\"ADD\",\"ALTER COLUMN\",\"ALTER TABLE\",\"BEGIN\",\"CONNECT BY\",\"DECLARE\",\"DELETE FROM\",\"DELETE\",\"END\",\"EXCEPT\",\"EXCEPTION\",\"FETCH FIRST\",\"FROM\",\"GROUP BY\",\"HAVING\",\"INSERT INTO\",\"INSERT\",\"INTERSECT\",\"LIMIT\",\"LOOP\",\"MODIFY\",\"ORDER BY\",\"SELECT\",\"SET CURRENT SCHEMA\",\"SET SCHEMA\",\"SET\",\"START WITH\",\"UNION ALL\",\"UNION\",\"UPDATE\",\"VALUES\",\"WHERE\"],i=[\"AND\",\"CROSS APPLY\",\"CROSS JOIN\",\"ELSE\",\"END\",\"INNER JOIN\",\"JOIN\",\"LEFT JOIN\",\"LEFT OUTER JOIN\",\"OR\",\"OUTER APPLY\",\"OUTER JOIN\",\"RIGHT JOIN\",\"RIGHT OUTER JOIN\",\"WHEN\",\"XOR\"],S=void 0,u=function(){function e(E){(0,T[\"default\"])(this,e),this.cfg=E}return e.prototype.format=function(e){return S||(S=new A[\"default\"]({reservedWords:I,reservedToplevelWords:O,reservedNewlineWords:i,stringTypes:['\"\"',\"N''\",\"''\",\"``\"],openParens:[\"(\",\"CASE\"],closeParens:[\")\",\"END\"],indexedPlaceholderTypes:[\"?\"],namedPlaceholderTypes:[\":\"],lineCommentTypes:[\"--\"],specialWordChars:[\"_\",\"$\",\"#\",\".\",\"@\"]})),new o[\"default\"](this.cfg,S).format(e)},e}();E[\"default\"]=u,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(4),o=n(R),N=t(5),A=n(N),I=[\"ACCESSIBLE\",\"ACTION\",\"AGAINST\",\"AGGREGATE\",\"ALGORITHM\",\"ALL\",\"ALTER\",\"ANALYSE\",\"ANALYZE\",\"AS\",\"ASC\",\"AUTOCOMMIT\",\"AUTO_INCREMENT\",\"BACKUP\",\"BEGIN\",\"BETWEEN\",\"BINLOG\",\"BOTH\",\"CASCADE\",\"CASE\",\"CHANGE\",\"CHANGED\",\"CHARACTER SET\",\"CHARSET\",\"CHECK\",\"CHECKSUM\",\"COLLATE\",\"COLLATION\",\"COLUMN\",\"COLUMNS\",\"COMMENT\",\"COMMIT\",\"COMMITTED\",\"COMPRESSED\",\"CONCURRENT\",\"CONSTRAINT\",\"CONTAINS\",\"CONVERT\",\"CREATE\",\"CROSS\",\"CURRENT_TIMESTAMP\",\"DATABASE\",\"DATABASES\",\"DAY\",\"DAY_HOUR\",\"DAY_MINUTE\",\"DAY_SECOND\",\"DEFAULT\",\"DEFINER\",\"DELAYED\",\"DELETE\",\"DESC\",\"DESCRIBE\",\"DETERMINISTIC\",\"DISTINCT\",\"DISTINCTROW\",\"DIV\",\"DO\",\"DROP\",\"DUMPFILE\",\"DUPLICATE\",\"DYNAMIC\",\"ELSE\",\"ENCLOSED\",\"END\",\"ENGINE\",\"ENGINES\",\"ENGINE_TYPE\",\"ESCAPE\",\"ESCAPED\",\"EVENTS\",\"EXEC\",\"EXECUTE\",\"EXISTS\",\"EXPLAIN\",\"EXTENDED\",\"FAST\",\"FETCH\",\"FIELDS\",\"FILE\",\"FIRST\",\"FIXED\",\"FLUSH\",\"FOR\",\"FORCE\",\"FOREIGN\",\"FULL\",\"FULLTEXT\",\"FUNCTION\",\"GLOBAL\",\"GRANT\",\"GRANTS\",\"GROUP_CONCAT\",\"HEAP\",\"HIGH_PRIORITY\",\"HOSTS\",\"HOUR\",\"HOUR_MINUTE\",\"HOUR_SECOND\",\"IDENTIFIED\",\"IF\",\"IFNULL\",\"IGNORE\",\"IN\",\"INDEX\",\"INDEXES\",\"INFILE\",\"INSERT\",\"INSERT_ID\",\"INSERT_METHOD\",\"INTERVAL\",\"INTO\",\"INVOKER\",\"IS\",\"ISOLATION\",\"KEY\",\"KEYS\",\"KILL\",\"LAST_INSERT_ID\",\"LEADING\",\"LEVEL\",\"LIKE\",\"LINEAR\",\"LINES\",\"LOAD\",\"LOCAL\",\"LOCK\",\"LOCKS\",\"LOGS\",\"LOW_PRIORITY\",\"MARIA\",\"MASTER\",\"MASTER_CONNECT_RETRY\",\"MASTER_HOST\",\"MASTER_LOG_FILE\",\"MATCH\",\"MAX_CONNECTIONS_PER_HOUR\",\"MAX_QUERIES_PER_HOUR\",\"MAX_ROWS\",\"MAX_UPDATES_PER_HOUR\",\"MAX_USER_CONNECTIONS\",\"MEDIUM\",\"MERGE\",\"MINUTE\",\"MINUTE_SECOND\",\"MIN_ROWS\",\"MODE\",\"MODIFY\",\"MONTH\",\"MRG_MYISAM\",\"MYISAM\",\"NAMES\",\"NATURAL\",\"NOT\",\"NOW()\",\"NULL\",\"OFFSET\",\"ON DELETE\",\"ON UPDATE\",\"ON\",\"ONLY\",\"OPEN\",\"OPTIMIZE\",\"OPTION\",\"OPTIONALLY\",\"OUTFILE\",\"PACK_KEYS\",\"PAGE\",\"PARTIAL\",\"PARTITION\",\"PARTITIONS\",\"PASSWORD\",\"PRIMARY\",\"PRIVILEGES\",\"PROCEDURE\",\"PROCESS\",\"PROCESSLIST\",\"PURGE\",\"QUICK\",\"RAID0\",\"RAID_CHUNKS\",\"RAID_CHUNKSIZE\",\"RAID_TYPE\",\"RANGE\",\"READ\",\"READ_ONLY\",\"READ_WRITE\",\"REFERENCES\",\"REGEXP\",\"RELOAD\",\"RENAME\",\"REPAIR\",\"REPEATABLE\",\"REPLACE\",\"REPLICATION\",\"RESET\",\"RESTORE\",\"RESTRICT\",\"RETURN\",\"RETURNS\",\"REVOKE\",\"RLIKE\",\"ROLLBACK\",\"ROW\",\"ROWS\",\"ROW_FORMAT\",\"SECOND\",\"SECURITY\",\"SEPARATOR\",\"SERIALIZABLE\",\"SESSION\",\"SHARE\",\"SHOW\",\"SHUTDOWN\",\"SLAVE\",\"SONAME\",\"SOUNDS\",\"SQL\",\"SQL_AUTO_IS_NULL\",\"SQL_BIG_RESULT\",\"SQL_BIG_SELECTS\",\"SQL_BIG_TABLES\",\"SQL_BUFFER_RESULT\",\"SQL_CACHE\",\"SQL_CALC_FOUND_ROWS\",\"SQL_LOG_BIN\",\"SQL_LOG_OFF\",\"SQL_LOG_UPDATE\",\"SQL_LOW_PRIORITY_UPDATES\",\"SQL_MAX_JOIN_SIZE\",\"SQL_NO_CACHE\",\"SQL_QUOTE_SHOW_CREATE\",\"SQL_SAFE_UPDATES\",\"SQL_SELECT_LIMIT\",\"SQL_SLAVE_SKIP_COUNTER\",\"SQL_SMALL_RESULT\",\"SQL_WARNINGS\",\"START\",\"STARTING\",\"STATUS\",\"STOP\",\"STORAGE\",\"STRAIGHT_JOIN\",\"STRING\",\"STRIPED\",\"SUPER\",\"TABLE\",\"TABLES\",\"TEMPORARY\",\"TERMINATED\",\"THEN\",\"TO\",\"TRAILING\",\"TRANSACTIONAL\",\"TRUE\",\"TRUNCATE\",\"TYPE\",\"TYPES\",\"UNCOMMITTED\",\"UNIQUE\",\"UNLOCK\",\"UNSIGNED\",\"USAGE\",\"USE\",\"USING\",\"VARIABLES\",\"VIEW\",\"WHEN\",\"WITH\",\"WORK\",\"WRITE\",\"YEAR_MONTH\"],O=[\"ADD\",\"AFTER\",\"ALTER COLUMN\",\"ALTER TABLE\",\"DELETE FROM\",\"EXCEPT\",\"FETCH FIRST\",\"FROM\",\"GROUP BY\",\"GO\",\"HAVING\",\"INSERT INTO\",\"INSERT\",\"INTERSECT\",\"LIMIT\",\"MODIFY\",\"ORDER BY\",\"SELECT\",\"SET CURRENT SCHEMA\",\"SET SCHEMA\",\"SET\",\"UNION ALL\",\"UNION\",\"UPDATE\",\"VALUES\",\"WHERE\"],i=[\"AND\",\"CROSS APPLY\",\"CROSS JOIN\",\"ELSE\",\"INNER JOIN\",\"JOIN\",\"LEFT JOIN\",\"LEFT OUTER JOIN\",\"OR\",\"OUTER APPLY\",\"OUTER JOIN\",\"RIGHT JOIN\",\"RIGHT OUTER JOIN\",\"WHEN\",\"XOR\"],S=void 0,u=function(){function e(E){(0,T[\"default\"])(this,e),this.cfg=E}return e.prototype.format=function(e){return S||(S=new A[\"default\"]({reservedWords:I,reservedToplevelWords:O,reservedNewlineWords:i,stringTypes:['\"\"',\"N''\",\"''\",\"``\",\"[]\"],openParens:[\"(\",\"CASE\"],closeParens:[\")\",\"END\"],indexedPlaceholderTypes:[\"?\"],namedPlaceholderTypes:[\"@\",\":\"],lineCommentTypes:[\"#\",\"--\"]})),new o[\"default\"](this.cfg,S).format(e)},e}();E[\"default\"]=u,e.exports=E[\"default\"]},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"DataView\");e.exports=T},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"Map\");e.exports=T},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"Promise\");e.exports=T},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"Set\");e.exports=T},function(e,E,t){var n=t(2),r=n.Symbol;e.exports=r},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"WeakMap\");e.exports=T},function(e,E){function t(e){return e.split(\"\")}e.exports=t},function(e,E){function t(e,E,t,n){for(var r=e.length,T=t+(n?1:-1);n?T--:++T<r;)if(E(e[T],T,e))return T;\nreturn-1}e.exports=t},function(e,E){function t(e){return r.call(e)}var n=Object.prototype,r=n.toString;e.exports=t},function(e,E,t){function n(e,E,t){return E===E?R(e,E,t):r(e,T,t)}var r=t(29),T=t(32),R=t(49);e.exports=n},function(e,E){function t(e){return e!==e}e.exports=t},function(e,E,t){function n(e){if(!R(e)||T(e))return!1;var E=r(e)?u:A;return E.test(o(e))}var r=t(12),T=t(45),R=t(6),o=t(11),N=/[\\\\^$.*+?()[\\]{}|]/g,A=/^\\[object .+?Constructor\\]$/,I=Function.prototype,O=Object.prototype,i=I.toString,S=O.hasOwnProperty,u=RegExp(\"^\"+i.call(S).replace(N,\"\\\\$&\").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g,\"$1.*?\")+\"$\");e.exports=n},function(e,E){function t(e,E){var t=\"\";if(!e||1>E||E>n)return t;do E%2&&(t+=e),E=r(E/2),E&&(e+=e);while(E);return t}var n=9007199254740991,r=Math.floor;e.exports=t},function(e,E){function t(e,E,t){var n=-1,r=e.length;0>E&&(E=-E>r?0:r+E),t=t>r?r:t,0>t&&(t+=r),r=E>t?0:t-E>>>0,E>>>=0;for(var T=Array(r);++n<r;)T[n]=e[n+E];return T}e.exports=t},function(e,E,t){function n(e,E,t){var n=e.length;return t=void 0===t?n:t,E||n>t?r(e,E,t):e}var r=t(35);e.exports=n},function(e,E,t){function n(e,E){for(var t=e.length;t--&&r(E,e[t],0)>-1;);return t}var r=t(31);e.exports=n},function(e,E,t){var n=t(2),r=n[\"__core-js_shared__\"];e.exports=r},function(e,E){(function(E){var t=\"object\"==typeof E&&E&&E.Object===Object&&E;e.exports=t}).call(E,function(){return this}())},function(e,E,t){var n=t(22),r=t(23),T=t(24),R=t(25),o=t(27),N=t(30),A=t(11),I=\"[object Map]\",O=\"[object Object]\",i=\"[object Promise]\",S=\"[object Set]\",u=\"[object WeakMap]\",L=\"[object DataView]\",C=Object.prototype,s=C.toString,a=A(n),f=A(r),c=A(T),p=A(R),l=A(o),D=N;(n&&D(new n(new ArrayBuffer(1)))!=L||r&&D(new r)!=I||T&&D(T.resolve())!=i||R&&D(new R)!=S||o&&D(new o)!=u)&&(D=function(e){var E=s.call(e),t=E==O?e.constructor:void 0,n=t?A(t):void 0;if(n)switch(n){case a:return L;case f:return I;case c:return i;case p:return S;case l:return u}return E}),e.exports=D},function(e,E){function t(e,E){return null==e?void 0:e[E]}e.exports=t},function(e,E){function t(e){return N.test(e)}var n=\"\\\\ud800-\\\\udfff\",r=\"\\\\u0300-\\\\u036f\\\\ufe20-\\\\ufe23\",T=\"\\\\u20d0-\\\\u20f0\",R=\"\\\\ufe0e\\\\ufe0f\",o=\"\\\\u200d\",N=RegExp(\"[\"+o+n+r+T+R+\"]\");e.exports=t},function(e,E){function t(e,E){return E=null==E?n:E,!!E&&(\"number\"==typeof e||r.test(e))&&e>-1&&e%1==0&&E>e}var n=9007199254740991,r=/^(?:0|[1-9]\\d*)$/;e.exports=t},function(e,E,t){function n(e,E,t){if(!o(t))return!1;var n=typeof E;return!!(\"number\"==n?T(t)&&R(E,t.length):\"string\"==n&&E in t)&&r(t[E],e)}var r=t(52),T=t(8),R=t(43),o=t(6);e.exports=n},function(e,E,t){function n(e){return!!T&&T in e}var r=t(38),T=function(){var e=/[^.]+$/.exec(r&&r.keys&&r.keys.IE_PROTO||\"\");return e?\"Symbol(src)_1.\"+e:\"\"}();e.exports=n},function(e,E){function t(e){var E=e&&e.constructor,t=\"function\"==typeof E&&E.prototype||n;return e===t}var n=Object.prototype;e.exports=t},function(e,E,t){var n=t(48),r=n(Object.keys,Object);e.exports=r},function(e,E){function t(e,E){return function(t){return e(E(t))}}e.exports=t},function(e,E){function t(e,E,t){for(var n=t-1,r=e.length;++n<r;)if(e[n]===E)return n;return-1}e.exports=t},function(e,E,t){function n(e){return T(e)?R(e):r(e)}var r=t(28),T=t(42),R=t(51);e.exports=n},function(e,E){function t(e){return e.match(c)||[]}var n=\"\\\\ud800-\\\\udfff\",r=\"\\\\u0300-\\\\u036f\\\\ufe20-\\\\ufe23\",T=\"\\\\u20d0-\\\\u20f0\",R=\"\\\\ufe0e\\\\ufe0f\",o=\"[\"+n+\"]\",N=\"[\"+r+T+\"]\",A=\"\\\\ud83c[\\\\udffb-\\\\udfff]\",I=\"(?:\"+N+\"|\"+A+\")\",O=\"[^\"+n+\"]\",i=\"(?:\\\\ud83c[\\\\udde6-\\\\uddff]){2}\",S=\"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\",u=\"\\\\u200d\",L=I+\"?\",C=\"[\"+R+\"]?\",s=\"(?:\"+u+\"(?:\"+[O,i,S].join(\"|\")+\")\"+C+L+\")*\",a=C+L+s,f=\"(?:\"+[O+N+\"?\",N,i,S,o].join(\"|\")+\")\",c=RegExp(A+\"(?=\"+A+\")|\"+f+a,\"g\");e.exports=t},function(e,E){function t(e,E){return e===E||e!==e&&E!==E}e.exports=t},function(e,E,t){function n(e){return e=r(e),e&&R.test(e)?e.replace(T,\"\\\\$&\"):e}var r=t(9),T=/[\\\\^$.*+?()[\\]{}|]/g,R=RegExp(T.source);e.exports=n},function(e,E,t){function n(e){return r(e)&&o.call(e,\"callee\")&&(!A.call(e,\"callee\")||N.call(e)==T)}var r=t(56),T=\"[object Arguments]\",R=Object.prototype,o=R.hasOwnProperty,N=R.toString,A=R.propertyIsEnumerable;e.exports=n},function(e,E){var t=Array.isArray;e.exports=t},function(e,E,t){function n(e){return T(e)&&r(e)}var r=t(8),T=t(13);e.exports=n},function(e,E,t){(function(e){var n=t(2),r=t(62),T=\"object\"==typeof E&&E&&!E.nodeType&&E,R=T&&\"object\"==typeof e&&e&&!e.nodeType&&e,o=R&&R.exports===T,N=o?n.Buffer:void 0,A=N?N.isBuffer:void 0,I=A||r;e.exports=I}).call(E,t(67)(e))},function(e,E,t){function n(e){if(o(e)&&(R(e)||\"string\"==typeof e||\"function\"==typeof e.splice||N(e)||T(e)))return!e.length;var E=r(e);if(E==O||E==i)return!e.size;if(A(e))return!I(e).length;for(var t in e)if(u.call(e,t))return!1;return!0}var r=t(40),T=t(54),R=t(55),o=t(8),N=t(57),A=t(46),I=t(47),O=\"[object Map]\",i=\"[object Set]\",S=Object.prototype,u=S.hasOwnProperty;e.exports=n},function(e,E){function t(e){return\"number\"==typeof e&&e>-1&&e%1==0&&n>=e}var n=9007199254740991;e.exports=t},function(e,E){function t(e){var E=e?e.length:0;return E?e[E-1]:void 0}e.exports=t},function(e,E,t){function n(e,E,t){return E=(t?T(e,E,t):void 0===E)?1:R(E),r(o(e),E)}var r=t(34),T=t(44),R=t(64),o=t(9);e.exports=n},function(e,E){function t(){return!1}e.exports=t},function(e,E,t){function n(e){if(!e)return 0===e?e:0;if(e=r(e),e===T||e===-T){var E=0>e?-1:1;return E*R}return e===e?e:0}var r=t(65),T=1/0,R=1.7976931348623157e308;e.exports=n},function(e,E,t){function n(e){var E=r(e),t=E%1;return E===E?t?E-t:E:0}var r=t(63);e.exports=n},function(e,E,t){function n(e){if(\"number\"==typeof e)return e;if(T(e))return R;if(r(e)){var E=\"function\"==typeof e.valueOf?e.valueOf():e;e=r(E)?E+\"\":E}if(\"string\"!=typeof e)return 0===e?e:+e;e=e.replace(o,\"\");var t=A.test(e);return t||I.test(e)?O(e.slice(2),t?2:8):N.test(e)?R:+e}var r=t(6),T=t(14),R=NaN,o=/^\\s+|\\s+$/g,N=/^[-+]0x[0-9a-f]+$/i,A=/^0b[01]+$/i,I=/^0o[0-7]+$/i,O=parseInt;e.exports=n},function(e,E,t){function n(e,E,t){if(e=N(e),e&&(t||void 0===E))return e.replace(A,\"\");if(!e||!(E=r(E)))return e;var n=o(e),I=R(n,o(E))+1;return T(n,0,I).join(\"\")}var r=t(10),T=t(36),R=t(37),o=t(50),N=t(9),A=/\\s+$/;e.exports=n},function(e,E){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children=[],e.webpackPolyfill=1),e}}])});\n\n\t\tfunction escape2Html(str) {\n    \t    var arrEntities = {'lt': '<', 'gt': '>', 'nbsp': '', 'amp': '&', 'quot': '\"'};\n    \t    return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, function (all, t) {\n    \t        return arrEntities[t];\n    \t    });\n    \t}\n\t\n    \tfunction load() {\n    \t    let codeList = document.getElementsByClassName('language-sql');\n\t\n    \t    for (let i = 0 ;i<codeList.length;i++) {\n    \t        codeList[i].innerHTML = window.sqlFormatter.format(escape2Html(codeList[i].innerHTML))\n    \t    }\n    \t};\n</script>\n<style id=\"soar_md\">\n\na:link,a:visited{text-decoration:none}h3,h4{margin-top:2em}h5,h6{margin-top:20px}h3,h4,h5,h6{margin-bottom:.5em;color:#000}body,h1,h2,h3,h4,h5,h6{color:#000}ol,ul{margin:0 0 0 30px;padding:0 0 12px 6px}ol,ol ol{list-style-position:outside}table td p,table th p{margin-bottom:0}input,select{vertical-align:middle;padding:0}h5,h6,input,select{padding:0}hr,table,textarea{width:100%}body{margin:20px auto;width:800px;background-color:#fff;font:13px \"Myriad Pro\",\"Lucida Grande\",Lucida,Verdana,sans-serif}h1,table th p{font-weight:700}a:link{color:#00f}a:visited{color:#00a}a:active,a:hover{color:#f60;text-decoration:underline}* html code,* html pre{font-size:101%}code,pre{font-size:11px;font-family:monaco,courier,consolas,monospace}pre{border:1px solid #c7cfd5;background:#f1f5f9;margin:20px 0;padding:8px;text-align:left}hr{color:#919699;size:1;noshade:\"noshade\"}h1,h2,h3,h4,h5,h6{font-family:\"Myriad Pro\",\"Lucida Grande\",Lucida,Verdana,sans-serif;font-weight:700}h1{margin-top:1em;margin-bottom:25px;font-size:30px}h2{margin-top:2.5em;font-size:24px;padding-bottom:2px;border-bottom:1px solid #919699}h3{font-size:17px}h4{font-size:15px}h5{font-size:13px}h6{font-size:11px}table td,table th{font-size:12px;border-bottom:1px solid #919699;border-right:1px solid #919699}p{margin-top:0;margin-bottom:10px}ul{list-style:square}li{margin-top:7px}ol{list-style-type:decimal}ol ol{list-style-type:lower-alpha;margin:7px 0 0 30px;padding:0 0 0 10px}ul ul{margin-left:40px;padding:0 0 0 6px}li>p{display:inline}li>a+p,li>p+p{display:block}table{border-top:1px solid #919699;border-left:1px solid #919699;border-spacing:0}table th{padding:4px 8px;background:#E2E2E2}table td{padding:8px;vertical-align:top}table td p+p,table td p+p+p{margin-top:5px}form{margin:0}button{margin:3px 0 10px}input{margin:0 0 5px}select{margin:0 0 3px}textarea{margin:0 0 10px}\n\n</style>\n</head>\n<body onload=load()>\n\n<h2>Explain信息</h2>\n\n<p>★ ★ ★ ★ ★ 100分</p>\n\n<table>\n<thead>\n<tr>\n<th>id</th>\n<th>select_type</th>\n<th>table</th>\n<th>partitions</th>\n<th>type</th>\n<th>possible_keys</th>\n<th>key</th>\n<th>key_len</th>\n<th>ref</th>\n<th>rows</th>\n<th>filtered</th>\n<th>scalability</th>\n<th>Extra</th>\n</tr>\n</thead>\n\n<tbody>\n<tr>\n<td>1</td>\n<td>SIMPLE</td>\n<td><em>country</em></td>\n<td>NULL</td>\n<td>index</td>\n<td>PRIMARY,<br>country_id</td>\n<td>country</td>\n<td>152</td>\n<td>NULL</td>\n<td>0</td>\n<td>0.00%</td>\n<td>☠️ <strong>O(n)</strong></td>\n<td>Using index</td>\n</tr>\n\n<tr>\n<td>1</td>\n<td>SIMPLE</td>\n<td><em>city</em></td>\n<td>NULL</td>\n<td>ref</td>\n<td>idx_fk_country_id,<br>idx_country_id_city,<br>idx_all,<br>idx_other</td>\n<td>idx_fk_country_id</td>\n<td>2</td>\n<td>sakila.country.country_id</td>\n<td>0</td>\n<td>0.00%</td>\n<td>O(log n)</td>\n<td>Using index</td>\n</tr>\n</tbody>\n</table>\n\n<h3>Explain信息解读</h3>\n\n<h4>SelectType信息解读</h4>\n\n<ul>\n<li><strong>SIMPLE</strong>: 简单SELECT(不使用UNION或子查询等).</li>\n</ul>\n\n<h4>Type信息解读</h4>\n\n<ul>\n<li><p><strong>index</strong>: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.</p></li>\n\n<li><p><strong>ref</strong>: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.</p></li>\n</ul>\n\n<h4>Extra信息解读</h4>\n\n<ul>\n<li><strong>Using index</strong>: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.</li>\n</ul>\n\n"
  },
  {
    "path": "advisor/testdata/TestIndexAdviseNoEnv.golden",
    "content": "map[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的address表添加索引\", Content:\"为列address添加索引,散粒度为: 100.00%; 为列district添加索引,散粒度为: 100.00%; \", Case:\"ALTER TABLE `sakila`.`address` add index `idx_address` (`address`), add index `idx_district` (`district`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列release_year添加索引,散粒度为: 0.10%; 为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`), add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的country表添加索引\", Content:\"为列last_update添加索引,散粒度为: 0.92%; \", Case:\"ALTER TABLE `sakila`.`country` add index `idx_last_update` (`last_update`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的city表添加索引\", Content:\"为列last_update添加索引,散粒度为: 0.17%; \", Case:\"ALTER TABLE `sakila`.`city` add index `idx_last_update` (`last_update`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的country表添加索引\", Content:\"为列last_update添加索引,散粒度为: 0.92%; \", Case:\"ALTER TABLE `sakila`.`country` add index `idx_last_update` (`last_update`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n    \"IDX.002\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的city表添加索引\", Content:\"为列last_update添加索引,散粒度为: 0.17%; \", Case:\"ALTER TABLE `sakila`.`city` add index `idx_last_update` (`last_update`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的country表添加索引\", Content:\"为列country添加索引,散粒度为: 100.00%; \", Case:\"ALTER TABLE `sakila`.`country` add index `idx_country` (`country`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列length添加索引,散粒度为: 14.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的actor表添加索引\", Content:\"为列last_update添加索引,散粒度为: 0.50%; 为列first_name添加索引,散粒度为: 64.00%; \", Case:\"ALTER TABLE `sakila`.`actor` add index `idx_last_update` (`last_update`), add index `idx_first_name` (`first_name`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的city表添加索引\", Content:\"为列city添加索引,散粒度为: 99.83%; \", Case:\"ALTER TABLE `sakila`.`city` add index `idx_city` (`city`) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\nmap[string]advisor.Rule{\n    \"IDX.001\": {Item:\"\", Severity:\"L2\", Summary:\"为sakila库的film表添加索引\", Content:\"为列description添加索引,散粒度为: 100.00%; \", Case:\"ALTER TABLE `sakila`.`film` add index `idx_description` (`description`(255)) ;\\n\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},\n}\n"
  },
  {
    "path": "advisor/testdata/TestListHeuristicRules.golden",
    "content": "# 启发式规则建议\n\n[toc]\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item**:ALI.001\n* **Severity**:L0\n* **Content**:在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n* **Case**:\n\n```sql\nselect name from tbl t1 where id < 1000\n```\n## 不建议给列通配符'\\*'设置别名\n\n* **Item**:ALI.002\n* **Severity**:L8\n* **Content**:例: \"SELECT tbl.\\* col1, col2\"上面这条 SQL 给列通配符设置了别名，这样的SQL可能存在逻辑错误。您可能意在查询 col1, 但是代替它的是重命名的是 tbl 的最后一列。\n* **Case**:\n\n```sql\nselect tbl.* as c1,c2,c3 from tbl where id < 1000\n```\n## 别名不要与表或列的名字相同\n\n* **Item**:ALI.003\n* **Severity**:L1\n* **Content**:表或列的别名与其真实名称相同, 这样的别名会使得查询更难去分辨。\n* **Case**:\n\n```sql\nselect name from tbl as tbl where id < 1000\n```\n## 修改表的默认字符集不会改表各个字段的字符集\n\n* **Item**:ALT.001\n* **Severity**:L4\n* **Content**:很多初学者会将 ALTER TABLE tbl\\_name [DEFAULT] CHARACTER SET 'UTF8' 误认为会修改所有字段的字符集，但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用 ALTER TABLE tbl\\_name CONVERT TO CHARACTER SET charset\\_name;\n* **Case**:\n\n```sql\nALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;\n```\n## 同一张表的多条 ALTER 请求建议合为一条\n\n* **Item**:ALT.002\n* **Severity**:L2\n* **Content**:每次表结构变更对线上服务都会产生影响，即使是能够通过在线工具进行调整也请尽量通过合并 ALTER 请求的试减少操作次数。\n* **Case**:\n\n```sql\nALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);\n```\n## 删除列为高危操作，操作前请注意检查业务逻辑是否还有依赖\n\n* **Item**:ALT.003\n* **Severity**:L0\n* **Content**:如业务逻辑依赖未完全消除，列被删除后可能导致数据无法写入或无法查询到已删除列数据导致程序异常的情况。这种情况下即使通过备份数据回滚也会丢失用户请求写入的数据。\n* **Case**:\n\n```sql\nALTER TABLE tbl DROP COLUMN col;\n```\n## 删除主键和外键为高危操作，操作前请与 DBA 确认影响\n\n* **Item**:ALT.004\n* **Severity**:L0\n* **Content**:主键和外键为关系型数据库中两种重要约束，删除已有约束会打破已有业务逻辑，操作前请业务开发与 DBA 确认影响，三思而行。\n* **Case**:\n\n```sql\nALTER TABLE tbl DROP PRIMARY KEY;\n```\n## 不建议使用前项通配符查找\n\n* **Item**:ARG.001\n* **Severity**:L4\n* **Content**:例如 \"％foo\"，查询参数有一个前项通配符的情况无法使用已有索引。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from tbl where name like '%foo'\n```\n## 没有通配符的 LIKE 查询\n\n* **Item**:ARG.002\n* **Severity**:L1\n* **Content**:不包含通配符的 LIKE 查询可能存在逻辑错误，因为逻辑上它与等值查询相同。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from tbl where name like 'foo'\n```\n## 参数比较包含隐式转换，无法使用索引\n\n* **Item**:ARG.003\n* **Severity**:L4\n* **Content**:隐式类型转换有无法命中索引的风险，在高并发、大数据量的情况下，命不中索引带来的后果非常严重。\n* **Case**:\n\n```sql\nSELECT * FROM sakila.film WHERE length >= '60';\n```\n## IN (NULL)/NOT IN (NULL) 永远非真\n\n* **Item**:ARG.004\n* **Severity**:L4\n* **Content**:正确的作法是 col IN ('val1', 'val2', 'val3') OR col IS NULL\n* **Case**:\n\n```sql\nSELECT * FROM tb WHERE col IN (NULL);\n```\n## IN 要慎用，元素过多会导致全表扫描\n\n* **Item**:ARG.005\n* **Severity**:L1\n* **Content**: 如：select id from t where num in(1,2,3)对于连续的数值，能用 BETWEEN 就不要用 IN 了：select id from t where num between 1 and 3。而当 IN 值过多时 MySQL 也可能会进入全表扫描导致性能急剧下降。\n* **Case**:\n\n```sql\nselect id from t where num in(1,2,3)\n```\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item**:ARG.006\n* **Severity**:L1\n* **Content**:使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n* **Case**:\n\n```sql\nselect id from t where num is null\n```\n## 避免使用模式匹配\n\n* **Item**:ARG.007\n* **Severity**:L3\n* **Content**:性能问题是使用模式匹配操作符的最大缺点。使用 LIKE 或正则表达式进行模式匹配进行查询的另一个问题，是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代 SQL，比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL，请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说，您不一定要使用SQL来解决所有问题。\n* **Case**:\n\n```sql\nselect c_id,c2,c3 from tbl where c2 like 'test%'\n```\n## OR 查询索引列时请尽量使用 IN 谓词\n\n* **Item**:ARG.008\n* **Severity**:L1\n* **Content**:IN-list 谓词可以用于索引检索，并且优化器可以对 IN-list 进行排序，以匹配索引的排序序列，从而获得更有效的检索。请注意，IN-list 必须只包含常量，或在查询块执行期间保持常量的值，例如外引用。\n* **Case**:\n\n```sql\nSELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17\n```\n## 引号中的字符串开头或结尾包含空格\n\n* **Item**:ARG.009\n* **Severity**:L1\n* **Content**:如果 VARCHAR 列的前后存在空格将可能引起逻辑问题，如在 MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。\n* **Case**:\n\n```sql\nSELECT 'abc '\n```\n## 不要使用 hint，如：sql\\_no\\_cache, force index, ignore key, straight join等\n\n* **Item**:ARG.010\n* **Severity**:L1\n* **Content**:hint 是用来强制 SQL 按照某个执行计划来执行，但随着数据量变化我们无法保证自己当初的预判是正确的。\n* **Case**:\n\n```sql\nSELECT * FROM t1 USE INDEX (i1) ORDER BY a;\n```\n## 不要使用负向查询，如：NOT IN/NOT LIKE\n\n* **Item**:ARG.011\n* **Severity**:L3\n* **Content**:请尽量不要使用负向查询，这将导致全表扫描，对查询性能影响较大。\n* **Case**:\n\n```sql\nselect id from t where num not in(1,2,3);\n```\n## 一次性 INSERT/REPLACE 的数据过多\n\n* **Item**:ARG.012\n* **Severity**:L2\n* **Content**:单条 INSERT/REPLACE 语句批量插入大量数据性能较差，甚至可能导致从库同步延迟。为了提升性能，减少批量写入数据对从库同步延时的影响，建议采用分批次插入的方法。\n* **Case**:\n\n```sql\nINSERT INTO tb (a) VALUES (1), (2)\n```\n## DDL 语句中使用了中文全角引号\n\n* **Item**:ARG.013\n* **Severity**:L0\n* **Content**:DDL 语句中使用了中文全角引号“”或‘’，这可能是书写错误，请确认是否符合预期。\n* **Case**:\n\n```sql\nCREATE TABLE tb (a varchar(10) default '“”'\n```\n## IN 条件中存在列名，可能导致数据匹配范围扩大\n\n* **Item**:ARG.014\n* **Severity**:L4\n* **Content**:如：delete from t where id in(1, 2, id) 可能会导致全表数据误删除。请仔细检查 IN 条件的正确性。\n* **Case**:\n\n```sql\nselect id from t where id in(1, 2, id)\n```\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item**:CLA.001\n* **Severity**:L4\n* **Content**:SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n* **Case**:\n\n```sql\nselect id from tbl\n```\n## 不建议使用 ORDER BY RAND()\n\n* **Item**:CLA.002\n* **Severity**:L3\n* **Content**:ORDER BY RAND() 是从结果集中检索随机行的一种非常低效的方法，因为它会对整个结果进行排序并丢弃其大部分数据。\n* **Case**:\n\n```sql\nselect name from tbl where id < 1000 order by rand(number)\n```\n## 不建议使用带 OFFSET 的LIMIT 查询\n\n* **Item**:CLA.003\n* **Severity**:L2\n* **Content**:使用 LIMIT 和 OFFSET 对结果集分页的复杂度是 O(n^2)，并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。\n* **Case**:\n\n```sql\nselect c1,c2 from tbl where name=xx order by number limit 1 offset 20\n```\n## 不建议对常量进行 GROUP BY\n\n* **Item**:CLA.004\n* **Severity**:L2\n* **Content**:GROUP BY 1 表示按第一列进行 GROUP BY。如果在 GROUP BY 子句中使用数字，而不是表达式或列名称，当查询列顺序改变时，可能会导致问题。\n* **Case**:\n\n```sql\nselect col1,col2 from tbl group by 1\n```\n## ORDER BY 常数列没有任何意义\n\n* **Item**:CLA.005\n* **Severity**:L2\n* **Content**:SQL 逻辑上可能存在错误; 最多只是一个无用的操作，不会更改查询结果。\n* **Case**:\n\n```sql\nselect id from test where id=1 order by id\n```\n## 在不同的表中 GROUP BY 或 ORDER BY\n\n* **Item**:CLA.006\n* **Severity**:L4\n* **Content**:这将强制使用临时表和 filesort，可能产生巨大性能隐患，并且可能消耗大量内存和磁盘上的临时空间。\n* **Case**:\n\n```sql\nselect tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col\n```\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item**:CLA.008\n* **Severity**:L2\n* **Content**:默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1 where c1='foo' group by c2\n```\n## ORDER BY 的条件为表达式\n\n* **Item**:CLA.009\n* **Severity**:L2\n* **Content**:当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n* **Case**:\n\n```sql\nselect description from film where title ='ACADEMY DINOSAUR' order by length-language_id;\n```\n## GROUP BY 的条件为表达式\n\n* **Item**:CLA.010\n* **Severity**:L2\n* **Content**:当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n* **Case**:\n\n```sql\nselect description from film where title ='ACADEMY DINOSAUR' GROUP BY length-language_id;\n```\n## 建议为表添加注释\n\n* **Item**:CLA.011\n* **Severity**:L1\n* **Content**:为表添加注释能够使得表的意义更明确，从而为日后的维护带来极大的便利。\n* **Case**:\n\n```sql\nCREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128) DEFAULT NULL,PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8\n```\n## 将复杂的裹脚布式查询分解成几个简单的查询\n\n* **Item**:CLA.012\n* **Severity**:L2\n* **Content**:SQL是一门极具表现力的语言，您可以在单个SQL查询或者单条语句中完成很多事情。但这并不意味着必须强制只使用一行代码，或者认为使用一行代码就搞定每个任务是个好主意。通过一个查询来获得所有结果的常见后果是得到了一个笛卡儿积。当查询中的两张表之间没有条件限制它们的关系时，就会发生这种情况。没有对应的限制而直接使用两张表进行联结查询，就会得到第一张表中的每一行和第二张表中的每一行的一个组合。每一个这样的组合就会成为结果集中的一行，最终您就会得到一个行数很多的结果集。重要的是要考虑这些查询很难编写、难以修改和难以调试。数据库查询请求的日益增加应该是预料之中的事。经理们想要更复杂的报告以及在用户界面上添加更多的字段。如果您的设计很复杂，并且是一个单一查询，要扩展它们就会很费时费力。不论对您还是项目来说，时间花在这些事情上面不值得。将复杂的意大利面条式查询分解成几个简单的查询。当您拆分一个复杂的SQL查询时，得到的结果可能是很多类似的查询，可能仅仅在数据类型上有所不同。编写所有的这些查询是很乏味的，因此，最好能够有个程序自动生成这些代码。SQL代码生成是一个很好的应用。尽管SQL支持用一行代码解决复杂的问题，但也别做不切实际的事情。\n* **Case**:\n\n```sql\n这是一条很长很长的 SQL，案例略。\n```\n## 不建议使用 HAVING 子句\n\n* **Item**:CLA.013\n* **Severity**:L3\n* **Content**:将查询的 HAVING 子句改写为 WHERE 中的查询条件，可以在查询处理期间使用索引。\n* **Case**:\n\n```sql\nSELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id\n```\n## 删除全表时建议使用 TRUNCATE 替代 DELETE\n\n* **Item**:CLA.014\n* **Severity**:L2\n* **Content**:删除全表时建议使用 TRUNCATE 替代 DELETE\n* **Case**:\n\n```sql\ndelete from tbl\n```\n## UPDATE 未指定 WHERE 条件\n\n* **Item**:CLA.015\n* **Severity**:L4\n* **Content**:UPDATE 不指定 WHERE 条件一般是致命的，请您三思后行\n* **Case**:\n\n```sql\nupdate tbl set col=1\n```\n## 不要 UPDATE 主键\n\n* **Item**:CLA.016\n* **Severity**:L2\n* **Content**:主键是数据表中记录的唯一标识符，不建议频繁更新主键列，这将影响元数据统计信息进而影响正常的查询。\n* **Case**:\n\n```sql\nupdate tbl set col=1\n```\n## 不建议使用 SELECT \\* 类型查询\n\n* **Item**:COL.001\n* **Severity**:L1\n* **Content**:当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n* **Case**:\n\n```sql\nselect * from tbl where id=1\n```\n## INSERT/REPLACE 未指定列名\n\n* **Item**:COL.002\n* **Severity**:L2\n* **Content**:当表结构发生变更，如果 INSERT 或 REPLACE 请求不明确指定列名，请求的结果将会与预想的不同; 建议使用 “INSERT INTO tbl(col1，col2)VALUES ...” 代替。\n* **Case**:\n\n```sql\ninsert into tbl values(1,'name')\n```\n## 建议修改自增 ID 为无符号类型\n\n* **Item**:COL.003\n* **Severity**:L2\n* **Content**:建议修改自增 ID 为无符号类型\n* **Case**:\n\n```sql\ncreate table test(`id` int(11) NOT NULL AUTO_INCREMENT)\n```\n## 请为列添加默认值\n\n* **Item**:COL.004\n* **Severity**:L1\n* **Content**:请为列添加默认值，如果是 ALTER 操作，请不要忘记将原字段的默认值写上。字段无默认值，当表较大时无法在线变更表结构。\n* **Case**:\n\n```sql\nCREATE TABLE tbl (col int) ENGINE=InnoDB;\n```\n## 列未添加注释\n\n* **Item**:COL.005\n* **Severity**:L1\n* **Content**:建议对表中每个列添加注释，来明确每个列在表中的含义及作用。\n* **Case**:\n\n```sql\nCREATE TABLE tbl (col int) ENGINE=InnoDB;\n```\n## 表中包含有太多的列\n\n* **Item**:COL.006\n* **Severity**:L3\n* **Content**:表中包含有太多的列\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( cols ....);\n```\n## 表中包含有太多的 text/blob 列\n\n* **Item**:COL.007\n* **Severity**:L3\n* **Content**:表中包含超过2个的 text/blob 列\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( cols ....);\n```\n## 可使用 VARCHAR 代替 CHAR， VARBINARY 代替 BINARY\n\n* **Item**:COL.008\n* **Severity**:L1\n* **Content**:为首先变长字段存储空间小，可以节省存储空间。其次对于查询来说，在一个相对较小的字段内搜索效率显然要高些。\n* **Case**:\n\n```sql\ncreate table t1(id int,name char(20),last_time date)\n```\n## 建议使用精确的数据类型\n\n* **Item**:COL.009\n* **Severity**:L2\n* **Content**:实际上，任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时，非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。\n* **Case**:\n\n```sql\nCREATE TABLE tab2 (p_id  BIGINT UNSIGNED NOT NULL,a_id  BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))\n```\n## 不建议使用 ENUM/BIT/SET 数据类型\n\n* **Item**:COL.010\n* **Severity**:L2\n* **Content**:ENUM 定义了列中值的类型，使用字符串表示 ENUM 里的值时，实际存储在列中的数据是这些值在定义时的序数。因此，这列的数据是字节对齐的，当您进行一次排序查询时，结果是按照实际存储的序数值排序的，而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值；您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项，您可能会为历史数据而烦恼。作为一种策略，改变元数据——也就是说，改变表和列的定义——应该是不常见的，并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表，每一行包含一个允许在列中出现的候选值；然后在引用新表的旧表上声明一个外键约束。\n* **Case**:\n\n```sql\ncreate table tab1(status ENUM('new','in progress','fixed'))\n```\n## 当需要唯一约束时才使用 NULL，仅当列不能有缺失值时才使用 NOT NULL\n\n* **Item**:COL.011\n* **Severity**:L0\n* **Content**:NULL 和0是不同的，10乘以 NULL 还是 NULL。NULL 和空字符串是不一样的。将一个字符串和标准 SQL 中的 NULL 联合起来的结果还是 NULL。NULL 和 FALSE 也是不同的。AND、OR 和 NOT 这三个布尔操作如果涉及 NULL，其结果也让很多人感到困惑。当您将一列声明为 NOT NULL 时，也就是说这列中的每一个值都必须存在且是有意义的。使用 NULL 来表示任意类型不存在的空值。 当您将一列声明为 NOT NULL 时，也就是说这列中的每一个值都必须存在且是有意义的。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from tbl where c4 is null or c4 <> 1\n```\n## TEXT、BLOB 和 JSON 类型的字段不建议设置为 NOT NULL\n\n* **Item**:COL.012\n* **Severity**:L5\n* **Content**:TEXT、BLOB 和 JSON 类型的字段无法指定非 NULL 的默认值，如果添加了 NOT NULL 限制，写入数据时又未对该字段指定值可能导致写入失败。\n* **Case**:\n\n```sql\nCREATE TABLE `tb`(`c` longblob NOT NULL);\n```\n## TIMESTAMP 类型默认值检查异常\n\n* **Item**:COL.013\n* **Severity**:L4\n* **Content**:TIMESTAMP 类型建议设置默认值，且不建议使用 0 或 0000-00-00 00:00:00 作为默认值。可以考虑使用 1970-08-02 01:01:01\n* **Case**:\n\n```sql\nCREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);\n```\n## 为列指定了字符集\n\n* **Item**:COL.014\n* **Severity**:L5\n* **Content**:建议列与表使用同一个字符集，不要单独指定列的字符集。\n* **Case**:\n\n```sql\nCREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)\n```\n## TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值\n\n* **Item**:COL.015\n* **Severity**:L4\n* **Content**:MySQL 数据库中 TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值。TEXT最大长度为2^16-1个字符，MEDIUMTEXT最大长度为2^32-1个字符，LONGTEXT最大长度为2^64-1个字符。\n* **Case**:\n\n```sql\nCREATE TABLE `tbl` (`c` blob DEFAULT NULL);\n```\n## 整型定义建议采用 INT(10) 或 BIGINT(20)\n\n* **Item**:COL.016\n* **Severity**:L1\n* **Content**:INT(M) 在 integer 数据类型中，M 表示最大显示宽度。 在 INT(M) 中，M 的值跟 INT(M) 所占多少存储空间并无任何关系。 INT(3)、INT(4)、INT(8) 在磁盘上都是占用 4 bytes 的存储空间。高版本 MySQL 已经不推荐设置整数显示宽度。\n* **Case**:\n\n```sql\nCREATE TABLE tab (a INT(1));\n```\n## VARCHAR 定义长度过长\n\n* **Item**:COL.017\n* **Severity**:L2\n* **Content**:varchar 是可变长字符串，不预先分配存储空间，长度不要超过1024，如果存储长度过长 MySQL 将定义字段类型为 text，独立出来一张表，用主键来对应，避免影响其它字段索引效率。\n* **Case**:\n\n```sql\nCREATE TABLE tab (a varchar(3500));\n```\n## 建表语句中使用了不推荐的字段类型\n\n* **Item**:COL.018\n* **Severity**:L9\n* **Content**:以下字段类型不被推荐使用：boolean\n* **Case**:\n\n```sql\nCREATE TABLE tab (a BOOLEAN);\n```\n## 不建议使用精度在秒级以下的时间数据类型\n\n* **Item**:COL.019\n* **Severity**:L1\n* **Content**:使用高精度的时间数据类型带来的存储空间消耗相对较大；MySQL 在5.6.4以上才可以支持精确到微秒的时间数据类型，使用时需要考虑版本兼容问题。\n* **Case**:\n\n```sql\nCREATE TABLE t1 (t TIME(3), dt DATETIME(6));\n```\n## 消除不必要的 DISTINCT 条件\n\n* **Item**:DIS.001\n* **Severity**:L1\n* **Content**:太多DISTINCT条件是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少DISTINCT条件的数量。如果主键列是列的结果集的一部分，则DISTINCT条件可能没有影响。\n* **Case**:\n\n```sql\nSELECT DISTINCT c.c_id,count(DISTINCT c.c_name),count(DISTINCT c.c_e),count(DISTINCT c.c_n),count(DISTINCT c.c_me),c.c_d FROM (select distinct id, name from B) as e WHERE e.country_id = c.country_id\n```\n## COUNT(DISTINCT) 多列时结果可能和你预想的不同\n\n* **Item**:DIS.002\n* **Severity**:L3\n* **Content**:COUNT(DISTINCT col) 计算该列除NULL之外的不重复行数，注意 COUNT(DISTINCT col, col2) 如果其中一列全为 NULL 那么即使另一列有不同的值，也返回0。\n* **Case**:\n\n```sql\nSELECT COUNT(DISTINCT col, col2) FROM tbl;\n```\n## DISTINCT \\* 对有主键的表没有意义\n\n* **Item**:DIS.003\n* **Severity**:L3\n* **Content**:当表已经有主键时，对所有列进行 DISTINCT 的输出结果与不进行 DISTINCT 操作的结果相同，请不要画蛇添足。\n* **Case**:\n\n```sql\nSELECT DISTINCT * FROM film;\n```\n## 避免在 WHERE 条件中使用函数或其他运算符\n\n* **Item**:FUN.001\n* **Severity**:L2\n* **Content**:虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\n* **Case**:\n\n```sql\nselect id from t where substring(name,1,3)='abc'\n```\n## 指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(\\*) 操作性能不佳\n\n* **Item**:FUN.002\n* **Severity**:L1\n* **Content**:COUNT(\\*) 的作用是统计表行数，COUNT(COL) 的作用是统计指定列非 NULL 的行数。MyISAM 表对于 COUNT(\\*) 统计全表行数进行了特殊的优化，通常情况下非常快。但对于非 MyISAM 表或指定了某些 WHERE 条件，COUNT(\\*) 操作需要扫描大量的行才能获取精确的结果，性能也因此不佳。有时候某些业务场景并不需要完全精确的 COUNT 值，此时可以用近似值来代替。EXPLAIN 出来的优化器估算的行数就是一个不错的近似值，执行 EXPLAIN 并不需要真正去执行查询，所以成本很低。\n* **Case**:\n\n```sql\nSELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY num\n```\n## 使用了合并为可空列的字符串连接\n\n* **Item**:FUN.003\n* **Severity**:L3\n* **Content**:在一些查询请求中，您需要强制让某一列或者某个表达式返回非 NULL 的值，从而让查询逻辑变得更简单，但又不想将这个值存下来。可以使用 COALESCE() 函数来构造连接的表达式，这样即使是空值列也不会使整表达式变为 NULL。\n* **Case**:\n\n```sql\nselect c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl\n```\n## 不建议使用 SYSDATE() 函数\n\n* **Item**:FUN.004\n* **Severity**:L4\n* **Content**:SYSDATE() 函数可能导致主从数据不一致，请使用 NOW() 函数替代 SYSDATE()。\n* **Case**:\n\n```sql\nSELECT SYSDATE();\n```\n## 不建议使用 COUNT(col) 或 COUNT(常量)\n\n* **Item**:FUN.005\n* **Severity**:L1\n* **Content**:不要使用 COUNT(col) 或 COUNT(常量) 来替代 COUNT(\\*), COUNT(\\*) 是 SQL92 定义的标准统计行数的方法，跟数据无关，跟 NULL 和非 NULL 也无关。\n* **Case**:\n\n```sql\nSELECT COUNT(1) FROM tbl;\n```\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item**:FUN.006\n* **Severity**:L1\n* **Content**:当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n* **Case**:\n\n```sql\nSELECT SUM(COL) FROM tbl;\n```\n## 不建议使用触发器\n\n* **Item**:FUN.007\n* **Severity**:L1\n* **Content**:触发器的执行没有反馈和日志，隐藏了实际的执行步骤，当数据库出现问题是，不能通过慢日志分析触发器的具体执行情况，不易发现问题。在MySQL中，触发器不能临时关闭或打开，在数据迁移或数据恢复等场景下，需要临时drop触发器，可能影响到生产环境。\n* **Case**:\n\n```sql\nCREATE TRIGGER t1 AFTER INSERT ON work FOR EACH ROW INSERT INTO time VALUES(NOW());\n```\n## 不建议使用存储过程\n\n* **Item**:FUN.008\n* **Severity**:L1\n* **Content**:存储过程无版本控制，配合业务的存储过程升级很难做到业务无感知。存储过程在拓展和移植上也存在问题。\n* **Case**:\n\n```sql\nCREATE PROCEDURE simpleproc (OUT param1 INT);\n```\n## 不建议使用自定义函数\n\n* **Item**:FUN.009\n* **Severity**:L1\n* **Content**:不建议使用自定义函数\n* **Case**:\n\n```sql\nCREATE FUNCTION hello (s CHAR(20));\n```\n## 不建议对等值查询列使用 GROUP BY\n\n* **Item**:GRP.001\n* **Severity**:L2\n* **Content**:GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询，对这样的列进行 GROUP BY 意义不大。\n* **Case**:\n\n```sql\nselect film_id, title from film where release_year='2006' group by release_year\n```\n## JOIN 语句混用逗号和 ANSI 模式\n\n* **Item**:JOI.001\n* **Severity**:L2\n* **Content**:表连接的时候混用逗号和 ANSI JOIN 不便于人类理解，并且MySQL不同版本的表连接行为和优先级均有所不同，当 MySQL 版本变化后可能会引入错误。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000\n```\n## 同一张表被连接两次\n\n* **Item**:JOI.002\n* **Severity**:L4\n* **Content**:相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\n* **Case**:\n\n```sql\nselect tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1\n```\n## OUTER JOIN 失效\n\n* **Item**:JOI.003\n* **Severity**:L4\n* **Content**:由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回，这会将查询隐式转换为 INNER JOIN 。如：select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解，因为 LEFT/RIGHT JOIN 是 LEFT/RIGHT OUTER JOIN 的缩写。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4\n```\n## 不建议使用排它 JOIN\n\n* **Item**:JOI.004\n* **Severity**:L4\n* **Content**:只在右侧表为 NULL 的带 WHERE 子句的 LEFT OUTER JOIN 语句，有可能是在WHERE子句中使用错误的列，如：“... FROM l LEFT OUTER JOIN r ON l.l = r.r WHERE r.z IS NULL”，这个查询正确的逻辑可能是 WHERE r.r IS NULL。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null\n```\n## 减少 JOIN 的数量\n\n* **Item**:JOI.005\n* **Severity**:L2\n* **Content**:太多的 JOIN 是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少 JOIN 的数量。\n* **Case**:\n\n```sql\nselect 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\n```\n## 将嵌套查询重写为 JOIN 通常会导致更高效的执行和更有效的优化\n\n* **Item**:JOI.006\n* **Severity**:L4\n* **Content**:一般来说，非嵌套子查询总是用于关联子查询，最多是来自FROM子句中的一个表，这些子查询用于 ANY, ALL 和 EXISTS 的谓词。如果可以根据查询语义决定子查询最多返回一个行，那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。\n* **Case**:\n\n```sql\nSELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 100996 AND s.q = 1 )\n```\n## 不建议使用联表删除或更新\n\n* **Item**:JOI.007\n* **Severity**:L4\n* **Content**:当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n* **Case**:\n\n```sql\nUPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano';\n```\n## 不要使用跨数据库的 JOIN 查询\n\n* **Item**:JOI.008\n* **Severity**:L4\n* **Content**:一般来说，跨数据库的 JOIN 查询意味着查询语句跨越了两个不同的子系统，这可能意味着系统耦合度过高或库表结构设计不合理。\n* **Case**:\n\n```sql\nSELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 100996 AND s.q = 1 )\n```\n## 建议使用自增列作为主键，如使用联合自增主键时请将自增键作为第一列\n\n* **Item**:KEY.001\n* **Severity**:L2\n* **Content**:建议使用自增列作为主键，如使用联合自增主键时请将自增键作为第一列\n* **Case**:\n\n```sql\ncreate table test(`id` int(11) NOT NULL PRIMARY KEY (`id`))\n```\n## 无主键或唯一键，无法在线变更表结构\n\n* **Item**:KEY.002\n* **Severity**:L4\n* **Content**:无主键或唯一键，无法在线变更表结构\n* **Case**:\n\n```sql\ncreate table test(col varchar(5000))\n```\n## 避免外键等递归关系\n\n* **Item**:KEY.003\n* **Severity**:L4\n* **Content**:存在递归关系的数据很常见，数据常会像树或者以层级方式组织。然而，创建一个外键约束来强制执行同一表中两列之间的关系，会导致笨拙的查询。树的每一层对应着另一个连接。您将需要发出递归查询，以获得节点的所有后代或所有祖先。解决方案是构造一个附加的闭包表。它记录了树中所有节点间的关系，而不仅仅是那些具有直接的父子关系。您也可以比较不同层次的数据设计：闭包表，路径枚举，嵌套集。然后根据应用程序的需要选择一个。\n* **Case**:\n\n```sql\nCREATE 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))\n```\n## 提醒：请将索引属性顺序与查询对齐\n\n* **Item**:KEY.004\n* **Severity**:L0\n* **Content**:如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\n* **Case**:\n\n```sql\ncreate index idx1 on tbl (last_name,first_name)\n```\n## 表建的索引过多\n\n* **Item**:KEY.005\n* **Severity**:L2\n* **Content**:表建的索引过多\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c(`c`));\n```\n## 主键中的列过多\n\n* **Item**:KEY.006\n* **Severity**:L4\n* **Content**:主键中的列过多\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));\n```\n## 未指定主键或主键非 int 或 bigint\n\n* **Item**:KEY.007\n* **Severity**:L4\n* **Content**:未指定主键或主键非 int 或 bigint，建议将主键设置为 int unsigned 或 bigint unsigned。\n* **Case**:\n\n```sql\nCREATE TABLE tbl (a int);\n```\n## ORDER BY 多个列但排序方向不同时可能无法使用索引\n\n* **Item**:KEY.008\n* **Severity**:L4\n* **Content**:在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\n* **Case**:\n\n```sql\nSELECT * FROM tbl ORDER BY a DESC, b ASC;\n```\n## 添加唯一索引前请注意检查数据唯一性\n\n* **Item**:KEY.009\n* **Severity**:L0\n* **Content**:请提前检查添加唯一索引列的数据唯一性，如果数据不唯一在线表结构调整时将有可能自动将重复列删除，这有可能导致数据丢失。\n* **Case**:\n\n```sql\nCREATE UNIQUE INDEX part_of_name ON customer (name(10));\n```\n## 全文索引不是银弹\n\n* **Item**:KEY.010\n* **Severity**:L0\n* **Content**:全文索引主要用于解决模糊查询的性能问题，但需要控制好查询的频率和并发度。同时注意调整 ft\\_min\\_word\\_len, ft\\_max\\_word\\_len, ngram\\_token\\_size 等参数。\n* **Case**:\n\n```sql\nCREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) ) ENGINE=InnoDB;\n```\n## SQL\\_CALC\\_FOUND\\_ROWS 效率低下\n\n* **Item**:KWR.001\n* **Severity**:L2\n* **Content**:因为 SQL\\_CALC\\_FOUND\\_ROWS 不能很好地扩展，所以可能导致性能问题; 建议业务使用其他策略来替代 SQL\\_CALC\\_FOUND\\_ROWS 提供的计数功能，比如：分页结果展示等。\n* **Case**:\n\n```sql\nselect SQL_CALC_FOUND_ROWS col from tbl where id>1000\n```\n## 不建议使用 MySQL 关键字做列名或表名\n\n* **Item**:KWR.002\n* **Severity**:L2\n* **Content**:当使用关键字做为列名或表名时程序需要对列名和表名进行转义，如果疏忽被将导致请求无法执行。\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( `select` int )\n```\n## 不建议使用复数做列名或表名\n\n* **Item**:KWR.003\n* **Severity**:L1\n* **Content**:表名应该仅仅表示表里面的实体内容，不应该表示实体数量，对应于 DO 类名也是单数形式，符合表达习惯。\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( `books` int )\n```\n## 不建议使用使用多字节编码字符(中文)命名\n\n* **Item**:KWR.004\n* **Severity**:L1\n* **Content**:为库、表、列、别名命名时建议使用英文，数字，下划线等字符，不建议使用中文或其他多字节编码字符。\n* **Case**:\n\n```sql\nselect col as 列 from tb\n```\n## SQL 中包含 unicode 特殊字符\n\n* **Item**:KWR.005\n* **Severity**:L1\n* **Content**:部分 IDE 会自动在 SQL 插入肉眼不可见的 unicode 字符。如：non-break space, zero-width space 等。Linux 下可使用 \\`cat -A file.sql\\` 命令查看不可见字符。\n* **Case**:\n\n```sql\nupdate tb set status = 1 where id = 1;\n```\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item**:LCK.001\n* **Severity**:L3\n* **Content**:INSERT INTO xx SELECT 加锁粒度较大请谨慎\n* **Case**:\n\n```sql\nINSERT INTO tbl SELECT * FROM tbl2;\n```\n## 请慎用 INSERT ON DUPLICATE KEY UPDATE\n\n* **Item**:LCK.002\n* **Severity**:L3\n* **Content**:当主键为自增键时使用 INSERT ON DUPLICATE KEY UPDATE 可能会导致主键出现大量不连续快速增长，导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。\n* **Case**:\n\n```sql\nINSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;\n```\n## 用字符类型存储IP地址\n\n* **Item**:LIT.001\n* **Severity**:L2\n* **Content**:字符串字面上看起来像IP地址，但不是 INET\\_ATON() 的参数，表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。\n* **Case**:\n\n```sql\ninsert into tbl (IP,name) values('10.20.306.122','test')\n```\n## 日期/时间未使用引号括起\n\n* **Item**:LIT.002\n* **Severity**:L4\n* **Content**:诸如“WHERE col <2010-02-12”之类的查询是有效的SQL，但可能是一个错误，因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号，且引号前后不应有空格。\n* **Case**:\n\n```sql\nselect col1,col2 from tbl where time < 2018-01-10\n```\n## 一列中存储一系列相关数据的集合\n\n* **Item**:LIT.003\n* **Severity**:L3\n* **Content**:将 ID 存储为一个列表，作为 VARCHAR/TEXT 列，这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证 ID 更加困难。考虑一下，列表最多支持存放多少数据呢？将 ID 存储在一张单独的表中，代替使用多值属性，从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询，也更有效地验证ID。\n* **Case**:\n\n```sql\nselect c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'\n```\n## 请使用分号或已设定的 DELIMITER 结尾\n\n* **Item**:LIT.004\n* **Severity**:L1\n* **Content**:USE database, SHOW DATABASES 等命令也需要使用使用分号或已设定的 DELIMITER 结尾。\n* **Case**:\n\n```sql\nUSE db\n```\n## 非确定性的 GROUP BY\n\n* **Item**:RES.001\n* **Severity**:L4\n* **Content**:SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1 where c2='foo' group by c2\n```\n## 未使用 ORDER BY 的 LIMIT 查询\n\n* **Item**:RES.002\n* **Severity**:L4\n* **Content**:没有 ORDER BY 的 LIMIT 会导致非确定性的结果，这取决于查询执行计划。\n* **Case**:\n\n```sql\nselect col1,col2 from tbl where name=xx limit 10\n```\n## UPDATE/DELETE 操作使用了 LIMIT 条件\n\n* **Item**:RES.003\n* **Severity**:L4\n* **Content**:UPDATE/DELETE 操作使用 LIMIT 条件和不添加 WHERE 条件一样危险，它可将会导致主从数据不一致或从库同步中断。\n* **Case**:\n\n```sql\nUPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;\n```\n## UPDATE/DELETE 操作指定了 ORDER BY 条件\n\n* **Item**:RES.004\n* **Severity**:L4\n* **Content**:UPDATE/DELETE 操作不要指定 ORDER BY 条件。\n* **Case**:\n\n```sql\nUPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title\n```\n## UPDATE 语句可能存在逻辑错误，导致数据损坏\n\n* **Item**:RES.005\n* **Severity**:L4\n* **Content**:在一条 UPDATE 语句中，如果要更新多个字段，字段间不能使用 AND ，而应该用逗号分隔。\n* **Case**:\n\n```sql\nupdate tbl set col = 1 and cl = 2 where col=3;\n```\n## 永远不真的比较条件\n\n* **Item**:RES.006\n* **Severity**:L4\n* **Content**:查询条件永远非真，如果该条件出现在 where 中可能导致查询无匹配到的结果。\n* **Case**:\n\n```sql\nselect * from tbl where 1 != 1;\n```\n## 永远为真的比较条件\n\n* **Item**:RES.007\n* **Severity**:L4\n* **Content**:查询条件永远为真，可能导致 WHERE 条件失效进行全表查询。\n* **Case**:\n\n```sql\nselect * from tbl where 1 = 1;\n```\n## 不建议使用LOAD DATA/SELECT ... INTO OUTFILE\n\n* **Item**:RES.008\n* **Severity**:L2\n* **Content**:SELECT INTO OUTFILE 需要授予 FILE 权限，这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度，但同时也可能导致从库同步延迟过大。\n* **Case**:\n\n```sql\nLOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;\n```\n## 不建议使用连续判断\n\n* **Item**:RES.009\n* **Severity**:L2\n* **Content**:类似这样的 SELECT \\* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误，您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。\n* **Case**:\n\n```sql\nSELECT * FROM tbl WHERE col = col = 'abc'\n```\n## 建表语句中定义为 ON UPDATE CURRENT\\_TIMESTAMP 的字段不建议包含业务逻辑\n\n* **Item**:RES.010\n* **Severity**:L2\n* **Content**:定义为 ON UPDATE CURRENT\\_TIMESTAMP 的字段在该表其他字段更新时会联动修改，如果包含业务逻辑用户可见会埋下隐患。后续如有批量修改数据却又不想修改该字段时会导致数据错误。\n* **Case**:\n\n```sql\nCREATE TABLE category (category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,\tname VARCHAR(25) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY  (category_id)\n```\n## 更新请求操作的表包含 ON UPDATE CURRENT\\_TIMESTAMP 字段\n\n* **Item**:RES.011\n* **Severity**:L2\n* **Content**:定义为 ON UPDATE CURRENT\\_TIMESTAMP 的字段在该表其他字段更新时会联动修改，请注意检查。如不想修改字段的更新时间可以使用如下方法：UPDATE category SET name='ActioN', last\\_update=last\\_update WHERE category\\_id=1\n* **Case**:\n\n```sql\nUPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1\n```\n## 请谨慎使用TRUNCATE操作\n\n* **Item**:SEC.001\n* **Severity**:L0\n* **Content**:一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl\\_name;语句。但TRUNCATE操作也并非是毫无代价的，TRUNCATE TABLE无法返回被删除的准确行数，如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO\\_INCREMENT，如果不想重置该值建议使用 DELETE FROM tbl\\_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL)，当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求，因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。\n* **Case**:\n\n```sql\nTRUNCATE TABLE tbl_name\n```\n## 不使用明文存储密码\n\n* **Item**:SEC.002\n* **Severity**:L0\n* **Content**:使用明文存储密码或者使用明文在网络上传递密码都是不安全的。如果攻击者能够截获您用来插入密码的SQL语句，他们就能直接读到密码。另外，将用户输入的字符串以明文的形式插入到纯SQL语句中，也会让攻击者发现它。如果您能够读取密码，黑客也可以。解决方案是使用单向哈希函数对原始密码进行加密编码。哈希是指将输入字符串转化成另一个新的、不可识别的字符串的函数。对密码加密表达式加点随机串来防御“字典攻击”。不要将明文密码输入到SQL查询语句中。在应用程序代码中计算哈希串，只在SQL查询中使用哈希串。\n* **Case**:\n\n```sql\ncreate table test(id int,name varchar(20) not null,password varchar(200)not null)\n```\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item**:SEC.003\n* **Severity**:L0\n* **Content**:在执行高危操作之前对数据进行备份是十分有必要的。\n* **Case**:\n\n```sql\ndelete from table where col = 'condition'\n```\n## 发现常见 SQL 注入函数\n\n* **Item**:SEC.004\n* **Severity**:L0\n* **Content**:SLEEP(), BENCHMARK(), GET\\_LOCK(), RELEASE\\_LOCK() 等函数通常出现在 SQL 注入语句中，会严重影响数据库性能。\n* **Case**:\n\n```sql\nSELECT BENCHMARK(10, RAND())\n```\n## '!=' 运算符是非标准的\n\n* **Item**:STA.001\n* **Severity**:L0\n* **Content**:\"<>\"才是标准SQL中的不等于运算符。\n* **Case**:\n\n```sql\nselect col1,col2 from tbl where type!=0\n```\n## 库名或表名点后建议不要加空格\n\n* **Item**:STA.002\n* **Severity**:L1\n* **Content**:当使用 db.table 或 table.column 格式访问表或字段时，请不要在点号后面添加空格，虽然这样语法正确。\n* **Case**:\n\n```sql\nselect col from sakila. film\n```\n## 索引起名不规范\n\n* **Item**:STA.003\n* **Severity**:L1\n* **Content**:建议普通二级索引以idx\\_为前缀，唯一索引以uk\\_为前缀。\n* **Case**:\n\n```sql\nselect col from now where type!=0\n```\n## 起名时请不要使用字母、数字和下划线之外的字符\n\n* **Item**:STA.004\n* **Severity**:L1\n* **Content**:以字母或下划线开头，名字只允许使用字母、数字和下划线。请统一大小写，不要使用驼峰命名法。不要在名字中出现连续下划线'\\_\\_'，这样很难辨认。\n* **Case**:\n\n```sql\nCREATE TABLE ` abc` (a int);\n```\n## MySQL 对子查询的优化效果不佳\n\n* **Item**:SUB.001\n* **Severity**:L4\n* **Content**:MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n* **Case**:\n\n```sql\nselect col1,col2,col3 from table1 where col2 in(select col from table2)\n```\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item**:SUB.002\n* **Severity**:L2\n* **Content**:与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n* **Case**:\n\n```sql\nselect teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id\n```\n## 考虑使用 EXISTS 而不是 DISTINCT 子查询\n\n* **Item**:SUB.003\n* **Severity**:L3\n* **Content**:DISTINCT 关键字在对元组排序后删除重复。相反，考虑使用一个带有 EXISTS 关键字的子查询，您可以避免返回整个表。\n* **Case**:\n\n```sql\nSELECT DISTINCT c.c_id, c.c_name FROM c,e WHERE e.c_id = c.c_id\n```\n## 执行计划中嵌套连接深度过深\n\n* **Item**:SUB.004\n* **Severity**:L3\n* **Content**:MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。\n* **Case**:\n\n```sql\nSELECT * from tb where id in (select id from (select id from tb))\n```\n## 子查询不支持LIMIT\n\n* **Item**:SUB.005\n* **Severity**:L8\n* **Content**:当前 MySQL 版本不支持在子查询中进行 'LIMIT & IN/ALL/ANY/SOME'。\n* **Case**:\n\n```sql\nSELECT * FROM staff WHERE name IN (SELECT NAME FROM customer ORDER BY name LIMIT 1)\n```\n## 不建议在子查询中使用函数\n\n* **Item**:SUB.006\n* **Severity**:L2\n* **Content**:MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\n* **Case**:\n\n```sql\nSELECT * FROM staff WHERE name IN (SELECT max(NAME) FROM customer)\n```\n## 外层带有 LIMIT 输出限制的 UNION 联合查询，其内层查询建议也添加 LIMIT 输出限制\n\n* **Item**:SUB.007\n* **Severity**:L2\n* **Content**:有时 MySQL 无法将限制条件从外层“下推”到内层，这会使得原本可以限制能够限制部分返回结果的条件无法应用到内层查询的优化上。比如：(SELECT \\* FROM tb1 ORDER BY name) UNION ALL (SELECT \\* FROM tb2 ORDER BY name) LIMIT 20;  MySQL 会将两个子查询的结果放在一个临时表中，然后取出 20 条结果，可以通过在两个子查询中添加 LIMIT 20 来减少临时表中的数据。(SELECT \\* FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT \\* FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;\n* **Case**:\n\n```sql\n(SELECT * FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT * FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;\n```\n## 不建议使用分区表\n\n* **Item**:TBL.001\n* **Severity**:L4\n* **Content**:不建议使用分区表\n* **Case**:\n\n```sql\nCREATE TABLE trb3(id INT, name VARCHAR(50), purchased DATE) PARTITION BY RANGE(YEAR(purchased)) (PARTITION p0 VALUES LESS THAN (1990), PARTITION p1 VALUES LESS THAN (1995), PARTITION p2 VALUES LESS THAN (2000), PARTITION p3 VALUES LESS THAN (2005) );\n```\n## 请为表选择合适的存储引擎\n\n* **Item**:TBL.002\n* **Severity**:L4\n* **Content**:建表或修改表的存储引擎时建议使用推荐的存储引擎，如：innodb\n* **Case**:\n\n```sql\ncreate table test(`id` int(11) NOT NULL AUTO_INCREMENT)\n```\n## 以DUAL命名的表在数据库中有特殊含义\n\n* **Item**:TBL.003\n* **Severity**:L8\n* **Content**:DUAL表为虚拟表，不需要创建即可使用，也不建议服务以DUAL命名表。\n* **Case**:\n\n```sql\ncreate table dual(id int, primary key (id));\n```\n## 表的初始AUTO\\_INCREMENT值不为0\n\n* **Item**:TBL.004\n* **Severity**:L2\n* **Content**:AUTO\\_INCREMENT不为0会导致数据空洞。\n* **Case**:\n\n```sql\nCREATE TABLE tbl (a int) AUTO_INCREMENT = 10;\n```\n## 请使用推荐的字符集\n\n* **Item**:TBL.005\n* **Severity**:L4\n* **Content**:表字符集只允许设置为'utf8,utf8mb4'\n* **Case**:\n\n```sql\nCREATE TABLE tbl (a int) DEFAULT CHARSET = latin1;\n```\n## 不建议使用视图\n\n* **Item**:TBL.006\n* **Severity**:L1\n* **Content**:不建议使用视图\n* **Case**:\n\n```sql\ncreate view v_today (today) AS SELECT CURRENT_DATE;\n```\n## 不建议使用临时表\n\n* **Item**:TBL.007\n* **Severity**:L1\n* **Content**:不建议使用临时表\n* **Case**:\n\n```sql\nCREATE TEMPORARY TABLE `work` (`time` time DEFAULT NULL) ENGINE=InnoDB;\n```\n## 请使用推荐的COLLATE\n\n* **Item**:TBL.008\n* **Severity**:L4\n* **Content**:COLLATE 只允许设置为''\n* **Case**:\n\n```sql\nCREATE TABLE tbl (a int) DEFAULT COLLATE = latin1_bin;\n```\n"
  },
  {
    "path": "advisor/testdata/TestListTestSQLs.golden",
    "content": "SELECT * FROM film WHERE length = 86;\nSELECT * FROM film WHERE length IS NULL;\nSELECT * FROM film HAVING title = 'abc';\nSELECT * FROM sakila.film WHERE length >= 60;\nSELECT * FROM sakila.film WHERE length >= '60';\nSELECT * FROM film WHERE length BETWEEN 60 AND 84;\nSELECT * FROM film WHERE title LIKE 'AIR%';\nSELECT * FROM film WHERE title IS NOT NULL;\nSELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';\nSELECT * FROM film WHERE length > 100 and language_id < 10;\nSELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;\nSELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;\nSELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;\nSELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);\nSELECT release_year, sum(film_id) FROM film GROUP BY release_year;\nSELECT * FROM address GROUP BY address,district;\nSELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;\nSELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;\nSELECT * FROM film ORDER BY release_year LIMIT 10;\nSELECT film_id FROM film ORDER BY release_year LIMIT 10;\nSELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;\nSELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;\nSELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;\nSELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;\nSELECT title FROM film WHERE release_year = 1995;\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;\nSELECT title FROM film WHERE language_id > 5 AND length > 70;\nSELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;\nSELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;\nSELECT * FROM film WHERE length > 100 ORDER BY release_year;\nSELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\nSELECT country_id, last_update FROM city NATURAL JOIN country;\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\nSELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;\nSELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN  (SELECT c.city_id FROM sakila.city c);\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC;\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\nDELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\nDELETE FROM film WHERE length > 100;\nUPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\nUPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\nUPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\nUPDATE film SET length = 10 WHERE language_id = 20;\nINSERT INTO city (country_id) SELECT country_id FROM country;\nINSERT INTO city (country_id) VALUES (1),(2),(3);\nINSERT INTO city (country_id) VALUES (10);\nINSERT INTO city (country_id) SELECT 10 FROM DUAL;\nREPLACE INTO city (country_id) SELECT country_id FROM country;\nREPLACE INTO city (country_id) VALUES (1),(2),(3);\nREPLACE INTO city (country_id) VALUES (10);\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL;\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM  film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\nSELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;\nSELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;\nSELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;\nSELECT * FROM film WHERE date(last_update)='2006-02-15';\nSELECT last_update FROM film GROUP BY date(last_update);\nSELECT last_update FROM film order by date(last_update);\nSELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;\nalter table address add index idx_city_id(city_id);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);\nSELECT\tDATE_FORMAT(t.last_update, '%Y-%m-%d'),\tCOUNT(DISTINCT (t.city))\tFROM city t WHERE t.last_update > '2018-10-22 00:00:00'\tAND t.city LIKE '%Chrome%'\tAND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d');\ncreate table hello.t (id int unsigned);\nselect * from tb where data >= '';\nalter table tb alter column id drop default;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d;\n"
  },
  {
    "path": "advisor/testdata/TestMergeConflictHeuristicRules.golden",
    "content": "advisor.Rule{Item:\"ALI.001\", Severity:\"L0\", Summary:\"建议使用 AS 关键字显示声明一个别名\", Content:\"在列或表别名(如\\\"tbl AS alias\\\")中, 明确使用 AS 关键字比隐含别名(如\\\"tbl alias\\\")更易懂。\", Case:\"select name from tbl t1 where id < 1000\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ALI.002\", Severity:\"L8\", Summary:\"不建议给列通配符'*'设置别名\", Content:\"例: \\\"SELECT tbl.* col1, col2\\\"上面这条 SQL 给列通配符设置了别名，这样的SQL可能存在逻辑错误。您可能意在查询 col1, 但是代替它的是重命名的是 tbl 的最后一列。\", Case:\"select tbl.* as c1,c2,c3 from tbl where id < 1000\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ALI.003\", Severity:\"L1\", Summary:\"别名不要与表或列的名字相同\", Content:\"表或列的别名与其真实名称相同, 这样的别名会使得查询更难去分辨。\", Case:\"select name from tbl as tbl where id < 1000\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ALT.001\", Severity:\"L4\", Summary:\"修改表的默认字符集不会改表各个字段的字符集\", Content:\"很多初学者会将 ALTER TABLE tbl_name [DEFAULT] CHARACTER SET 'UTF8' 误认为会修改所有字段的字符集，但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用 ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;\", Case:\"ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ALT.002\", Severity:\"L2\", Summary:\"同一张表的多条 ALTER 请求建议合为一条\", Content:\"每次表结构变更对线上服务都会产生影响，即使是能够通过在线工具进行调整也请尽量通过合并 ALTER 请求的试减少操作次数。\", Case:\"ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ALT.003\", Severity:\"L0\", Summary:\"删除列为高危操作，操作前请注意检查业务逻辑是否还有依赖\", Content:\"如业务逻辑依赖未完全消除，列被删除后可能导致数据无法写入或无法查询到已删除列数据导致程序异常的情况。这种情况下即使通过备份数据回滚也会丢失用户请求写入的数据。\", Case:\"ALTER TABLE tbl DROP COLUMN col;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ALT.004\", Severity:\"L0\", Summary:\"删除主键和外键为高危操作，操作前请与 DBA 确认影响\", Content:\"主键和外键为关系型数据库中两种重要约束，删除已有约束会打破已有业务逻辑，操作前请业务开发与 DBA 确认影响，三思而行。\", Case:\"ALTER TABLE tbl DROP PRIMARY KEY;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.001\", Severity:\"L4\", Summary:\"不建议使用前项通配符查找\", Content:\"例如 \\\"％foo\\\"，查询参数有一个前项通配符的情况无法使用已有索引。\", Case:\"select c1,c2,c3 from tbl where name like '%foo'\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.002\", Severity:\"L1\", Summary:\"没有通配符的 LIKE 查询\", Content:\"不包含通配符的 LIKE 查询可能存在逻辑错误，因为逻辑上它与等值查询相同。\", Case:\"select c1,c2,c3 from tbl where name like 'foo'\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.003\", Severity:\"L4\", Summary:\"参数比较包含隐式转换，无法使用索引\", Content:\"隐式类型转换有无法命中索引的风险，在高并发、大数据量的情况下，命不中索引带来的后果非常严重。\", Case:\"SELECT * FROM sakila.film WHERE length >= '60';\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.004\", Severity:\"L4\", Summary:\"IN (NULL)/NOT IN (NULL) 永远非真\", Content:\"正确的作法是 col IN ('val1', 'val2', 'val3') OR col IS NULL\", Case:\"SELECT * FROM tb WHERE col IN (NULL);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.006\", Severity:\"L1\", Summary:\"应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\", Content:\"使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\", Case:\"select id from t where num is null\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.007\", Severity:\"L3\", Summary:\"避免使用模式匹配\", Content:\"性能问题是使用模式匹配操作符的最大缺点。使用 LIKE 或正则表达式进行模式匹配进行查询的另一个问题，是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代 SQL，比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL，请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说，您不一定要使用SQL来解决所有问题。\", Case:\"select c_id,c2,c3 from tbl where c2 like 'test%'\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.008\", Severity:\"L1\", Summary:\"OR 查询索引列时请尽量使用 IN 谓词\", Content:\"IN-list 谓词可以用于索引检索，并且优化器可以对 IN-list 进行排序，以匹配索引的排序序列，从而获得更有效的检索。请注意，IN-list 必须只包含常量，或在查询块执行期间保持常量的值，例如外引用。\", Case:\"SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.009\", Severity:\"L1\", Summary:\"引号中的字符串开头或结尾包含空格\", Content:\"如果 VARCHAR 列的前后存在空格将可能引起逻辑问题，如在 MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。\", Case:\"SELECT 'abc '\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.010\", Severity:\"L1\", Summary:\"不要使用 hint，如：sql_no_cache, force index, ignore key, straight join等\", Content:\"hint 是用来强制 SQL 按照某个执行计划来执行，但随着数据量变化我们无法保证自己当初的预判是正确的。\", Case:\"SELECT * FROM t1 USE INDEX (i1) ORDER BY a;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.011\", Severity:\"L3\", Summary:\"不要使用负向查询，如：NOT IN/NOT LIKE\", Content:\"请尽量不要使用负向查询，这将导致全表扫描，对查询性能影响较大。\", Case:\"select id from t where num not in(1,2,3);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.012\", Severity:\"L2\", Summary:\"一次性 INSERT/REPLACE 的数据过多\", Content:\"单条 INSERT/REPLACE 语句批量插入大量数据性能较差，甚至可能导致从库同步延迟。为了提升性能，减少批量写入数据对从库同步延时的影响，建议采用分批次插入的方法。\", Case:\"INSERT INTO tb (a) VALUES (1), (2)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.013\", Severity:\"L0\", Summary:\"DDL 语句中使用了中文全角引号\", Content:\"DDL 语句中使用了中文全角引号“”或‘’，这可能是书写错误，请确认是否符合预期。\", Case:\"CREATE TABLE tb (a varchar(10) default '“”'\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"ARG.014\", Severity:\"L4\", Summary:\"IN 条件中存在列名，可能导致数据匹配范围扩大\", Content:\"如：delete from t where id in(1, 2, id) 可能会导致全表数据误删除。请仔细检查 IN 条件的正确性。\", Case:\"select id from t where id in(1, 2, id)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.001\", Severity:\"L4\", Summary:\"最外层 SELECT 未指定 WHERE 条件\", Content:\"SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\", Case:\"select id from tbl\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.002\", Severity:\"L3\", Summary:\"不建议使用 ORDER BY RAND()\", Content:\"ORDER BY RAND() 是从结果集中检索随机行的一种非常低效的方法，因为它会对整个结果进行排序并丢弃其大部分数据。\", Case:\"select name from tbl where id < 1000 order by rand(number)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.003\", Severity:\"L2\", Summary:\"不建议使用带 OFFSET 的LIMIT 查询\", Content:\"使用 LIMIT 和 OFFSET 对结果集分页的复杂度是 O(n^2)，并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。\", Case:\"select c1,c2 from tbl where name=xx order by number limit 1 offset 20\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.004\", Severity:\"L2\", Summary:\"不建议对常量进行 GROUP BY\", Content:\"GROUP BY 1 表示按第一列进行 GROUP BY。如果在 GROUP BY 子句中使用数字，而不是表达式或列名称，当查询列顺序改变时，可能会导致问题。\", Case:\"select col1,col2 from tbl group by 1\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.005\", Severity:\"L2\", Summary:\"ORDER BY 常数列没有任何意义\", Content:\"SQL 逻辑上可能存在错误; 最多只是一个无用的操作，不会更改查询结果。\", Case:\"select id from test where id=1 order by id\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.006\", Severity:\"L4\", Summary:\"在不同的表中 GROUP BY 或 ORDER BY\", Content:\"这将强制使用临时表和 filesort，可能产生巨大性能隐患，并且可能消耗大量内存和磁盘上的临时空间。\", Case:\"select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.008\", Severity:\"L2\", Summary:\"请为 GROUP BY 显示添加 ORDER BY 条件\", Content:\"默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\", Case:\"select c1,c2,c3 from t1 where c1='foo' group by c2\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.009\", Severity:\"L2\", Summary:\"ORDER BY 的条件为表达式\", Content:\"当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\", Case:\"select description from film where title ='ACADEMY DINOSAUR' order by length-language_id;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.010\", Severity:\"L2\", Summary:\"GROUP BY 的条件为表达式\", Content:\"当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\", Case:\"select description from film where title ='ACADEMY DINOSAUR' GROUP BY length-language_id;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.011\", Severity:\"L1\", Summary:\"建议为表添加注释\", Content:\"为表添加注释能够使得表的意义更明确，从而为日后的维护带来极大的便利。\", Case:\"CREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128) DEFAULT NULL,PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.012\", Severity:\"L2\", Summary:\"将复杂的裹脚布式查询分解成几个简单的查询\", Content:\"SQL是一门极具表现力的语言，您可以在单个SQL查询或者单条语句中完成很多事情。但这并不意味着必须强制只使用一行代码，或者认为使用一行代码就搞定每个任务是个好主意。通过一个查询来获得所有结果的常见后果是得到了一个笛卡儿积。当查询中的两张表之间没有条件限制它们的关系时，就会发生这种情况。没有对应的限制而直接使用两张表进行联结查询，就会得到第一张表中的每一行和第二张表中的每一行的一个组合。每一个这样的组合就会成为结果集中的一行，最终您就会得到一个行数很多的结果集。重要的是要考虑这些查询很难编写、难以修改和难以调试。数据库查询请求的日益增加应该是预料之中的事。经理们想要更复杂的报告以及在用户界面上添加更多的字段。如果您的设计很复杂，并且是一个单一查询，要扩展它们就会很费时费力。不论对您还是项目来说，时间花在这些事情上面不值得。将复杂的意大利面条式查询分解成几个简单的查询。当您拆分一个复杂的SQL查询时，得到的结果可能是很多类似的查询，可能仅仅在数据类型上有所不同。编写所有的这些查询是很乏味的，因此，最好能够有个程序自动生成这些代码。SQL代码生成是一个很好的应用。尽管SQL支持用一行代码解决复杂的问题，但也别做不切实际的事情。\", Case:\"这是一条很长很长的 SQL，案例略。\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.013\", Severity:\"L3\", Summary:\"不建议使用 HAVING 子句\", Content:\"将查询的 HAVING 子句改写为 WHERE 中的查询条件，可以在查询处理期间使用索引。\", Case:\"SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.014\", Severity:\"L2\", Summary:\"删除全表时建议使用 TRUNCATE 替代 DELETE\", Content:\"删除全表时建议使用 TRUNCATE 替代 DELETE\", Case:\"delete from tbl\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.015\", Severity:\"L4\", Summary:\"UPDATE 未指定 WHERE 条件\", Content:\"UPDATE 不指定 WHERE 条件一般是致命的，请您三思后行\", Case:\"update tbl set col=1\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"CLA.016\", Severity:\"L2\", Summary:\"不要 UPDATE 主键\", Content:\"主键是数据表中记录的唯一标识符，不建议频繁更新主键列，这将影响元数据统计信息进而影响正常的查询。\", Case:\"update tbl set col=1\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.001\", Severity:\"L1\", Summary:\"不建议使用 SELECT * 类型查询\", Content:\"当表结构变更时，使用 * 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\", Case:\"select * from tbl where id=1\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.002\", Severity:\"L2\", Summary:\"INSERT/REPLACE 未指定列名\", Content:\"当表结构发生变更，如果 INSERT 或 REPLACE 请求不明确指定列名，请求的结果将会与预想的不同; 建议使用 “INSERT INTO tbl(col1，col2)VALUES ...” 代替。\", Case:\"insert into tbl values(1,'name')\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.003\", Severity:\"L2\", Summary:\"建议修改自增 ID 为无符号类型\", Content:\"建议修改自增 ID 为无符号类型\", Case:\"create table test(`id` int(11) NOT NULL AUTO_INCREMENT)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.004\", Severity:\"L1\", Summary:\"请为列添加默认值\", Content:\"请为列添加默认值，如果是 ALTER 操作，请不要忘记将原字段的默认值写上。字段无默认值，当表较大时无法在线变更表结构。\", Case:\"CREATE TABLE tbl (col int) ENGINE=InnoDB;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.005\", Severity:\"L1\", Summary:\"列未添加注释\", Content:\"建议对表中每个列添加注释，来明确每个列在表中的含义及作用。\", Case:\"CREATE TABLE tbl (col int) ENGINE=InnoDB;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.006\", Severity:\"L3\", Summary:\"表中包含有太多的列\", Content:\"表中包含有太多的列\", Case:\"CREATE TABLE tbl ( cols ....);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.007\", Severity:\"L3\", Summary:\"表中包含有太多的 text/blob 列\", Content:\"表中包含超过2个的 text/blob 列\", Case:\"CREATE TABLE tbl ( cols ....);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.008\", Severity:\"L1\", Summary:\"可使用 VARCHAR 代替 CHAR， VARBINARY 代替 BINARY\", Content:\"为首先变长字段存储空间小，可以节省存储空间。其次对于查询来说，在一个相对较小的字段内搜索效率显然要高些。\", Case:\"create table t1(id int,name char(20),last_time date)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.009\", Severity:\"L2\", Summary:\"建议使用精确的数据类型\", Content:\"实际上，任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时，非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。\", Case:\"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))\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.010\", Severity:\"L2\", Summary:\"不建议使用 ENUM/BIT/SET 数据类型\", Content:\"ENUM 定义了列中值的类型，使用字符串表示 ENUM 里的值时，实际存储在列中的数据是这些值在定义时的序数。因此，这列的数据是字节对齐的，当您进行一次排序查询时，结果是按照实际存储的序数值排序的，而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值；您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项，您可能会为历史数据而烦恼。作为一种策略，改变元数据——也就是说，改变表和列的定义——应该是不常见的，并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表，每一行包含一个允许在列中出现的候选值；然后在引用新表的旧表上声明一个外键约束。\", Case:\"create table tab1(status ENUM('new','in progress','fixed'))\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.011\", Severity:\"L0\", Summary:\"当需要唯一约束时才使用 NULL，仅当列不能有缺失值时才使用 NOT NULL\", Content:\"NULL 和0是不同的，10乘以 NULL 还是 NULL。NULL 和空字符串是不一样的。将一个字符串和标准 SQL 中的 NULL 联合起来的结果还是 NULL。NULL 和 FALSE 也是不同的。AND、OR 和 NOT 这三个布尔操作如果涉及 NULL，其结果也让很多人感到困惑。当您将一列声明为 NOT NULL 时，也就是说这列中的每一个值都必须存在且是有意义的。使用 NULL 来表示任意类型不存在的空值。 当您将一列声明为 NOT NULL 时，也就是说这列中的每一个值都必须存在且是有意义的。\", Case:\"select c1,c2,c3 from tbl where c4 is null or c4 <> 1\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.012\", Severity:\"L5\", Summary:\"TEXT、BLOB 和 JSON 类型的字段不建议设置为 NOT NULL\", Content:\"TEXT、BLOB 和 JSON 类型的字段无法指定非 NULL 的默认值，如果添加了 NOT NULL 限制，写入数据时又未对该字段指定值可能导致写入失败。\", Case:\"CREATE TABLE `tb`(`c` longblob NOT NULL);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.013\", Severity:\"L4\", Summary:\"TIMESTAMP 类型默认值检查异常\", Content:\"TIMESTAMP 类型建议设置默认值，且不建议使用 0 或 0000-00-00 00:00:00 作为默认值。可以考虑使用 1970-08-02 01:01:01\", Case:\"CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.014\", Severity:\"L5\", Summary:\"为列指定了字符集\", Content:\"建议列与表使用同一个字符集，不要单独指定列的字符集。\", Case:\"CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.015\", Severity:\"L4\", Summary:\"TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值\", Content:\"MySQL 数据库中 TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值。TEXT最大长度为2^16-1个字符，MEDIUMTEXT最大长度为2^32-1个字符，LONGTEXT最大长度为2^64-1个字符。\", Case:\"CREATE TABLE `tbl` (`c` blob DEFAULT NULL);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.016\", Severity:\"L1\", Summary:\"整型定义建议采用 INT(10) 或 BIGINT(20)\", Content:\"INT(M) 在 integer 数据类型中，M 表示最大显示宽度。 在 INT(M) 中，M 的值跟 INT(M) 所占多少存储空间并无任何关系。 INT(3)、INT(4)、INT(8) 在磁盘上都是占用 4 bytes 的存储空间。高版本 MySQL 已经不推荐设置整数显示宽度。\", Case:\"CREATE TABLE tab (a INT(1));\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.017\", Severity:\"L2\", Summary:\"VARCHAR 定义长度过长\", Content:\"varchar 是可变长字符串，不预先分配存储空间，长度不要超过1024，如果存储长度过长 MySQL 将定义字段类型为 text，独立出来一张表，用主键来对应，避免影响其它字段索引效率。\", Case:\"CREATE TABLE tab (a varchar(3500));\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.018\", Severity:\"L9\", Summary:\"建表语句中使用了不推荐的字段类型\", Content:\"以下字段类型不被推荐使用：boolean\", Case:\"CREATE TABLE tab (a BOOLEAN);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"COL.019\", Severity:\"L1\", Summary:\"不建议使用精度在秒级以下的时间数据类型\", Content:\"使用高精度的时间数据类型带来的存储空间消耗相对较大；MySQL 在5.6.4以上才可以支持精确到微秒的时间数据类型，使用时需要考虑版本兼容问题。\", Case:\"CREATE TABLE t1 (t TIME(3), dt DATETIME(6));\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"DIS.001\", Severity:\"L1\", Summary:\"消除不必要的 DISTINCT 条件\", Content:\"太多DISTINCT条件是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少DISTINCT条件的数量。如果主键列是列的结果集的一部分，则DISTINCT条件可能没有影响。\", Case:\"SELECT DISTINCT c.c_id,count(DISTINCT c.c_name),count(DISTINCT c.c_e),count(DISTINCT c.c_n),count(DISTINCT c.c_me),c.c_d FROM (select distinct id, name from B) as e WHERE e.country_id = c.country_id\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"DIS.002\", Severity:\"L3\", Summary:\"COUNT(DISTINCT) 多列时结果可能和你预想的不同\", Content:\"COUNT(DISTINCT col) 计算该列除NULL之外的不重复行数，注意 COUNT(DISTINCT col, col2) 如果其中一列全为 NULL 那么即使另一列有不同的值，也返回0。\", Case:\"SELECT COUNT(DISTINCT col, col2) FROM tbl;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"DIS.003\", Severity:\"L3\", Summary:\"DISTINCT * 对有主键的表没有意义\", Content:\"当表已经有主键时，对所有列进行 DISTINCT 的输出结果与不进行 DISTINCT 操作的结果相同，请不要画蛇添足。\", Case:\"SELECT DISTINCT * FROM film;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.001\", Severity:\"L2\", Summary:\"避免在 WHERE 条件中使用函数或其他运算符\", Content:\"虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\", Case:\"select id from t where substring(name,1,3)='abc'\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.002\", Severity:\"L1\", Summary:\"指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(*) 操作性能不佳\", Content:\"COUNT(*) 的作用是统计表行数，COUNT(COL) 的作用是统计指定列非 NULL 的行数。MyISAM 表对于 COUNT(*) 统计全表行数进行了特殊的优化，通常情况下非常快。但对于非 MyISAM 表或指定了某些 WHERE 条件，COUNT(*) 操作需要扫描大量的行才能获取精确的结果，性能也因此不佳。有时候某些业务场景并不需要完全精确的 COUNT 值，此时可以用近似值来代替。EXPLAIN 出来的优化器估算的行数就是一个不错的近似值，执行 EXPLAIN 并不需要真正去执行查询，所以成本很低。\", Case:\"SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY num\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.003\", Severity:\"L3\", Summary:\"使用了合并为可空列的字符串连接\", Content:\"在一些查询请求中，您需要强制让某一列或者某个表达式返回非 NULL 的值，从而让查询逻辑变得更简单，但又不想将这个值存下来。可以使用 COALESCE() 函数来构造连接的表达式，这样即使是空值列也不会使整表达式变为 NULL。\", Case:\"select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.004\", Severity:\"L4\", Summary:\"不建议使用 SYSDATE() 函数\", Content:\"SYSDATE() 函数可能导致主从数据不一致，请使用 NOW() 函数替代 SYSDATE()。\", Case:\"SELECT SYSDATE();\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.005\", Severity:\"L1\", Summary:\"不建议使用 COUNT(col) 或 COUNT(常量)\", Content:\"不要使用 COUNT(col) 或 COUNT(常量) 来替代 COUNT(*), COUNT(*) 是 SQL92 定义的标准统计行数的方法，跟数据无关，跟 NULL 和非 NULL 也无关。\", Case:\"SELECT COUNT(1) FROM tbl;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.006\", Severity:\"L1\", Summary:\"使用 SUM(COL) 时需注意 NPE 问题\", Content:\"当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\", Case:\"SELECT SUM(COL) FROM tbl;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.007\", Severity:\"L1\", Summary:\"不建议使用触发器\", Content:\"触发器的执行没有反馈和日志，隐藏了实际的执行步骤，当数据库出现问题是，不能通过慢日志分析触发器的具体执行情况，不易发现问题。在MySQL中，触发器不能临时关闭或打开，在数据迁移或数据恢复等场景下，需要临时drop触发器，可能影响到生产环境。\", Case:\"CREATE TRIGGER t1 AFTER INSERT ON work FOR EACH ROW INSERT INTO time VALUES(NOW());\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.008\", Severity:\"L1\", Summary:\"不建议使用存储过程\", Content:\"存储过程无版本控制，配合业务的存储过程升级很难做到业务无感知。存储过程在拓展和移植上也存在问题。\", Case:\"CREATE PROCEDURE simpleproc (OUT param1 INT);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"FUN.009\", Severity:\"L1\", Summary:\"不建议使用自定义函数\", Content:\"不建议使用自定义函数\", Case:\"CREATE FUNCTION hello (s CHAR(20));\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"GRP.001\", Severity:\"L2\", Summary:\"不建议对等值查询列使用 GROUP BY\", Content:\"GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询，对这样的列进行 GROUP BY 意义不大。\", Case:\"select film_id, title from film where release_year='2006' group by release_year\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"JOI.001\", Severity:\"L2\", Summary:\"JOIN 语句混用逗号和 ANSI 模式\", Content:\"表连接的时候混用逗号和 ANSI JOIN 不便于人类理解，并且MySQL不同版本的表连接行为和优先级均有所不同，当 MySQL 版本变化后可能会引入错误。\", Case:\"select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"JOI.002\", Severity:\"L4\", Summary:\"同一张表被连接两次\", Content:\"相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\", Case:\"select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"JOI.003\", Severity:\"L4\", Summary:\"OUTER JOIN 失效\", Content:\"由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回，这会将查询隐式转换为 INNER JOIN 。如：select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解，因为 LEFT/RIGHT JOIN 是 LEFT/RIGHT OUTER JOIN 的缩写。\", Case:\"select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"JOI.004\", Severity:\"L4\", Summary:\"不建议使用排它 JOIN\", Content:\"只在右侧表为 NULL 的带 WHERE 子句的 LEFT OUTER JOIN 语句，有可能是在WHERE子句中使用错误的列，如：“... FROM l LEFT OUTER JOIN r ON l.l = r.r WHERE r.z IS NULL”，这个查询正确的逻辑可能是 WHERE r.r IS NULL。\", Case:\"select c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"JOI.005\", Severity:\"L2\", Summary:\"减少 JOIN 的数量\", Content:\"太多的 JOIN 是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少 JOIN 的数量。\", Case:\"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\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"JOI.008\", Severity:\"L4\", Summary:\"不要使用跨数据库的 JOIN 查询\", Content:\"一般来说，跨数据库的 JOIN 查询意味着查询语句跨越了两个不同的子系统，这可能意味着系统耦合度过高或库表结构设计不合理。\", Case:\"SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 100996 AND s.q = 1 )\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.001\", Severity:\"L2\", Summary:\"建议使用自增列作为主键，如使用联合自增主键时请将自增键作为第一列\", Content:\"建议使用自增列作为主键，如使用联合自增主键时请将自增键作为第一列\", Case:\"create table test(`id` int(11) NOT NULL PRIMARY KEY (`id`))\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.003\", Severity:\"L4\", Summary:\"避免外键等递归关系\", Content:\"存在递归关系的数据很常见，数据常会像树或者以层级方式组织。然而，创建一个外键约束来强制执行同一表中两列之间的关系，会导致笨拙的查询。树的每一层对应着另一个连接。您将需要发出递归查询，以获得节点的所有后代或所有祖先。解决方案是构造一个附加的闭包表。它记录了树中所有节点间的关系，而不仅仅是那些具有直接的父子关系。您也可以比较不同层次的数据设计：闭包表，路径枚举，嵌套集。然后根据应用程序的需要选择一个。\", Case:\"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))\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.004\", Severity:\"L0\", Summary:\"提醒：请将索引属性顺序与查询对齐\", Content:\"如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\", Case:\"create index idx1 on tbl (last_name,first_name)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.005\", Severity:\"L2\", Summary:\"表建的索引过多\", Content:\"表建的索引过多\", Case:\"CREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c(`c`));\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.006\", Severity:\"L4\", Summary:\"主键中的列过多\", Content:\"主键中的列过多\", Case:\"CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.007\", Severity:\"L4\", Summary:\"未指定主键或主键非 int 或 bigint\", Content:\"未指定主键或主键非 int 或 bigint，建议将主键设置为 int unsigned 或 bigint unsigned。\", Case:\"CREATE TABLE tbl (a int);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.008\", Severity:\"L4\", Summary:\"ORDER BY 多个列但排序方向不同时可能无法使用索引\", Content:\"在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\", Case:\"SELECT * FROM tbl ORDER BY a DESC, b ASC;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.009\", Severity:\"L0\", Summary:\"添加唯一索引前请注意检查数据唯一性\", Content:\"请提前检查添加唯一索引列的数据唯一性，如果数据不唯一在线表结构调整时将有可能自动将重复列删除，这有可能导致数据丢失。\", Case:\"CREATE UNIQUE INDEX part_of_name ON customer (name(10));\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KEY.010\", Severity:\"L0\", Summary:\"全文索引不是银弹\", Content:\"全文索引主要用于解决模糊查询的性能问题，但需要控制好查询的频率和并发度。同时注意调整 ft_min_word_len, ft_max_word_len, ngram_token_size 等参数。\", Case:\"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) ) ENGINE=InnoDB;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KWR.001\", Severity:\"L2\", Summary:\"SQL_CALC_FOUND_ROWS 效率低下\", Content:\"因为 SQL_CALC_FOUND_ROWS 不能很好地扩展，所以可能导致性能问题; 建议业务使用其他策略来替代 SQL_CALC_FOUND_ROWS 提供的计数功能，比如：分页结果展示等。\", Case:\"select SQL_CALC_FOUND_ROWS col from tbl where id>1000\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KWR.002\", Severity:\"L2\", Summary:\"不建议使用 MySQL 关键字做列名或表名\", Content:\"当使用关键字做为列名或表名时程序需要对列名和表名进行转义，如果疏忽被将导致请求无法执行。\", Case:\"CREATE TABLE tbl ( `select` int )\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KWR.003\", Severity:\"L1\", Summary:\"不建议使用复数做列名或表名\", Content:\"表名应该仅仅表示表里面的实体内容，不应该表示实体数量，对应于 DO 类名也是单数形式，符合表达习惯。\", Case:\"CREATE TABLE tbl ( `books` int )\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KWR.004\", Severity:\"L1\", Summary:\"不建议使用使用多字节编码字符(中文)命名\", Content:\"为库、表、列、别名命名时建议使用英文，数字，下划线等字符，不建议使用中文或其他多字节编码字符。\", Case:\"select col as 列 from tb\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"KWR.005\", Severity:\"L1\", Summary:\"SQL 中包含 unicode 特殊字符\", Content:\"部分 IDE 会自动在 SQL 插入肉眼不可见的 unicode 字符。如：non-break space, zero-width space 等。Linux 下可使用 `cat -A file.sql` 命令查看不可见字符。\", Case:\"update\\u00a0tb set\\u00a0status\\u00a0=\\u00a01 where\\u00a0id\\u00a0=\\u00a01;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"LCK.001\", Severity:\"L3\", Summary:\"INSERT INTO xx SELECT 加锁粒度较大请谨慎\", Content:\"INSERT INTO xx SELECT 加锁粒度较大请谨慎\", Case:\"INSERT INTO tbl SELECT * FROM tbl2;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"LCK.002\", Severity:\"L3\", Summary:\"请慎用 INSERT ON DUPLICATE KEY UPDATE\", Content:\"当主键为自增键时使用 INSERT ON DUPLICATE KEY UPDATE 可能会导致主键出现大量不连续快速增长，导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。\", Case:\"INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"LIT.001\", Severity:\"L2\", Summary:\"用字符类型存储IP地址\", Content:\"字符串字面上看起来像IP地址，但不是 INET_ATON() 的参数，表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。\", Case:\"insert into tbl (IP,name) values('10.20.306.122','test')\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"LIT.002\", Severity:\"L4\", Summary:\"日期/时间未使用引号括起\", Content:\"诸如“WHERE col <2010-02-12”之类的查询是有效的SQL，但可能是一个错误，因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号，且引号前后不应有空格。\", Case:\"select col1,col2 from tbl where time < 2018-01-10\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"LIT.003\", Severity:\"L3\", Summary:\"一列中存储一系列相关数据的集合\", Content:\"将 ID 存储为一个列表，作为 VARCHAR/TEXT 列，这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证 ID 更加困难。考虑一下，列表最多支持存放多少数据呢？将 ID 存储在一张单独的表中，代替使用多值属性，从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询，也更有效地验证ID。\", Case:\"select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"LIT.004\", Severity:\"L1\", Summary:\"请使用分号或已设定的 DELIMITER 结尾\", Content:\"USE database, SHOW DATABASES 等命令也需要使用使用分号或已设定的 DELIMITER 结尾。\", Case:\"USE db\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"OK\", Severity:\"L0\", Summary:\"OK\", Content:\"OK\", Case:\"OK\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.001\", Severity:\"L4\", Summary:\"非确定性的 GROUP BY\", Content:\"SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\\\"bar\\\" group by a，该 SQL 返回的结果就是不确定的。\", Case:\"select c1,c2,c3 from t1 where c2='foo' group by c2\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.002\", Severity:\"L4\", Summary:\"未使用 ORDER BY 的 LIMIT 查询\", Content:\"没有 ORDER BY 的 LIMIT 会导致非确定性的结果，这取决于查询执行计划。\", Case:\"select col1,col2 from tbl where name=xx limit 10\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.003\", Severity:\"L4\", Summary:\"UPDATE/DELETE 操作使用了 LIMIT 条件\", Content:\"UPDATE/DELETE 操作使用 LIMIT 条件和不添加 WHERE 条件一样危险，它可将会导致主从数据不一致或从库同步中断。\", Case:\"UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.004\", Severity:\"L4\", Summary:\"UPDATE/DELETE 操作指定了 ORDER BY 条件\", Content:\"UPDATE/DELETE 操作不要指定 ORDER BY 条件。\", Case:\"UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.005\", Severity:\"L4\", Summary:\"UPDATE 语句可能存在逻辑错误，导致数据损坏\", Content:\"在一条 UPDATE 语句中，如果要更新多个字段，字段间不能使用 AND ，而应该用逗号分隔。\", Case:\"update tbl set col = 1 and cl = 2 where col=3;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.006\", Severity:\"L4\", Summary:\"永远不真的比较条件\", Content:\"查询条件永远非真，如果该条件出现在 where 中可能导致查询无匹配到的结果。\", Case:\"select * from tbl where 1 != 1;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.007\", Severity:\"L4\", Summary:\"永远为真的比较条件\", Content:\"查询条件永远为真，可能导致 WHERE 条件失效进行全表查询。\", Case:\"select * from tbl where 1 = 1;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.008\", Severity:\"L2\", Summary:\"不建议使用LOAD DATA/SELECT ... INTO OUTFILE\", Content:\"SELECT INTO OUTFILE 需要授予 FILE 权限，这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度，但同时也可能导致从库同步延迟过大。\", Case:\"LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.009\", Severity:\"L2\", Summary:\"不建议使用连续判断\", Content:\"类似这样的 SELECT * FROM tbl WHERE col = col = 'abc' 语句可能是书写错误，您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。\", Case:\"SELECT * FROM tbl WHERE col = col = 'abc'\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.010\", Severity:\"L2\", Summary:\"建表语句中定义为 ON UPDATE CURRENT_TIMESTAMP 的字段不建议包含业务逻辑\", Content:\"定义为 ON UPDATE CURRENT_TIMESTAMP 的字段在该表其他字段更新时会联动修改，如果包含业务逻辑用户可见会埋下隐患。后续如有批量修改数据却又不想修改该字段时会导致数据错误。\", Case:\"CREATE TABLE category (category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,\\tname VARCHAR(25) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY  (category_id)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"RES.011\", Severity:\"L2\", Summary:\"更新请求操作的表包含 ON UPDATE CURRENT_TIMESTAMP 字段\", Content:\"定义为 ON UPDATE CURRENT_TIMESTAMP 的字段在该表其他字段更新时会联动修改，请注意检查。如不想修改字段的更新时间可以使用如下方法：UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1\", Case:\"UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SEC.001\", Severity:\"L0\", Summary:\"请谨慎使用TRUNCATE操作\", Content:\"一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl_name;语句。但TRUNCATE操作也并非是毫无代价的，TRUNCATE TABLE无法返回被删除的准确行数，如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO_INCREMENT，如果不想重置该值建议使用 DELETE FROM tbl_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL)，当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求，因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。\", Case:\"TRUNCATE TABLE tbl_name\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SEC.002\", Severity:\"L0\", Summary:\"不使用明文存储密码\", Content:\"使用明文存储密码或者使用明文在网络上传递密码都是不安全的。如果攻击者能够截获您用来插入密码的SQL语句，他们就能直接读到密码。另外，将用户输入的字符串以明文的形式插入到纯SQL语句中，也会让攻击者发现它。如果您能够读取密码，黑客也可以。解决方案是使用单向哈希函数对原始密码进行加密编码。哈希是指将输入字符串转化成另一个新的、不可识别的字符串的函数。对密码加密表达式加点随机串来防御“字典攻击”。不要将明文密码输入到SQL查询语句中。在应用程序代码中计算哈希串，只在SQL查询中使用哈希串。\", Case:\"create table test(id int,name varchar(20) not null,password varchar(200)not null)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SEC.003\", Severity:\"L0\", Summary:\"使用DELETE/DROP/TRUNCATE等操作时注意备份\", Content:\"在执行高危操作之前对数据进行备份是十分有必要的。\", Case:\"delete from table where col = 'condition'\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SEC.004\", Severity:\"L0\", Summary:\"发现常见 SQL 注入函数\", Content:\"SLEEP(), BENCHMARK(), GET_LOCK(), RELEASE_LOCK() 等函数通常出现在 SQL 注入语句中，会严重影响数据库性能。\", Case:\"SELECT BENCHMARK(10, RAND())\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"STA.001\", Severity:\"L0\", Summary:\"'!=' 运算符是非标准的\", Content:\"\\\"<>\\\"才是标准SQL中的不等于运算符。\", Case:\"select col1,col2 from tbl where type!=0\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"STA.002\", Severity:\"L1\", Summary:\"库名或表名点后建议不要加空格\", Content:\"当使用 db.table 或 table.column 格式访问表或字段时，请不要在点号后面添加空格，虽然这样语法正确。\", Case:\"select col from sakila. film\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"STA.003\", Severity:\"L1\", Summary:\"索引起名不规范\", Content:\"建议普通二级索引以idx_为前缀，唯一索引以uk_为前缀。\", Case:\"select col from now where type!=0\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"STA.004\", Severity:\"L1\", Summary:\"起名时请不要使用字母、数字和下划线之外的字符\", Content:\"以字母或下划线开头，名字只允许使用字母、数字和下划线。请统一大小写，不要使用驼峰命名法。不要在名字中出现连续下划线'__'，这样很难辨认。\", Case:\"CREATE TABLE ` abc` (a int);\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SUB.002\", Severity:\"L2\", Summary:\"如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\", Content:\"与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\", Case:\"select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SUB.003\", Severity:\"L3\", Summary:\"考虑使用 EXISTS 而不是 DISTINCT 子查询\", Content:\"DISTINCT 关键字在对元组排序后删除重复。相反，考虑使用一个带有 EXISTS 关键字的子查询，您可以避免返回整个表。\", Case:\"SELECT DISTINCT c.c_id, c.c_name FROM c,e WHERE e.c_id = c.c_id\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SUB.004\", Severity:\"L3\", Summary:\"执行计划中嵌套连接深度过深\", Content:\"MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。\", Case:\"SELECT * from tb where id in (select id from (select id from tb))\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SUB.005\", Severity:\"L8\", Summary:\"子查询不支持LIMIT\", Content:\"当前 MySQL 版本不支持在子查询中进行 'LIMIT & IN/ALL/ANY/SOME'。\", Case:\"SELECT * FROM staff WHERE name IN (SELECT NAME FROM customer ORDER BY name LIMIT 1)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SUB.006\", Severity:\"L2\", Summary:\"不建议在子查询中使用函数\", Content:\"MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\", Case:\"SELECT * FROM staff WHERE name IN (SELECT max(NAME) FROM customer)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"SUB.007\", Severity:\"L2\", Summary:\"外层带有 LIMIT 输出限制的 UNION 联合查询，其内层查询建议也添加 LIMIT 输出限制\", Content:\"有时 MySQL 无法将限制条件从外层“下推”到内层，这会使得原本可以限制能够限制部分返回结果的条件无法应用到内层查询的优化上。比如：(SELECT * FROM tb1 ORDER BY name) UNION ALL (SELECT * FROM tb2 ORDER BY name) LIMIT 20;  MySQL 会将两个子查询的结果放在一个临时表中，然后取出 20 条结果，可以通过在两个子查询中添加 LIMIT 20 来减少临时表中的数据。(SELECT * FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT * FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;\", Case:\"(SELECT * FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT * FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"TBL.001\", Severity:\"L4\", Summary:\"不建议使用分区表\", Content:\"不建议使用分区表\", Case:\"CREATE TABLE trb3(id INT, name VARCHAR(50), purchased DATE) PARTITION BY RANGE(YEAR(purchased)) (PARTITION p0 VALUES LESS THAN (1990), PARTITION p1 VALUES LESS THAN (1995), PARTITION p2 VALUES LESS THAN (2000), PARTITION p3 VALUES LESS THAN (2005) );\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"TBL.002\", Severity:\"L4\", Summary:\"请为表选择合适的存储引擎\", Content:\"建表或修改表的存储引擎时建议使用推荐的存储引擎，如：innodb\", Case:\"create table test(`id` int(11) NOT NULL AUTO_INCREMENT)\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"TBL.003\", Severity:\"L8\", Summary:\"以DUAL命名的表在数据库中有特殊含义\", Content:\"DUAL表为虚拟表，不需要创建即可使用，也不建议服务以DUAL命名表。\", Case:\"create table dual(id int, primary key (id));\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"TBL.004\", Severity:\"L2\", Summary:\"表的初始AUTO_INCREMENT值不为0\", Content:\"AUTO_INCREMENT不为0会导致数据空洞。\", Case:\"CREATE TABLE tbl (a int) AUTO_INCREMENT = 10;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"TBL.005\", Severity:\"L4\", Summary:\"请使用推荐的字符集\", Content:\"表字符集只允许设置为'utf8,utf8mb4'\", Case:\"CREATE TABLE tbl (a int) DEFAULT CHARSET = latin1;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"TBL.006\", Severity:\"L1\", Summary:\"不建议使用视图\", Content:\"不建议使用视图\", Case:\"create view v_today (today) AS SELECT CURRENT_DATE;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"TBL.007\", Severity:\"L1\", Summary:\"不建议使用临时表\", Content:\"不建议使用临时表\", Case:\"CREATE TEMPORARY TABLE `work` (`time` time DEFAULT NULL) ENGINE=InnoDB;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\nadvisor.Rule{Item:\"TBL.008\", Severity:\"L4\", Summary:\"请使用推荐的COLLATE\", Content:\"COLLATE 只允许设置为''\", Case:\"CREATE TABLE tbl (a int) DEFAULT COLLATE = latin1_bin;\", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}\n"
  },
  {
    "path": "ast/doc.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package ast is an interface for Abstract Syntax Tree parser\npackage ast\n"
  },
  {
    "path": "ast/meta.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// GetTableFromExprs 从sqlparser.Exprs中获取所有的库表\nfunc GetTableFromExprs(exprs sqlparser.TableExprs, metas ...common.Meta) common.Meta {\n\tmeta := make(map[string]*common.DB)\n\tif len(metas) >= 1 {\n\t\tmeta = metas[0]\n\t}\n\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.AliasedTableExpr:\n\n\t\t\tswitch table := expr.Expr.(type) {\n\t\t\tcase sqlparser.TableName:\n\t\t\t\tdb := table.Qualifier.String()\n\t\t\t\ttb := table.Name.String()\n\n\t\t\t\tif meta[db] == nil {\n\t\t\t\t\tmeta[db] = common.NewDB(db)\n\t\t\t\t}\n\n\t\t\t\tmeta[db].Table[tb] = common.NewTable(tb)\n\n\t\t\t\t// alias去重\n\t\t\t\taliasExist := false\n\t\t\t\tfor _, existedAlias := range meta[db].Table[tb].TableAliases {\n\t\t\t\t\tif existedAlias == expr.As.String() {\n\t\t\t\t\t\taliasExist = true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif !aliasExist {\n\t\t\t\t\tmeta[db].Table[tb].TableAliases = append(meta[db].Table[tb].TableAliases, expr.As.String())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, exprs)\n\tcommon.LogIfWarn(err, \"\")\n\treturn meta\n}\n\n// GetMeta 获取元数据信息，构建到db->table层级。\n// 从 SQL 或 Statement 中获取表信息，并返回。当 meta 不为 nil 时，返回值会将新老 meta 合并去重\nfunc GetMeta(stmt sqlparser.Statement, meta common.Meta) common.Meta {\n\t// 初始化meta\n\tif meta == nil {\n\t\tmeta = make(map[string]*common.DB)\n\t}\n\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.DDL:\n\t\t\t// 如果 SQL 是一个 DDL，则不需要继续遍历语法树了\n\t\t\tfor _, tb := range expr.FromTables {\n\t\t\t\tappendTable(tb, \"\", meta)\n\t\t\t}\n\n\t\t\tfor _, tb := range expr.ToTables {\n\t\t\t\tappendTable(tb, \"\", meta)\n\t\t\t}\n\n\t\t\tappendTable(expr.Table, \"\", meta)\n\t\t\treturn false, nil\n\t\tcase *sqlparser.AliasedTableExpr:\n\t\t\t// 非 DDL 情况下处理 TableExpr\n\t\t\t// 在 sqlparser 中存在三种 TableExpr: AliasedTableExpr，ParenTableExpr 以及 JoinTableExpr。\n\t\t\t// 其中 AliasedTableExpr 是其他两种 TableExpr 的基础组成，SQL中的 表信息（别名、前缀）在这个结构体中。\n\n\t\t\tswitch table := expr.Expr.(type) {\n\n\t\t\t// 获取表名、别名与前缀名（数据库名）\n\t\t\t// 表名存放在 AST 中 TableName 里，包含表名与表前缀名。\n\t\t\t// 当与 As 相对应的 Expr 为 TableName 的时候，别名才是一张实体表的别名，否则为结果集的别名。\n\t\t\tcase sqlparser.TableName:\n\t\t\t\tappendTable(table, expr.As.String(), meta)\n\t\t\tdefault:\n\t\t\t\t// 如果 AliasedTableExpr 中的 Expr 不是 TableName 结构体，则表示该表为一个查询结果集（子查询或临时表）。\n\t\t\t\t// 在这里记录一下别名，但将列名制空，用来保证在其他环节中判断列前缀的时候不会有遗漏\n\t\t\t\t// 最终结果为所有的子查询别名都会归于 \"\"（空） 数据库 \"\"（空） 表下，对于空数据库，空表后续在索引优化时直接PASS\n\t\t\t\tif meta == nil {\n\t\t\t\t\tmeta = make(map[string]*common.DB)\n\t\t\t\t}\n\n\t\t\t\tif meta[\"\"] == nil {\n\t\t\t\t\tmeta[\"\"] = common.NewDB(\"\")\n\t\t\t\t}\n\n\t\t\t\tmeta[\"\"].Table[\"\"] = common.NewTable(\"\")\n\t\t\t\tmeta[\"\"].Table[\"\"].TableAliases = append(meta[\"\"].Table[\"\"].TableAliases, expr.As.String())\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, stmt)\n\tcommon.LogIfWarn(err, \"\")\n\treturn meta\n}\n\n// appendTable 将 sqlparser.TableName 中的库表信息提取后放到 meta 中\n// @tb 为 sqlparser.TableName 对象\n// @as 为该表的别名，无别名时为空\n// @meta 为信息集合\nfunc appendTable(tb sqlparser.TableName, as string, meta map[string]*common.DB) map[string]*common.DB {\n\tif meta == nil {\n\t\treturn meta\n\t}\n\n\tdbName := tb.Qualifier.String()\n\ttbName := tb.Name.String()\n\tif tbName == \"\" {\n\t\treturn meta\n\t}\n\n\tif meta[dbName] == nil {\n\t\tmeta[dbName] = common.NewDB(dbName)\n\t}\n\n\tmeta[dbName].Table[tbName] = common.NewTable(tbName)\n\tmergeAlias(dbName, tbName, as, meta)\n\n\treturn meta\n}\n\n// mergeAlias 将所有的表别名归并到一个表下\nfunc mergeAlias(db, tb, as string, meta map[string]*common.DB) {\n\tif meta == nil || as == \"\" {\n\t\treturn\n\t}\n\n\taliasExist := false\n\tfor _, existedAlias := range meta[db].Table[tb].TableAliases {\n\t\tif existedAlias == as {\n\t\t\taliasExist = true\n\t\t}\n\t}\n\n\tif !aliasExist {\n\t\tmeta[db].Table[tb].TableAliases = append(meta[db].Table[tb].TableAliases, as)\n\t}\n}\n\n// eqOperators 等值条件判断关键字\nvar eqOperators = map[string]string{\n\t\"=\":            \"eq\",\n\t\"<=>\":          \"eq\",\n\t\"is true\":      \"eq\",\n\t\"is false\":     \"eq\",\n\t\"is not true\":  \"eq\",\n\t\"is not false\": \"eq\",\n\t\"is null\":      \"eq\",\n\t\"in\":           \"eq\", // 单值的in属于等值条件\n}\n\n// inEqOperators 非等值条件判断关键字\nvar inEqOperators = map[string]string{\n\t\"<\":           \"inEq\",\n\t\">\":           \"inEq\",\n\t\"<=\":          \"inEq\",\n\t\">=\":          \"inEq\",\n\t\"!=\":          \"inEq\",\n\t\"is not null\": \"inEq\",\n\t\"like\":        \"inEq\",\n\t\"not like\":    \"inEq\",\n\t\"->\":          \"inEq\",\n\t\"->>\":         \"inEq\",\n\t\"between\":     \"inEq\",\n\t\"not between\": \"inEq\",\n\t\"in\":          \"inEq\", // 多值in属于非等值条件\n\n\t// 某些非等值条件无需添加索引，所以忽略即可\n\t// 比如\"not in\"，比如\"exists\"、 \"not exists\"等\n}\n\n// FindColumn 从传入的 node 中获取所有可能加索引的的 column 信息\nfunc FindColumn(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindColumn, Caller: %s\", common.Caller())\n\tvar result []*common.Column\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch col := node.(type) {\n\t\tcase *sqlparser.FuncExpr:\n\t\t\t// 忽略function\n\t\t\treturn false, nil\n\t\tcase *sqlparser.ColName:\n\t\t\tresult = common.MergeColumn(result, &common.Column{\n\t\t\t\tName:  col.Name.String(),\n\t\t\t\tTable: col.Qualifier.Name.String(),\n\t\t\t\tDB:    col.Qualifier.Qualifier.String(),\n\t\t\t\tAlias: make([]string, 0),\n\t\t\t})\n\t\t}\n\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn result\n}\n\n// inEqIndexAble 判断非等值查询条件是否可以复用到索引\n// Output: true 可以考虑添加索引， false 不需要添加索引\nfunc inEqIndexAble(node sqlparser.SQLNode) bool {\n\tcommon.Log.Debug(\"Enter:  inEqIndexAble(), Caller: %s\", common.Caller())\n\tvar indexAble bool\n\tswitch expr := node.(type) {\n\tcase *sqlparser.ComparisonExpr:\n\t\t// like前百分号查询无法使用索引\n\t\t// TODO: date 类型的 like 属于隐式数据类型转换，会导致无法使用索引\n\t\tif expr.Operator == \"like\" || expr.Operator == \"not like\" {\n\t\t\tswitch right := expr.Right.(type) {\n\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\treturn !(strings.HasPrefix(string(right.Val), \"%\"))\n\t\t\t}\n\t\t}\n\n\t\t// 如果是in查询，则需要判断in查询是否是多值查询\n\t\tif expr.Operator == \"in\" {\n\t\t\tswitch right := expr.Right.(type) {\n\t\t\tcase sqlparser.ValTuple:\n\t\t\t\t// 若是单值查询则应该属于等值条件而非非等值条件\n\t\t\t\treturn len(right) > 1\n\t\t\t}\n\t\t}\n\n\t\t_, indexAble = inEqOperators[expr.Operator]\n\n\tcase *sqlparser.IsExpr:\n\t\t_, indexAble = inEqOperators[expr.Operator]\n\n\tcase *sqlparser.RangeCond:\n\t\t_, indexAble = inEqOperators[expr.Operator]\n\n\tdefault:\n\t\tindexAble = false\n\t}\n\treturn indexAble\n}\n\n// FindWhereEQ 找到Where中的等值条件\nfunc FindWhereEQ(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindWhereEQ(), Caller: %s\", common.Caller())\n\treturn append(FindEQColsInWhere(node), FindEQColsInJoinCond(node)...)\n}\n\n// FindWhereINEQ 找到Where条件中的非等值条件\nfunc FindWhereINEQ(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindWhereINEQ(), Caller: %s\", common.Caller())\n\treturn append(FindINEQColsInWhere(node), FindINEQColsInJoinCond(node)...)\n}\n\n// FindEQColsInWhere 获取等值条件信息\n// 将所有值得加索引的condition条件信息进行过滤\nfunc FindEQColsInWhere(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindEQColsInWhere(), Caller: %s\", common.Caller())\n\tvar columns []*common.Column\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node := node.(type) {\n\t\t// 对AST中所有节点进行扫描\n\t\tcase *sqlparser.Subquery, *sqlparser.JoinTableExpr, *sqlparser.BinaryExpr, *sqlparser.OrExpr,\n\t\t\t*sqlparser.CaseExpr, *sqlparser.FuncExpr:\n\t\t\t// 忽略子查询，join condition，数值计算，or condition\n\t\t\treturn false, nil\n\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\tvar newCols []*common.Column\n\t\t\t// ComparisonExpr中可能含有等值查询列条件\n\t\t\tswitch node.Operator {\n\t\t\tcase \"in\":\n\t\t\t\t// 对in进行特别判断，只有单值的in条件才算做是等值查询\n\t\t\t\tswitch right := node.Right.(type) {\n\t\t\t\tcase sqlparser.ValTuple:\n\t\t\t\t\tif len(right) == 1 {\n\t\t\t\t\t\tnewCols = FindColumn(node)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\tswitch node.Left.(type) {\n\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\tif _, ok := eqOperators[node.Operator]; ok {\n\t\t\t\t\t\tnewCols = FindColumn(node)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// operator两边都为列的情况不提供索引建议\n\t\t\t// 如果该列位于function中则不予提供索引建议\n\t\t\tif len(newCols) == 1 {\n\t\t\t\tcolumns = common.MergeColumn(columns, newCols[0])\n\t\t\t}\n\n\t\tcase *sqlparser.IsExpr:\n\t\t\t// IsExpr中可能含有等值查询列条件\n\t\t\tif _, ok := eqOperators[node.Operator]; ok {\n\t\t\t\tnewCols := FindColumn(node)\n\t\t\t\tif len(newCols) == 1 {\n\t\t\t\t\tcolumns = common.MergeColumn(columns, newCols[0])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn columns\n}\n\n// FindINEQColsInWhere 获取非等值条件中可能需要加索引的列\n// 将所有值得加索引的condition条件信息进行过滤\n// TODO: 将 where 条件中隐含的 join 条件合并到 join condition中\nfunc FindINEQColsInWhere(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindINEQColsInWhere(), Caller: %s\", common.Caller())\n\tvar columns []*common.Column\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node := node.(type) {\n\t\t// 对AST中所有节点进行扫描\n\t\tcase *sqlparser.Subquery, *sqlparser.JoinTableExpr, *sqlparser.BinaryExpr, *sqlparser.OrExpr:\n\t\t\t// 忽略子查询，join condition，数值计算，or condition\n\t\t\treturn false, nil\n\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t// ComparisonExpr中可能含有非等值查询列条件\n\t\t\tif inEqIndexAble(node) {\n\t\t\t\tnewCols := FindColumn(node)\n\t\t\t\t// operator两边都为列的情况不提供索引建议\n\t\t\t\tif len(newCols) == 1 {\n\t\t\t\t\tcolumns = common.MergeColumn(columns, newCols[0])\n\t\t\t\t}\n\t\t\t}\n\t\tcase *sqlparser.IsExpr:\n\t\t\t// IsExpr中可能含有非等值查询列条件\n\t\t\tif inEqIndexAble(node) {\n\t\t\t\tnewCols := FindColumn(node)\n\t\t\t\tif len(newCols) == 1 {\n\t\t\t\t\tcolumns = common.MergeColumn(columns, newCols[0])\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase *sqlparser.RangeCond:\n\t\t\t// RangeCond中只存在非等值条件查询\n\t\t\tif inEqIndexAble(node) {\n\t\t\t\tcolumns = common.MergeColumn(columns, FindColumn(node)...)\n\t\t\t}\n\t\t}\n\n\t\treturn true, nil\n\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn columns\n}\n\n// FindGroupByCols 获取groupBy中可能需要加索引的列信息\nfunc FindGroupByCols(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindGroupByCols(), Caller: %s\", common.Caller())\n\tisIgnore := false\n\tvar columns []*common.Column\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase sqlparser.GroupBy:\n\t\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\t\tswitch node := node.(type) {\n\t\t\t\tcase *sqlparser.BinaryExpr, *sqlparser.FuncExpr:\n\t\t\t\t\t// 如果group by中出现数值计算、函数等\n\t\t\t\t\tisIgnore = true\n\t\t\t\t\treturn false, nil\n\t\t\t\tdefault:\n\t\t\t\t\tcolumns = common.MergeColumn(columns, FindColumn(node)...)\n\t\t\t\t}\n\t\t\t\treturn true, nil\n\t\t\t}, expr)\n\t\t\tcommon.LogIfWarn(err, \"\")\n\t\tcase *sqlparser.Subquery, *sqlparser.JoinTableExpr, *sqlparser.BinaryExpr:\n\t\t\t// 忽略子查询，join condition以及数值计算\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\tif isIgnore {\n\t\treturn []*common.Column{}\n\t}\n\n\treturn columns\n}\n\n// FindOrderByCols 为索引优化获取orderBy中可能添加索引的列信息\nfunc FindOrderByCols(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindOrderByCols(), Caller: %s\", common.Caller())\n\tvar columns []*common.Column\n\tlastDirection := \"\"\n\tdirectionNotEq := false\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.Order:\n\t\t\t// MySQL对于排序顺序不同的查询无法使用索引（8.0后支持）\n\t\t\tif lastDirection != \"\" && expr.Direction != lastDirection {\n\t\t\t\tdirectionNotEq = true\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\tlastDirection = expr.Direction\n\t\t\tcolumns = common.MergeColumn(columns, FindColumn(expr)...)\n\t\tcase *sqlparser.Subquery, *sqlparser.JoinTableExpr, *sqlparser.BinaryExpr:\n\t\t\t// 忽略子查询，join condition以及数值计算\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\tif directionNotEq {\n\t\t// 当发现Order by中排序顺序不同时，即放弃Oder by条件中的字段\n\t\treturn []*common.Column{}\n\t}\n\n\treturn columns\n}\n\n// FindJoinTable 获取 Join 中需要添加索引的表\n// join 优化添加索引分为三种类型：1. inner join, 2. left join, 3.right join\n// 针对三种优化类型，需要三种不同的索引添加方案:\n// 1. inner join 需要对 join 左右的表添加索引\n// 2. left join 由于左表为全表扫描，需要对右表的关联列添加索引。\n// 3. right join 与 left join 相反，需要对左表的关联列添加索引。\n// 以上添加索引的策略前提为join的表为实体表而非临时表。\nfunc FindJoinTable(node sqlparser.SQLNode, meta common.Meta) common.Meta {\n\tcommon.Log.Debug(\"Enter:  FindJoinTable(), Caller: %s\", common.Caller())\n\tif meta == nil {\n\t\tmeta = make(common.Meta)\n\t}\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.JoinTableExpr:\n\t\t\tswitch expr.Join {\n\t\t\tcase sqlparser.JoinStr, sqlparser.NaturalJoinStr:\n\t\t\t\t// 两边表都需要\n\t\t\t\tfindJoinTable(expr.LeftExpr, meta)\n\t\t\t\tfindJoinTable(expr.RightExpr, meta)\n\t\t\tcase sqlparser.LeftJoinStr, sqlparser.NaturalLeftJoinStr, sqlparser.StraightJoinStr:\n\t\t\t\t// 只需要右表\n\t\t\t\tfindJoinTable(expr.RightExpr, meta)\n\n\t\t\tcase sqlparser.RightJoinStr, sqlparser.NaturalRightJoinStr:\n\t\t\t\t// 只需要左表\n\t\t\t\tfindJoinTable(expr.LeftExpr, meta)\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn meta\n}\n\n// findJoinTable 获取join table\nfunc findJoinTable(expr sqlparser.TableExpr, meta common.Meta) {\n\tcommon.Log.Debug(\"Enter:  findJoinTable(), Caller: %s\", common.Caller())\n\tif meta == nil {\n\t\tmeta = make(common.Meta)\n\t}\n\tswitch tableExpr := expr.(type) {\n\tcase *sqlparser.AliasedTableExpr:\n\t\tswitch table := tableExpr.Expr.(type) {\n\t\t// 获取表名、别名与前缀名（数据库名）\n\t\t// 表名存放在 AST 中 TableName 里，包含表名与表前缀名。\n\t\t// 当与 As 相对应的 Expr 为 TableName 的时候，别名才是一张实体表的别名，否则为结果集的别名。\n\t\tcase sqlparser.TableName:\n\t\t\tdb := table.Qualifier.String()\n\t\t\ttb := table.Name.String()\n\n\t\t\tif meta == nil {\n\t\t\t\tmeta = make(map[string]*common.DB)\n\t\t\t}\n\n\t\t\tif meta[db] == nil {\n\t\t\t\tmeta[db] = common.NewDB(db)\n\t\t\t}\n\n\t\t\tmeta[db].Table[tb] = common.NewTable(tb)\n\n\t\t\t// alias去重\n\t\t\taliasExist := false\n\t\t\tfor _, existedAlias := range meta[db].Table[tb].TableAliases {\n\t\t\t\tif existedAlias == tableExpr.As.String() {\n\t\t\t\t\taliasExist = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !aliasExist {\n\t\t\t\tmeta[db].Table[tb].TableAliases = append(meta[db].Table[tb].TableAliases, tableExpr.As.String())\n\t\t\t}\n\t\t}\n\tcase *sqlparser.ParenTableExpr:\n\t\t// join 时可能会同时 join 多张表\n\t\tfor _, tbExpr := range tableExpr.Exprs {\n\t\t\tfindJoinTable(tbExpr, meta)\n\t\t}\n\tdefault:\n\t\t// 如果是如上两种类型都没有命中，说明join的表为临时表，递归调用 FindJoinTable 继续下探查找。\n\t\t// NOTE: 这里需要注意的是，如果不递归寻找，如果存在子查询结果集的join表，subquery也会把这个查询提取出。\n\t\t// 所以针对default这一段理论上可以忽略处理（待测试）\n\t\tFindJoinTable(tableExpr, meta)\n\t}\n}\n\n// FindJoinCols 获取 join condition 中使用到的列（必须是 `列 operator 列` 的情况。\n// 如果列对应的值或是 function，则应该移到where condition中）\n// 某些 where 条件隐含在 join 条件中（INNER JOIN）\nfunc FindJoinCols(node sqlparser.SQLNode) [][]*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindJoinCols(), Caller: %s\", common.Caller())\n\tvar columns [][]*common.Column\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase *sqlparser.JoinTableExpr:\n\t\t\t// on\n\t\t\tif on := expr.Condition.On; on != nil {\n\t\t\t\tcols := FindColumn(expr.Condition.On)\n\t\t\t\tif len(cols) > 1 {\n\t\t\t\t\tcolumns = append(columns, cols)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// using\n\t\t\tif using := expr.Condition.Using; using != nil {\n\t\t\t\tleft := \"\"\n\t\t\t\tright := \"\"\n\n\t\t\t\tswitch tableExpr := expr.LeftExpr.(type) {\n\t\t\t\tcase *sqlparser.AliasedTableExpr:\n\t\t\t\t\tswitch table := tableExpr.Expr.(type) {\n\t\t\t\t\tcase sqlparser.TableName:\n\t\t\t\t\t\tleft = table.Name.String()\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch tableExpr := expr.RightExpr.(type) {\n\t\t\t\tcase *sqlparser.AliasedTableExpr:\n\t\t\t\t\tswitch table := tableExpr.Expr.(type) {\n\t\t\t\t\tcase sqlparser.TableName:\n\t\t\t\t\t\tright = table.Name.String()\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar cols []*common.Column\n\t\t\t\tfor _, col := range using {\n\t\t\t\t\tif left != \"\" {\n\t\t\t\t\t\tcols = append(cols, &common.Column{\n\t\t\t\t\t\t\tName:  col.String(),\n\t\t\t\t\t\t\tTable: left,\n\t\t\t\t\t\t\tAlias: make([]string, 0),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\n\t\t\t\t\tif right != \"\" {\n\t\t\t\t\t\tcols = append(cols, &common.Column{\n\t\t\t\t\t\t\tName:  col.String(),\n\t\t\t\t\t\t\tTable: right,\n\t\t\t\t\t\t\tAlias: make([]string, 0),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t\tcolumns = append(columns, cols)\n\n\t\t\t}\n\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn columns\n}\n\n// FindEQColsInJoinCond 获取 join condition 中应转为whereEQ条件的列\nfunc FindEQColsInJoinCond(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindEQColsInJoinCond(), Caller: %s\", common.Caller())\n\tvar columns []*common.Column\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase sqlparser.JoinCondition:\n\t\t\tcolumns = append(columns, FindEQColsInWhere(expr)...)\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn columns\n}\n\n// FindINEQColsInJoinCond 获取 join condition 中应转为whereINEQ条件的列\nfunc FindINEQColsInJoinCond(node sqlparser.SQLNode) []*common.Column {\n\tcommon.Log.Debug(\"Enter:  FindINEQColsInJoinCond(), Caller: %s\", common.Caller())\n\tvar columns []*common.Column\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\tcase sqlparser.JoinCondition:\n\t\t\tcolumns = append(columns, FindINEQColsInWhere(expr)...)\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn columns\n}\n\n// FindSubquery 拆分subquery，获取最深层的subquery\n// 为索引优化获取subquery中包含的列信息\nfunc FindSubquery(depth int, node sqlparser.SQLNode, queries ...string) []string {\n\tcommon.Log.Debug(\"Enter:  FindSubquery(), Caller: %s\", common.Caller())\n\tif queries == nil {\n\t\tqueries = make([]string, 0)\n\t}\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch expr := node.(type) {\n\t\t// 查找SQL中的子查询\n\t\tcase *sqlparser.Subquery:\n\t\t\tnoSub := true\n\t\t\t// 查看子查询中是否还包含子查询，如果包含，递归找到最深层的子查询\n\t\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\t\tswitch sub := node.(type) {\n\t\t\t\tcase *sqlparser.Subquery:\n\t\t\t\t\tnoSub = false\n\t\t\t\t\t// 查找深度depth，超过最大深度后不再向下查找\n\t\t\t\t\tdepth = depth + 1\n\t\t\t\t\tif depth < common.Config.MaxSubqueryDepth {\n\t\t\t\t\t\tqueries = append(queries, FindSubquery(depth, sub.Select)...)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true, nil\n\t\t\t}, expr.Select)\n\t\t\tcommon.LogIfWarn(err, \"\")\n\n\t\t\t// 如果没有嵌套的子查询了，返回子查询的SQL\n\t\t\tif noSub {\n\t\t\t\tsql := sqlparser.String(expr)\n\t\t\t\t// 去除SQL前后的括号\n\t\t\t\tqueries = append(queries, sql[1:len(sql)-1])\n\t\t\t}\n\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn queries\n}\n\n// FindAllCondition 获取 AST 中所有的 condition 条件\nfunc FindAllCondition(node sqlparser.SQLNode) []interface{} {\n\tcommon.Log.Debug(\"Enter:  FindAllCondition(), Caller: %s\", common.Caller())\n\tvar conditions []interface{}\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node := node.(type) {\n\t\tcase *sqlparser.ComparisonExpr, *sqlparser.RangeCond, *sqlparser.IsExpr:\n\t\t\tconditions = append(conditions, node)\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn conditions\n}\n\n// Expression describe sql expression type\ntype Expression string\n\nconst (\n\t// WhereExpression 用于标记 where\n\tWhereExpression Expression = \"where\"\n\t// JoinExpression 用于标记 join\n\tJoinExpression Expression = \"join\"\n\t// GroupByExpression 用于标记 group by\n\tGroupByExpression Expression = \"group by\"\n\t// OrderByExpression 用于标记 order by\n\tOrderByExpression Expression = \"order by\"\n)\n\n// FindAllCols 获取 AST 中某个节点下所有的 columns\nfunc FindAllCols(node sqlparser.SQLNode, targets ...Expression) []*common.Column {\n\tvar result []*common.Column\n\t// 获取节点内所有的列\n\tf := func(node sqlparser.SQLNode) {\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch col := node.(type) {\n\t\t\tcase *sqlparser.ColName:\n\t\t\t\tresult = common.MergeColumn(result, &common.Column{\n\t\t\t\t\tName:  col.Name.String(),\n\t\t\t\t\tTable: col.Qualifier.Name.String(),\n\t\t\t\t\tDB:    col.Qualifier.Qualifier.String(),\n\t\t\t\t\tAlias: make([]string, 0),\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, node)\n\t\tcommon.LogIfWarn(err, \"\")\n\t}\n\n\tif len(targets) == 0 {\n\t\t// 如果不指定具体节点类型，则获取全部的column\n\t\tf(node)\n\t} else {\n\t\t// 根据target获取所有的节点\n\t\tfor _, target := range targets {\n\t\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\t\tswitch node := node.(type) {\n\t\t\t\tcase *sqlparser.Subquery:\n\t\t\t\t\t// 忽略子查询\n\t\t\t\tcase *sqlparser.JoinTableExpr:\n\t\t\t\t\tif target == JoinExpression {\n\t\t\t\t\t\tf(node)\n\t\t\t\t\t}\n\t\t\t\tcase *sqlparser.Where:\n\t\t\t\t\tif target == WhereExpression {\n\t\t\t\t\t\tf(node)\n\t\t\t\t\t}\n\t\t\t\tcase sqlparser.GroupBy:\n\t\t\t\t\tif target == GroupByExpression {\n\t\t\t\t\t\tf(node)\n\t\t\t\t\t}\n\t\t\t\tcase sqlparser.OrderBy:\n\t\t\t\t\tif target == OrderByExpression {\n\t\t\t\t\t\tf(node)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true, nil\n\t\t\t}, node)\n\t\t\tcommon.LogIfWarn(err, \"\")\n\t\t}\n\t}\n\n\treturn result\n}\n\n// GetSubqueryDepth 获取一条SQL的嵌套深度\nfunc GetSubqueryDepth(node sqlparser.SQLNode) int {\n\tdepth := 1\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node.(type) {\n\t\tcase *sqlparser.Subquery:\n\t\t\tdepth++\n\t\t}\n\t\treturn true, nil\n\t}, node)\n\tcommon.LogIfWarn(err, \"\")\n\treturn depth\n}\n\n// getColumnName 获取 node 中 column 具体的定义以及名称\nfunc getColumnName(node sqlparser.SQLNode) (*sqlparser.ColName, string) {\n\tvar colName *sqlparser.ColName\n\tstr := \"\"\n\tswitch c := node.(type) {\n\tcase *sqlparser.ColName:\n\t\tif c.Qualifier.Name.IsEmpty() {\n\t\t\tstr = fmt.Sprintf(\"`%s`\", c.Name.String())\n\t\t} else {\n\t\t\tif c.Qualifier.Qualifier.IsEmpty() {\n\t\t\t\tstr = fmt.Sprintf(\"`%s`.`%s`\", c.Qualifier.Name.String(), c.Name.String())\n\t\t\t} else {\n\t\t\t\tstr = fmt.Sprintf(\"`%s`.`%s`.`%s`\",\n\t\t\t\t\tc.Qualifier.Qualifier.String(), c.Qualifier.Name.String(), c.Name.String())\n\t\t\t}\n\t\t}\n\t\tcolName = c\n\t}\n\treturn colName, str\n}\n"
  },
  {
    "path": "ast/meta_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\nvar update = flag.Bool(\"update\", false, \"update .golden files\")\n\nfunc TestMain(m *testing.M) {\n\t// 初始化 init\n\tif common.DevPath == \"\" {\n\t\t_, file, _, _ := runtime.Caller(0)\n\t\tcommon.DevPath, _ = filepath.Abs(filepath.Dir(filepath.Join(file, \"..\"+string(filepath.Separator))))\n\t}\n\tcommon.BaseDir = common.DevPath\n\terr := common.ParseConfig(\"\")\n\tcommon.LogIfError(err, \"init ParseConfig\")\n\tcommon.Log.Debug(\"ast_test init\")\n\n\t// 分割线\n\tflag.Parse()\n\tm.Run()\n\n\t// 环境清理\n\t//\n}\n\nfunc TestGetTableFromExprs(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttbExprs := sqlparser.TableExprs{\n\t\t&sqlparser.AliasedTableExpr{\n\t\t\tExpr: sqlparser.TableName{\n\t\t\t\tName:      sqlparser.NewTableIdent(\"table\"),\n\t\t\t\tQualifier: sqlparser.NewTableIdent(\"db\"),\n\t\t\t},\n\t\t\tAs: sqlparser.NewTableIdent(\"as\"),\n\t\t},\n\t}\n\tmeta := GetTableFromExprs(tbExprs)\n\tif tb, ok := meta[\"db\"]; !ok {\n\t\tt.Errorf(\"no table qualifier, meta: %s\", pretty.Sprint(tb))\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestGetParseTableWithStmt(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tfor _, sql := range common.TestSQLs {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"SQL Parsed error: %v\", err)\n\t\t}\n\t\tmeta := GetMeta(stmt, nil)\n\t\tpretty.Println(meta)\n\t\tfmt.Println()\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindCondition(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`SELECT * FROM film WHERE length % 20 = 4;`,\n\t\t`select * from actor where actor_id = 1 order by if(first_name=\"PENELOPE\", last_name, \"\") desc`,\n\t}\n\tfor _, sql := range append(sqls, common.TestSQLs...) {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\t// pretty.Println(stmt)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\teq := FindEQColsInWhere(stmt)\n\t\tinEq := FindINEQColsInWhere(stmt)\n\t\tfmt.Println(\"WhereEQ:\")\n\t\tpretty.Println(eq)\n\t\tfmt.Println(\"WhereINEQ:\")\n\t\tpretty.Println(inEq)\n\t\tfmt.Println()\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindGroupBy(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"select a from t group by c\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tres := FindGroupByCols(stmt)\n\t\tpretty.Println(res)\n\t\tfmt.Println()\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindOrderBy(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"select a from t group by c order by d, c desc\",\n\t\t\"select a from t group by c order by d desc\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tres := FindOrderByCols(stmt)\n\t\tpretty.Println(res)\n\t\tfmt.Println()\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindSubquery(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM (SELECT column1 FROM t2) a);\",\n\t\t\"select column1 from t2\",\n\t\t\"SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);\",\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tsubquery := FindSubquery(0, stmt)\n\t\tfmt.Println(len(subquery))\n\t\tpretty.Println(subquery)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindJoinTable(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT * FROM t1 RIGHT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT left_tbl.* FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t\t\"SELECT left_tbl.* FROM left_tbl RIGHT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\t// pretty.Println(stmt)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tjoinMeta := FindJoinTable(stmt, nil)\n\t\tpretty.Println(joinMeta)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindJoinCols(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"select t from a LEFT JOIN b USING (c1, c2, c3)\",\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT * FROM t1 RIGHT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT left_tbl.* FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t\t\"SELECT left_tbl.* FROM left_tbl RIGHT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\t// pretty.Println(stmt)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tcolumns := FindJoinCols(stmt)\n\t\tpretty.Println(columns)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindJoinColBeWhereEQ(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT * FROM t1 RIGHT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT left_tbl.* FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t\t\"SELECT left_tbl.* FROM left_tbl RIGHT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\t// pretty.Println(stmt)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tcolumns := FindEQColsInJoinCond(stmt)\n\t\tpretty.Println(columns)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindJoinColBeWhereINEQ(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT * FROM t1 RIGHT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b > 'b' AND t4.c = t1.c)\",\n\t\t\"SELECT left_tbl.* FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t\t\"SELECT left_tbl.* FROM left_tbl RIGHT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\t// pretty.Println(stmt)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tcolumns := FindINEQColsInJoinCond(stmt)\n\t\tpretty.Println(columns)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindAllCondition(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"select t from a LEFT JOIN b USING (c1, c2, c3)\",\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT * FROM t1 RIGHT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT left_tbl.* FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t\t\"SELECT left_tbl.* FROM left_tbl RIGHT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t\t\"SELECT * FROM t1 where a in ('a','b')\",\n\t\t\"SELECT * FROM t1 where a BETWEEN 'bar' AND 'foo'\",\n\t\t\"SELECT * FROM t1 where a = sum(a,b)\",\n\t\t\"SELECT distinct a FROM t1 where a = '2001-01-01 01:01:01'\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\t// pretty.Println(stmt)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tcolumns := FindAllCondition(stmt)\n\t\tpretty.Println(columns)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindColumn(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"select col, col2, sum(col1) from tb group by col\",\n\t\t\"select col from tb group by col,sum(col1)\",\n\t\t\"select col, sum(col1) from tb group by col\",\n\t}\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\t// pretty.Println(stmt)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tcolumns := FindColumn(stmt)\n\t\tpretty.Println(columns)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindAllCols(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"select * from tb where a = '1' order by c\",\n\t\t\"select * from tb where a = '1' group by c\",\n\t\t\"select * from tb where c = '1' group by a\",\n\t\t\"select * from tb join tb2 on c = c where c = '1' group by a\",\n\t}\n\n\ttargets := []Expression{\n\t\tOrderByExpression,\n\t\tGroupByExpression,\n\t\tWhereExpression,\n\t\tJoinExpression,\n\t}\n\n\tfor i, sql := range sqlList {\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\tcolumns := FindAllCols(stmt, targets[i])\n\t\tif columns[0].Name != \"c\" {\n\t\t\tfmt.Println(sql)\n\t\t\tt.Error(fmt.Errorf(\"want 'c' got %v\", columns))\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestGetSubqueryDepth(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"select t from a LEFT JOIN b USING (c1, c2, c3)\",\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t\t\"SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT * FROM t1 RIGHT JOIN (t2, t3, t4) ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\",\n\t\t\"SELECT left_tbl.* FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t\t\"SELECT left_tbl.* FROM left_tbl RIGHT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;\",\n\t\t\"SELECT * FROM t1 where a in ('a','b')\",\n\t\t\"SELECT * FROM t1 where a BETWEEN 'bar' AND 'foo'\",\n\t\t\"SELECT * FROM t1 where a = sum(a,b)\",\n\t\t\"SELECT distinct a FROM t1 where a = '2001-01-01 01:01:01'\",\n\t}\n\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\tif err != nil {\n\t\t\tt.Error(\"syntax check error.\")\n\t\t}\n\n\t\tdep := GetSubqueryDepth(stmt)\n\t\tfmt.Println(dep)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestAppendTable(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqlList := []string{\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t}\n\n\tmeta := make(map[string]*common.DB)\n\tfor _, sql := range sqlList {\n\t\tfmt.Println(sql)\n\t\tstmt, err := sqlparser.Parse(sql)\n\t\tif err != nil {\n\t\t\tt.Error(\"syntax check error.\")\n\t\t}\n\n\t\terr = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch expr := node.(type) {\n\t\t\tcase *sqlparser.AliasedTableExpr:\n\t\t\t\tswitch table := expr.Expr.(type) {\n\t\t\t\tcase sqlparser.TableName:\n\t\t\t\t\tappendTable(table, expr.As.String(), meta)\n\t\t\t\tdefault:\n\t\t\t\t\tif meta == nil {\n\t\t\t\t\t\tmeta = make(map[string]*common.DB)\n\t\t\t\t\t}\n\t\t\t\t\tif meta[\"\"] == nil {\n\t\t\t\t\t\tmeta[\"\"] = common.NewDB(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tmeta[\"\"].Table[\"\"] = common.NewTable(\"\")\n\t\t\t\t\tmeta[\"\"].Table[\"\"].TableAliases = append(meta[\"\"].Table[\"\"].TableAliases, expr.As.String())\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, stmt)\n\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\t// 仅对第一条测试SQL进行测试，验证别名正确性\n\tif meta[\"\"].Table[\"customer_list\"].TableAliases[0] != \"l\" || meta[\"\"].Table[\"city\"].TableAliases[0] != \"c\" {\n\t\tt.Error(\"alias filed\\n\", pretty.Sprint(meta))\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "ast/node_array.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"errors\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// 该文件用于构造一个存储AST生成节点的链表\n// 以能够更好的对AST中的每个节点进行查询、跳转、重建等\n\n// NodeItem 链表节点\ntype NodeItem struct {\n\tID    int               // NodeItem 在 List 中的编号，与顺序有关\n\tPrev  *NodeItem         // 前一个节点\n\tSelf  sqlparser.SQLNode // 自身指向的AST Node\n\tNext  *NodeItem         // 后一个节点\n\tArray *NodeList         // 指针指向所在的链表，用于快速跳转node\n}\n\n// NodeList 链表结构体\ntype NodeList struct {\n\tLength  int\n\tHead    *NodeItem\n\tNodeMap map[int]*NodeItem\n}\n\n// NewNodeList 从抽象语法树中构造一个链表\nfunc NewNodeList(statement sqlparser.Statement) *NodeList {\n\t// 将AST构造成链表\n\tl := &NodeList{NodeMap: make(map[int]*NodeItem)}\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tl.Add(node)\n\t\treturn true, nil\n\t}, statement)\n\tcommon.LogIfWarn(err, \"\")\n\treturn l\n}\n\n// Add 将会把一个sqlparser.SQLNode添加到节点中\nfunc (l *NodeList) Add(node sqlparser.SQLNode) *NodeItem {\n\tif l.Length == 0 {\n\t\tl.Head = &NodeItem{\n\t\t\tID:    0,\n\t\t\tSelf:  node,\n\t\t\tNext:  nil,\n\t\t\tPrev:  nil,\n\t\t\tArray: l,\n\t\t}\n\t\tl.NodeMap[l.Length] = l.Head\n\t} else {\n\t\tif n, ok := l.NodeMap[l.Length-1]; ok {\n\t\t\tn.Next = &NodeItem{\n\t\t\t\tID:    l.Length - 1,\n\t\t\t\tPrev:  n,\n\t\t\t\tSelf:  node,\n\t\t\t\tNext:  nil,\n\t\t\t\tArray: l,\n\t\t\t}\n\t\t\tl.NodeMap[l.Length] = n.Next\n\t\t}\n\t}\n\tl.Length++\n\n\treturn l.NodeMap[l.Length-1]\n}\n\n// Remove 从链表中移除一个节点\nfunc (l *NodeList) Remove(node *NodeItem) error {\n\tvar err error\n\tdefer func() {\n\t\terr := recover()\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"func (l *NodeList) Remove recovered: %v\", err)\n\t\t}\n\t}()\n\n\tif node.Array != l {\n\t\treturn errors.New(\"node not belong to this array\")\n\t}\n\n\tif node.Prev == nil {\n\t\t// 如果是头结点\n\t\tnode.Next.Prev = nil\n\t} else if node.Next == nil {\n\t\t// 如果是尾节点\n\t\tnode.Prev.Next = nil\n\t} else {\n\t\t// 删除节点，连接断开的链表\n\t\tnode.Prev.Next = node.Next\n\t\tnode.Next.Prev = node.Prev\n\t\tdelete(l.NodeMap, node.ID)\n\t}\n\n\treturn err\n}\n\n// First 返回链表头结点\nfunc (l *NodeList) First() *NodeItem {\n\treturn l.Head\n}\n\n// Last 返回链表末尾节点\nfunc (l *NodeList) Last() *NodeItem {\n\treturn l.NodeMap[l.Length-1]\n}\n"
  },
  {
    "path": "ast/pretty.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"container/list\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/percona/go-mysql/query\"\n)\n\n// Pretty 格式化输出SQL\nfunc Pretty(sql string, method string) (output string) {\n\tcommon.Log.Debug(\"Pretty, Query: %s, method: %s\", sql, method)\n\t// 超出 Config.MaxPrettySQLLength 长度的 SQL 会对其指纹进行 pretty\n\tif len(sql) > common.Config.MaxPrettySQLLength {\n\t\tfingerprint := query.Fingerprint(sql)\n\t\t// 超出 Config.MaxPrettySQLLength 长度的指纹不会进行pretty\n\t\tif len(fingerprint) > common.Config.MaxPrettySQLLength {\n\t\t\treturn sql\n\t\t}\n\t\tsql = fingerprint\n\t}\n\n\tswitch method {\n\tcase \"builtin\", \"markdown\":\n\t\treturn format(sql)\n\tdefault:\n\t\treturn sql\n\t}\n}\n\n// format the whitespace in a SQL string to make it easier to read.\n// @param string  $query    The SQL string\n// @return String The SQL string with HTML styles and formatting wrapped in a <pre> tag\nfunc format(query string) string {\n\t// This variable will be populated with formatted html\n\tresult := \"\"\n\t// Use an actual tab while formatting and then switch out with self::$tab at the end\n\ttab := \"  \"\n\tindentLevel := 0\n\tvar newline bool\n\tvar inlineParentheses bool\n\tvar increaseSpecialIndent bool\n\tvar increaseBlockIndent bool\n\tvar addedNewline bool\n\tvar inlineCount int\n\tvar inlineIndented bool\n\tvar clauseLimit bool\n\tindentTypes := list.New()\n\n\t// Tokenize String\n\toriginalTokens := Tokenize(query)\n\n\t// Remove existing whitespace//\n\tvar tokens []Token\n\tfor i, token := range originalTokens {\n\t\tif token.Type != TokenTypeWhitespace {\n\t\t\ttoken.i = i\n\t\t\ttokens = append(tokens, token)\n\t\t}\n\t}\n\n\tfor i, token := range tokens {\n\t\thighlighted := token.Val\n\n\t\t// If we are increasing the special indent level now\n\t\tif increaseSpecialIndent {\n\t\t\tindentLevel++\n\t\t\tincreaseSpecialIndent = false\n\t\t\tindentTypes.PushFront(\"special\")\n\t\t}\n\n\t\t// If we are increasing the block indent level now\n\t\tif increaseBlockIndent {\n\t\t\tindentLevel++\n\t\t\tincreaseBlockIndent = false\n\t\t\tindentTypes.PushFront(\"block\")\n\t\t}\n\n\t\t// If we need a new line before the token\n\t\tif newline {\n\t\t\tresult += \"\\n\" + strings.Repeat(tab, indentLevel)\n\t\t\tnewline = false\n\t\t\taddedNewline = true\n\t\t} else {\n\t\t\taddedNewline = false\n\t\t}\n\n\t\t// Display comments directly where they appear in the source\n\t\tif token.Type == TokenTypeComment || token.Type == TokenTypeBlockComment {\n\t\t\tif token.Type == TokenTypeBlockComment {\n\t\t\t\tindent := strings.Repeat(tab, indentLevel)\n\t\t\t\tresult += \"\\n\" + indent\n\t\t\t\thighlighted = strings.Replace(highlighted, \"\\n\", \"\\n\"+indent, -1)\n\t\t\t}\n\n\t\t\tresult += highlighted\n\t\t\tnewline = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif inlineParentheses {\n\t\t\t// End of inline parentheses\n\t\t\tif token.Val == \")\" {\n\t\t\t\tresult = strings.TrimRight(result, \" \")\n\n\t\t\t\tif inlineIndented {\n\t\t\t\t\tindentTypes.Remove(indentTypes.Front())\n\t\t\t\t\tif indentLevel > 0 {\n\t\t\t\t\t\tindentLevel--\n\t\t\t\t\t}\n\t\t\t\t\tresult += strings.Repeat(tab, indentLevel)\n\t\t\t\t}\n\n\t\t\t\tinlineParentheses = false\n\n\t\t\t\tresult += highlighted + \" \"\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif token.Val == \",\" {\n\t\t\t\tif inlineCount >= 30 {\n\t\t\t\t\tinlineCount = 0\n\t\t\t\t\tnewline = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tinlineCount += len(token.Val)\n\t\t}\n\n\t\t// Opening parentheses increase the block indent level and start a new line\n\t\tif token.Val == \"(\" {\n\t\t\t// First check if this should be an inline parentheses block\n\t\t\t// Examples are \"NOW()\", \"COUNT(*)\", \"int(10)\", key(`somecolumn`), DECIMAL(7,2)\n\t\t\t// Allow up to 3 non-whitespace tokens inside inline parentheses\n\t\t\tlength := 0\n\t\t\tfor j := 1; j <= 250; j++ {\n\t\t\t\t// Reached end of string\n\t\t\t\tif i+j >= len(tokens) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tnext := tokens[i+j]\n\n\t\t\t\t// Reached closing parentheses, able to inline it\n\t\t\t\tif next.Val == \")\" {\n\t\t\t\t\tinlineParentheses = true\n\t\t\t\t\tinlineCount = 0\n\t\t\t\t\tinlineIndented = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Reached an invalid token for inline parentheses\n\t\t\t\tif next.Val == \";\" || next.Val == \"(\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Reached an invalid token type for inline parentheses\n\t\t\t\tif next.Type == TokenTypeReservedToplevel ||\n\t\t\t\t\tnext.Type == TokenTypeReservedNewline ||\n\t\t\t\t\tnext.Type == TokenTypeComment ||\n\t\t\t\t\tnext.Type == TokenTypeBlockComment {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tlength += len(next.Val)\n\t\t\t}\n\n\t\t\tif inlineParentheses && length > 30 {\n\t\t\t\tincreaseBlockIndent = true\n\t\t\t\tinlineIndented = true\n\t\t\t\tnewline = true\n\t\t\t}\n\n\t\t\t// Take out the preceding space unless there was whitespace there in the original query\n\t\t\tif token.i != 0 && (token.i-1) > len(originalTokens)-1 &&\n\t\t\t\toriginalTokens[token.i-1].Type != TokenTypeWhitespace {\n\n\t\t\t\tresult = strings.TrimRight(result, \" \")\n\t\t\t}\n\n\t\t\tif inlineParentheses {\n\t\t\t\tincreaseBlockIndent = true\n\t\t\t\t// Add a newline after the parentheses\n\t\t\t\tnewline = true\n\t\t\t}\n\n\t\t} else if token.Val == \")\" {\n\t\t\t// Closing parentheses decrease the block indent level\n\t\t\t// Remove whitespace before the closing parentheses\n\t\t\tresult = strings.TrimRight(result, \" \")\n\n\t\t\tif indentLevel > 0 {\n\t\t\t\tindentLevel--\n\t\t\t}\n\n\t\t\t// Reset indent level\n\t\t\tfor j := indentTypes.Front(); indentTypes.Len() > 0; indentTypes.Remove(j) {\n\t\t\t\tif j.Value.(string) == \"special\" {\n\t\t\t\t\tif indentLevel > 0 {\n\t\t\t\t\t\tindentLevel--\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif indentLevel < 0 {\n\t\t\t\t// This is an error\n\t\t\t\tindentLevel = 0\n\t\t\t}\n\n\t\t\t// Add a newline before the closing parentheses (if not already added)\n\t\t\tif !addedNewline {\n\t\t\t\tresult += \"\\n\" + strings.Repeat(tab, indentLevel)\n\t\t\t}\n\n\t\t} else if token.Type == TokenTypeReservedToplevel {\n\t\t\t// Top level reserved words start a new line and increase the special indent level\n\t\t\tincreaseSpecialIndent = true\n\n\t\t\t// If the last indent type was 'special', decrease the special indent for this round\n\t\t\tif indentTypes.Len() > 0 && indentTypes.Front().Value.(string) == \"special\" {\n\t\t\t\tif indentLevel > 0 {\n\t\t\t\t\tindentLevel--\n\t\t\t\t}\n\t\t\t\tindentTypes.Remove(indentTypes.Front())\n\t\t\t}\n\n\t\t\t// Add a newline after the top level reserved word\n\t\t\tnewline = true\n\t\t\t// Add a newline before the top level reserved word (if not already added)\n\t\t\tif !addedNewline {\n\t\t\t\tresult += \"\\n\" + strings.Repeat(tab, indentLevel)\n\t\t\t} else {\n\t\t\t\t// If we already added a newline, redo the indentation since it may be different now\n\t\t\t\tresult = strings.TrimSuffix(result, tab) + strings.Repeat(tab, indentLevel)\n\t\t\t}\n\n\t\t\t// If the token may have extra whitespace\n\t\t\tif strings.Index(token.Val, \" \") != 0 ||\n\t\t\t\tstrings.Index(token.Val, \"\\n\") != 0 ||\n\t\t\t\tstrings.Index(token.Val, \"\\t\") != 0 {\n\n\t\t\t\tre, _ := regexp.Compile(`\\s+`)\n\t\t\t\thighlighted = re.ReplaceAllString(highlighted, \" \")\n\n\t\t\t}\n\n\t\t\t//if SQL 'LIMIT' clause, start variable to reset newline\n\t\t\tif token.Val == \"LIMIT\" && inlineParentheses {\n\t\t\t\tclauseLimit = true\n\t\t\t}\n\n\t\t} else if clauseLimit && token.Val != \",\" &&\n\t\t\ttoken.Type != TokenTypeNumber &&\n\t\t\ttoken.Type != TokenTypeWhitespace {\n\t\t\t// Checks if we are out of the limit clause\n\n\t\t\tclauseLimit = false\n\n\t\t} else if token.Val == \",\" && !inlineParentheses {\n\t\t\t// Commas start a new line (unless within inline parentheses or SQL 'LIMIT' clause)\n\t\t\tif clauseLimit {\n\t\t\t\tnewline = false\n\t\t\t\tclauseLimit = false\n\t\t\t} else {\n\t\t\t\t// All other cases of commas\n\t\t\t\tnewline = true\n\t\t\t}\n\n\t\t} else if token.Type == TokenTypeReservedNewline {\n\t\t\t// Newline reserved words start a new line\n\t\t\t// Add a newline before the reserved word (if not already added)\n\t\t\tif !addedNewline {\n\t\t\t\tresult += \"\\n\" + strings.Repeat(tab, indentLevel)\n\t\t\t}\n\n\t\t\t// If the token may have extra whitespace\n\t\t\tif strings.Index(token.Val, \" \") != 0 ||\n\t\t\t\tstrings.Index(token.Val, \"\\n\") != 0 ||\n\t\t\t\tstrings.Index(token.Val, \"\\t\") != 0 {\n\n\t\t\t\tre, _ := regexp.Compile(`\\s+`)\n\t\t\t\thighlighted = re.ReplaceAllString(highlighted, \" \")\n\t\t\t}\n\n\t\t} else if token.Type == TokenTypeBoundary {\n\t\t\t// Multiple boundary characters in a row should not have spaces between them (not including parentheses)\n\t\t\tif i != 0 && i < len(tokens) &&\n\t\t\t\ttokens[i-1].Type == TokenTypeBoundary {\n\n\t\t\t\tif token.i != 0 && token.i < len(originalTokens) &&\n\t\t\t\t\toriginalTokens[token.i-1].Type != TokenTypeWhitespace {\n\n\t\t\t\t\tresult = strings.TrimRight(result, \" \")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If the token shouldn't have a space before it\n\t\tif token.Val == \".\" || token.Val == \",\" || token.Val == \";\" {\n\t\t\tresult = strings.TrimRight(result, \" \")\n\t\t}\n\n\t\tresult += highlighted + \" \"\n\n\t\t// If the token shouldn't have a space after it\n\t\tif token.Val == \"(\" || token.Val == \".\" {\n\t\t\tresult = strings.TrimRight(result, \" \")\n\t\t}\n\n\t\t// If this is the \"-\" of a negative number, it shouldn't have a space after it\n\t\tif token.Val == \"-\" && i+1 < len(tokens) && tokens[i+1].Type == TokenTypeNumber && i != 0 {\n\t\t\tprev := tokens[i-1].Type\n\t\t\tif prev != TokenTypeQuote &&\n\t\t\t\tprev != TokenTypeBacktickQuote &&\n\t\t\t\tprev != TokenTypeWord &&\n\t\t\t\tprev != TokenTypeNumber {\n\n\t\t\t\tresult = strings.TrimRight(result, \" \")\n\t\t\t}\n\t\t}\n\t}\n\n\t// Replace tab characters with the configuration tab character\n\tresult = strings.TrimRight(strings.Replace(result, \"\\t\", tab, -1), \" \")\n\n\treturn result\n}\n"
  },
  {
    "path": "ast/pretty_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\nvar TestSqlsPretty = []string{\n\t\"select sourcetable, if(f.lastcontent = ?, f.lastupdate, f.lastcontent) as lastactivity, f.totalcount as activity, type.class as type, (f.nodeoptions & ?) as nounsubscribe from node as f inner join contenttype as type on type.contenttypeid = f.contenttypeid inner join subscribed as sd on sd.did = f.nodeid and sd.userid = ? union all select f.name as title, f.userid as keyval, ? as sourcetable, ifnull(f.lastpost, f.joindate) as lastactivity, f.posts as activity, ? as type, ? as nounsubscribe from user as f inner join userlist as ul on ul.relationid = f.userid and ul.userid = ? where ul.type = ? and ul.aq = ? order by title limit ?\",\n\t\"administrator command: Init DB\",\n\t\"CALL foo(1, 2, 3)\",\n\t\"### Channels ###\\n\\u0009\\u0009\\u0009\\u0009\\u0009SELECT sourcetable, IF(f.lastcontent = 0, f.lastupdate, f.lastcontent) AS lastactivity,\\n\\u0009\\u0009\\u0009\\u0009\\u0009f.totalcount AS activity, type.class AS type,\\n\\u0009\\u0009\\u0009\\u0009\\u0009(f.nodeoptions \\u0026 512) AS noUnsubscribe\\n\\u0009\\u0009\\u0009\\u0009\\u0009FROM node AS f\\n\\u0009\\u0009\\u0009\\u0009\\u0009INNER JOIN contenttype AS type ON type.contenttypeid = f.contenttypeid \\n\\n\\u0009\\u0009\\u0009\\u0009\\u0009INNER JOIN subscribed AS sd ON sd.did = f.nodeid AND sd.userid = 15965\\n UNION  ALL \\n\\n\\u0009\\u0009\\u0009\\u0009\\u0009### Users ###\\n\\u0009\\u0009\\u0009\\u0009\\u0009SELECT f.name AS title, f.userid AS keyval, 'user' AS sourcetable, IFNULL(f.lastpost, f.joindate) AS lastactivity,\\n\\u0009\\u0009\\u0009\\u0009\\u0009f.posts as activity, 'Member' AS type,\\n\\u0009\\u0009\\u0009\\u0009\\u00090 AS noUnsubscribe\\n\\u0009\\u0009\\u0009\\u0009\\u0009FROM user AS f\\n\\u0009\\u0009\\u0009\\u0009\\u0009INNER JOIN userlist AS ul ON ul.relationid = f.userid AND ul.userid = 15965\\n\\u0009\\u0009\\u0009\\u0009\\u0009WHERE ul.type = 'f' AND ul.aq = 'yes'\\n ORDER BY title ASC LIMIT 100\",\n\t\"CREATE DATABASE org235_percona345 COLLATE 'utf8_general_ci'\",\n\t\"insert into abtemp.coxed select foo.bar from foo\",\n\t\"insert into foo(a, b, c) value(2, 4, 5)\",\n\t\"insert into foo(a, b, c) values(2, 4, 5)\",\n\t\"insert into foo(a, b, c) values(2, 4, 5) , (2,4,5)\",\n\t\"insert into foo values (1, '(2)', 'This is a trick: ). More values.', 4)\",\n\t\"insert into tb values (1)\",\n\t\"INSERT INTO t (ts) VALUES ('()', '\\\\(', '\\\\)')\",\n\t\"INSERT INTO t (ts) VALUES (NOW())\",\n\t\"INSERT INTO t () VALUES ()\",\n\t\"insert into t values (1), (2), (3)\\n\\n\\ton duplicate key update query_count=1\",\n\t\"insert into t values (1) on duplicate key update query_count=COALESCE(query_count, 0) + VALUES(query_count)\",\n\t\"LOAD DATA INFILE '/tmp/foo.txt' INTO db.tbl\",\n\t\"select 0e0, +6e-30, -6.00 from foo where a = 5.5 or b=0.5 or c=.5\",\n\t\"select 0x0, x'123', 0b1010, b'10101' from foo\",\n\t\"select 123_foo from 123_foo\",\n\t\"select 123foo from 123foo\",\n\t`SELECT \t1 AS one FROM calls USE INDEX(index_name)`,\n\t\"SELECT /*!40001 SQL_NO_CACHE */ * FROM `film`\",\n\t\"SELECT 'a' 'b' 'c' 'd' FROM kamil\",\n\t\"SELECT BENCHMARK(100000000, pow(rand(), rand())), 1 FROM `-hj-7d6-shdj5-7jd-kf-g988h-`.`-aaahj-7d6-shdj5-7&^%$jd-kf-g988h-9+4-5*6ab-`\",\n\t\"SELECT c FROM org235.t WHERE id=0xdeadbeaf\",\n\t\"select c from t where i=1 order by c asc\",\n\t\"SELECT c FROM t WHERE id=0xdeadbeaf\",\n\t\"SELECT c FROM t WHERE id=1\",\n\t\"select `col` from `table-1` where `id` = 5\",\n\t\"SELECT `db`.*, (CASE WHEN (`date_start` <=  '2014-09-10 09:17:59' AND `date_end` >=  '2014-09-10 09:17:59') THEN 'open' WHEN (`date_start` >  '2014-09-10 09:17:59' AND `date_end` >  '2014-09-10 09:17:59') THEN 'tbd' ELSE 'none' END) AS `status` FROM `foo` AS `db` WHERE (a_b in ('1', '10101'))\",\n\t\"select field from `-master-db-1`.`-table-1-` order by id, ?;\",\n\t\"select   foo\",\n\t\"select foo_1 from foo_2_3\",\n\t\"select foo -- bar\\n\",\n\t\"select foo-- bar\\n,foo\",\n\t\"select '\\\\\\\\' from foo\",\n\t\"select * from foo limit 5\",\n\t\"select * from foo limit 5, 10\",\n\t\"select * from foo limit 5 offset 10\",\n\t\"SELECT * from foo where a = 5\",\n\t\"select * from foo where a in (5) and b in (5, 8,9 ,9 , 10)\",\n\t\"SELECT '' '' '' FROM kamil\",\n\t\" select  * from\\nfoo where a = 5\",\n\t\"SELECT * FROM prices.rt_5min where id=1\",\n\t\"SELECT * FROM table WHERE field = 'value' /*arbitrary/31*/ \",\n\t\"SELECT * FROM table WHERE field = 'value' /*arbitrary31*/ \",\n\t\"SELECT *    FROM t WHERE 1=1 AND id=1\",\n\t\"select * from t where (base.nid IN  ('1412', '1410', '1411'))\",\n\t`select * from t where i=1      order            by\n             a,  b          ASC, d    DESC,\n\n                                    e asc`,\n\t\"select * from t where i=1 order by a, b ASC, d DESC, e asc\",\n\t\"select 'hello'\\n\",\n\t\"select 'hello', '\\nhello\\n', \\\"hello\\\", '\\\\'' from foo\",\n\t\"SELECT ID, name, parent, type FROM posts WHERE _name IN ('perf','caching') AND (type = 'page' OR type = 'attachment')\",\n\t\"SELECT name, value FROM variable\",\n\t\"select \\n-- bar\\n foo\",\n\t\"select null, 5.001, 5001. from foo\",\n\t\"select sleep(2) from test.n\",\n\t\"SELECT t FROM field WHERE  (entity_type = 'node') AND (entity_id IN  ('609')) AND (language IN  ('und')) AND (deleted = '0') ORDER BY delta ASC\",\n\t\"select  t.table_schema,t.table_name,engine  from information_schema.tables t  inner join information_schema.columns c  on t.table_schema=c.table_schema and t.table_name=c.table_name group by t.table_schema,t.table_name having  sum(if(column_key in ('PRI','UNI'),1,0))=0\",\n\t\"/* -- S++ SU ABORTABLE -- spd_user: rspadim */SELECT SQL_SMALL_RESULT SQL_CACHE DISTINCT centro_atividade FROM est_dia WHERE unidade_id=1001 AND item_id=67 AND item_id_red=573\",\n\t`UPDATE groups_search SET  charter = '   -------3\\'\\' XXXXXXXXX.\\n    \\n    -----------------------------------------------------', show_in_list = 'Y' WHERE group_id='aaaaaaaa'`,\n\t\"use `foo`\",\n\t\"select sourcetable, if(f.lastcontent = ?, f.lastupdate, f.lastcontent) as lastactivity, f.totalcount as activity, type.class as type, (f.nodeoptions & ?) as nounsubscribe from node as f inner join contenttype as type on type.contenttypeid = f.contenttypeid inner join subscribed as sd on sd.did = f.nodeid and sd.userid = ? union all select f.name as title, f.userid as keyval, ? as sourcetable, ifnull(f.lastpost, f.joindate) as lastactivity, f.posts as activity, ? as type, ? as nounsubscribe from user as f inner join userlist as ul on ul.relationid = f.userid and ul.userid = ? where ul.type = ? and ul.aq = ? order by title limit ?\",\n\t\"CREATE INDEX part_of_name ON customer (name(10));\",\n\t\"alter table `sakila`.`t1` add index `idx_col`(`col`)\",\n\t\"alter table `sakila`.`t1` add UNIQUE index `idx_col`(`col`)\",\n\t\"alter table `sakila`.`t1` add index `idx_ID`(`ID`)\",\n\n\t// ADD|DROP COLUMN\n\t\"ALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;\",\n\t\"ALTER TABLE T2 ADD COLUMN C int;\",\n\t\"ALTER TABLE T2 ADD COLUMN D int FIRST;\",\n\t\"ALTER TABLE T2 ADD COLUMN E int AFTER D;\",\n\n\t// RENAME COLUMN\n\t\"ALTER TABLE t1 RENAME COLUMN a TO b\",\n\n\t// RENAME INDEX\n\t\"ALTER TABLE t1 RENAME INDEX idx_a TO idx_b\",\n\t\"ALTER TABLE t1 RENAME KEY idx_a TO idx_b\",\n\n\t// RENAME TABLE\n\t\"ALTER TABLE db.old_table RENAME new_table;\",\n\t\"ALTER TABLE old_table RENAME TO new_table;\",\n\t\"ALTER TABLE old_table RENAME AS new_table;\",\n\n\t// MODIFY & CHANGE\n\t\"ALTER TABLE t1 MODIFY col1 BIGINT UNSIGNED DEFAULT 1 COMMENT 'my column';\",\n\t\"ALTER TABLE t1 CHANGE b a INT NOT NULL;\",\n\n\t// COMMENT\n\t\"/*!40000 select 1*/;\",\n}\n\nfunc TestPretty(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range append(TestSqlsPretty, common.TestSQLs...) {\n\t\t\tfmt.Println(sql)\n\t\t\tfmt.Println(Pretty(sql, \"builtin\"))\n\t\t}\n\t\torgMaxPrettySQLLength := common.Config.MaxPrettySQLLength\n\t\tcommon.Config.MaxPrettySQLLength = 1\n\t\tfmt.Println(Pretty(\"select 1\", \"builtin\"))\n\t\tcommon.Config.MaxPrettySQLLength = orgMaxPrettySQLLength\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestIsKeyword(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttks := map[string]bool{\n\t\t\"AGAINST\":        true,\n\t\t\"AUTO_INCREMENT\": true,\n\t\t\"ADD\":            true,\n\t\t\"BETWEEN\":        true,\n\t\t\".\":              false,\n\t\t\"actions\":        false,\n\t\t`\"`:              false,\n\t\t\":\":              false,\n\t}\n\tfor tk, v := range tks {\n\t\tif IsMysqlKeyword(tk) != v {\n\t\t\tt.Error(\"isKeyword:\", tk)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRemoveComments(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tfor _, sql := range TestSqlsPretty {\n\t\tstmt, _ := sqlparser.Parse(sql)\n\t\tnewSQL := sqlparser.String(stmt)\n\t\tif newSQL != sql {\n\t\t\tfmt.Print(newSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "ast/rewrite.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/pingcap/parser/ast\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// Rule SQL重写规则\ntype Rule struct {\n\tName        string                  `json:\"Name\"`\n\tDescription string                  `json:\"Description\"`\n\tOriginal    string                  `json:\"Original\"` // 错误示范。为空或\"暂不支持\"不会出现在list-rewrite-rules中\n\tSuggest     string                  `json:\"Suggest\"`  // 正确示范。\n\tFunc        func(*Rewrite) *Rewrite `json:\"-\"`        // 如果不定义 Func 需要多条 SQL 联动改写\n}\n\n// RewriteRules SQL重写规则，注意这个规则是有序的，先后顺序不能乱\nvar RewriteRules []Rule\n\nfunc init() {\n\tRewriteRules = []Rule{\n\t\t{\n\t\t\tName:        \"dml2select\",\n\t\t\tDescription: \"将数据库更新请求转换为只读查询请求，便于执行EXPLAIN\",\n\t\t\tOriginal:    \"DELETE FROM film WHERE length > 100\",\n\t\t\tSuggest:     \"select * from film where length > 100\",\n\t\t\tFunc:        (*Rewrite).RewriteDML2Select,\n\t\t},\n\t\t{\n\t\t\tName:        \"reg2select\",\n\t\t\tDescription: \"使用正则的方式将数据库更新请求转换为只读查询请求，便于执行EXPLAIN\",\n\t\t\tOriginal:    \"DELETE FROM film WHERE length > 100\",\n\t\t\tSuggest:     \"select * from film where length > 100\",\n\t\t\tFunc:        (*Rewrite).RewriteReg2Select,\n\t\t},\n\t\t{\n\t\t\tName:        \"star2columns\",\n\t\t\tDescription: \"为SELECT *补全表的列信息\",\n\t\t\tOriginal:    \"SELECT * FROM film\",\n\t\t\tSuggest:     \"select film.film_id, film.title from film\",\n\t\t\tFunc:        (*Rewrite).RewriteStar2Columns,\n\t\t},\n\t\t{\n\t\t\tName:        \"insertcolumns\",\n\t\t\tDescription: \"为INSERT补全表的列信息\",\n\t\t\tOriginal:    \"insert into film values(1,2,3,4,5)\",\n\t\t\tSuggest:     \"insert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5)\",\n\t\t\tFunc:        (*Rewrite).RewriteInsertColumns,\n\t\t},\n\t\t{\n\t\t\tName:        \"having\",\n\t\t\tDescription: \"将查询的 HAVING 子句改写为 WHERE 中的查询条件\",\n\t\t\tOriginal:    \"SELECT state, COUNT(*) FROM Drivers GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state\",\n\t\t\tSuggest:     \"select state, COUNT(*) from Drivers where state in ('GA', 'TX') group by state order by state asc\",\n\t\t\tFunc:        (*Rewrite).RewriteHaving,\n\t\t},\n\t\t{\n\t\t\tName:        \"orderbynull\",\n\t\t\tDescription: \"如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 ORDER BY NULL\",\n\t\t\tOriginal:    \"SELECT sum(col1) FROM tbl GROUP BY col\",\n\t\t\tSuggest:     \"select sum(col1) from tbl group by col order by null\",\n\t\t\tFunc:        (*Rewrite).RewriteAddOrderByNull,\n\t\t},\n\t\t{\n\t\t\tName:        \"unionall\",\n\t\t\tDescription: \"可以接受重复的时间，使用 UNION ALL 替代 UNION 以提高查询效率\",\n\t\t\tOriginal:    \"select country_id from city union select country_id from country\",\n\t\t\tSuggest:     \"select country_id from city union all select country_id from country\",\n\t\t\tFunc:        (*Rewrite).RewriteUnionAll,\n\t\t},\n\t\t{\n\t\t\tName:        \"or2in\",\n\t\t\tDescription: \"将同一列不同条件的 OR 查询转写为 IN 查询\",\n\t\t\tOriginal:    \"select country_id from city where col1 = 1 or (col2 = 1 or col2 = 2 ) or col1 = 3;\",\n\t\t\tSuggest:     \"select country_id from city where (col2 in (1, 2)) or col1 in (1, 3);\",\n\t\t\tFunc:        (*Rewrite).RewriteOr2In,\n\t\t},\n\t\t{\n\t\t\tName:        \"innull\",\n\t\t\tDescription: \"如果 IN 条件中可能有 NULL 值而又想匹配 NULL 值时，建议添加OR col IS NULL\",\n\t\t\tOriginal:    \"暂不支持\",\n\t\t\tSuggest:     \"暂不支持\",\n\t\t\tFunc:        (*Rewrite).RewriteInNull,\n\t\t},\n\t\t// 把所有跟 or 相关的重写完之后才进行 or 转 union 的重写\n\t\t{\n\t\t\tName:        \"or2union\",\n\t\t\tDescription: \"将不同列的 OR 查询转为 UNION 查询，建议结合 unionall 重写策略一起使用\",\n\t\t\tOriginal:    \"暂不支持\",\n\t\t\tSuggest:     \"暂不支持\",\n\t\t\tFunc:        (*Rewrite).RewriteOr2Union,\n\t\t},\n\t\t{\n\t\t\tName:        \"dmlorderby\",\n\t\t\tDescription: \"删除 DML 更新操作中无意义的 ORDER BY\",\n\t\t\tOriginal:    \"DELETE FROM tbl WHERE col1=1 ORDER BY col\",\n\t\t\tSuggest:     \"delete from tbl where col1 = 1\",\n\t\t\tFunc:        (*Rewrite).RewriteRemoveDMLOrderBy,\n\t\t},\n\t\t/*\n\t\t\t{\n\t\t\t\tName:        \"groupbyconst\",\n\t\t\t\tDescription: \"删除无意义的GROUP BY常量\",\n\t\t\t\tOriginal:    \"SELECT sum(col1) FROM tbl GROUP BY 1;\",\n\t\t\t\tSuggest:     \"select sum(col1) from tbl\",\n\t\t\t\tFunc:        (*Rewrite).RewriteGroupByConst,\n\t\t\t},\n\t\t*/\n\t\t{\n\t\t\tName:        \"sub2join\",\n\t\t\tDescription: \"将子查询转换为JOIN查询\",\n\t\t\tOriginal:    \"暂不支持\",\n\t\t\tSuggest:     \"暂不支持\",\n\t\t\tFunc:        (*Rewrite).RewriteSubQuery2Join,\n\t\t},\n\t\t{\n\t\t\tName:        \"join2sub\",\n\t\t\tDescription: \"将JOIN查询转换为子查询\",\n\t\t\tOriginal:    \"暂不支持\",\n\t\t\tSuggest:     \"暂不支持\",\n\t\t\tFunc:        (*Rewrite).RewriteJoin2SubQuery,\n\t\t},\n\t\t{\n\t\t\tName:        \"distinctstar\",\n\t\t\tDescription: \"DISTINCT *对有主键的表没有意义，可以将DISTINCT删掉\",\n\t\t\tOriginal:    \"SELECT DISTINCT * FROM film;\",\n\t\t\tSuggest:     \"SELECT * FROM film\",\n\t\t\tFunc:        (*Rewrite).RewriteDistinctStar,\n\t\t},\n\t\t{\n\t\t\tName:        \"standard\",\n\t\t\tDescription: \"SQL标准化，如：关键字转换为小写\",\n\t\t\tOriginal:    \"SELECT sum(col1) FROM tbl GROUP BY 1;\",\n\t\t\tSuggest:     \"select sum(col1) from tbl group by 1\",\n\t\t\tFunc:        (*Rewrite).RewriteStandard,\n\t\t},\n\t\t{\n\t\t\tName:        \"mergealter\",\n\t\t\tDescription: \"合并同一张表的多条ALTER语句\",\n\t\t\tOriginal:    \"ALTER TABLE t2 DROP COLUMN c;ALTER TABLE t2 DROP COLUMN d;\",\n\t\t\tSuggest:     \"ALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;\",\n\t\t},\n\t\t{\n\t\t\tName:        \"alwaystrue\",\n\t\t\tDescription: \"删除无用的恒真判断条件\",\n\t\t\tOriginal:    \"SELECT count(col) FROM tbl where 'a'= 'a' or ('b' = 'b' and a = 'b');\",\n\t\t\tSuggest:     \"select count(col) from tbl where (a = 'b');\",\n\t\t\tFunc:        (*Rewrite).RewriteAlwaysTrue,\n\t\t},\n\t\t{\n\t\t\tName:        \"countstar\",\n\t\t\tDescription: \"不建议使用COUNT(col)或COUNT(常量)，建议改写为COUNT(*)\",\n\t\t\tOriginal:    \"SELECT count(col) FROM tbl GROUP BY 1;\",\n\t\t\tSuggest:     \"SELECT count(*) FROM tbl GROUP BY 1;\",\n\t\t\tFunc:        (*Rewrite).RewriteCountStar,\n\t\t},\n\t\t{\n\t\t\tName:        \"innodb\",\n\t\t\tDescription: \"建表时建议使用InnoDB引擎，非 InnoDB 引擎表自动转 InnoDB\",\n\t\t\tOriginal:    \"CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT);\",\n\t\t\tSuggest:     \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB;\",\n\t\t\tFunc:        (*Rewrite).RewriteInnoDB,\n\t\t},\n\t\t{\n\t\t\tName:        \"autoincrement\",\n\t\t\tDescription: \"将autoincrement初始化为1\",\n\t\t\tOriginal:    \"CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;\",\n\t\t\tSuggest:     \"create table t1(id bigint(20) not null auto_increment) ENGINE=InnoDB auto_increment=1;\",\n\t\t\tFunc:        (*Rewrite).RewriteAutoIncrement,\n\t\t},\n\t\t{\n\t\t\tName:        \"intwidth\",\n\t\t\tDescription: \"整型数据类型修改默认显示宽度\",\n\t\t\tOriginal:    \"create table t1 (id int(20) not null auto_increment) ENGINE=InnoDB;\",\n\t\t\tSuggest:     \"create table t1 (id int(10) not null auto_increment) ENGINE=InnoDB;\",\n\t\t\tFunc:        (*Rewrite).RewriteIntWidth,\n\t\t},\n\t\t{\n\t\t\tName:        \"truncate\",\n\t\t\tDescription: \"不带 WHERE 条件的 DELETE 操作建议修改为 TRUNCATE\",\n\t\t\tOriginal:    \"DELETE FROM tbl\",\n\t\t\tSuggest:     \"truncate table tbl\",\n\t\t\tFunc:        (*Rewrite).RewriteTruncate,\n\t\t},\n\t\t{\n\t\t\tName:        \"rmparenthesis\",\n\t\t\tDescription: \"去除没有意义的括号\",\n\t\t\tOriginal:    \"select col from table where (col = 1);\",\n\t\t\tSuggest:     \"select col from table where col = 1;\",\n\t\t\tFunc:        (*Rewrite).RewriteRmParenthesis,\n\t\t},\n\t\t// delimiter要放在最后，不然补不上\n\t\t{\n\t\t\tName:        \"delimiter\",\n\t\t\tDescription: \"补全DELIMITER\",\n\t\t\tOriginal:    \"use sakila\",\n\t\t\tSuggest:     \"use sakila;\",\n\t\t\tFunc:        (*Rewrite).RewriteDelimiter,\n\t\t},\n\t\t// TODO in to exists\n\t\t// TODO exists to in\n\t}\n}\n\n// ListRewriteRules 打印SQL重写规则\nfunc ListRewriteRules(rules []Rule) {\n\tswitch common.Config.ReportType {\n\tcase \"json\":\n\t\tjs, err := json.MarshalIndent(rules, \"\", \"  \")\n\t\tif err == nil {\n\t\t\tfmt.Println(string(js))\n\t\t}\n\tdefault:\n\n\t\tfmt.Print(\"# 重写规则\\n\\n[toc]\\n\\n\")\n\t\tfor _, r := range rules {\n\t\t\tif !common.Config.Verbose && (r.Original == \"\" || r.Original == \"暂不支持\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfmt.Print(\"## \", common.MarkdownEscape(r.Name),\n\t\t\t\t\"\\n* **Description**:\", r.Description+\"\\n\",\n\t\t\t\t\"\\n* **Original**:\\n\\n```sql\\n\", r.Original, \"\\n```\\n\",\n\t\t\t\t\"\\n* **Suggest**:\\n\\n```sql\\n\", r.Suggest, \"\\n```\\n\")\n\n\t\t}\n\t}\n}\n\n// Rewrite 用于重写SQL\ntype Rewrite struct {\n\tSQL     string\n\tNewSQL  string\n\tStmt    sqlparser.Statement\n\tColumns common.TableColumns\n}\n\n// NewRewrite 返回一个*Rewrite对象，如果SQL无法被正常解析，将错误输出到日志中，返回一个nil\nfunc NewRewrite(sql string) *Rewrite {\n\tstmt, err := sqlparser.Parse(sql)\n\tif err != nil {\n\t\tcommon.Log.Error(err.Error(), sql)\n\t\treturn nil\n\t}\n\n\treturn &Rewrite{\n\t\tSQL:  sql,\n\t\tStmt: stmt,\n\t}\n}\n\n// Rewrite 入口函数\nfunc (rw *Rewrite) Rewrite() *Rewrite {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tcommon.Log.Error(\"Query rewrite Error: %s, maybe hit a bug.\\nQuery: %s \\nAST: %s\",\n\t\t\t\terr, rw.SQL, pretty.Sprint(rw.Stmt))\n\t\t\treturn\n\t\t}\n\t}()\n\n\tfor _, rule := range RewriteRules {\n\t\tif RewriteRuleMatch(rule.Name) && rule.Func != nil {\n\t\t\trule.Func(rw)\n\t\t\tcommon.Log.Debug(\"Rewrite Rule:%s Output NewSQL: %s\", rule.Name, rw.NewSQL)\n\t\t}\n\t}\n\tif rw.NewSQL == \"\" {\n\t\trw.NewSQL = rw.SQL\n\t}\n\trw.Stmt, _ = sqlparser.Parse(rw.NewSQL)\n\n\t// TODO: 重新前后返回结果一致性对比\n\n\t// TODO: 前后SQL性能对比\n\treturn rw\n}\n\n// RewriteDelimiter delimiter: 补分号，可以指定不同的DELIMITER\nfunc (rw *Rewrite) RewriteDelimiter() *Rewrite {\n\tif rw.NewSQL != \"\" {\n\t\trw.NewSQL = strings.TrimSuffix(rw.NewSQL, common.Config.Delimiter) + common.Config.Delimiter\n\t} else {\n\t\trw.NewSQL = strings.TrimSuffix(rw.SQL, common.Config.Delimiter) + common.Config.Delimiter\n\t}\n\treturn rw\n}\n\n// RewriteStandard standard: 使用 vitess 提供的 String 功能将抽象语法树转写回 SQL，注意：这可能转写失败。\nfunc (rw *Rewrite) RewriteStandard() *Rewrite {\n\tif _, err := sqlparser.Parse(rw.SQL); err == nil {\n\t\trw.NewSQL = sqlparser.String(rw.Stmt)\n\t}\n\treturn rw\n}\n\n// RewriteAlwaysTrue always true: 删除恒真条件\nfunc (rw *Rewrite) RewriteAlwaysTrue() (reWriter *Rewrite) {\n\tarray := NewNodeList(rw.Stmt)\n\ttNode := array.Head\n\tfor {\n\t\tomitAlwaysTrue(tNode)\n\t\ttNode = tNode.Next\n\t\tif tNode == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// isAlwaysTrue 用于判断ComparisonExpr是否是恒真\nfunc isAlwaysTrue(expr *sqlparser.ComparisonExpr) bool {\n\tif expr == nil {\n\t\treturn true\n\t}\n\n\tvar result bool\n\tswitch expr.Operator {\n\tcase \"<>\":\n\t\texpr.Operator = \"!=\"\n\tcase \"<=>\":\n\t\texpr.Operator = \"=\"\n\tcase \">=\", \"<=\", \"!=\", \"=\", \">\", \"<\":\n\tdefault:\n\t\treturn false\n\t}\n\n\tvar left []byte\n\tvar right []byte\n\n\t// left\n\tswitch l := expr.Left.(type) {\n\tcase *sqlparser.SQLVal:\n\t\tleft = l.Val\n\tdefault:\n\t\treturn false\n\t}\n\n\t// right\n\tswitch r := expr.Right.(type) {\n\tcase *sqlparser.SQLVal:\n\t\tright = r.Val\n\tdefault:\n\t\treturn false\n\t}\n\n\tswitch expr.Operator {\n\tcase \"=\":\n\t\tresult = bytes.Equal(left, right)\n\tcase \"!=\":\n\t\tresult = !bytes.Equal(left, right)\n\tcase \">\":\n\t\tresult = bytes.Compare(left, right) > 0\n\tcase \">=\":\n\t\tresult = bytes.Compare(left, right) >= 0\n\tcase \"<\":\n\t\tresult = bytes.Compare(left, right) < 0\n\tcase \"<=\":\n\t\tresult = bytes.Compare(left, right) <= 0\n\tdefault:\n\t\tresult = false\n\t}\n\n\treturn result\n}\n\n// omitAlwaysTrue 移除AST中的恒真条件\nfunc omitAlwaysTrue(node *NodeItem) {\n\tif node == nil {\n\t\treturn\n\t}\n\n\tswitch self := node.Self.(type) {\n\tcase *sqlparser.Where:\n\t\tif self != nil {\n\t\t\tswitch cond := self.Expr.(type) {\n\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\tif isAlwaysTrue(cond) {\n\t\t\t\t\tself.Expr = nil\n\t\t\t\t}\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tif cond.Expr == nil {\n\t\t\t\t\tself.Expr = nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase *sqlparser.ParenExpr:\n\t\tif self != nil {\n\t\t\tswitch cond := self.Expr.(type) {\n\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\tif isAlwaysTrue(cond) {\n\t\t\t\t\tself.Expr = nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase *sqlparser.AndExpr:\n\t\tif self != nil {\n\t\t\tvar tmp sqlparser.Expr\n\t\t\tisRightTrue := false\n\t\t\tisLeftTrue := false\n\t\t\ttmp = nil\n\n\t\t\t// 查看左树的情况\n\t\t\tswitch l := self.Left.(type) {\n\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\tif isAlwaysTrue(l) {\n\t\t\t\t\tself.Left = nil\n\t\t\t\t\tisLeftTrue = true\n\t\t\t\t\ttmp = self.Right\n\t\t\t\t}\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tif l.Expr == nil {\n\t\t\t\t\tself.Left = nil\n\t\t\t\t\tisLeftTrue = true\n\t\t\t\t\ttmp = self.Right\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif l == nil {\n\t\t\t\t\tisLeftTrue = true\n\t\t\t\t\ttmp = self.Right\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 查看右树的情况\n\t\t\tswitch r := self.Right.(type) {\n\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\tif isAlwaysTrue(r) {\n\t\t\t\t\tself.Right = nil\n\t\t\t\t\tisRightTrue = true\n\t\t\t\t\ttmp = self.Left\n\t\t\t\t}\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tif r.Expr == nil {\n\t\t\t\t\tself.Right = nil\n\t\t\t\t\tisRightTrue = true\n\t\t\t\t\ttmp = self.Left\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif r == nil {\n\t\t\t\t\tisRightTrue = true\n\t\t\t\t\ttmp = self.Left\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif isRightTrue && isLeftTrue {\n\t\t\t\ttmp = nil\n\t\t\t} else if !isLeftTrue && !isRightTrue {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 根据类型开始替换节点\n\t\t\tswitch l := node.Prev.Self.(type) {\n\t\t\tcase *sqlparser.Where:\n\t\t\t\tl.Expr = tmp\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tl.Expr = tmp\n\t\t\tcase *sqlparser.AndExpr:\n\t\t\t\tif l.Left == self {\n\t\t\t\t\tl.Left = tmp\n\t\t\t\t} else if l.Right == self {\n\t\t\t\t\tl.Right = tmp\n\t\t\t\t}\n\t\t\tcase *sqlparser.OrExpr:\n\t\t\t\tif l.Left == self {\n\t\t\t\t\tl.Left = tmp\n\t\t\t\t} else if l.Right == self {\n\t\t\t\t\tl.Right = tmp\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// 未匹配到对应数据类型则从链表中移除该节点\n\t\t\t\terr := node.Array.Remove(node.Prev)\n\t\t\t\tcommon.LogIfError(err, \"\")\n\t\t\t}\n\n\t\t}\n\n\tcase *sqlparser.OrExpr:\n\t\t// 与AndExpr相同\n\t\tif self != nil {\n\t\t\tvar tmp sqlparser.Expr\n\t\t\tisRightTrue := false\n\t\t\tisLeftTrue := false\n\t\t\ttmp = nil\n\n\t\t\tswitch l := self.Left.(type) {\n\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\tif isAlwaysTrue(l) {\n\t\t\t\t\tself.Left = nil\n\t\t\t\t\tisLeftTrue = true\n\t\t\t\t\ttmp = self.Right\n\t\t\t\t}\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tif l.Expr == nil {\n\t\t\t\t\tself.Left = nil\n\t\t\t\t\tisLeftTrue = true\n\t\t\t\t\ttmp = self.Right\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif l == nil {\n\t\t\t\t\tisLeftTrue = true\n\t\t\t\t\ttmp = self.Right\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch r := self.Right.(type) {\n\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\tif isAlwaysTrue(r) {\n\t\t\t\t\tself.Right = nil\n\t\t\t\t\tisRightTrue = true\n\t\t\t\t\ttmp = self.Left\n\t\t\t\t}\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tif r.Expr == nil {\n\t\t\t\t\tself.Right = nil\n\t\t\t\t\tisRightTrue = true\n\t\t\t\t\ttmp = self.Left\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif r == nil {\n\t\t\t\t\tisRightTrue = true\n\t\t\t\t\ttmp = self.Left\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif isRightTrue && isLeftTrue {\n\t\t\t\ttmp = nil\n\t\t\t} else if !isLeftTrue && !isRightTrue {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch l := node.Prev.Self.(type) {\n\t\t\tcase *sqlparser.Where:\n\t\t\t\tl.Expr = tmp\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tl.Expr = tmp\n\t\t\tcase *sqlparser.AndExpr:\n\t\t\t\tif l.Left == self {\n\t\t\t\t\tl.Left = tmp\n\t\t\t\t} else if l.Right == self {\n\t\t\t\t\tl.Right = tmp\n\t\t\t\t}\n\t\t\tcase *sqlparser.OrExpr:\n\t\t\t\tif l.Left == self {\n\t\t\t\t\tl.Left = tmp\n\t\t\t\t} else if l.Right == self {\n\t\t\t\t\tl.Right = tmp\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\terr := node.Array.Remove(node.Prev)\n\t\t\t\tcommon.LogIfError(err, \"\")\n\t\t\t}\n\t\t}\n\t}\n\n\tomitAlwaysTrue(node.Prev)\n}\n\n// RewriteCountStar countstar: 将COUNT(col)改写为COUNT(*)\n// COUNT(DISTINCT col)不能替换为COUNT(*)\nfunc (rw *Rewrite) RewriteCountStar() *Rewrite {\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch f := node.(type) {\n\t\tcase *sqlparser.FuncExpr:\n\t\t\tif strings.ToLower(f.Name.String()) == \"count\" && len(f.Exprs) > 0 {\n\t\t\t\tswitch colExpr := f.Exprs[0].(type) {\n\t\t\t\tcase *sqlparser.AliasedExpr:\n\t\t\t\t\tswitch col := colExpr.Expr.(type) {\n\t\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\t\tf.Exprs[0] = &sqlparser.StarExpr{TableName: col.Qualifier}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, rw.Stmt)\n\tcommon.LogIfError(err, \"\")\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteInnoDB InnoDB: 为未指定 Engine 的表默认添加 InnoDB 引擎，将其他存储引擎转为 InnoDB\nfunc (rw *Rewrite) RewriteInnoDB() *Rewrite {\n\tswitch create := rw.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif create.Action != \"create\" {\n\t\t\treturn rw\n\t\t}\n\n\t\tif strings.Contains(strings.ToLower(create.TableSpec.Options), \"engine=\") {\n\t\t\treg := regexp.MustCompile(`(?i)engine=[a-z]+`)\n\t\t\tcreate.TableSpec.Options = reg.ReplaceAllString(create.TableSpec.Options, \"ENGINE=InnoDB \")\n\t\t} else {\n\t\t\tcreate.TableSpec.Options = \" ENGINE=InnoDB \" + create.TableSpec.Options\n\t\t}\n\n\t}\n\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteAutoIncrement autoincrement: 将auto_increment设置为1\nfunc (rw *Rewrite) RewriteAutoIncrement() *Rewrite {\n\tswitch create := rw.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif create.Action != \"create\" || create.TableSpec == nil {\n\t\t\treturn rw\n\t\t}\n\t\tif strings.Contains(strings.ToLower(create.TableSpec.Options), \"auto_increment=\") {\n\t\t\treg := regexp.MustCompile(`(?i)auto_increment=[0-9]+`)\n\t\t\tcreate.TableSpec.Options = reg.ReplaceAllString(create.TableSpec.Options, \"auto_increment=1 \")\n\t\t}\n\t}\n\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteIntWidth intwidth: int 类型转为 int(10)，bigint 类型转为 bigint(20)\nfunc (rw *Rewrite) RewriteIntWidth() *Rewrite {\n\tswitch create := rw.Stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\tif create.Action != \"create\" || create.TableSpec == nil {\n\t\t\treturn rw\n\t\t}\n\t\tfor _, col := range create.TableSpec.Columns {\n\t\t\tswitch col.Type.Type {\n\t\t\tcase \"int\", \"integer\":\n\t\t\t\tif col.Type.Length != nil &&\n\t\t\t\t\t(string(col.Type.Length.Val) != \"10\" && string(col.Type.Length.Val) != \"11\") {\n\t\t\t\t\tcol.Type.Length = sqlparser.NewIntVal([]byte(\"10\"))\n\t\t\t\t}\n\t\t\tcase \"bigint\":\n\t\t\t\tif col.Type.Length != nil && string(col.Type.Length.Val) != \"20\" || col.Type.Length == nil {\n\t\t\t\t\tcol.Type.Length = sqlparser.NewIntVal([]byte(\"20\"))\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t}\n\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteStar2Columns star2columns: 对应COL.001，SELECT补全*指代的列名\nfunc (rw *Rewrite) RewriteStar2Columns() *Rewrite {\n\t// 如果未配置mysql环境或从环境中获取失败，*不进行替换\n\tif common.Config.TestDSN.Disable || len(rw.Columns) == 0 {\n\t\tcommon.Log.Debug(\"(rw *Rewrite) RewriteStar2Columns(): Rewrite failed. TestDSN.Disable: %v, len(rw.Columns):%d\",\n\t\t\tcommon.Config.TestDSN.Disable, len(rw.Columns))\n\t\treturn rw\n\t}\n\n\t// 单张表 select * 不补全表名，避免SQL过长，多张表的 select tb1.*, tb2.* 需要补全表名\n\tvar multiTable bool\n\tif len(rw.Columns) > 1 {\n\t\tmultiTable = true\n\t} else {\n\t\tfor db := range rw.Columns {\n\t\t\tif len(rw.Columns[db]) > 1 {\n\t\t\t\tmultiTable = true\n\t\t\t}\n\t\t}\n\t}\n\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\n\t\t\t// select * 可能出现的情况：\n\t\t\t// 1. select * from tb;\n\t\t\t// 2. select * from tb1,tb2;\n\t\t\t// 3. select tb1.* from tb1;\n\t\t\t// 4. select tb1.*,tb2.col from tb1,tb2;\n\t\t\t// 5. select db.tb1.* from tb1;\n\t\t\t// 6. select db.tb1.*,db.tb2.col from db.tb1,db.tb2;\n\n\t\t\tnewSelectExprs := make(sqlparser.SelectExprs, 0)\n\t\t\tfor _, expr := range n.SelectExprs {\n\t\t\t\tswitch e := expr.(type) {\n\t\t\t\tcase *sqlparser.StarExpr:\n\t\t\t\t\t// 一般情况下最外层循环不会超过两层\n\t\t\t\t\tfor _, tables := range rw.Columns {\n\t\t\t\t\t\tfor _, cols := range tables {\n\t\t\t\t\t\t\tfor _, col := range cols {\n\t\t\t\t\t\t\t\tvar table string\n\t\t\t\t\t\t\t\tif multiTable {\n\t\t\t\t\t\t\t\t\ttable = col.Table\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tnewExpr := &sqlparser.AliasedExpr{\n\t\t\t\t\t\t\t\t\tExpr: &sqlparser.ColName{\n\t\t\t\t\t\t\t\t\t\tMetadata: nil,\n\t\t\t\t\t\t\t\t\t\tName:     sqlparser.NewColIdent(col.Name),\n\t\t\t\t\t\t\t\t\t\tQualifier: sqlparser.TableName{\n\t\t\t\t\t\t\t\t\t\t\tName: sqlparser.NewTableIdent(table),\n\t\t\t\t\t\t\t\t\t\t\t// 因为不建议跨DB的查询，所以这里的db前缀将不进行补齐\n\t\t\t\t\t\t\t\t\t\t\tQualifier: sqlparser.TableIdent{},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tAs: sqlparser.ColIdent{},\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif e.TableName.Name.IsEmpty() {\n\t\t\t\t\t\t\t\t\t// 情况1，2\n\t\t\t\t\t\t\t\t\tnewSelectExprs = append(newSelectExprs, newExpr)\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// 其他情况下只有在匹配表名的时候才会进行替换\n\t\t\t\t\t\t\t\t\tif e.TableName.Name.String() == col.Table {\n\t\t\t\t\t\t\t\t\t\tnewSelectExprs = append(newSelectExprs, newExpr)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tnewSelectExprs = append(newSelectExprs, e)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tn.SelectExprs = newSelectExprs\n\t\t}\n\t\treturn true, nil\n\t}, rw.Stmt)\n\tcommon.LogIfError(err, \"\")\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteInsertColumns insertcolumns: 对应COL.002，INSERT补全列名\nfunc (rw *Rewrite) RewriteInsertColumns() *Rewrite {\n\n\tswitch insert := rw.Stmt.(type) {\n\tcase *sqlparser.Insert:\n\t\tswitch insert.Action {\n\t\tcase \"insert\", \"replace\":\n\t\t\tif insert.Columns != nil {\n\t\t\t\treturn rw\n\t\t\t}\n\n\t\t\tnewColumns := make(sqlparser.Columns, 0)\n\t\t\tdb := insert.Table.Qualifier.String()\n\t\t\ttable := insert.Table.Name.String()\n\t\t\t// 支持INSERT/REPLACE INTO VALUES形式，支持INSERT/REPLACE INTO SELECT\n\t\t\tcolCount := 0\n\t\t\tswitch v := insert.Rows.(type) {\n\t\t\tcase sqlparser.Values:\n\t\t\t\tif len(v) > 0 {\n\t\t\t\t\tcolCount = len(v[0])\n\t\t\t\t}\n\n\t\t\tcase *sqlparser.Select:\n\t\t\t\tif l := len(v.SelectExprs); l > 0 {\n\t\t\t\t\tcolCount = l\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 开始对ast进行替换，补全前N列\n\t\t\tcounter := 0\n\t\t\tfor dbName, tb := range rw.Columns {\n\t\t\t\tfor tbName, cols := range tb {\n\t\t\t\t\tfor _, col := range cols {\n\t\t\t\t\t\t// 只有全部列补全完成的时候才会替换ast\n\t\t\t\t\t\tif counter == colCount {\n\t\t\t\t\t\t\tinsert.Columns = newColumns\n\t\t\t\t\t\t\trw.NewSQL = sqlparser.String(rw.Stmt)\n\t\t\t\t\t\t\treturn rw\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif db != \"\" {\n\t\t\t\t\t\t\t// 指定了DB的时候，只能怼指定DB的列\n\t\t\t\t\t\t\tif db == dbName && table == tbName {\n\t\t\t\t\t\t\t\tnewColumns = append(newColumns, sqlparser.NewColIdent(col.Name))\n\t\t\t\t\t\t\t\tcounter++\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// 没有指定DB的时候，将column中的列按顺序往里怼\n\t\t\t\t\t\t\tif table == tbName {\n\t\t\t\t\t\t\t\tnewColumns = append(newColumns, sqlparser.NewColIdent(col.Name))\n\t\t\t\t\t\t\t\tcounter++\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rw\n}\n\n// RewriteHaving having: 对应CLA.013，使用 WHERE 过滤条件替代 HAVING\nfunc (rw *Rewrite) RewriteHaving() *Rewrite {\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tif n.Having != nil {\n\t\t\t\tif n.Where == nil {\n\t\t\t\t\t// WHERE 条件为空直接用 HAVING 替代 WHERE 即可\n\t\t\t\t\tn.Where = n.Having\n\t\t\t\t} else {\n\t\t\t\t\t// WHERE 条件不为空，需要对已有的条件进行括号保护，然后再 AND+HAVING\n\t\t\t\t\tn.Where = &sqlparser.Where{\n\t\t\t\t\t\tExpr: &sqlparser.AndExpr{\n\t\t\t\t\t\t\tLeft: &sqlparser.ParenExpr{\n\t\t\t\t\t\t\t\tExpr: n.Where.Expr,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRight: n.Having.Expr,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// 别忘了重置 HAVING 和 Where.Type\n\t\t\t\tn.Where.Type = \"where\"\n\t\t\t\tn.Having = nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, rw.Stmt)\n\tcommon.LogIfError(err, \"\")\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteAddOrderByNull orderbynull: 对应 CLA.008，GROUP BY 无排序要求时添加 ORDER BY NULL\nfunc (rw *Rewrite) RewriteAddOrderByNull() *Rewrite {\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tif n.GroupBy != nil && n.OrderBy == nil {\n\t\t\t\tn.OrderBy = sqlparser.OrderBy{\n\t\t\t\t\t&sqlparser.Order{\n\t\t\t\t\t\tExpr:      &sqlparser.NullVal{},\n\t\t\t\t\t\tDirection: \"asc\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, rw.Stmt)\n\tcommon.LogIfError(err, \"\")\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteOr2Union or2union: 将 OR 查询转写为 UNION ALL TODO: 暂无对应 HeuristicRules\n// TODO: https://sqlperformance.com/2014/09/sql-plan/rewriting-queries-improve-performance\nfunc (rw *Rewrite) RewriteOr2Union() *Rewrite {\n\treturn rw\n}\n\n// RewriteUnionAll unionall: 不介意重复数据的情况下使用 union all 替换 union\nfunc (rw *Rewrite) RewriteUnionAll() *Rewrite {\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Union:\n\t\t\tn.Type = \"union all\"\n\t\t}\n\t\treturn true, nil\n\t}, rw.Stmt)\n\tcommon.LogIfError(err, \"\")\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteOr2In or2in: 同一列的 OR 过滤条件使用 IN() 替代，如果值有相等的会进行合并\nfunc (rw *Rewrite) RewriteOr2In() *Rewrite {\n\t// 通过 AST 生成 node 的双向链表，链表顺序为书写顺序\n\tnodeList := NewNodeList(rw.Stmt)\n\ttNode := nodeList.First()\n\n\tfor {\n\t\ttNode.or2in()\n\t\tif tNode.Next == nil {\n\t\t\tbreak\n\t\t}\n\t\ttNode = tNode.Next\n\t}\n\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// or2in 用于将 or 转换成 in\nfunc (node *NodeItem) or2in() {\n\tif node == nil || node.Self == nil {\n\t\treturn\n\t}\n\n\tswitch selfNode := node.Self.(type) {\n\tcase *sqlparser.OrExpr:\n\t\tnewExpr := mergeExprs(selfNode.Left, selfNode.Right)\n\t\tif newExpr != nil {\n\t\t\t// or 自身两个节点可以合并的情况下，将父节点中的 expr 替换成新的\n\t\t\tswitch pre := node.Prev.Self.(type) {\n\t\t\tcase *sqlparser.OrExpr:\n\t\t\t\tif pre.Left == node.Self {\n\t\t\t\t\tnode.Self = newExpr\n\t\t\t\t\tpre.Left = newExpr\n\t\t\t\t} else if pre.Right == node.Self {\n\t\t\t\t\tnode.Self = newExpr\n\t\t\t\t\tpre.Right = newExpr\n\t\t\t\t}\n\t\t\tcase *sqlparser.AndExpr:\n\t\t\t\tif pre.Left == node.Self {\n\t\t\t\t\tnode.Self = newExpr\n\t\t\t\t\tpre.Left = newExpr\n\t\t\t\t} else if pre.Right == node.Self {\n\t\t\t\t\tnode.Self = newExpr\n\t\t\t\t\tpre.Right = newExpr\n\t\t\t\t}\n\t\t\tcase *sqlparser.Where:\n\t\t\t\tnode.Self = newExpr\n\t\t\t\tpre.Expr = newExpr\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\t// 如果 SQL 书写中带了括号，暂不会进行跨括号的合并\n\t\t\t\t// TODO: 无意义括号打平，加个 rewrite rule\n\t\t\t\tnode.Self = newExpr\n\t\t\t\tpre.Expr = newExpr\n\t\t\t}\n\t\t} else {\n\t\t\t// or 自身两个节点如不可以合并，则检测是否可以与父节点合并\n\t\t\t// 与父节点的合并不能跨越and、括号等，可能会改变语义\n\t\t\t// 检查自身左右节点是否能与上层节点中合并，or 只能与 or 合并\n\t\t\tswitch pre := node.Prev.Self.(type) {\n\t\t\tcase *sqlparser.OrExpr:\n\t\t\t\t// AST 中如果出现复合条件，则一定在左树，所以只需要判断左边就可以\n\t\t\t\tif pre.Left == selfNode {\n\t\t\t\t\tswitch n := pre.Right.(type) {\n\t\t\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\t\t\tnewLeftExpr := mergeExprs(selfNode.Left, n)\n\t\t\t\t\t\tnewRightExpr := mergeExprs(selfNode.Right, n)\n\n\t\t\t\t\t\t// newLeftExpr 与 newRightExpr 一定有一个是 nil，\n\t\t\t\t\t\t// 否则说明该 orExpr 下的两个节点可合并，可以通过最后的向前递归合并 pre 节点中的 expr\n\t\t\t\t\t\tif newLeftExpr == nil || newRightExpr == nil {\n\t\t\t\t\t\t\tif newLeftExpr != nil {\n\t\t\t\t\t\t\t\tpre.Right = newLeftExpr\n\t\t\t\t\t\t\t\tpre.Left = selfNode.Right\n\t\t\t\t\t\t\t\terr := node.Array.Remove(node)\n\t\t\t\t\t\t\t\tcommon.LogIfError(err, \"\")\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif newRightExpr != nil {\n\t\t\t\t\t\t\t\tpre.Right = newRightExpr\n\t\t\t\t\t\t\t\tpre.Left = selfNode.Left\n\t\t\t\t\t\t\t\terr := node.Array.Remove(node)\n\t\t\t\t\t\t\t\tcommon.LogIfError(err, \"\")\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 逆向合并由更改 AST 后产生的新的可合并节点\n\tnode.Prev.or2in()\n}\n\n// mergeExprs 将两个属于同一个列的 ComparisonExpr 合并成一个，如果不能合并则返回 nil\nfunc mergeExprs(left, right sqlparser.Expr) *sqlparser.ComparisonExpr {\n\t// 用于对比两个列是否相同\n\tcolInLeft := \"\"\n\tcolInRight := \"\"\n\tlOperator := \"\"\n\trOperator := \"\"\n\n\t// 用于存放 expr 左右子树中的值\n\tvar values []sqlparser.SQLNode\n\n\t// SQL 中使用到的列\n\tvar colName *sqlparser.ColName\n\n\t// 左子树\n\tswitch l := left.(type) {\n\tcase *sqlparser.ComparisonExpr:\n\t\t// 获取列名\n\t\tcolName, colInLeft = getColumnName(l.Left)\n\t\t// 获取值\n\t\tif colInLeft != \"\" {\n\t\t\tswitch v := l.Right.(type) {\n\t\t\tcase *sqlparser.SQLVal, sqlparser.ValTuple, *sqlparser.BoolVal, *sqlparser.NullVal:\n\t\t\t\tvalues = append(values, v)\n\t\t\t}\n\t\t}\n\t\t// 获取 operator\n\t\tlOperator = l.Operator\n\tdefault:\n\t\treturn nil\n\t}\n\n\t// 右子树\n\tswitch r := right.(type) {\n\tcase *sqlparser.ComparisonExpr:\n\t\t// 获取列名\n\t\tif colName.Name.String() != \"\" {\n\t\t\tcommon.Log.Warn(\"colName shouldn't has value, but now it's %s\", colName.Name.String())\n\t\t}\n\t\tcolName, colInRight = getColumnName(r.Left)\n\t\t// 获取值\n\t\tif colInRight != \"\" {\n\t\t\tswitch v := r.Right.(type) {\n\t\t\tcase *sqlparser.SQLVal, sqlparser.ValTuple, *sqlparser.BoolVal, *sqlparser.NullVal:\n\t\t\t\tvalues = append(values, v)\n\t\t\t}\n\t\t}\n\t\t// 获取 operator\n\t\trOperator = r.Operator\n\tdefault:\n\t\treturn nil\n\t}\n\n\t// operator 替换，用于在之后判断是否可以合并\n\tswitch lOperator {\n\tcase \"in\", \"=\":\n\t\tlOperator = \"=\"\n\tdefault:\n\t\treturn nil\n\t}\n\n\tswitch rOperator {\n\tcase \"in\", \"=\":\n\t\trOperator = \"=\"\n\tdefault:\n\t\treturn nil\n\t}\n\n\t// 不匹配则返回\n\tif colInLeft == \"\" || colInLeft != colInRight ||\n\t\tlOperator == \"\" || lOperator != rOperator {\n\t\treturn nil\n\t}\n\n\t// 合并左右子树的值\n\tnewValTuple := make(sqlparser.ValTuple, 0)\n\tfor _, v := range values {\n\t\tswitch v := v.(type) {\n\t\tcase *sqlparser.SQLVal:\n\t\t\tnewValTuple = append(newValTuple, v)\n\t\tcase *sqlparser.BoolVal:\n\t\t\tnewValTuple = append(newValTuple, v)\n\t\tcase *sqlparser.NullVal:\n\t\t\tnewValTuple = append(newValTuple, v)\n\t\tcase sqlparser.ValTuple:\n\t\t\tnewValTuple = append(newValTuple, v...)\n\t\t}\n\t}\n\n\t// 去 expr 中除重复的 value,\n\tnewValTuple = removeDup(newValTuple...)\n\tnewExpr := &sqlparser.ComparisonExpr{\n\t\tOperator: \"in\",\n\t\tLeft:     colName,\n\t\tRight:    newValTuple,\n\t}\n\t// 如果只有一个值则是一个等式，没有必要转写成 in\n\tif len(newValTuple) == 1 {\n\t\tnewExpr = &sqlparser.ComparisonExpr{\n\t\t\tOperator: lOperator,\n\t\t\tLeft:     colName,\n\t\t\tRight:    newValTuple[0],\n\t\t}\n\t}\n\n\treturn newExpr\n}\n\n// removeDup 清除 sqlparser.ValTuple 中重复的值\nfunc removeDup(vt ...sqlparser.Expr) sqlparser.ValTuple {\n\tuni := make(sqlparser.ValTuple, 0)\n\tm := make(map[string]sqlparser.SQLNode)\n\n\tfor _, value := range vt {\n\t\tswitch v := value.(type) {\n\t\tcase *sqlparser.SQLVal:\n\t\t\t// Type:Val, 冒号用于分隔 Type 和 Val，防止两种不同类型拼接后出现同一个值\n\t\t\tif _, ok := m[fmt.Sprint(v.Type)+\":\"+sqlparser.String(v)]; !ok {\n\t\t\t\tuni = append(uni, v)\n\t\t\t\tm[fmt.Sprint(v.Type)+\":\"+sqlparser.String(v)] = v\n\t\t\t}\n\t\tcase *sqlparser.BoolVal:\n\t\t\tif _, ok := m[sqlparser.String(v)]; !ok {\n\t\t\t\tuni = append(uni, v)\n\t\t\t\tm[sqlparser.String(v)] = v\n\t\t\t}\n\t\tcase *sqlparser.NullVal:\n\t\t\tif _, ok := m[sqlparser.String(v)]; !ok {\n\t\t\t\tuni = append(uni, v)\n\t\t\t\tm[sqlparser.String(v)] = v\n\t\t\t}\n\t\tcase sqlparser.ValTuple:\n\t\t\tfor _, val := range removeDup(v...) {\n\t\t\t\tswitch v := val.(type) {\n\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\tif _, ok := m[fmt.Sprint(v.Type)+\":\"+sqlparser.String(v)]; !ok {\n\t\t\t\t\t\tuni = append(uni, v)\n\t\t\t\t\t\tm[fmt.Sprint(v.Type)+\":\"+sqlparser.String(v)] = v\n\t\t\t\t\t}\n\t\t\t\tcase *sqlparser.BoolVal:\n\t\t\t\t\tif _, ok := m[sqlparser.String(v)]; !ok {\n\t\t\t\t\t\tuni = append(uni, v)\n\t\t\t\t\t\tm[sqlparser.String(v)] = v\n\t\t\t\t\t}\n\t\t\t\tcase *sqlparser.NullVal:\n\t\t\t\t\tif _, ok := m[sqlparser.String(v)]; !ok {\n\t\t\t\t\t\tuni = append(uni, v)\n\t\t\t\t\t\tm[sqlparser.String(v)] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn uni\n}\n\n// RewriteInNull innull: TODO: 对应ARG.004\nfunc (rw *Rewrite) RewriteInNull() *Rewrite {\n\treturn rw\n}\n\n// RewriteRmParenthesis rmparenthesis: 去除无意义的括号\nfunc (rw *Rewrite) RewriteRmParenthesis() *Rewrite {\n\trw.rmParenthesis()\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// rmParenthesis 用于语出无用的括号\nfunc (rw *Rewrite) rmParenthesis() {\n\tcontinueFlag := false\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch node := node.(type) {\n\t\tcase *sqlparser.Where:\n\t\t\tif node == nil {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\tswitch paren := node.Expr.(type) {\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tswitch paren.Expr.(type) {\n\t\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\t\tnode.Expr = paren.Expr\n\t\t\t\t\tcontinueFlag = true\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase *sqlparser.ParenExpr:\n\t\t\tswitch paren := node.Expr.(type) {\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tswitch paren.Expr.(type) {\n\t\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\t\tnode.Expr = paren.Expr\n\t\t\t\t\tcontinueFlag = true\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase *sqlparser.AndExpr:\n\t\t\tswitch left := node.Left.(type) {\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tswitch inner := left.Expr.(type) {\n\t\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\t\tnode.Left = inner\n\t\t\t\t\tcontinueFlag = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch right := node.Right.(type) {\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tswitch inner := right.Expr.(type) {\n\t\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\t\tnode.Right = inner\n\t\t\t\t\tcontinueFlag = true\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase *sqlparser.OrExpr:\n\t\t\tswitch left := node.Left.(type) {\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tswitch inner := left.Expr.(type) {\n\t\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\t\tnode.Left = inner\n\t\t\t\t\tcontinueFlag = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch right := node.Right.(type) {\n\t\t\tcase *sqlparser.ParenExpr:\n\t\t\t\tswitch inner := right.Expr.(type) {\n\t\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\t\tnode.Right = inner\n\t\t\t\t\tcontinueFlag = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, rw.Stmt)\n\tcommon.LogIfError(err, \"\")\n\t// 本层的修改可能使得原本不符合条件的括号变为无意义括号\n\t// 每次修改都需要再过滤一遍语法树\n\tif continueFlag {\n\t\trw.rmParenthesis()\n\t} else {\n\t\treturn\n\t}\n}\n\n// RewriteRemoveDMLOrderBy dmlorderby: 对应 RES.004，删除无 LIMIT 条件时 UPDATE, DELETE 中包含的 ORDER BY\nfunc (rw *Rewrite) RewriteRemoveDMLOrderBy() *Rewrite {\n\tswitch st := rw.Stmt.(type) {\n\tcase *sqlparser.Update:\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch n := node.(type) {\n\t\t\tcase *sqlparser.Select:\n\t\t\t\tif n.OrderBy != nil && n.Limit == nil {\n\t\t\t\t\tn.OrderBy = nil\n\t\t\t\t}\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, rw.Stmt)\n\t\tcommon.LogIfError(err, \"\")\n\t\tif st.OrderBy != nil && st.Limit == nil {\n\t\t\tst.OrderBy = nil\n\t\t}\n\tcase *sqlparser.Delete:\n\t\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\tswitch n := node.(type) {\n\t\t\tcase *sqlparser.Select:\n\t\t\t\tif n.OrderBy != nil && n.Limit == nil {\n\t\t\t\t\tn.OrderBy = nil\n\t\t\t\t}\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}, rw.Stmt)\n\t\tcommon.LogIfError(err, \"\")\n\t\tif st.OrderBy != nil && st.Limit == nil {\n\t\t\tst.OrderBy = nil\n\t\t}\n\t}\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteGroupByConst 对应CLA.004，将GROUP BY CONST替换为列名\n// TODO:\nfunc (rw *Rewrite) RewriteGroupByConst() *Rewrite {\n\terr := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch n := node.(type) {\n\t\tcase *sqlparser.Select:\n\t\t\tgroupByCol := false\n\t\t\tif n.GroupBy != nil {\n\t\t\t\tfor _, group := range n.GroupBy {\n\t\t\t\t\tswitch group.(type) {\n\t\t\t\t\tcase *sqlparser.SQLVal:\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tgroupByCol = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !groupByCol {\n\t\t\t\t\t// TODO: 这里只是去掉了GROUP BY并没解决问题\n\t\t\t\t\tn.GroupBy = nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, rw.Stmt)\n\tcommon.LogIfError(err, \"\")\n\trw.NewSQL = sqlparser.String(rw.Stmt)\n\treturn rw\n}\n\n// RewriteSubQuery2Join 将 subquery 转写成 join\nfunc (rw *Rewrite) RewriteSubQuery2Join() *Rewrite {\n\tvar err error\n\t// 如果未配置 mysql 环境或从环境中获取失败\n\tif common.Config.TestDSN.Disable || len(rw.Columns) == 0 {\n\t\tcommon.Log.Debug(\"(rw *Rewrite) RewriteSubQuery2Join(): Rewrite failed. TestDSN.Disable: %v, len(rw.Columns):%d\",\n\t\t\tcommon.Config.TestDSN.Disable, len(rw.Columns))\n\t\treturn rw\n\t}\n\n\tif rw.NewSQL == \"\" {\n\t\trw.NewSQL = sqlparser.String(rw.Stmt)\n\t}\n\n\t// query backup\n\tbackup := rw.NewSQL\n\tvar subQueryList []string\n\terr = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch sub := node.(type) {\n\t\tcase sqlparser.SelectStatement:\n\t\t\tsubStr := sqlparser.String(sub)\n\t\t\tif strings.HasPrefix(subStr, \"(\") {\n\t\t\t\tsubStr = subStr[1 : len(subStr)-1]\n\t\t\t}\n\t\t\tsubQueryList = append(subQueryList, subStr)\n\t\t}\n\t\treturn true, nil\n\t}, rw.Stmt)\n\tcommon.LogIfError(err, \"\")\n\tif length := len(subQueryList); length > 1 {\n\t\tlastResult := \"\"\n\t\tfor i := length - 1; i > 0; i-- {\n\t\t\tif lastResult == \"\" {\n\t\t\t\tlastResult, err = rw.sub2Join(subQueryList[i-1], subQueryList[i])\n\t\t\t} else {\n\t\t\t\t// 将subquery的部分替换成上次合并的结果\n\t\t\t\tsubQueryList[i-1] = strings.Replace(subQueryList[i-1], subQueryList[i], lastResult, -1)\n\t\t\t\tlastResult, err = rw.sub2Join(subQueryList[i-1], lastResult)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tcommon.Log.Error(\"RewriteSubQuery2Join Error: %v\", err)\n\t\t\t\treturn rw\n\t\t\t}\n\t\t}\n\t\trw.NewSQL = lastResult\n\t} else if length == 1 {\n\t\tvar newSQL string\n\t\tnewSQL, err = rw.sub2Join(rw.NewSQL, subQueryList[0])\n\t\tif err == nil {\n\t\t\trw.NewSQL = newSQL\n\t\t}\n\t}\n\n\t// 因为这个修改不会直接修改rw.stmt，所以需要将rw.stmt也更新一下\n\tnewStmt, err := sqlparser.Parse(rw.NewSQL)\n\tif err != nil {\n\t\trw.NewSQL = backup\n\t\trw.Stmt, _ = sqlparser.Parse(backup)\n\t} else {\n\t\trw.Stmt = newStmt\n\t}\n\n\treturn rw\n}\n\n// sub2Join 将 subquery 转写成 join\nfunc (rw *Rewrite) sub2Join(parent, sub string) (string, error) {\n\t// 只处理SelectStatement\n\tif sqlparser.Preview(parent) != sqlparser.StmtSelect || sqlparser.Preview(sub) != sqlparser.StmtSelect {\n\t\treturn \"\", nil\n\t}\n\n\t// 如果子查询不属于parent,则不处理\n\tif !strings.Contains(parent, sub) {\n\t\treturn \"\", nil\n\t}\n\n\t// 解析外层SQL语法树\n\tstmt, err := sqlparser.Parse(parent)\n\tif err != nil {\n\t\tcommon.Log.Warn(\"(rw *Rewrite) RewriteSubQuery2Join() sub2Join sql `%s` parsed error: %v\", parent, err)\n\t\treturn \"\", err\n\t}\n\n\tswitch stmt.(type) {\n\tcase sqlparser.SelectStatement:\n\tdefault:\n\t\tcommon.Log.Debug(\"Query `%s` not select statement.\", parent)\n\t\treturn \"\", nil\n\t}\n\n\t// 解析子查询语法树\n\tsubStmt, err := sqlparser.Parse(sub)\n\tif err != nil {\n\t\tcommon.Log.Warn(\"(rw *Rewrite) RewriteSubQuery2Join() sub2Join sql `%s` parsed error: %v\", sub, err)\n\t\treturn \"\", err\n\t}\n\n\t// 获取外部SQL用到的表\n\tstmtMeta := GetTableFromExprs(stmt.(*sqlparser.Select).From)\n\t// 获取内部SQL用到的表\n\tsubMeta := GetTableFromExprs(subStmt.(*sqlparser.Select).From)\n\n\t// 处理关联条件\n\terr = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\tswitch p := node.(type) {\n\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t// a in (select * from tb)\n\t\t\tswitch subquery := p.Right.(type) {\n\t\t\tcase *sqlparser.Subquery:\n\n\t\t\t\t// 获取左边的列\n\t\t\t\tvar leftColumn *sqlparser.ColName\n\n\t\t\t\tswitch l := p.Left.(type) {\n\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\tleftColumn = l\n\t\t\t\tdefault:\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\n\t\t\t\t// 用于存放获取的subquery中的列，有且只有一个\n\t\t\t\tvar rightColumn sqlparser.SQLNode\n\n\t\t\t\t// 对subquery中的列进行替换\n\t\t\t\tswitch subSelectStmt := subquery.Select.(type) {\n\t\t\t\tcase *sqlparser.Select:\n\t\t\t\t\tcachingOperator := p.Operator\n\n\t\t\t\t\trightColumn = subSelectStmt.SelectExprs[0]\n\n\t\t\t\t\trightCol, _ := getColumnName(rightColumn.(*sqlparser.AliasedExpr).Expr)\n\t\t\t\t\tif rightCol != nil {\n\t\t\t\t\t\t// 将subquery替换为等值条件\n\t\t\t\t\t\tp.Operator = \"=\"\n\n\t\t\t\t\t\t// selectExpr 信息补齐\n\t\t\t\t\t\tvar newExprs []sqlparser.SelectExpr\n\t\t\t\t\t\terr = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\t\t\t\t\tswitch col := node.(type) {\n\t\t\t\t\t\t\tcase *sqlparser.StarExpr:\n\t\t\t\t\t\t\t\tif col.TableName.Name.IsEmpty() {\n\t\t\t\t\t\t\t\t\tfor dbName, db := range stmtMeta {\n\t\t\t\t\t\t\t\t\t\tfor tbName := range db.Table {\n\n\t\t\t\t\t\t\t\t\t\t\tcol.TableName.Name = sqlparser.NewTableIdent(tbName)\n\t\t\t\t\t\t\t\t\t\t\tif dbName != \"\" {\n\t\t\t\t\t\t\t\t\t\t\t\tcol.TableName.Qualifier = sqlparser.NewTableIdent(dbName)\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tnewExprs = append(newExprs, col)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase *sqlparser.AliasedExpr:\n\t\t\t\t\t\t\t\tswitch n := col.Expr.(type) {\n\t\t\t\t\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\t\t\t\t\tcol.Expr = columnFromWhere(n, stmtMeta, rw.Columns)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true, nil\n\t\t\t\t\t\t}, stmt.(*sqlparser.Select).SelectExprs)\n\t\t\t\t\t\tcommon.LogIfError(err, \"\")\n\n\t\t\t\t\t\t// 原节点列信息补齐\n\t\t\t\t\t\tp.Left = columnFromWhere(leftColumn, stmtMeta, rw.Columns)\n\n\t\t\t\t\t\t// 将子查询中的节点上提，补充前缀信息\n\t\t\t\t\t\tp.Right = columnFromWhere(rightCol, subMeta, rw.Columns)\n\n\t\t\t\t\t\t// subquery Where条件中的列信息补齐\n\t\t\t\t\t\tsubWhereExpr := subStmt.(*sqlparser.Select).Where\n\t\t\t\t\t\terr = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {\n\t\t\t\t\t\t\tswitch n := node.(type) {\n\t\t\t\t\t\t\tcase *sqlparser.ComparisonExpr:\n\t\t\t\t\t\t\t\tswitch left := n.Left.(type) {\n\t\t\t\t\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\t\t\t\t\tn.Left = columnFromWhere(left, subMeta, rw.Columns)\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tswitch right := n.Right.(type) {\n\t\t\t\t\t\t\t\tcase *sqlparser.ColName:\n\t\t\t\t\t\t\t\t\tn.Right = columnFromWhere(right, subMeta, rw.Columns)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true, nil\n\t\t\t\t\t\t}, subWhereExpr)\n\t\t\t\t\t\tcommon.LogIfError(err, \"\")\n\t\t\t\t\t\t// 如果 subquery 中存在 Where 条件，怼在 parent 的 where 中后面\n\t\t\t\t\t\tif subWhereExpr != nil {\n\t\t\t\t\t\t\tif stmt.(*sqlparser.Select).Where != nil {\n\t\t\t\t\t\t\t\tstmt.(*sqlparser.Select).Where.Expr = &sqlparser.AndExpr{\n\t\t\t\t\t\t\t\t\tLeft:  stmt.(*sqlparser.Select).Where.Expr,\n\t\t\t\t\t\t\t\t\tRight: subWhereExpr.Expr,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tstmt.(*sqlparser.Select).Where = subWhereExpr\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tswitch cachingOperator {\n\t\t\t\t\t\tcase \"in\":\n\t\t\t\t\t\t\t// 将表以 inner join 的形式追加到 parent 的 from 中\n\t\t\t\t\t\t\tvar newTables []sqlparser.TableExpr\n\t\t\t\t\t\t\tfor _, subExpr := range subStmt.(*sqlparser.Select).From {\n\t\t\t\t\t\t\t\thas := false\n\t\t\t\t\t\t\t\tfor _, expr := range stmt.(*sqlparser.Select).From {\n\t\t\t\t\t\t\t\t\tif reflect.DeepEqual(expr, subExpr) {\n\t\t\t\t\t\t\t\t\t\thas = true\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !has {\n\t\t\t\t\t\t\t\t\tnewTables = append(newTables, subExpr)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tstmt.(*sqlparser.Select).From = append(stmt.(*sqlparser.Select).From, newTables...)\n\t\t\t\t\t\tcase \"not in\":\n\t\t\t\t\t\t\t// 将表以left join 的形式 追加到 parent 的 from 中\n\t\t\t\t\t\t\t// TODO:\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\t}, stmt)\n\tcommon.LogIfError(err, \"\")\n\tnewSQL := sqlparser.String(stmt)\n\treturn newSQL, nil\n}\n\n// columnFromWhere 获取列是来自哪个表，并补充前缀\nfunc columnFromWhere(col *sqlparser.ColName, meta common.Meta, columns common.TableColumns) *sqlparser.ColName {\n\n\tfor dbName, db := range meta {\n\t\tfor tbName := range db.Table {\n\t\t\tfor _, tables := range columns {\n\t\t\t\tfor _, columns := range tables {\n\t\t\t\t\tfor _, column := range columns {\n\t\t\t\t\t\tif strings.EqualFold(col.Name.String(), column.Name) {\n\t\t\t\t\t\t\tif col.Qualifier.Name.IsEmpty() && tbName == column.Table {\n\t\t\t\t\t\t\t\tcol.Qualifier.Name = sqlparser.NewTableIdent(column.Table)\n\t\t\t\t\t\t\t\treturn col\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (dbName == \"\" && tbName == column.Table) || (tbName == column.Table && dbName == column.DB) {\n\t\t\t\t\t\t\t\tcol.Qualifier.Name = sqlparser.NewTableIdent(column.Table)\n\t\t\t\t\t\t\t\tif dbName != \"\" {\n\t\t\t\t\t\t\t\t\tcol.Qualifier.Qualifier = sqlparser.NewTableIdent(column.DB)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn col\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n\treturn col\n}\n\n// RewriteJoin2SubQuery join2sub: TODO:\n// https://mariadb.com/kb/en/library/subqueries-and-joins/\nfunc (rw *Rewrite) RewriteJoin2SubQuery() *Rewrite {\n\treturn rw\n}\n\n// RewriteDistinctStar distinctstar: 对应DIS.003，将多余的`DISTINCT *`删除\nfunc (rw *Rewrite) RewriteDistinctStar() *Rewrite {\n\t// 注意：这里并未对表是否有主键做检查，按照我们的SQL编程规范，一张表必须有主键\n\tswitch rw.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\tmeta := GetMeta(rw.Stmt, nil)\n\t\tfor _, m := range meta {\n\t\t\tif len(m.Table) == 1 {\n\t\t\t\t// distinct tbl.*, distinct *, count(distinct *)\n\t\t\t\tre := regexp.MustCompile(`(?i)((distinct\\s*\\*)|(distinct\\s+[0-9a-z_` + \"`\" + `]*\\.\\*))`)\n\t\t\t\tif re.MatchString(rw.SQL) {\n\t\t\t\t\trw.NewSQL = re.ReplaceAllString(rw.SQL, \"*\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif rw.NewSQL == \"\" {\n\t\trw.NewSQL = rw.SQL\n\t}\n\trw.Stmt, _ = sqlparser.Parse(rw.NewSQL)\n\treturn rw\n}\n\n// RewriteTruncate truncate: DELETE 全表修改为 TRUNCATE TABLE\nfunc (rw *Rewrite) RewriteTruncate() *Rewrite {\n\tswitch n := rw.Stmt.(type) {\n\tcase *sqlparser.Delete:\n\t\tmeta := GetMeta(rw.Stmt, nil)\n\t\tif len(meta) == 1 && n.Where == nil {\n\t\t\tfor _, db := range meta {\n\t\t\t\tfor _, tbl := range db.Table {\n\t\t\t\t\trw.NewSQL = \"truncate table \" + tbl.TableName\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn rw\n}\n\n// RewriteDML2Select dml2select: DML 转成 SELECT，兼容低版本的 EXPLAIN\nfunc (rw *Rewrite) RewriteDML2Select() *Rewrite {\n\tif rw.Stmt == nil {\n\t\treturn rw\n\t}\n\n\tswitch stmt := rw.Stmt.(type) {\n\tcase *sqlparser.Select:\n\t\trw.NewSQL = rw.SQL\n\tcase *sqlparser.Delete: // Multi DELETE not support yet.\n\t\trw.NewSQL = delete2Select(stmt)\n\tcase *sqlparser.Insert:\n\t\trw.NewSQL = insert2Select(stmt)\n\tcase *sqlparser.Update: // Multi UPDATE not support yet.\n\t\trw.NewSQL = update2Select(stmt)\n\t}\n\trw.Stmt, _ = sqlparser.Parse(rw.NewSQL)\n\treturn rw\n}\n\nfunc (rw *Rewrite) RewriteReg2Select() *Rewrite {\n\tvar pre = 9\n\tif len(rw.SQL) < pre {\n\t\t// SQL to short no need convert\n\t\treturn rw\n\t}\n\tif strings.HasPrefix(strings.ToLower(rw.SQL[:pre]), \"select\") {\n\t\trw.NewSQL = rw.SQL\n\t}\n\tif strings.HasPrefix(strings.ToLower(rw.SQL[:pre]), \"update\") {\n\t\trw.NewSQL = regUpdate2Select(rw.SQL)\n\t}\n\tif strings.HasPrefix(strings.ToLower(rw.SQL[:pre]), \"delete\") {\n\t\trw.NewSQL = regDelete2Select(rw.SQL)\n\t}\n\treturn rw\n}\n\n// delete2Select 将 Delete 语句改写成 Select\nfunc delete2Select(stmt *sqlparser.Delete) string {\n\tnewSQL := &sqlparser.Select{\n\t\tSelectExprs: []sqlparser.SelectExpr{\n\t\t\tnew(sqlparser.StarExpr),\n\t\t},\n\t\tFrom:    stmt.TableExprs,\n\t\tWhere:   stmt.Where,\n\t\tOrderBy: stmt.OrderBy,\n\t}\n\treturn sqlparser.String(newSQL)\n}\n\n// regDelete2Select convert delete to select by regexp\nfunc regDelete2Select(sql string) string {\n\tsql = strings.TrimSpace(sql)\n\tsqlRegexp := regexp.MustCompile(`^(?i)delete\\s+from\\s+(.*)$`)\n\tparams := sqlRegexp.FindStringSubmatch(sql)\n\tif len(params) > 1 {\n\t\treturn fmt.Sprintf(`select * from %s`, params[1])\n\t}\n\treturn sql\n}\n\n// update2Select 将 Update 语句改写成 Select\nfunc update2Select(stmt *sqlparser.Update) string {\n\tnewSQL := &sqlparser.Select{\n\t\tSelectExprs: []sqlparser.SelectExpr{\n\t\t\tnew(sqlparser.StarExpr),\n\t\t},\n\t\tFrom:    stmt.TableExprs,\n\t\tWhere:   stmt.Where,\n\t\tOrderBy: stmt.OrderBy,\n\t\tLimit:   stmt.Limit,\n\t}\n\treturn sqlparser.String(newSQL)\n}\n\n// regUpdate2Select convert update to select by regexp\nfunc regUpdate2Select(sql string) string {\n\tsql = strings.TrimSpace(sql)\n\tsqlRegexp := regexp.MustCompile(`^(?i)update\\s+(.*)\\s+set\\s+(.*)\\s+(where\\s+.*)$`)\n\tparams := sqlRegexp.FindStringSubmatch(sql)\n\tif len(params) > 2 {\n\t\treturn fmt.Sprintf(`select * from %s %s`, params[1], params[3])\n\t}\n\treturn sql\n}\n\n// insert2Select 将 Insert 语句改写成 Select\nfunc insert2Select(stmt *sqlparser.Insert) string {\n\tswitch row := stmt.Rows.(type) {\n\t// 如果insert包含子查询，只需要explain该子树\n\tcase *sqlparser.Select, *sqlparser.Union, *sqlparser.ParenSelect:\n\t\treturn sqlparser.String(row)\n\t}\n\n\treturn \"select 1 from DUAL\"\n}\n\n// AlterAffectTable 获取ALTER影响的库表名，返回：`db`.`table`\nfunc AlterAffectTable(stmt sqlparser.Statement) string {\n\tswitch n := stmt.(type) {\n\tcase *sqlparser.DDL:\n\t\ttableName := strings.ToLower(n.Table.Name.String())\n\t\tdbName := strings.ToLower(n.Table.Qualifier.String())\n\t\tif tableName != \"\" && tableName != \"dual\" {\n\t\t\tif dbName == \"\" {\n\t\t\t\treturn \"`\" + tableName + \"`\"\n\t\t\t}\n\n\t\t\treturn \"`\" + dbName + \"`.`\" + tableName + \"`\"\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// MergeAlterTables mergealter: 将同一张表的多条 ALTER 语句合成一条 ALTER 语句\n// @input: sql, alter string\n// @output: [[db.]table]sql, 如果找不到 DB，key 为表名；如果找得到 DB，key 为 db.table\nfunc MergeAlterTables(sqls ...string) map[string]string {\n\talterSQLs := make(map[string][]string)\n\tmergedAlterStr := make(map[string]string)\n\n\t// table/column/index name can be quoted in back ticks\n\tbackTicks := \"(`[^\\\\s]*`)\"\n\n\talterExp := regexp.MustCompile(`(?i)alter\\s*table\\s*(` + backTicks + `|([^\\s]*))\\s*`)   // ALTER TABLE\n\trenameExp := regexp.MustCompile(`(?i)rename\\s*table\\s*(` + backTicks + `|([^\\s]*))\\s*`) // RENAME TABLE\n\t// CREATE [UNIQUE|FULLTEXT|SPATIAL|PRIMARY] [KEY|INDEX] idx_name ON tbl_name\n\tcreateIndexExp := regexp.MustCompile(`(?i)create((unique)|(fulltext)|(spatial)|(primary)|(\\s*)\\s*)((index)|(key))\\s*`)\n\tindexNameExp := regexp.MustCompile(`(?i)(` + backTicks + `|([^\\s]*))\\s*`)\n\n\tfor _, sql := range sqls {\n\t\tsql = strings.Trim(sql, common.Config.Delimiter)\n\t\tstmts, err := TiParse(sql, \"\", \"\")\n\t\tif err != nil {\n\t\t\tcommon.Log.Warn(err.Error())\n\t\t\tcontinue\n\t\t}\n\t\t// stmt, _ := sqlparser.Parse(sql)\n\t\talterSQL := \"\"\n\t\tdbName := \"\"\n\t\ttableName := \"\"\n\t\tfor _, stmt := range stmts {\n\t\t\tswitch n := stmt.(type) {\n\t\t\tcase *ast.AlterTableStmt:\n\t\t\t\t// 注意: 表名和库名不区分大小写\n\t\t\t\ttableName = n.Table.Name.L\n\t\t\t\tdbName = n.Table.Schema.L\n\t\t\t\tif alterExp.MatchString(sql) {\n\t\t\t\t\tcommon.Log.Debug(\"rename alterExp: ALTER %v %v\", tableName, alterExp.ReplaceAllString(sql, \"\"))\n\t\t\t\t\talterSQL = fmt.Sprint(alterExp.ReplaceAllString(sql, \"\"))\n\t\t\t\t}\n\t\t\tcase *ast.CreateIndexStmt:\n\t\t\t\ttableName = n.Table.Name.L\n\t\t\t\tdbName = n.Table.Schema.L\n\n\t\t\t\tbuf := createIndexExp.ReplaceAllString(sql, \"\")\n\t\t\t\tidxName := strings.TrimSpace(indexNameExp.FindString(buf))\n\t\t\t\tbuf = string([]byte(buf)[strings.Index(buf, \"(\"):])\n\t\t\t\tcommon.Log.Error(buf)\n\t\t\t\tcommon.Log.Debug(\"alter createIndexExp: ALTER %v ADD INDEX %v %v\", tableName, \"ADD INDEX\", idxName, buf)\n\t\t\t\talterSQL = fmt.Sprint(\"ADD INDEX\", \" \"+idxName+\" \", buf)\n\t\t\tcase *ast.RenameTableStmt:\n\t\t\t\t// 注意: 表名和库名不区分大小写\n\t\t\t\ttableName = n.TableToTables[0].OldTable.Name.L\n\t\t\t\tdbName = n.TableToTables[0].OldTable.Schema.L\n\t\t\t\tif alterExp.MatchString(sql) {\n\t\t\t\t\tcommon.Log.Debug(\"rename alterExp: ALTER %v %v\", tableName, alterExp.ReplaceAllString(sql, \"\"))\n\t\t\t\t\talterSQL = fmt.Sprint(alterExp.ReplaceAllString(sql, \"\"))\n\t\t\t\t} else if renameExp.MatchString(sql) {\n\t\t\t\t\tcommon.Log.Debug(\"rename renameExp: ALTER %v %v\", tableName, alterExp.ReplaceAllString(sql, \"\"))\n\t\t\t\t\talterSQL = fmt.Sprint(alterExp.ReplaceAllString(sql, \"\"))\n\t\t\t\t} else {\n\t\t\t\t\tcommon.Log.Warn(\"rename not match: ALTER %v %v\", tableName, sql)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\n\t\tif alterSQL != \"\" && tableName != \"\" && tableName != \"dual\" {\n\t\t\tif dbName == \"\" {\n\t\t\t\talterSQLs[\"`\"+tableName+\"`\"] = append(alterSQLs[\"`\"+tableName+\"`\"], alterSQL)\n\t\t\t} else {\n\t\t\t\talterSQLs[\"`\"+dbName+\"`.`\"+tableName+\"`\"] = append(alterSQLs[\"`\"+dbName+\"`.`\"+tableName+\"`\"], alterSQL)\n\t\t\t}\n\t\t}\n\t}\n\tfor k, v := range alterSQLs {\n\t\tmergedAlterStr[k] = fmt.Sprintln(\"ALTER TABLE\", k, strings.Join(v, \", \"), common.Config.Delimiter)\n\t}\n\treturn mergedAlterStr\n}\n\n// RewriteRuleMatch 检查重写规则是否生效\nfunc RewriteRuleMatch(name string) bool {\n\tfor _, r := range common.Config.RewriteRules {\n\t\tif r == name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "ast/rewrite_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\nfunc TestRewrite(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgTestDSNStatus := common.Config.TestDSN.Disable\n\tcommon.Config.TestDSN.Disable = false\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `SELECT * FROM film`,\n\t\t\t\"output\": `select film_id, title, description, release_year, language_id, original_language_id, rental_duration from film;`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT film.*, actor.actor_id FROM film,actor`,\n\t\t\t\"output\": `select film_id, title, description, release_year, language_id, original_language_id, rental_duration, actor.actor_id from film, actor;`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `insert into film values(1,2,3,4,5)`,\n\t\t\t\"output\": `insert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5);`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `insert into sakila.film values(1,2)`,\n\t\t\t\"output\": `insert into sakila.film(film_id, title) values (1, 2);`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `replace into sakila.film select id from tb`,\n\t\t\t\"output\": `replace into sakila.film(film_id) select id from tb;`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `replace into sakila.film select id, title, description from tb`,\n\t\t\t\"output\": `replace into sakila.film(film_id, title, description) select id, title, description from tb;`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `insert into film values(1,2,3,4,5)`,\n\t\t\t\"output\": `insert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5);`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `insert into sakila.film values(1,2)`,\n\t\t\t\"output\": `insert into sakila.film(film_id, title) values (1, 2);`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `replace into sakila.film select id from tb`,\n\t\t\t\"output\": `replace into sakila.film(film_id) select id from tb;`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `replace into sakila.film select id, title, description from tb`,\n\t\t\t\"output\": `replace into sakila.film(film_id, title, description) select id, title, description from tb;`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"DELETE FROM tbl WHERE col1=1 ORDER BY col\",\n\t\t\t\"output\": \"delete from tbl where col1 = 1;\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"UPDATE tbl SET col =1 WHERE col1=1 ORDER BY col\",\n\t\t\t\"output\": \"update tbl set col = 1 where col1 = 1;\",\n\t\t},\n\t}\n\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"])\n\t\trw.Columns = map[string]map[string][]*common.Column{\n\t\t\t\"sakila\": {\n\t\t\t\t\"film\": {\n\t\t\t\t\t{Name: \"film_id\", Table: \"film\"},\n\t\t\t\t\t{Name: \"title\", Table: \"film\"},\n\t\t\t\t\t{Name: \"description\", Table: \"film\"},\n\t\t\t\t\t{Name: \"release_year\", Table: \"film\"},\n\t\t\t\t\t{Name: \"language_id\", Table: \"film\"},\n\t\t\t\t\t{Name: \"original_language_id\", Table: \"film\"},\n\t\t\t\t\t{Name: \"rental_duration\", Table: \"film\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\trw.Rewrite()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Config.TestDSN.Disable = orgTestDSNStatus\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteStar2Columns(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgTestDSNStatus := common.Config.TestDSN.Disable\n\tcommon.Config.TestDSN.Disable = false\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `SELECT * FROM film`,\n\t\t\t\"output\": `select film_id, title from film`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT film.* FROM film`,\n\t\t\t\"output\": `select film_id, title from film`,\n\t\t},\n\t}\n\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"])\n\t\trw.Columns = map[string]map[string][]*common.Column{\n\t\t\t\"sakila\": {\n\t\t\t\t\"film\": {\n\t\t\t\t\t{Name: \"film_id\", Table: \"film\"},\n\t\t\t\t\t{Name: \"title\", Table: \"film\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\trw.RewriteStar2Columns()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\n\ttestSQL2 := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `SELECT film.* FROM film, actor`,\n\t\t\t\"output\": `select film.film_id, film.title from film, actor`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT film.*, actor.actor_id FROM film, actor`,\n\t\t\t\"output\": `select film.film_id, film.title, actor.actor_id from film, actor`,\n\t\t},\n\t}\n\n\tfor _, sql := range testSQL2 {\n\t\trw := NewRewrite(sql[\"input\"])\n\t\trw.Columns = map[string]map[string][]*common.Column{\n\t\t\t\"sakila\": {\n\t\t\t\t\"film\": {\n\t\t\t\t\t{Name: \"film_id\", Table: \"film\"},\n\t\t\t\t\t{Name: \"title\", Table: \"film\"},\n\t\t\t\t},\n\t\t\t\t\"actor\": {\n\t\t\t\t\t{Name: \"actor_id\", Table: \"actor\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\trw.RewriteStar2Columns()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Config.TestDSN.Disable = orgTestDSNStatus\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteInsertColumns(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `insert into film values(1,2,3,4,5)`,\n\t\t\t\"output\": `insert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5)`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `insert into sakila.film values(1,2)`,\n\t\t\t\"output\": `insert into sakila.film(film_id, title) values (1, 2)`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `replace into sakila.film select id from tb`,\n\t\t\t\"output\": `replace into sakila.film(film_id) select id from tb`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `replace into sakila.film select id, title, description from tb`,\n\t\t\t\"output\": `replace into sakila.film(film_id, title, description) select id, title, description from tb`,\n\t\t},\n\t}\n\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"])\n\t\trw.Columns = map[string]map[string][]*common.Column{\n\t\t\t\"sakila\": {\n\t\t\t\t\"film\": {\n\t\t\t\t\t{Name: \"film_id\", Table: \"film\"},\n\t\t\t\t\t{Name: \"title\", Table: \"film\"},\n\t\t\t\t\t{Name: \"description\", Table: \"film\"},\n\t\t\t\t\t{Name: \"release_year\", Table: \"film\"},\n\t\t\t\t\t{Name: \"language_id\", Table: \"film\"},\n\t\t\t\t\t{Name: \"original_language_id\", Table: \"film\"},\n\t\t\t\t\t{Name: \"rental_duration\", Table: \"film\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\trw.RewriteInsertColumns()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteHaving(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `SELECT state, COUNT(*) FROM Drivers GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state`,\n\t\t\t\"output\": \"select state, COUNT(*) from Drivers where state in ('GA', 'TX') group by state order by state asc\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT state, COUNT(*) FROM Drivers WHERE col =1 GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state`,\n\t\t\t\"output\": \"select state, COUNT(*) from Drivers where (col = 1) and state in ('GA', 'TX') group by state order by state asc\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT state, COUNT(*) FROM Drivers WHERE col =1 or col1 =2 GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state`,\n\t\t\t\"output\": \"select state, COUNT(*) from Drivers where (col = 1 or col1 = 2) and state in ('GA', 'TX') group by state order by state asc\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteHaving()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteAddOrderByNull(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"SELECT sum(col1) FROM tbl GROUP BY col\",\n\t\t\t\"output\": \"select sum(col1) from tbl group by col order by null\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteAddOrderByNull()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteRemoveDMLOrderBy(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"DELETE FROM tbl WHERE col1=1 ORDER BY col\",\n\t\t\t\"output\": \"delete from tbl where col1 = 1\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"UPDATE tbl SET col =1 WHERE col1=1 ORDER BY col\",\n\t\t\t\"output\": \"update tbl set col = 1 where col1 = 1\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteRemoveDMLOrderBy()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteGroupByConst(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"select 1;\",\n\t\t\t\"output\": \"select 1 from dual\",\n\t\t},\n\t\t/*\n\t\t\t\t{\n\t\t\t\t\t\"input\":  \"SELECT col1 FROM tbl GROUP BY 1;\",\n\t\t\t\t\t\"output\": \"select col1 from tbl GROUP BY col1\",\n\t\t\t\t},\n\t\t\t    {\n\t\t\t\t\t\"input\":  \"SELECT col1, col2 FROM tbl GROUP BY 1, 2;\",\n\t\t\t\t\t\"output\": \"select col1, col2 from tbl GROUP BY col1, col2\",\n\t\t\t\t},\n\t\t\t    {\n\t\t\t\t\t\"input\":  \"SELECT col1, col2, col3 FROM tbl GROUP BY 1, 3;\",\n\t\t\t\t\t\"output\": \"select col1, col2, col3 from tbl GROUP BY col1, col3\",\n\t\t\t\t},\n\t\t*/\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteGroupByConst()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteStandard(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"SELECT sum(col1) FROM tbl GROUP BY 1;\",\n\t\t\t\"output\": \"select sum(col1) from tbl group by 1\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteStandard()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteCountStar(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl GROUP BY 1;\",\n\t\t\t\"output\": \"select count(*) from tbl group by 1\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT COUNT(tb.col) FROM tbl GROUP BY 1;\",\n\t\t\t\"output\": \"select COUNT(tb.*) from tbl group by 1\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteCountStar()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteInnoDB(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT);\",\n\t\t\t\"output\": \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB \",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=memory \",\n\t\t\t\"output\": \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB \",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteInnoDB()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteAutoIncrement(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;\",\n\t\t\t\"output\": \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB auto_increment=1 \",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB\",\n\t\t\t\"output\": \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteAutoIncrement()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteIntWidth(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"CREATE TABLE t1(id bigint(10) NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;\",\n\t\t\t\"output\": \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB AUTO_INCREMENT=123802\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"CREATE TABLE t1(id bigint NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;\",\n\t\t\t\"output\": \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB AUTO_INCREMENT=123802\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"create table t1(id int(20) not null auto_increment) ENGINE=InnoDB;\",\n\t\t\t\"output\": \"create table t1 (\\n\\tid int(10) not null auto_increment\\n) ENGINE=InnoDB\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"create table t1(id int not null auto_increment) ENGINE=InnoDB;\",\n\t\t\t\"output\": \"create table t1 (\\n\\tid int not null auto_increment\\n) ENGINE=InnoDB\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteIntWidth()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteAlwaysTrue(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1=1;\",\n\t\t\t\"output\": \"select count(col) from tbl\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where col=col;\",\n\t\t\t\"output\": \"select count(col) from tbl where col = col\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where col=col2;\",\n\t\t\t\"output\": \"select count(col) from tbl where col = col2\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1>=1;\",\n\t\t\t\"output\": \"select count(col) from tbl\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 2>1;\",\n\t\t\t\"output\": \"select count(col) from tbl\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1<=1;\",\n\t\t\t\"output\": \"select count(col) from tbl\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1<2;\",\n\t\t\t\"output\": \"select count(col) from tbl\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1=1 and 2=2;\",\n\t\t\t\"output\": \"select count(col) from tbl\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1=1 or 2=3;\",\n\t\t\t\"output\": \"select count(col) from tbl where 2 = 3\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1=1 and 3=3 or 2=3;\",\n\t\t\t\"output\": \"select count(col) from tbl where 2 = 3\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1=1 and 3=3 or 2!=3;\",\n\t\t\t\"output\": \"select count(col) from tbl\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 1=1 or 2=3 and 3=3 ;\",\n\t\t\t\"output\": \"select count(col) from tbl where 2 = 3\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where (1=1);\",\n\t\t\t\"output\": \"select count(col) from tbl\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where a=1;\",\n\t\t\t\"output\": \"select count(col) from tbl where a = 1\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where ('a'= 'a' or 'b' = 'b') and a = 'b';\",\n\t\t\t\"output\": \"select count(col) from tbl where a = 'b'\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where (('a'= 'a' or 'b' = 'b') and a = 'b');\",\n\t\t\t\"output\": \"select count(col) from tbl where (a = 'b')\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT count(col) FROM tbl where 'a'= 'a' or ('b' = 'b' and a = 'b');\",\n\t\t\t\"output\": \"select count(col) from tbl where (a = 'b')\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteAlwaysTrue()\n\t\tif rw == nil {\n\t\t\tt.Errorf(\"NoRw\")\n\t\t} else if rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\n// TODO:\nfunc TestRewriteSubQuery2Join(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgTestDSNStatus := common.Config.TestDSN.Disable\n\tcommon.Config.TestDSN.Disable = true\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t// 这个case是官方文档给的，但不一定正确，需要视表结构的定义来进行判断\n\t\t\t\"input\":  `SELECT * FROM t1 WHERE id IN (SELECT id FROM t2);`,\n\t\t\t\"output\": \"\",\n\t\t\t//\"output\": `SELECT DISTINCT t1.* FROM t1, t2 WHERE t1.id=t2.id;`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT * FROM t1 WHERE id NOT IN (SELECT id FROM t2);`,\n\t\t\t\"output\": \"\",\n\t\t\t//\"output\": `SELECT table1.* FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;`,\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT * FROM t1 WHERE NOT EXISTS (SELECT id FROM t2 WHERE t1.id=t2.id);`,\n\t\t\t\"output\": \"\",\n\t\t\t//\"output\": `SELECT table1.* FROM table1 LEFT JOIN table2 ON table1.id=table2.id WHERE table2.id IS NULL;`,\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteSubQuery2Join()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Config.TestDSN.Disable = orgTestDSNStatus\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteDML2Select(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"DELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\",\n\t\t\t\"output\": \"select * from city join country using (country_id) where city.city_id = 1\",\n\t\t}, {\n\t\t\t\"input\":  \"DELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\",\n\t\t\t\"output\": \"select * from city left join country on city.country_id = country.country_id where country.country is null\",\n\t\t}, {\n\t\t\t\"input\":  \"DELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id\",\n\t\t\t\"output\": \"select * from city as a1 join country as a2 where a1.country_id = a2.country_id\",\n\t\t}, {\n\t\t\t\"input\":  \"DELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id\",\n\t\t\t\"output\": \"select * from city as a1 join country as a2 where a1.country_id = a2.country_id\",\n\t\t}, {\n\t\t\t\"input\":  \"DELETE FROM film WHERE length > 100;\",\n\t\t\t\"output\": \"select * from film where length > 100\",\n\t\t}, {\n\t\t\t\"input\":  \"UPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\",\n\t\t\t\"output\": \"select * from city join country using (country_id) where city.city_id = 10\",\n\t\t}, {\n\t\t\t\"input\":  \"UPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\",\n\t\t\t\"output\": \"select * from city join country on city.country_id = country.country_id join address on city.city_id = address.city_id where city.city_id = 10\",\n\t\t}, {\n\t\t\t\"input\":  \"UPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\",\n\t\t\t\"output\": \"select * from city, country where city.country_id = country.country_id and city.city_id = 10\",\n\t\t}, {\n\t\t\t\"input\":  \"UPDATE film SET length = 10 WHERE language_id = 20;\",\n\t\t\t\"output\": \"select * from film where language_id = 20\",\n\t\t}, {\n\t\t\t\"input\":  \"INSERT INTO city (country_id) SELECT country_id FROM country;\",\n\t\t\t\"output\": \"select country_id from country\",\n\t\t}, {\n\t\t\t\"input\":  \"INSERT INTO city (country_id) VALUES (1),(2),(3);\",\n\t\t\t\"output\": \"select 1 from DUAL\",\n\t\t}, {\n\t\t\t\"input\":  \"INSERT INTO city (country_id) VALUES (10);\",\n\t\t\t\"output\": \"select 1 from DUAL\",\n\t\t}, {\n\t\t\t\"input\":  \"INSERT INTO city (country_id) SELECT 10 FROM DUAL;\",\n\t\t\t\"output\": \"select 10 from dual\",\n\t\t}, {\n\t\t\t\"input\":  \"replace INTO city (country_id) SELECT 10 FROM DUAL;\",\n\t\t\t\"output\": \"select 10 from dual\",\n\t\t},\n\t}\n\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteDML2Select()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteReg2Select(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  \"select 1 from dual\",\n\t\t\t\"output\": \"select 1 from dual\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"delete from dual\",\n\t\t\t\"output\": \"select * from dual\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"update tb set col = 1 where col = 2\",\n\t\t\t\"output\": \"select * from tb where col = 2\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteReg2Select()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteDistinctStar(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `SELECT DISTINCT * FROM film;`,\n\t\t\t\"output\": \"SELECT * FROM film;\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT COUNT(DISTINCT *) FROM film;`,\n\t\t\t\"output\": \"SELECT COUNT(*) FROM film;\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `SELECT DISTINCT film.* FROM film;`,\n\t\t\t\"output\": \"SELECT * FROM film;\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT DISTINCT col FROM film;\",\n\t\t\t\"output\": \"SELECT DISTINCT col FROM film;\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  \"SELECT DISTINCT film.* FROM film, tbl;\",\n\t\t\t\"output\": \"SELECT DISTINCT film.* FROM film, tbl;\",\n\t\t},\n\t\t{\n\n\t\t\t\"input\":  \"SELECT DISTINCT * FROM film, tbl;\",\n\t\t\t\"output\": \"SELECT DISTINCT * FROM film, tbl;\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteDistinctStar()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestMergeAlterTables(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t// ADD|DROP INDEX\n\t\t// TODO: PRIMARY KEY, [UNIQUE|FULLTEXT|SPATIAL] INDEX\n\t\t\"CREATE INDEX part_of_name ON customer (name(10));\",\n\t\t\"create index idx_test_cca on test_bb(test_cc);\", // https://github.com/XiaoMi/soar/issues/205\n\t\t\"alter table `sakila`.`t1` add index `idx_col`(`col`)\",\n\t\t\"alter table `sakila`.`t1` add UNIQUE index `idx_col`(`col`)\",\n\t\t\"alter table `sakila`.`t1` add index `idx_ID`(`ID`)\",\n\n\t\t// ADD|DROP COLUMN\n\t\t\"ALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;\",\n\t\t\"ALTER TABLE T2 ADD COLUMN C int;\",\n\t\t\"ALTER TABLE T2 ADD COLUMN D int FIRST;\",\n\t\t\"ALTER TABLE T2 ADD COLUMN E int AFTER D;\",\n\n\t\t// RENAME COLUMN\n\t\t\"ALTER TABLE t1 RENAME COLUMN a TO b\",\n\n\t\t// RENAME INDEX\n\t\t\"ALTER TABLE t1 RENAME INDEX idx_a TO idx_b\",\n\t\t\"ALTER TABLE t1 RENAME KEY idx_a TO idx_b\",\n\n\t\t// RENAME TABLE\n\t\t\"ALTER TABLE db.old_table RENAME new_table;\",\n\t\t\"ALTER TABLE old_table RENAME TO new_table;\",\n\t\t\"ALTER TABLE old_table RENAME AS new_table;\",\n\n\t\t// MODIFY & CHANGE\n\t\t\"ALTER TABLE t1 MODIFY col1 BIGINT UNSIGNED DEFAULT 1 COMMENT 'my column';\",\n\t\t\"ALTER TABLE t1 CHANGE b a INT NOT NULL;\",\n\n\t\t// table name quote in back ticks\n\t\t\"alter table `t3`add index `idx_a`(a)\",\n\t\t\"alter table`t3`drop index`idx_b`\",\n\t}\n\n\talterSQLs := MergeAlterTables(sqls...)\n\tvar sortedAlterSQLs []string\n\tfor item := range alterSQLs {\n\t\tsortedAlterSQLs = append(sortedAlterSQLs, item)\n\t}\n\tsort.Strings(sortedAlterSQLs)\n\n\terr := common.GoldenDiff(func() {\n\t\tfor _, tb := range sortedAlterSQLs {\n\t\t\tfmt.Println(tb, \":\", alterSQLs[tb])\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteUnionAll(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `select country_id from city union select country_id from country;`,\n\t\t\t\"output\": \"select country_id from city union all select country_id from country\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteUnionAll()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\nfunc TestRewriteTruncate(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `delete from tbl;`,\n\t\t\t\"output\": \"truncate table tbl\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteTruncate()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRewriteOr2In(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `select country_id from city where country_id = 1 or country_id = 2 or country_id = 3;`,\n\t\t\t\"output\": \"select country_id from city where country_id in (1, 2, 3)\",\n\t\t},\n\t\t// TODO or中的恒真条件\n\t\t{\n\t\t\t\"input\":  `select country_id from city where country_id != 1 or country_id != 2 or country_id = 3;`,\n\t\t\t\"output\": \"select country_id from city where country_id != 1 or country_id != 2 or country_id = 3\",\n\t\t},\n\t\t// col = 1 or col is null不可转为IN\n\t\t{\n\t\t\t\"input\":  `select country_id from city where col = 1 or col is null;`,\n\t\t\t\"output\": \"select country_id from city where col = 1 or col is null\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `select country_id from city where col1 = 1 or col2 = 1 or col2 = 2;`,\n\t\t\t\"output\": \"select country_id from city where col1 = 1 or col2 in (1, 2)\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `select country_id from city where col1 = 1 or col2 = 1 or col2 = 2 or col1 = 3;`,\n\t\t\t\"output\": \"select country_id from city where col2 in (1, 2) or col1 in (1, 3)\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `select country_id from city where (col1 = 1 or col2 = 1 or col2 = 2 ) or col1 = 3;`,\n\t\t\t\"output\": \"select country_id from city where (col1 = 1 or col2 in (1, 2)) or col1 = 3\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `select country_id from city where col1 = 1 or (col2 = 1 or col2 = 2 ) or col1 = 3;`,\n\t\t\t\"output\": \"select country_id from city where (col2 in (1, 2)) or col1 in (1, 3)\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteOr2In()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRmParenthesis(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []map[string]string{\n\t\t{\n\t\t\t\"input\":  `select country_id from city where (country_id = 1);`,\n\t\t\t\"output\": \"select country_id from city where country_id = 1\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `select * from city where a = 1 and (country_id = 1);`,\n\t\t\t\"output\": \"select * from city where a = 1 and country_id = 1\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `select country_id from city where (country_id = 1) or country_id = 1 ;`,\n\t\t\t\"output\": \"select country_id from city where country_id = 1 or country_id = 1\",\n\t\t},\n\t\t{\n\t\t\t\"input\":  `select country_id from city where col = 1 or (country_id = 1) or country_id = 1 ;`,\n\t\t\t\"output\": \"select country_id from city where col = 1 or country_id = 1 or country_id = 1\",\n\t\t},\n\t}\n\tfor _, sql := range testSQL {\n\t\trw := NewRewrite(sql[\"input\"]).RewriteRmParenthesis()\n\t\tif rw.NewSQL != sql[\"output\"] {\n\t\t\tt.Errorf(\"want: %s\\ngot: %s\", sql[\"output\"], rw.NewSQL)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestListRewriteRules(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := common.GoldenDiff(func() {\n\t\tListRewriteRules(RewriteRules)\n\t\torgReportType := common.Config.ReportType\n\t\tcommon.Config.ReportType = \"json\"\n\t\tListRewriteRules(RewriteRules)\n\t\tcommon.Config.ReportType = orgReportType\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "ast/testdata/TestCompress.golden",
    "content": "SELECT * FROM film WHERE length = 86;\nSELECT * FROM film WHERE length = 86;\nSELECT * FROM film WHERE length IS NULL;\nSELECT * FROM film WHERE length IS NULL;\nSELECT * FROM film HAVING title = 'abc';\nSELECT * FROM film HAVING title = 'abc';\nSELECT * FROM sakila.film WHERE length >= 60;\nSELECT * FROM sakila.film WHERE length >= 60;\nSELECT * FROM sakila.film WHERE length >= '60';\nSELECT * FROM sakila.film WHERE length >= '60';\nSELECT * FROM film WHERE length BETWEEN 60 AND 84;\nSELECT * FROM film WHERE length BETWEEN 60 AND 84;\nSELECT * FROM film WHERE title LIKE 'AIR%';\nSELECT * FROM film WHERE title LIKE 'AIR%';\nSELECT * FROM film WHERE title IS NOT NULL;\nSELECT * FROM film WHERE title IS NOT NULL;\nSELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';\nSELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';\nSELECT * FROM film WHERE length > 100 and language_id < 10;\nSELECT * FROM film WHERE length > 100 and language_id < 10;\nSELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;\nSELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;\nSELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;\nSELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;\nSELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;\nSELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;\nSELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);\nSELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);\nSELECT release_year, sum(film_id) FROM film GROUP BY release_year;\nSELECT release_year, sum(film_id) FROM film GROUP BY release_year;\nSELECT * FROM address GROUP BY address,district;\nSELECT * FROM address GROUP BY address,district;\nSELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;\nSELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;\nSELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;\nSELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;\nSELECT * FROM film ORDER BY release_year LIMIT 10;\nSELECT * FROM film ORDER BY release_year LIMIT 10;\nSELECT film_id FROM film ORDER BY release_year LIMIT 10;\nSELECT film_id FROM film ORDER BY release_year LIMIT 10;\nSELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;\nSELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;\nSELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;\nSELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;\nSELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;\nSELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;\nSELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;\nSELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;\nSELECT title FROM film WHERE release_year = 1995;\nSELECT title FROM film WHERE release_year = 1995;\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;\nSELECT title FROM film WHERE language_id > 5 AND length > 70;\nSELECT title FROM film WHERE language_id > 5 AND length > 70;\nSELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;\nSELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;\nSELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;\nSELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;\nSELECT * FROM film WHERE length > 100 ORDER BY release_year;\nSELECT * FROM film WHERE length > 100 ORDER BY release_year;\nSELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\nSELECT country_id, last_update FROM city NATURAL JOIN country;\nSELECT country_id, last_update FROM city NATURAL JOIN country;\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\nSELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;\nSELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;\nSELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN  (SELECT c.city_id FROM sakila.city c);\nSELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN (SELECT c.city_id FROM sakila.city c);\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC;\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC;\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\nDELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\nDELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\nDELETE FROM film WHERE length > 100;\nDELETE FROM film WHERE length > 100;\nUPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\nUPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\nUPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\nUPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\nUPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\nUPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\nUPDATE film SET length = 10 WHERE language_id = 20;\nUPDATE film SET length = 10 WHERE language_id = 20;\nINSERT INTO city (country_id) SELECT country_id FROM country;\nINSERT INTO city (country_id) SELECT country_id FROM country;\nINSERT INTO city (country_id) VALUES (1),(2),(3);\nINSERT INTO city (country_id) VALUES (1),(2),(3);\nINSERT INTO city (country_id) VALUES (10);\nINSERT INTO city (country_id) VALUES (10);\nINSERT INTO city (country_id) SELECT 10 FROM DUAL;\nINSERT INTO city (country_id) SELECT 10 FROM DUAL;\nREPLACE INTO city (country_id) SELECT country_id FROM country;\nREPLACE INTO city (country_id) SELECT country_id FROM country;\nREPLACE INTO city (country_id) VALUES (1),(2),(3);\nREPLACE INTO city (country_id) VALUES (1),(2),(3);\nREPLACE INTO city (country_id) VALUES (10);\nREPLACE INTO city (country_id) VALUES (10);\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL;\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL;\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM  film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\nSELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;\nSELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;\nSELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;\nSELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;\nSELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;\nSELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;\nSELECT * FROM film WHERE date(last_update)='2006-02-15';\nSELECT * FROM film WHERE date(last_update)='2006-02-15';\nSELECT last_update FROM film GROUP BY date(last_update);\nSELECT last_update FROM film GROUP BY date(last_update);\nSELECT last_update FROM film order by date(last_update);\nSELECT last_update FROM film order by date(last_update);\nSELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;\nSELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;\nalter table address add index idx_city_id(city_id);\nalter table address add index idx_city_id(city_id);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);\nSELECT\tDATE_FORMAT(t.last_update, '%Y-%m-%d'),\tCOUNT(DISTINCT (t.city))\tFROM city t WHERE t.last_update > '2018-10-22 00:00:00'\tAND t.city LIKE '%Chrome%'\tAND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d');\nSELECT DATE_FORMAT(t.last_update, '%Y-%m-%d'), COUNT(DISTINCT (t.city)) FROM city t WHERE t.last_update > '2018-10-22 00:00:00' AND t.city LIKE '%Chrome%' AND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d');\ncreate table hello.t (id int unsigned);\ncreate table hello.t (id int unsigned);\nselect * from tb where data >= '';\nselect * from tb where data >= '';\nalter table tb alter column id drop default;\nalter table tb alter column id drop default;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d;\n"
  },
  {
    "path": "ast/testdata/TestFormat.golden",
    "content": "SELECT * FROM film WHERE length = 86;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 86;\nSELECT * FROM film WHERE length IS NULL;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  IS  NULL;\nSELECT * FROM film HAVING title = 'abc';\n\nSELECT  \n  * \nFROM  \n  film  \nHAVING  \n  title  = 'abc';\nSELECT * FROM sakila.film WHERE length >= 60;\n\nSELECT  \n  * \nFROM  \n  sakila. film  \nWHERE  \n  LENGTH  >= 60;\nSELECT * FROM sakila.film WHERE length >= '60';\n\nSELECT  \n  * \nFROM  \n  sakila. film  \nWHERE  \n  LENGTH  >= '60';\nSELECT * FROM film WHERE length BETWEEN 60 AND 84;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  BETWEEN  60  \n  AND  84;\nSELECT * FROM film WHERE title LIKE 'AIR%';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  LIKE  'AIR%';\nSELECT * FROM film WHERE title IS NOT NULL;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  IS  NOT  NULL;\nSELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 114  \n  AND  title  = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10  \n  AND  title  = 'xyz';\nSELECT * FROM film WHERE length > 100 and language_id < 10;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10;\nSELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \n  AND  language_id  = 1  \nGROUP BY  \n  release_year;\nSELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  >= 123  \nGROUP BY  \n  release_year;\nSELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;\n\nSELECT  \n  release_year, language_id, SUM( LENGTH) \nFROM  \n  film  \nGROUP BY  \n  release_year, language_id;\nSELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year, (LENGTH+ language_id);\nSELECT release_year, sum(film_id) FROM film GROUP BY release_year;\n\nSELECT  \n  release_year, SUM( film_id) \nFROM  \n  film  \nGROUP BY  \n  release_year;\nSELECT * FROM address GROUP BY address,district;\n\nSELECT  \n  * \nFROM  \n  address  \nGROUP BY  \n  address, district;\nSELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  ABS( language_id) = 3  \nGROUP BY  \n  title;\nSELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;\n\nSELECT  \n  language_id  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  language_id;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  ASC, language_id  DESC;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year  \nLIMIT  \n  10;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  \nLIMIT  \n  10;\nSELECT * FROM film ORDER BY release_year LIMIT 10;\n\nSELECT  \n  * \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10;\nSELECT film_id FROM film ORDER BY release_year LIMIT 10;\n\nSELECT  \n  film_id  \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10;\nSELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10;\nSELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  < 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10;\nSELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;\n\nSELECT  \n  * \nFROM  \n  customer  \nWHERE  \n  address_id  in  (224, 510) \nORDER BY  \n  last_name;\nSELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  release_year  = 2016  \n  AND  LENGTH  != 1  \nORDER BY  \n  title;\nSELECT title FROM film WHERE release_year = 1995;\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  release_year  = 1995;\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;\n\nSELECT  \n  title, replacement_cost  \nFROM  \n  film  \nWHERE  \n  language_id  = 5  \n  AND  LENGTH  = 70;\nSELECT title FROM film WHERE language_id > 5 AND length > 70;\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  language_id  > 5  \n  AND  LENGTH  > 70;\nSELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year;\nSELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year;\nSELECT * FROM film WHERE length > 100 ORDER BY release_year;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  release_year;\nSELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  INNER JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL;\nSELECT country_id, last_update FROM city NATURAL JOIN country;\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  JOIN  country;\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  LEFT JOIN  country;\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  RIGHT JOIN  country;\nSELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  a. country_id, a. last_update  \nFROM  \n  city  a  STRAIGHT_JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN  (SELECT c.city_id FROM sakila.city c);\n\nSELECT  \n  a. address, a. postal_code  \nFROM  \n  sakila. address  a  \nWHERE  \n  a. city_id  IN  (\nSELECT  \n  c. city_id  \nFROM  \n  sakila. city  c);\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC;\n\nSELECT  \n  city  \nFROM( \nSELECT  \n  city_id  \nFROM  \n  city  \nWHERE  \n  city  = \"A Corua (La Corua)\" \nORDER BY  \n  last_update  DESC  \nLIMIT  \n  50, 10) I  \n  JOIN  city  ON  (I. city_id  = city. city_id) \n  JOIN  country  ON  (country. country_id  = city. country_id) \nORDER BY  \n  city  DESC;\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\nDELETE  city, country  \nFROM  \n  city  \n  INNER JOIN  country  using  (country_id) \nWHERE  \n  city. city_id  = 1;\nDELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\nDELETE  city  \nFROM  \n  city  \n  LEFT JOIN  country  ON  city. country_id  = country. country_id  \nWHERE  \n  country. country  IS  NULL;\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\nDELETE  a1, a2  \nFROM  \n  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id;\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\n\nDELETE FROM  \n  a1, a2  USING  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id;\nDELETE FROM film WHERE length > 100;\n\nDELETE FROM  \n  film  \nWHERE  \n  LENGTH  > 100;\nUPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\n\nUPDATE  \n  city  \n  INNER JOIN  country  USING( country_id) \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10;\nUPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\n\nUPDATE  \n  city  \n  INNER JOIN  country  ON  city. country_id  = country. country_id  \n  INNER JOIN  address  ON  city. city_id  = address. city_id  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10;\nUPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\n\nUPDATE  \n  city, country  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. country_id  = country. country_id  \n  AND  city. city_id= 10;\nUPDATE film SET length = 10 WHERE language_id = 20;\n\nUPDATE  \n  film  \nSET  \n  LENGTH  = 10  \nWHERE  \n  language_id  = 20;\nINSERT INTO city (country_id) SELECT country_id FROM country;\nINSERT  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country;\nINSERT INTO city (country_id) VALUES (1),(2),(3);\nINSERT  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3);\nINSERT INTO city (country_id) VALUES (10);\nINSERT  INTO  city  (country_id) \nVALUES  \n  (10);\nINSERT INTO city (country_id) SELECT 10 FROM DUAL;\nINSERT  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL;\nREPLACE INTO city (country_id) SELECT country_id FROM country;\nREPLACE  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country;\nREPLACE INTO city (country_id) VALUES (1),(2),(3);\nREPLACE  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3);\nREPLACE INTO city (country_id) VALUES (10);\nREPLACE  INTO  city  (country_id) \nVALUES  \n  (10);\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL;\nREPLACE  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL;\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM  film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\n\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film;\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  language_id  = (\nSELECT  \n  language_id  \nFROM  \n  language  \nLIMIT  \n  1);\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id;\nSELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;\n\nSELECT  \n  * \nFROM  \n  (\nSELECT  \n  * \nFROM  \n  actor  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE'\n) t  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE' \nGROUP BY  \n  first_name;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  o. country_id  is  null  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  i. city_id  is  null;\nSELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;\n\nSELECT  \n  first_name, last_name, email  \nFROM  \n  customer  STRAIGHT_JOIN  address  ON  customer. address_id= address. address_id;\nSELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;\n\nSELECT  \n  ID, name  \nFROM  \n  (\nSELECT  \n  address  \nFROM  \n  customer_list  \nWHERE  \n  SID= 1  \nORDER BY  \n  phone  \nLIMIT  \n  50, 10) a  \n  JOIN  customer_list  l  ON  (a. address= l. address) \n  JOIN  city  c  ON  (c. city= l. city) \nORDER BY  \n  phone  desc;\nSELECT * FROM film WHERE date(last_update)='2006-02-15';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  DATE( last_update) = '2006-02-15';\nSELECT last_update FROM film GROUP BY date(last_update);\n\nSELECT  \n  last_update  \nFROM  \n  film  \nGROUP BY  \n  DATE( last_update);\nSELECT last_update FROM film order by date(last_update);\n\nSELECT  \n  last_update  \nFROM  \n  film  \nORDER BY  \n  DATE( last_update);\nSELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;\n\nSELECT  \n  description  \nFROM  \n  film  \nWHERE  \n  description  IN( 'NEWS', \n  'asd'\n) \nGROUP BY  \n  description;\nalter table address add index idx_city_id(city_id);\n\nALTER TABLE  \n  address  \nADD  \n  index  idx_city_id( city_id);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`);\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`), \n      ADD  \n      index  `idx_store_film` (\n        `store_id`, `film_id`), \n              ADD  \n          index  `idx_store_film` (\n            `store_id`, `film_id`);\nSELECT\tDATE_FORMAT(t.last_update, '%Y-%m-%d'),\tCOUNT(DISTINCT (t.city))\tFROM city t WHERE t.last_update > '2018-10-22 00:00:00'\tAND t.city LIKE '%Chrome%'\tAND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d');\n\nSELECT  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n), \nCOUNT( DISTINCT  (\n  t. city)) \n  FROM  \n    city  t  \n  WHERE  \n    t. last_update  > '2018-10-22 00:00:00' \n    AND  t. city  LIKE  '%Chrome%' \n    AND  t. city  = 'eip' \n  GROUP BY  \n    DATE_FORMAT( t. last_update, '%Y-%m-%d'\n) \nORDER BY  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n);\ncreate table hello.t (id int unsigned);\ncreate  table  hello. t  (id  int  unsigned);\nselect * from tb where data >= '';\n\nSELECT  \n  * \nFROM  \n  tb  \nWHERE  \n  data  >= '';\nalter table tb alter column id drop default;\n\nALTER TABLE  \n  tb  alter  column  id  \nDROP  \n  DEFAULT;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d;\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film  \nWHERE  \n  last_update  > '2016-03-27 02:01:01'\n) as  d;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d;\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film) as  d;\n"
  },
  {
    "path": "ast/testdata/TestGetQuotedString.golden",
    "content": "orignal: \"hello world\"\nquoted: \"hello world\"\norignal: `hello world`\nquoted: `hello world`\norignal: 'hello world'\nquoted: 'hello world'\norignal: hello world\nquoted: \norignal: 'hello \\'world'\nquoted: 'hello \\'world'\norignal: \"hello \\\"wor\\\"ld\"\nquoted: \"hello \\\"wor\\\"ld\"\norignal: \"hello \\\"world\"\nquoted: \"hello \\\"world\"\norignal: \"\"\nquoted: \"\"\norignal: ''\nquoted: ''\norignal: ``\nquoted: ``\norignal: 'hello 'world'\nquoted: 'hello '\norignal: \"hello \"world\"\nquoted: \"hello \"\norignal: ' LOGGER.error(\"\"); }'\nquoted: ' LOGGER.error(\"\"); }'\n"
  },
  {
    "path": "ast/testdata/TestLeftNewLines.golden",
    "content": "1\n0\n0\n"
  },
  {
    "path": "ast/testdata/TestListRewriteRules.golden",
    "content": "# 重写规则\n\n[toc]\n\n## dml2select\n* **Description**:将数据库更新请求转换为只读查询请求，便于执行EXPLAIN\n\n* **Original**:\n\n```sql\nDELETE FROM film WHERE length > 100\n```\n\n* **Suggest**:\n\n```sql\nselect * from film where length > 100\n```\n## reg2select\n* **Description**:使用正则的方式将数据库更新请求转换为只读查询请求，便于执行EXPLAIN\n\n* **Original**:\n\n```sql\nDELETE FROM film WHERE length > 100\n```\n\n* **Suggest**:\n\n```sql\nselect * from film where length > 100\n```\n## star2columns\n* **Description**:为SELECT *补全表的列信息\n\n* **Original**:\n\n```sql\nSELECT * FROM film\n```\n\n* **Suggest**:\n\n```sql\nselect film.film_id, film.title from film\n```\n## insertcolumns\n* **Description**:为INSERT补全表的列信息\n\n* **Original**:\n\n```sql\ninsert into film values(1,2,3,4,5)\n```\n\n* **Suggest**:\n\n```sql\ninsert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5)\n```\n## having\n* **Description**:将查询的 HAVING 子句改写为 WHERE 中的查询条件\n\n* **Original**:\n\n```sql\nSELECT state, COUNT(*) FROM Drivers GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state\n```\n\n* **Suggest**:\n\n```sql\nselect state, COUNT(*) from Drivers where state in ('GA', 'TX') group by state order by state asc\n```\n## orderbynull\n* **Description**:如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 ORDER BY NULL\n\n* **Original**:\n\n```sql\nSELECT sum(col1) FROM tbl GROUP BY col\n```\n\n* **Suggest**:\n\n```sql\nselect sum(col1) from tbl group by col order by null\n```\n## unionall\n* **Description**:可以接受重复的时间，使用 UNION ALL 替代 UNION 以提高查询效率\n\n* **Original**:\n\n```sql\nselect country_id from city union select country_id from country\n```\n\n* **Suggest**:\n\n```sql\nselect country_id from city union all select country_id from country\n```\n## or2in\n* **Description**:将同一列不同条件的 OR 查询转写为 IN 查询\n\n* **Original**:\n\n```sql\nselect country_id from city where col1 = 1 or (col2 = 1 or col2 = 2 ) or col1 = 3;\n```\n\n* **Suggest**:\n\n```sql\nselect country_id from city where (col2 in (1, 2)) or col1 in (1, 3);\n```\n## dmlorderby\n* **Description**:删除 DML 更新操作中无意义的 ORDER BY\n\n* **Original**:\n\n```sql\nDELETE FROM tbl WHERE col1=1 ORDER BY col\n```\n\n* **Suggest**:\n\n```sql\ndelete from tbl where col1 = 1\n```\n## distinctstar\n* **Description**:DISTINCT *对有主键的表没有意义，可以将DISTINCT删掉\n\n* **Original**:\n\n```sql\nSELECT DISTINCT * FROM film;\n```\n\n* **Suggest**:\n\n```sql\nSELECT * FROM film\n```\n## standard\n* **Description**:SQL标准化，如：关键字转换为小写\n\n* **Original**:\n\n```sql\nSELECT sum(col1) FROM tbl GROUP BY 1;\n```\n\n* **Suggest**:\n\n```sql\nselect sum(col1) from tbl group by 1\n```\n## mergealter\n* **Description**:合并同一张表的多条ALTER语句\n\n* **Original**:\n\n```sql\nALTER TABLE t2 DROP COLUMN c;ALTER TABLE t2 DROP COLUMN d;\n```\n\n* **Suggest**:\n\n```sql\nALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;\n```\n## alwaystrue\n* **Description**:删除无用的恒真判断条件\n\n* **Original**:\n\n```sql\nSELECT count(col) FROM tbl where 'a'= 'a' or ('b' = 'b' and a = 'b');\n```\n\n* **Suggest**:\n\n```sql\nselect count(col) from tbl where (a = 'b');\n```\n## countstar\n* **Description**:不建议使用COUNT(col)或COUNT(常量)，建议改写为COUNT(*)\n\n* **Original**:\n\n```sql\nSELECT count(col) FROM tbl GROUP BY 1;\n```\n\n* **Suggest**:\n\n```sql\nSELECT count(*) FROM tbl GROUP BY 1;\n```\n## innodb\n* **Description**:建表时建议使用InnoDB引擎，非 InnoDB 引擎表自动转 InnoDB\n\n* **Original**:\n\n```sql\nCREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT);\n```\n\n* **Suggest**:\n\n```sql\ncreate table t1 (\n\tid bigint(20) not null auto_increment\n) ENGINE=InnoDB;\n```\n## autoincrement\n* **Description**:将autoincrement初始化为1\n\n* **Original**:\n\n```sql\nCREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;\n```\n\n* **Suggest**:\n\n```sql\ncreate table t1(id bigint(20) not null auto_increment) ENGINE=InnoDB auto_increment=1;\n```\n## intwidth\n* **Description**:整型数据类型修改默认显示宽度\n\n* **Original**:\n\n```sql\ncreate table t1 (id int(20) not null auto_increment) ENGINE=InnoDB;\n```\n\n* **Suggest**:\n\n```sql\ncreate table t1 (id int(10) not null auto_increment) ENGINE=InnoDB;\n```\n## truncate\n* **Description**:不带 WHERE 条件的 DELETE 操作建议修改为 TRUNCATE\n\n* **Original**:\n\n```sql\nDELETE FROM tbl\n```\n\n* **Suggest**:\n\n```sql\ntruncate table tbl\n```\n## rmparenthesis\n* **Description**:去除没有意义的括号\n\n* **Original**:\n\n```sql\nselect col from table where (col = 1);\n```\n\n* **Suggest**:\n\n```sql\nselect col from table where col = 1;\n```\n## delimiter\n* **Description**:补全DELIMITER\n\n* **Original**:\n\n```sql\nuse sakila\n```\n\n* **Suggest**:\n\n```sql\nuse sakila;\n```\n[\n  {\n    \"Name\": \"dml2select\",\n    \"Description\": \"将数据库更新请求转换为只读查询请求，便于执行EXPLAIN\",\n    \"Original\": \"DELETE FROM film WHERE length \\u003e 100\",\n    \"Suggest\": \"select * from film where length \\u003e 100\"\n  },\n  {\n    \"Name\": \"reg2select\",\n    \"Description\": \"使用正则的方式将数据库更新请求转换为只读查询请求，便于执行EXPLAIN\",\n    \"Original\": \"DELETE FROM film WHERE length \\u003e 100\",\n    \"Suggest\": \"select * from film where length \\u003e 100\"\n  },\n  {\n    \"Name\": \"star2columns\",\n    \"Description\": \"为SELECT *补全表的列信息\",\n    \"Original\": \"SELECT * FROM film\",\n    \"Suggest\": \"select film.film_id, film.title from film\"\n  },\n  {\n    \"Name\": \"insertcolumns\",\n    \"Description\": \"为INSERT补全表的列信息\",\n    \"Original\": \"insert into film values(1,2,3,4,5)\",\n    \"Suggest\": \"insert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5)\"\n  },\n  {\n    \"Name\": \"having\",\n    \"Description\": \"将查询的 HAVING 子句改写为 WHERE 中的查询条件\",\n    \"Original\": \"SELECT state, COUNT(*) FROM Drivers GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state\",\n    \"Suggest\": \"select state, COUNT(*) from Drivers where state in ('GA', 'TX') group by state order by state asc\"\n  },\n  {\n    \"Name\": \"orderbynull\",\n    \"Description\": \"如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 ORDER BY NULL\",\n    \"Original\": \"SELECT sum(col1) FROM tbl GROUP BY col\",\n    \"Suggest\": \"select sum(col1) from tbl group by col order by null\"\n  },\n  {\n    \"Name\": \"unionall\",\n    \"Description\": \"可以接受重复的时间，使用 UNION ALL 替代 UNION 以提高查询效率\",\n    \"Original\": \"select country_id from city union select country_id from country\",\n    \"Suggest\": \"select country_id from city union all select country_id from country\"\n  },\n  {\n    \"Name\": \"or2in\",\n    \"Description\": \"将同一列不同条件的 OR 查询转写为 IN 查询\",\n    \"Original\": \"select country_id from city where col1 = 1 or (col2 = 1 or col2 = 2 ) or col1 = 3;\",\n    \"Suggest\": \"select country_id from city where (col2 in (1, 2)) or col1 in (1, 3);\"\n  },\n  {\n    \"Name\": \"innull\",\n    \"Description\": \"如果 IN 条件中可能有 NULL 值而又想匹配 NULL 值时，建议添加OR col IS NULL\",\n    \"Original\": \"暂不支持\",\n    \"Suggest\": \"暂不支持\"\n  },\n  {\n    \"Name\": \"or2union\",\n    \"Description\": \"将不同列的 OR 查询转为 UNION 查询，建议结合 unionall 重写策略一起使用\",\n    \"Original\": \"暂不支持\",\n    \"Suggest\": \"暂不支持\"\n  },\n  {\n    \"Name\": \"dmlorderby\",\n    \"Description\": \"删除 DML 更新操作中无意义的 ORDER BY\",\n    \"Original\": \"DELETE FROM tbl WHERE col1=1 ORDER BY col\",\n    \"Suggest\": \"delete from tbl where col1 = 1\"\n  },\n  {\n    \"Name\": \"sub2join\",\n    \"Description\": \"将子查询转换为JOIN查询\",\n    \"Original\": \"暂不支持\",\n    \"Suggest\": \"暂不支持\"\n  },\n  {\n    \"Name\": \"join2sub\",\n    \"Description\": \"将JOIN查询转换为子查询\",\n    \"Original\": \"暂不支持\",\n    \"Suggest\": \"暂不支持\"\n  },\n  {\n    \"Name\": \"distinctstar\",\n    \"Description\": \"DISTINCT *对有主键的表没有意义，可以将DISTINCT删掉\",\n    \"Original\": \"SELECT DISTINCT * FROM film;\",\n    \"Suggest\": \"SELECT * FROM film\"\n  },\n  {\n    \"Name\": \"standard\",\n    \"Description\": \"SQL标准化，如：关键字转换为小写\",\n    \"Original\": \"SELECT sum(col1) FROM tbl GROUP BY 1;\",\n    \"Suggest\": \"select sum(col1) from tbl group by 1\"\n  },\n  {\n    \"Name\": \"mergealter\",\n    \"Description\": \"合并同一张表的多条ALTER语句\",\n    \"Original\": \"ALTER TABLE t2 DROP COLUMN c;ALTER TABLE t2 DROP COLUMN d;\",\n    \"Suggest\": \"ALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;\"\n  },\n  {\n    \"Name\": \"alwaystrue\",\n    \"Description\": \"删除无用的恒真判断条件\",\n    \"Original\": \"SELECT count(col) FROM tbl where 'a'= 'a' or ('b' = 'b' and a = 'b');\",\n    \"Suggest\": \"select count(col) from tbl where (a = 'b');\"\n  },\n  {\n    \"Name\": \"countstar\",\n    \"Description\": \"不建议使用COUNT(col)或COUNT(常量)，建议改写为COUNT(*)\",\n    \"Original\": \"SELECT count(col) FROM tbl GROUP BY 1;\",\n    \"Suggest\": \"SELECT count(*) FROM tbl GROUP BY 1;\"\n  },\n  {\n    \"Name\": \"innodb\",\n    \"Description\": \"建表时建议使用InnoDB引擎，非 InnoDB 引擎表自动转 InnoDB\",\n    \"Original\": \"CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT);\",\n    \"Suggest\": \"create table t1 (\\n\\tid bigint(20) not null auto_increment\\n) ENGINE=InnoDB;\"\n  },\n  {\n    \"Name\": \"autoincrement\",\n    \"Description\": \"将autoincrement初始化为1\",\n    \"Original\": \"CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;\",\n    \"Suggest\": \"create table t1(id bigint(20) not null auto_increment) ENGINE=InnoDB auto_increment=1;\"\n  },\n  {\n    \"Name\": \"intwidth\",\n    \"Description\": \"整型数据类型修改默认显示宽度\",\n    \"Original\": \"create table t1 (id int(20) not null auto_increment) ENGINE=InnoDB;\",\n    \"Suggest\": \"create table t1 (id int(10) not null auto_increment) ENGINE=InnoDB;\"\n  },\n  {\n    \"Name\": \"truncate\",\n    \"Description\": \"不带 WHERE 条件的 DELETE 操作建议修改为 TRUNCATE\",\n    \"Original\": \"DELETE FROM tbl\",\n    \"Suggest\": \"truncate table tbl\"\n  },\n  {\n    \"Name\": \"rmparenthesis\",\n    \"Description\": \"去除没有意义的括号\",\n    \"Original\": \"select col from table where (col = 1);\",\n    \"Suggest\": \"select col from table where col = 1;\"\n  },\n  {\n    \"Name\": \"delimiter\",\n    \"Description\": \"补全DELIMITER\",\n    \"Original\": \"use sakila\",\n    \"Suggest\": \"use sakila;\"\n  }\n]\n"
  },
  {
    "path": "ast/testdata/TestMergeAlterTables.golden",
    "content": "`customer` : ALTER TABLE `customer` ADD INDEX part_of_name (name(10)) ;\n\n`db`.`old_table` : ALTER TABLE `db`.`old_table` RENAME new_table ;\n\n`old_table` : ALTER TABLE `old_table` RENAME TO new_table, RENAME AS new_table ;\n\n`sakila`.`t1` : ALTER TABLE `sakila`.`t1` add index `idx_col`(`col`), add UNIQUE index `idx_col`(`col`), add index `idx_ID`(`ID`) ;\n\n`t1` : ALTER TABLE `t1` RENAME COLUMN a TO b, RENAME INDEX idx_a TO idx_b, RENAME KEY idx_a TO idx_b, MODIFY col1 BIGINT UNSIGNED DEFAULT 1 COMMENT 'my column', CHANGE b a INT NOT NULL ;\n\n`t2` : ALTER TABLE `t2` DROP COLUMN c, DROP COLUMN d, ADD COLUMN C int, ADD COLUMN D int FIRST, ADD COLUMN E int AFTER D ;\n\n`t3` : ALTER TABLE `t3` add index `idx_a`(a), drop index`idx_b` ;\n\n`test_bb` : ALTER TABLE `test_bb` ADD INDEX idx_test_cca (test_cc) ;\n\n"
  },
  {
    "path": "ast/testdata/TestNewLines.golden",
    "content": "1\n2\n0\n"
  },
  {
    "path": "ast/testdata/TestPretty.golden",
    "content": "select sourcetable, if(f.lastcontent = ?, f.lastupdate, f.lastcontent) as lastactivity, f.totalcount as activity, type.class as type, (f.nodeoptions & ?) as nounsubscribe from node as f inner join contenttype as type on type.contenttypeid = f.contenttypeid inner join subscribed as sd on sd.did = f.nodeid and sd.userid = ? union all select f.name as title, f.userid as keyval, ? as sourcetable, ifnull(f.lastpost, f.joindate) as lastactivity, f.posts as activity, ? as type, ? as nounsubscribe from user as f inner join userlist as ul on ul.relationid = f.userid and ul.userid = ? where ul.type = ? and ul.aq = ? order by title limit ?\n\nSELECT  \n  sourcetable, IF( f. lastcontent  = ?, f. lastupdate, f. lastcontent) as  lastactivity, f. totalcount  as  activity, type. class  as  type, (f. nodeoptions  & ?) as  nounsubscribe  \nFROM  \n  node  as  f  \n  INNER JOIN  contenttype  as  type  on  type. contenttypeid  = f. contenttypeid  \n  INNER JOIN  subscribed  as  sd  on  sd. did  = f. nodeid  \n  AND  sd. userid  = ?  \nUNION ALL  \nSELECT  \n  f. name  as  title, f. userid  as  keyval, ?  as  sourcetable, IFNULL( f. lastpost, f. joindate) as  lastactivity, f. posts  as  activity, ?  as  type, ?  as  nounsubscribe  \nFROM  \n  USER  as  f  \n  INNER JOIN  userlist  as  ul  on  ul. relationid  = f. userid  \n  AND  ul. userid  = ?  \nWHERE  \n  ul. type  = ?  \n  AND  ul. aq  = ?  \nORDER BY  \n  title  \nLIMIT  \n  ?\nadministrator command: Init DB\nadministrator  command: Init  DB\nCALL foo(1, 2, 3)\nCALL  foo( 1, 2, 3)\n### Channels ###\n\t\t\t\t\tSELECT sourcetable, IF(f.lastcontent = 0, f.lastupdate, f.lastcontent) AS lastactivity,\n\t\t\t\t\tf.totalcount AS activity, type.class AS type,\n\t\t\t\t\t(f.nodeoptions & 512) AS noUnsubscribe\n\t\t\t\t\tFROM node AS f\n\t\t\t\t\tINNER JOIN contenttype AS type ON type.contenttypeid = f.contenttypeid \n\n\t\t\t\t\tINNER JOIN subscribed AS sd ON sd.did = f.nodeid AND sd.userid = 15965\n UNION  ALL \n\n\t\t\t\t\t### Users ###\n\t\t\t\t\tSELECT f.name AS title, f.userid AS keyval, 'user' AS sourcetable, IFNULL(f.lastpost, f.joindate) AS lastactivity,\n\t\t\t\t\tf.posts as activity, 'Member' AS type,\n\t\t\t\t\t0 AS noUnsubscribe\n\t\t\t\t\tFROM user AS f\n\t\t\t\t\tINNER JOIN userlist AS ul ON ul.relationid = f.userid AND ul.userid = 15965\n\t\t\t\t\tWHERE ul.type = 'f' AND ul.aq = 'yes'\n ORDER BY title ASC LIMIT 100\n### Channels ###\nSELECT  \n  sourcetable, IF( f. lastcontent  = 0, f. lastupdate, f. lastcontent) AS  lastactivity, f. totalcount  AS  activity, type. class  AS  type, (f. nodeoptions  & 512) AS  noUnsubscribe\n \nFROM  \n  node  AS  f\n \n  INNER JOIN  contenttype  AS  type  ON  type. contenttypeid  = f. contenttypeid  \n  INNER JOIN  subscribed  AS  sd  ON  sd. did  = f. nodeid  \n  AND  sd. userid  = 15965\n \nUNION  \n  ALL  ### Users ###\nSELECT  \n  f. name  AS  title, f. userid  AS  keyval, 'user' AS  sourcetable, IFNULL( f. lastpost, f. joindate) AS  lastactivity, f. posts  as  activity, 'Member' AS  type, 0  AS  noUnsubscribe\n \nFROM  \n  USER  AS  f\n \n  INNER JOIN  userlist  AS  ul  ON  ul. relationid  = f. userid  \n  AND  ul. userid  = 15965\n \nWHERE  \n  ul. type  = 'f' \n  AND  ul. aq  = 'yes' \nORDER BY  \n  title  ASC  \nLIMIT  \n  100\nCREATE DATABASE org235_percona345 COLLATE 'utf8_general_ci'\nCREATE  DATABASE  org235_percona345  COLLATE  'utf8_general_ci'\ninsert into abtemp.coxed select foo.bar from foo\nINSERT  into  abtemp. coxed  \nSELECT  \n  foo. bar  \nFROM  \n  foo\ninsert into foo(a, b, c) value(2, 4, 5)\nINSERT  into  foo( a, b, c) value( 2, 4, 5)\ninsert into foo(a, b, c) values(2, 4, 5)\nINSERT  into  foo( a, b, c) \nVALUES( \n  2, 4, 5)\ninsert into foo(a, b, c) values(2, 4, 5) , (2,4,5)\nINSERT  into  foo( a, b, c) \nVALUES( \n  2, 4, 5), \n  (2, 4, 5)\ninsert into foo values (1, '(2)', 'This is a trick: ). More values.', 4)\nINSERT  into  foo  \nVALUES  \n  (1, '(2)', \n  'This is a trick: ). More values.', \n  4)\ninsert into tb values (1)\nINSERT  into  tb  \nVALUES  \n  (1)\nINSERT INTO t (ts) VALUES ('()', '\\(', '\\)')\nINSERT  INTO  t  (ts) \nVALUES  \n  (\n    '()', '\\(', '\\)')\nINSERT INTO t (ts) VALUES (NOW())\nINSERT  INTO  t  (ts) \nVALUES  \n  (\n    NOW()\n  )\nINSERT INTO t () VALUES ()\nINSERT  INTO  t  (\n) \n  VALUES  \n    (\n)\ninsert into t values (1), (2), (3)\n\n\ton duplicate key update query_count=1\nINSERT  into  t  \nVALUES  \n  (1), \n  (2), \n  (3) on  duplicate  key  \nUPDATE  \n  query_count= 1\ninsert into t values (1) on duplicate key update query_count=COALESCE(query_count, 0) + VALUES(query_count)\nINSERT  into  t  \nVALUES  \n  (1) on  duplicate  key  \nUPDATE  \n  query_count= COALESCE( query_count, 0) + \nVALUES( \n  query_count)\nLOAD DATA INFILE '/tmp/foo.txt' INTO db.tbl\nLOAD  DATA  INFILE  '/tmp/foo.txt' INTO  db. tbl\nselect 0e0, +6e-30, -6.00 from foo where a = 5.5 or b=0.5 or c=.5\n\nSELECT  \n  0e0, + 6e- 30, - 6.00  \nFROM  \n  foo  \nWHERE  \n  a  = 5.5  \n  OR  b= 0.5  \n  OR  c=.5\nselect 0x0, x'123', 0b1010, b'10101' from foo\n\nSELECT  \n  0x0, x' 123', \n  0b1010, b' 10101' \nFROM  \n  foo\nselect 123_foo from 123_foo\n\nSELECT  \n  123_foo  \nFROM  \n  123_foo\nselect 123foo from 123foo\n\nSELECT  \n  123foo  \nFROM  \n  123foo\nSELECT \t1 AS one FROM calls USE INDEX(index_name)\n\nSELECT  \n  1  AS  one  \nFROM  \n  calls  USE  INDEX( index_name)\nSELECT /*!40001 SQL_NO_CACHE */ * FROM `film`\n\nSELECT  \n  \n  /*!40001 SQL_NO_CACHE \n  */ * \nFROM  \n  `film`\nSELECT 'a' 'b' 'c' 'd' FROM kamil\n\nSELECT  \n  'a' 'b' 'c' 'd' \nFROM  \n  kamil\nSELECT BENCHMARK(100000000, pow(rand(), rand())), 1 FROM `-hj-7d6-shdj5-7jd-kf-g988h-`.`-aaahj-7d6-shdj5-7&^%$jd-kf-g988h-9+4-5*6ab-`\n\nSELECT  \n  BENCHMARK( 100000000, POW( RAND(\n), \nRAND(\n)\n)\n), \n1  \nFROM  \n  `-hj-7d6-shdj5-7jd-kf-g988h-`.`-aaahj-7d6-shdj5-7&^%$jd-kf-g988h-9+4-5*6ab-`\nSELECT c FROM org235.t WHERE id=0xdeadbeaf\n\nSELECT  \n  c  \nFROM  \n  org235. t  \nWHERE  \n  id= 0xdeadbeaf\nselect c from t where i=1 order by c asc\n\nSELECT  \n  c  \nFROM  \n  t  \nWHERE  \n  i= 1  \nORDER BY  \n  c  asc\nSELECT c FROM t WHERE id=0xdeadbeaf\n\nSELECT  \n  c  \nFROM  \n  t  \nWHERE  \n  id= 0xdeadbeaf\nSELECT c FROM t WHERE id=1\n\nSELECT  \n  c  \nFROM  \n  t  \nWHERE  \n  id= 1\nselect `col` from `table-1` where `id` = 5\n\nSELECT  \n  `col` \nFROM  \n  `table-1` \nWHERE  \n  `id` = 5\nSELECT `db`.*, (CASE WHEN (`date_start` <=  '2014-09-10 09:17:59' AND `date_end` >=  '2014-09-10 09:17:59') THEN 'open' WHEN (`date_start` >  '2014-09-10 09:17:59' AND `date_end` >  '2014-09-10 09:17:59') THEN 'tbd' ELSE 'none' END) AS `status` FROM `foo` AS `db` WHERE (a_b in ('1', '10101'))\n\nSELECT  \n  `db`.*, \n  (CASE  WHEN  (`date_start` <= '2014-09-10 09:17:59' \n  AND  `date_end` >= '2014-09-10 09:17:59'\n) THEN  'open' WHEN  (`date_start` > '2014-09-10 09:17:59' \nAND  `date_end` > '2014-09-10 09:17:59'\n) THEN  'tbd' ELSE  'none' END) AS  `status` \nFROM  \n  `foo` AS  `db` \nWHERE  \n  (a_b  in  (\n    '1', '10101')\n  )\nselect field from `-master-db-1`.`-table-1-` order by id, ?;\n\nSELECT  \n  FIELD  \nFROM  \n  `-master-db-1`.`-table-1-` \nORDER BY  \n  id, ?;\nselect   foo\n\nSELECT  \n  foo\nselect foo_1 from foo_2_3\n\nSELECT  \n  foo_1  \nFROM  \n  foo_2_3\nselect foo -- bar\n\n\nSELECT  \n  foo  -- bar\nselect foo-- bar\n,foo\n\nSELECT  \n  foo- - bar\n, \n  foo\nselect '\\\\' from foo\n\nSELECT  \n  '\\\\' \nFROM  \n  foo\nselect * from foo limit 5\n\nSELECT  \n  * \nFROM  \n  foo  \nLIMIT  \n  5\nselect * from foo limit 5, 10\n\nSELECT  \n  * \nFROM  \n  foo  \nLIMIT  \n  5, 10\nselect * from foo limit 5 offset 10\n\nSELECT  \n  * \nFROM  \n  foo  \nLIMIT  \n  5  offset  10\nSELECT * from foo where a = 5\n\nSELECT  \n  * \nFROM  \n  foo  \nWHERE  \n  a  = 5\nselect * from foo where a in (5) and b in (5, 8,9 ,9 , 10)\n\nSELECT  \n  * \nFROM  \n  foo  \nWHERE  \n  a  in  (5) \n  AND  b  in  (5, 8, 9, \n  9, \n  10)\nSELECT '' '' '' FROM kamil\n\nSELECT  \n  '' '' '' \nFROM  \n  kamil\n select  * from\nfoo where a = 5\n\nSELECT  \n  * \nFROM  \n  foo  \nWHERE  \n  a  = 5\nSELECT * FROM prices.rt_5min where id=1\n\nSELECT  \n  * \nFROM  \n  prices. rt_5min  \nWHERE  \n  id= 1\nSELECT * FROM table WHERE field = 'value' /*arbitrary/31*/ \n\nSELECT  \n  * \nFROM  \n  table  \nWHERE  \n  FIELD  = 'value' \n  /*arbitrary/31\n  */\nSELECT * FROM table WHERE field = 'value' /*arbitrary31*/ \n\nSELECT  \n  * \nFROM  \n  table  \nWHERE  \n  FIELD  = 'value' \n  /*arbitrary31\n  */\nSELECT *    FROM t WHERE 1=1 AND id=1\n\nSELECT  \n  * \nFROM  \n  t  \nWHERE  \n  1= 1  \n  AND  id= 1\nselect * from t where (base.nid IN  ('1412', '1410', '1411'))\n\nSELECT  \n  * \nFROM  \n  t  \nWHERE  \n  (base. nid  IN  (\n    '1412', '1410', '1411')\n  )\nselect * from t where i=1      order            by\n             a,  b          ASC, d    DESC,\n\n                                    e asc\n\nSELECT  \n  * \nFROM  \n  t  \nWHERE  \n  i= 1  order  by\n a, b  ASC, d  DESC, e  asc\nselect * from t where i=1 order by a, b ASC, d DESC, e asc\n\nSELECT  \n  * \nFROM  \n  t  \nWHERE  \n  i= 1  \nORDER BY  \n  a, b  ASC, d  DESC, e  asc\nselect 'hello'\n\n\nSELECT  \n  'hello'\nselect 'hello', '\nhello\n', \"hello\", '\\'' from foo\n\nSELECT  \n  'hello', \n  '\nhello\n', \n  \"hello\", \n  '\\'' \nFROM  \n  foo\nSELECT ID, name, parent, type FROM posts WHERE _name IN ('perf','caching') AND (type = 'page' OR type = 'attachment')\n\nSELECT  \n  ID, name, parent, type  \nFROM  \n  posts  \nWHERE  \n  _name  IN  (\n    'perf', 'caching') \n    AND  (type  = 'page' \n    OR  type  = 'attachment'\n  )\nSELECT name, value FROM variable\n\nSELECT  \n  name, value  \nFROM  \n  variable\nselect \n-- bar\n foo\n\nSELECT  \n  -- bar\n  foo\nselect null, 5.001, 5001. from foo\n\nSELECT  \n  null, 5.001, 5001. \nFROM  \n  foo\nselect sleep(2) from test.n\n\nSELECT  \n  SLEEP( 2) \nFROM  \n  test. n\nSELECT t FROM field WHERE  (entity_type = 'node') AND (entity_id IN  ('609')) AND (language IN  ('und')) AND (deleted = '0') ORDER BY delta ASC\n\nSELECT  \n  t  \nFROM  \n  FIELD  \nWHERE  \n  (\n    entity_type  = 'node') \n    AND  (entity_id  IN  (\n      '609')\n    ) \n    AND  (language  IN  (\n      'und')\n    ) \n    AND  (\n      deleted  = '0') \n      ORDER BY  \n        delta  ASC\nselect  t.table_schema,t.table_name,engine  from information_schema.tables t  inner join information_schema.columns c  on t.table_schema=c.table_schema and t.table_name=c.table_name group by t.table_schema,t.table_name having  sum(if(column_key in ('PRI','UNI'),1,0))=0\n\nSELECT  \n  t. table_schema, t. table_name, engine  \nFROM  \n  information_schema. tables  t  \n  INNER JOIN  information_schema. columns  c  on  t. table_schema= c. table_schema  \n  AND  t. table_name= c. table_name  \nGROUP BY  \n  t. table_schema, t. table_name  \nHAVING  \n  SUM( IF( column_key  in  (\n    'PRI', 'UNI'), \n    1, 0)\n  )= 0\n/* -- S++ SU ABORTABLE -- spd_user: rspadim */SELECT SQL_SMALL_RESULT SQL_CACHE DISTINCT centro_atividade FROM est_dia WHERE unidade_id=1001 AND item_id=67 AND item_id_red=573\n\n/* -- S++ SU ABORTABLE -- spd_user: rspadim \n*/ \nSELECT  \n  SQL_SMALL_RESULT  SQL_CACHE  DISTINCT  centro_atividade  \nFROM  \n  est_dia  \nWHERE  \n  unidade_id= 1001  \n  AND  item_id= 67  \n  AND  item_id_red= 573\nUPDATE groups_search SET  charter = '   -------3\\'\\' XXXXXXXXX.\\n    \\n    -----------------------------------------------------', show_in_list = 'Y' WHERE group_id='aaaaaaaa'\n\nUPDATE  \n  groups_search  \nSET  \n  charter  = '   -------3\\'\\' XXXXXXXXX.\\n    \\n    -----------------------------------------------------', \n  show_in_list  = 'Y' \nWHERE  \n  group_id= 'aaaaaaaa'\nuse `foo`\nuse  `foo`\nselect sourcetable, if(f.lastcontent = ?, f.lastupdate, f.lastcontent) as lastactivity, f.totalcount as activity, type.class as type, (f.nodeoptions & ?) as nounsubscribe from node as f inner join contenttype as type on type.contenttypeid = f.contenttypeid inner join subscribed as sd on sd.did = f.nodeid and sd.userid = ? union all select f.name as title, f.userid as keyval, ? as sourcetable, ifnull(f.lastpost, f.joindate) as lastactivity, f.posts as activity, ? as type, ? as nounsubscribe from user as f inner join userlist as ul on ul.relationid = f.userid and ul.userid = ? where ul.type = ? and ul.aq = ? order by title limit ?\n\nSELECT  \n  sourcetable, IF( f. lastcontent  = ?, f. lastupdate, f. lastcontent) as  lastactivity, f. totalcount  as  activity, type. class  as  type, (f. nodeoptions  & ?) as  nounsubscribe  \nFROM  \n  node  as  f  \n  INNER JOIN  contenttype  as  type  on  type. contenttypeid  = f. contenttypeid  \n  INNER JOIN  subscribed  as  sd  on  sd. did  = f. nodeid  \n  AND  sd. userid  = ?  \nUNION ALL  \nSELECT  \n  f. name  as  title, f. userid  as  keyval, ?  as  sourcetable, IFNULL( f. lastpost, f. joindate) as  lastactivity, f. posts  as  activity, ?  as  type, ?  as  nounsubscribe  \nFROM  \n  USER  as  f  \n  INNER JOIN  userlist  as  ul  on  ul. relationid  = f. userid  \n  AND  ul. userid  = ?  \nWHERE  \n  ul. type  = ?  \n  AND  ul. aq  = ?  \nORDER BY  \n  title  \nLIMIT  \n  ?\nCREATE INDEX part_of_name ON customer (name(10));\nCREATE  INDEX  part_of_name  ON  customer  (\n  name( 10));\nalter table `sakila`.`t1` add index `idx_col`(`col`)\n\nALTER TABLE  \n  `sakila`.`t1` \nADD  \n  index  `idx_col` (\n    `col`)\nalter table `sakila`.`t1` add UNIQUE index `idx_col`(`col`)\n\nALTER TABLE  \n  `sakila`.`t1` \nADD  \n  UNIQUE  index  `idx_col` (\n    `col`)\nalter table `sakila`.`t1` add index `idx_ID`(`ID`)\n\nALTER TABLE  \n  `sakila`.`t1` \nADD  \n  index  `idx_ID` (\n    `ID`)\nALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;\n\nALTER TABLE  \n  t2  \nDROP  \n  COLUMN  c, \nDROP  \n  COLUMN  d;\nALTER TABLE T2 ADD COLUMN C int;\n\nALTER TABLE  \n  T2  \nADD  \n  COLUMN  C  int;\nALTER TABLE T2 ADD COLUMN D int FIRST;\n\nALTER TABLE  \n  T2  \nADD  \n  COLUMN  D  int  FIRST;\nALTER TABLE T2 ADD COLUMN E int AFTER D;\n\nALTER TABLE  \n  T2  \nADD  \n  COLUMN  E  int  \nAFTER  \n  D;\nALTER TABLE t1 RENAME COLUMN a TO b\n\nALTER TABLE  \n  t1  RENAME  COLUMN  a  TO  b\nALTER TABLE t1 RENAME INDEX idx_a TO idx_b\n\nALTER TABLE  \n  t1  RENAME  INDEX  idx_a  TO  idx_b\nALTER TABLE t1 RENAME KEY idx_a TO idx_b\n\nALTER TABLE  \n  t1  RENAME  KEY  idx_a  TO  idx_b\nALTER TABLE db.old_table RENAME new_table;\n\nALTER TABLE  \n  db. old_table  RENAME  new_table;\nALTER TABLE old_table RENAME TO new_table;\n\nALTER TABLE  \n  old_table  RENAME  TO  new_table;\nALTER TABLE old_table RENAME AS new_table;\n\nALTER TABLE  \n  old_table  RENAME  AS  new_table;\nALTER TABLE t1 MODIFY col1 BIGINT UNSIGNED DEFAULT 1 COMMENT 'my column';\n\nALTER TABLE  \n  t1  MODIFY  col1  BIGINT  UNSIGNED  DEFAULT  1  COMMENT  'my column';\nALTER TABLE t1 CHANGE b a INT NOT NULL;\n\nALTER TABLE  \n  t1  CHANGE  b  a  INT  NOT  NULL;\n/*!40000 select 1*/;\n\n/*!40000 select 1\n*/;\nSELECT * FROM film WHERE length = 86;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 86;\nSELECT * FROM film WHERE length IS NULL;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  IS  NULL;\nSELECT * FROM film HAVING title = 'abc';\n\nSELECT  \n  * \nFROM  \n  film  \nHAVING  \n  title  = 'abc';\nSELECT * FROM sakila.film WHERE length >= 60;\n\nSELECT  \n  * \nFROM  \n  sakila. film  \nWHERE  \n  LENGTH  >= 60;\nSELECT * FROM sakila.film WHERE length >= '60';\n\nSELECT  \n  * \nFROM  \n  sakila. film  \nWHERE  \n  LENGTH  >= '60';\nSELECT * FROM film WHERE length BETWEEN 60 AND 84;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  BETWEEN  60  \n  AND  84;\nSELECT * FROM film WHERE title LIKE 'AIR%';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  LIKE  'AIR%';\nSELECT * FROM film WHERE title IS NOT NULL;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  IS  NOT  NULL;\nSELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 114  \n  AND  title  = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10  \n  AND  title  = 'xyz';\nSELECT * FROM film WHERE length > 100 and language_id < 10;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10;\nSELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \n  AND  language_id  = 1  \nGROUP BY  \n  release_year;\nSELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  >= 123  \nGROUP BY  \n  release_year;\nSELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;\n\nSELECT  \n  release_year, language_id, SUM( LENGTH) \nFROM  \n  film  \nGROUP BY  \n  release_year, language_id;\nSELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year, (LENGTH+ language_id);\nSELECT release_year, sum(film_id) FROM film GROUP BY release_year;\n\nSELECT  \n  release_year, SUM( film_id) \nFROM  \n  film  \nGROUP BY  \n  release_year;\nSELECT * FROM address GROUP BY address,district;\n\nSELECT  \n  * \nFROM  \n  address  \nGROUP BY  \n  address, district;\nSELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  ABS( language_id) = 3  \nGROUP BY  \n  title;\nSELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;\n\nSELECT  \n  language_id  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  language_id;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  ASC, language_id  DESC;\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year  \nLIMIT  \n  10;\nSELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  \nLIMIT  \n  10;\nSELECT * FROM film ORDER BY release_year LIMIT 10;\n\nSELECT  \n  * \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10;\nSELECT film_id FROM film ORDER BY release_year LIMIT 10;\n\nSELECT  \n  film_id  \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10;\nSELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10;\nSELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  < 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10;\nSELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;\n\nSELECT  \n  * \nFROM  \n  customer  \nWHERE  \n  address_id  in  (224, 510) \nORDER BY  \n  last_name;\nSELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  release_year  = 2016  \n  AND  LENGTH  != 1  \nORDER BY  \n  title;\nSELECT title FROM film WHERE release_year = 1995;\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  release_year  = 1995;\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;\n\nSELECT  \n  title, replacement_cost  \nFROM  \n  film  \nWHERE  \n  language_id  = 5  \n  AND  LENGTH  = 70;\nSELECT title FROM film WHERE language_id > 5 AND length > 70;\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  language_id  > 5  \n  AND  LENGTH  > 70;\nSELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year;\nSELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year;\nSELECT * FROM film WHERE length > 100 ORDER BY release_year;\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  release_year;\nSELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  INNER JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL;\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL;\nSELECT country_id, last_update FROM city NATURAL JOIN country;\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  JOIN  country;\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  LEFT JOIN  country;\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  RIGHT JOIN  country;\nSELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;\n\nSELECT  \n  a. country_id, a. last_update  \nFROM  \n  city  a  STRAIGHT_JOIN  country  b  ON  a. country_id= b. country_id;\nSELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN  (SELECT c.city_id FROM sakila.city c);\n\nSELECT  \n  a. address, a. postal_code  \nFROM  \n  sakila. address  a  \nWHERE  \n  a. city_id  IN  (\nSELECT  \n  c. city_id  \nFROM  \n  sakila. city  c);\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC;\n\nSELECT  \n  city  \nFROM( \nSELECT  \n  city_id  \nFROM  \n  city  \nWHERE  \n  city  = \"A Corua (La Corua)\" \nORDER BY  \n  last_update  DESC  \nLIMIT  \n  50, 10) I  \n  JOIN  city  ON  (I. city_id  = city. city_id) \n  JOIN  country  ON  (country. country_id  = city. country_id) \nORDER BY  \n  city  DESC;\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\nDELETE  city, country  \nFROM  \n  city  \n  INNER JOIN  country  using  (country_id) \nWHERE  \n  city. city_id  = 1;\nDELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\nDELETE  city  \nFROM  \n  city  \n  LEFT JOIN  country  ON  city. country_id  = country. country_id  \nWHERE  \n  country. country  IS  NULL;\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\nDELETE  a1, a2  \nFROM  \n  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id;\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\n\nDELETE FROM  \n  a1, a2  USING  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id;\nDELETE FROM film WHERE length > 100;\n\nDELETE FROM  \n  film  \nWHERE  \n  LENGTH  > 100;\nUPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\n\nUPDATE  \n  city  \n  INNER JOIN  country  USING( country_id) \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10;\nUPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\n\nUPDATE  \n  city  \n  INNER JOIN  country  ON  city. country_id  = country. country_id  \n  INNER JOIN  address  ON  city. city_id  = address. city_id  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10;\nUPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\n\nUPDATE  \n  city, country  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. country_id  = country. country_id  \n  AND  city. city_id= 10;\nUPDATE film SET length = 10 WHERE language_id = 20;\n\nUPDATE  \n  film  \nSET  \n  LENGTH  = 10  \nWHERE  \n  language_id  = 20;\nINSERT INTO city (country_id) SELECT country_id FROM country;\nINSERT  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country;\nINSERT INTO city (country_id) VALUES (1),(2),(3);\nINSERT  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3);\nINSERT INTO city (country_id) VALUES (10);\nINSERT  INTO  city  (country_id) \nVALUES  \n  (10);\nINSERT INTO city (country_id) SELECT 10 FROM DUAL;\nINSERT  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL;\nREPLACE INTO city (country_id) SELECT country_id FROM country;\nREPLACE  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country;\nREPLACE INTO city (country_id) VALUES (1),(2),(3);\nREPLACE  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3);\nREPLACE INTO city (country_id) VALUES (10);\nREPLACE  INTO  city  (country_id) \nVALUES  \n  (10);\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL;\nREPLACE  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL;\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM  film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\n\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film;\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  language_id  = (\nSELECT  \n  language_id  \nFROM  \n  language  \nLIMIT  \n  1);\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id;\nSELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;\n\nSELECT  \n  * \nFROM  \n  (\nSELECT  \n  * \nFROM  \n  actor  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE'\n) t  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE' \nGROUP BY  \n  first_name;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id;\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  o. country_id  is  null  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  i. city_id  is  null;\nSELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;\n\nSELECT  \n  first_name, last_name, email  \nFROM  \n  customer  STRAIGHT_JOIN  address  ON  customer. address_id= address. address_id;\nSELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;\n\nSELECT  \n  ID, name  \nFROM  \n  (\nSELECT  \n  address  \nFROM  \n  customer_list  \nWHERE  \n  SID= 1  \nORDER BY  \n  phone  \nLIMIT  \n  50, 10) a  \n  JOIN  customer_list  l  ON  (a. address= l. address) \n  JOIN  city  c  ON  (c. city= l. city) \nORDER BY  \n  phone  desc;\nSELECT * FROM film WHERE date(last_update)='2006-02-15';\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  DATE( last_update) = '2006-02-15';\nSELECT last_update FROM film GROUP BY date(last_update);\n\nSELECT  \n  last_update  \nFROM  \n  film  \nGROUP BY  \n  DATE( last_update);\nSELECT last_update FROM film order by date(last_update);\n\nSELECT  \n  last_update  \nFROM  \n  film  \nORDER BY  \n  DATE( last_update);\nSELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;\n\nSELECT  \n  description  \nFROM  \n  film  \nWHERE  \n  description  IN( 'NEWS', \n  'asd'\n) \nGROUP BY  \n  description;\nalter table address add index idx_city_id(city_id);\n\nALTER TABLE  \n  address  \nADD  \n  index  idx_city_id( city_id);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`);\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`);\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`), \n      ADD  \n      index  `idx_store_film` (\n        `store_id`, `film_id`), \n              ADD  \n          index  `idx_store_film` (\n            `store_id`, `film_id`);\nSELECT\tDATE_FORMAT(t.last_update, '%Y-%m-%d'),\tCOUNT(DISTINCT (t.city))\tFROM city t WHERE t.last_update > '2018-10-22 00:00:00'\tAND t.city LIKE '%Chrome%'\tAND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d');\n\nSELECT  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n), \nCOUNT( DISTINCT  (\n  t. city)) \n  FROM  \n    city  t  \n  WHERE  \n    t. last_update  > '2018-10-22 00:00:00' \n    AND  t. city  LIKE  '%Chrome%' \n    AND  t. city  = 'eip' \n  GROUP BY  \n    DATE_FORMAT( t. last_update, '%Y-%m-%d'\n) \nORDER BY  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n);\ncreate table hello.t (id int unsigned);\ncreate  table  hello. t  (id  int  unsigned);\nselect * from tb where data >= '';\n\nSELECT  \n  * \nFROM  \n  tb  \nWHERE  \n  data  >= '';\nalter table tb alter column id drop default;\n\nALTER TABLE  \n  tb  alter  column  id  \nDROP  \n  DEFAULT;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d;\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film  \nWHERE  \n  last_update  > '2016-03-27 02:01:01'\n) as  d;\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d;\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film) as  d;\nselect 1\n"
  },
  {
    "path": "ast/testdata/TestPrintPrettyStmtNode.golden",
    "content": "[]ast.StmtNode{\n    &ast.SelectStmt{\n        dmlNode: ast.dmlNode{\n            stmtNode: ast.stmtNode{\n                node: ast.node{text:\"select 1\", offset:0},\n            },\n        },\n        SelectStmtOpts: &ast.SelectStmtOpts{\n            Distinct:        false,\n            SQLBigResult:    false,\n            SQLBufferResult: false,\n            SQLCache:        true,\n            SQLSmallResult:  false,\n            CalcFoundRows:   false,\n            StraightJoin:    false,\n            Priority:        0,\n            TableHints:      nil,\n            ExplicitAll:     false,\n        },\n        Distinct: false,\n        From:     (*ast.TableRefsClause)(nil),\n        Where:    nil,\n        Fields:   &ast.FieldList{\n            node:   ast.node{},\n            Fields: {\n                &ast.SelectField{\n                    node:     ast.node{text:\"1\", offset:0},\n                    Offset:   7,\n                    WildCard: (*ast.WildCardField)(nil),\n                    Expr:     &driver.ValueExpr{\n                        TexprNode: ast.exprNode{\n                            node: ast.node{text:\"\", offset:7},\n                            Type: types.FieldType{\n                                Tp:      0x8,\n                                Flag:    0x80,\n                                Flen:    1,\n                                Decimal: 0,\n                                Charset: \"binary\",\n                                Collate: \"binary\",\n                                Elems:   nil,\n                            },\n                            flag: 0x0,\n                        },\n                        Datum: types.Datum{\n                            k:         0x1,\n                            decimal:   0x0,\n                            length:    0x0,\n                            i:         1,\n                            collation: \"\",\n                            b:         nil,\n                            x:         nil,\n                        },\n                        projectionOffset: -1,\n                    },\n                    AsName:    model.CIStr{},\n                    Auxiliary: false,\n                },\n            },\n        },\n        GroupBy:          (*ast.GroupByClause)(nil),\n        Having:           (*ast.HavingClause)(nil),\n        WindowSpecs:      nil,\n        OrderBy:          (*ast.OrderByClause)(nil),\n        Limit:            (*ast.Limit)(nil),\n        LockInfo:         (*ast.SelectLockInfo)(nil),\n        TableHints:       nil,\n        IsInBraces:       false,\n        WithBeforeBraces: false,\n        QueryBlockOffset: 0,\n        SelectIntoOpt:    (*ast.SelectIntoOption)(nil),\n        AfterSetOperator: (*ast.SetOprType)(nil),\n        Kind:             0x0,\n        Lists:            nil,\n        With:             (*ast.WithClause)(nil),\n    },\n}\n"
  },
  {
    "path": "ast/testdata/TestPrintPrettyVitessStmtNode.golden",
    "content": "&sqlparser.Select{\n    Cache:       \"\",\n    Comments:    nil,\n    Distinct:    \"\",\n    Hints:       \"\",\n    SelectExprs: {\n        &sqlparser.AliasedExpr{\n            Expr: &sqlparser.SQLVal{\n                Type: 1,\n                Val:  {0x31},\n            },\n            As: sqlparser.ColIdent{},\n        },\n    },\n    From: {\n        &sqlparser.AliasedTableExpr{\n            Expr: sqlparser.TableName{\n                Name:      sqlparser.TableIdent{v:\"dual\"},\n                Qualifier: sqlparser.TableIdent{},\n            },\n            Partitions: nil,\n            As:         sqlparser.TableIdent{},\n            Hints:      (*sqlparser.IndexHints)(nil),\n        },\n    },\n    Where:   (*sqlparser.Where)(nil),\n    GroupBy: nil,\n    Having:  (*sqlparser.Where)(nil),\n    OrderBy: nil,\n    Limit:   (*sqlparser.Limit)(nil),\n    Lock:    \"\",\n}\n"
  },
  {
    "path": "ast/testdata/TestQueryType.golden",
    "content": "SELECT\nSELECT\nSELECT\nGRANT\nREVOKE\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nDELETE\nDELETE\nDELETE\nDELETE\nDELETE\nUPDATE\nUPDATE\nUPDATE\nUPDATE\nINSERT\nINSERT\nINSERT\nINSERT\nREPLACE\nREPLACE\nREPLACE\nREPLACE\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nSELECT\nALTER\nALTER\nALTER\nSELECT\nCREATE\nSELECT\nALTER\nSELECT\nSELECT\n"
  },
  {
    "path": "ast/testdata/TestSchemaMetaInfo.golden",
    "content": "use world_x;\n[`world_x`.`dual`]\nselect 1;\n[]\nsyntax error case\n[]\nselect * from ta join tb using (id)\n[`sakila`.`ta` `sakila`.`tb`]\nselect * from ta, tb limit 1\n[`sakila`.`ta` `sakila`.`tb`]\ndrop table tb\n[`sakila`.`tb`]\ndrop table db.tb\n[`db`.`tb`]\ndrop database db\n[`db`.`dual`]\ncreate database db\n[`db`.`dual`]\ncreate index idx_col on tbl (col)\n[`sakila`.`tbl`]\nDROP INDEX idx_col on tbl\n[`sakila`.`tbl`]\nSELECT * FROM film WHERE length = 86;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length IS NULL;\n[`sakila`.`film`]\nSELECT * FROM film HAVING title = 'abc';\n[`sakila`.`film`]\nSELECT * FROM sakila.film WHERE length >= 60;\n[`sakila`.`film`]\nSELECT * FROM sakila.film WHERE length >= '60';\n[`sakila`.`film`]\nSELECT * FROM film WHERE length BETWEEN 60 AND 84;\n[`sakila`.`film`]\nSELECT * FROM film WHERE title LIKE 'AIR%';\n[`sakila`.`film`]\nSELECT * FROM film WHERE title IS NOT NULL;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';\n[`sakila`.`film`]\nSELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';\n[`sakila`.`film`]\nSELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';\n[`sakila`.`film`]\nSELECT * FROM film WHERE length > 100 and language_id < 10;\n[`sakila`.`film`]\nSELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;\n[`sakila`.`film`]\nSELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;\n[`sakila`.`film`]\nSELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;\n[`sakila`.`film`]\nSELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);\n[`sakila`.`film`]\nSELECT release_year, sum(film_id) FROM film GROUP BY release_year;\n[`sakila`.`film`]\nSELECT * FROM address GROUP BY address,district;\n[`sakila`.`address`]\nSELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;\n[`sakila`.`film`]\nSELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;\n[`sakila`.`film`]\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;\n[`sakila`.`film`]\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;\n[`sakila`.`film`]\nSELECT * FROM film ORDER BY release_year LIMIT 10;\n[`sakila`.`film`]\nSELECT film_id FROM film ORDER BY release_year LIMIT 10;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;\n[`sakila`.`film`]\nSELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;\n[`sakila`.`customer`]\nSELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;\n[`sakila`.`film`]\nSELECT title FROM film WHERE release_year = 1995;\n[`sakila`.`film`]\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;\n[`sakila`.`film`]\nSELECT title FROM film WHERE language_id > 5 AND length > 70;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;\n[`sakila`.`film`]\nSELECT * FROM film WHERE length > 100 ORDER BY release_year;\n[`sakila`.`film`]\nSELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;\n[`sakila`.`city` `sakila`.`country`]\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\n[`sakila`.`city` `sakila`.`country`]\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n[`sakila`.`city` `sakila`.`country`]\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\n[`sakila`.`city` `sakila`.`country`]\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;\n[`sakila`.`city` `sakila`.`country`]\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n[`sakila`.`city` `sakila`.`country`]\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\n[`sakila`.`city` `sakila`.`country`]\nSELECT country_id, last_update FROM city NATURAL JOIN country;\n[`sakila`.`city` `sakila`.`country`]\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\n[`sakila`.`city` `sakila`.`country`]\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\n[`sakila`.`city` `sakila`.`country`]\nSELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;\n[`sakila`.`city` `sakila`.`country`]\nSELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN  (SELECT c.city_id FROM sakila.city c);\n[`sakila`.`address` `sakila`.`city`]\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC;\n[`sakila`.`city` `sakila`.`country`]\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\n[`sakila`.`city` `sakila`.`country`]\nDELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\n[`sakila`.`city` `sakila`.`country`]\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\n[`sakila`.`city` `sakila`.`country`]\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\n[`sakila`.`city` `sakila`.`country`]\nDELETE FROM film WHERE length > 100;\n[`sakila`.`film`]\nUPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\n[`sakila`.`city` `sakila`.`country`]\nUPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\n[`sakila`.`address` `sakila`.`city` `sakila`.`country`]\nUPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\n[`sakila`.`city` `sakila`.`country`]\nUPDATE film SET length = 10 WHERE language_id = 20;\n[`sakila`.`film`]\nINSERT INTO city (country_id) SELECT country_id FROM country;\n[`sakila`.`city` `sakila`.`country`]\nINSERT INTO city (country_id) VALUES (1),(2),(3);\n[`sakila`.`city`]\nINSERT INTO city (country_id) VALUES (10);\n[`sakila`.`city`]\nINSERT INTO city (country_id) SELECT 10 FROM DUAL;\n[`sakila`.`city`]\nREPLACE INTO city (country_id) SELECT country_id FROM country;\n[`sakila`.`city` `sakila`.`country`]\nREPLACE INTO city (country_id) VALUES (1),(2),(3);\n[`sakila`.`city`]\nREPLACE INTO city (country_id) VALUES (10);\n[`sakila`.`city`]\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL;\n[`sakila`.`city`]\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM  film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\n[`sakila`.`film`]\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\n[`sakila`.`film` `sakila`.`language`]\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\n[`sakila`.`city` `sakila`.`country`]\nSELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;\n[`sakila`.`actor`]\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\n[`sakila`.`city` `sakila`.`country`]\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;\n[`sakila`.`city` `sakila`.`country`]\nSELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;\n[`sakila`.`address` `sakila`.`customer`]\nSELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;\n[`sakila`.`city` `sakila`.`customer_list`]\nSELECT * FROM film WHERE date(last_update)='2006-02-15';\n[`sakila`.`film`]\nSELECT last_update FROM film GROUP BY date(last_update);\n[`sakila`.`film`]\nSELECT last_update FROM film order by date(last_update);\n[`sakila`.`film`]\nSELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;\n[`sakila`.`film`]\nalter table address add index idx_city_id(city_id);\n[`sakila`.`address`]\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`);\n[`sakila`.`inventory`]\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);\n[`sakila`.`inventory`]\nSELECT\tDATE_FORMAT(t.last_update, '%Y-%m-%d'),\tCOUNT(DISTINCT (t.city))\tFROM city t WHERE t.last_update > '2018-10-22 00:00:00'\tAND t.city LIKE '%Chrome%'\tAND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d');\n[`sakila`.`city`]\ncreate table hello.t (id int unsigned);\n[`hello`.`t`]\nselect * from tb where data >= '';\n[`sakila`.`tb`]\nalter table tb alter column id drop default;\n[`sakila`.`tb`]\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d;\n[`sakila`.`film`]\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d;\n[`sakila`.`film`]\n"
  },
  {
    "path": "ast/testdata/TestSplitStatement.golden",
    "content": "0 select * from test;\n1 select 'asd;fas', col from test;\n2 -- select * from test;hello\n3 #select * from test;hello\n4 select * /*comment*/from test;\n5 select * /*comment;*/from test;\n6 select * /*comment\n\t\t;*/\n\t\tfrom test;\n7 select * from test\n8 /*comment*/\n9 /*comment*/;\n10 --\n11 -- comment\n12 # comment\n13 select\n*\n-- comment\nfrom tb\nwhere col = 1\n14 select\n* --\nfrom tb\nwhere col = 1\n15 select\n* #\nfrom tb\nwhere col = 1\n16 select\n*\n--\nfrom tb\nwhere col = 1\n17 select * from\n-- comment\ntb;\n18 \n-- comment\n19 INSERT /*+ SET_VAR(foreign_key_checks=OFF) */ INTO t2 VALUES(2);\n20 select /*!50000 1,*/ 1;\n21 UPDATE xxx SET c1=' LOGGER.error(\"\"); }' WHERE id = 2 ;\n22 UPDATE `xxx` SET aaa='a;' WHERE `id` = 15;\n23 UPDATE `xxx` SET aaa='a -- b' WHERE `id` = 15;\n0 select * from test\\G\n1 select 'hello\\Gworld', col from test\\G\n2 -- select * from test\\Ghello\n3 #select * from test\\Ghello\n4 select * /*comment*/from test\\G\n5 select * /*comment;*/from test\\G\n6 select * /*comment\n        \\\\G*/\n        from test\\\\G\n"
  },
  {
    "path": "ast/testdata/TestStmtNode2JSON.golden",
    "content": "[\n  {\n    \"text\": \"select 1\",\n    \"offset\": 0,\n    \"SQLBigResult\": false,\n    \"SQLBufferResult\": false,\n    \"SQLCache\": true,\n    \"SQLSmallResult\": false,\n    \"CalcFoundRows\": false,\n    \"StraightJoin\": false,\n    \"Priority\": 0,\n    \"ExplicitAll\": false,\n    \"Distinct\": false,\n    \"From\": null,\n    \"Where\": null,\n    \"Fields\": {\n      \"text\": \"\",\n      \"offset\": 0,\n      \"Fields\": [\n        {\n          \"text\": \"1\",\n          \"offset\": 0,\n          \"Offset\": 7,\n          \"WildCard\": null,\n          \"Expr\": {\n            \"text\": \"\",\n            \"offset\": 7,\n            \"Type\": {\n              \"Tp\": 8,\n              \"Flag\": 128,\n              \"Flen\": 1,\n              \"Decimal\": 0,\n              \"Charset\": \"binary\",\n              \"Collate\": \"binary\",\n              \"Elems\": null\n            },\n            \"flag\": 0,\n            \"k\": 1,\n            \"decimal\": 0,\n            \"length\": 0,\n            \"i\": 1,\n            \"collation\": \"\",\n            \"b\": null,\n            \"x\": null,\n            \"projectionOffset\": -1\n          },\n          \"AsName\": {\n            \"O\": \"\",\n            \"L\": \"\"\n          },\n          \"Auxiliary\": false\n        }\n      ]\n    },\n    \"GroupBy\": null,\n    \"Having\": null,\n    \"WindowSpecs\": null,\n    \"OrderBy\": null,\n    \"Limit\": null,\n    \"LockInfo\": null,\n    \"TableHints\": null,\n    \"IsInBraces\": false,\n    \"WithBeforeBraces\": false,\n    \"QueryBlockOffset\": 0,\n    \"SelectIntoOpt\": null,\n    \"AfterSetOperator\": null,\n    \"Kind\": 0,\n    \"Lists\": null,\n    \"With\": null\n  }\n]\n\n"
  },
  {
    "path": "ast/testdata/TestTokenize.golden",
    "content": "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} {7 = 0} {0   0} {10 86; 0}]\nSELECT * FROM film WHERE length IS NULL;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {1 IS  0} {1 NULL; 0}]\nSELECT * FROM film HAVING title = 'abc';\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 HAVING  0} {1 title  0} {7 = 0} {0   0} {2 'abc' 0} {7 ; 0}]\nSELECT * FROM sakila.film WHERE length >= 60;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 sakila. 0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 >= 0} {0   0} {10 60; 0}]\nSELECT * FROM sakila.film WHERE length >= '60';\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 sakila. 0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 >= 0} {0   0} {2 '60' 0} {7 ; 0}]\nSELECT * FROM film WHERE length BETWEEN 60 AND 84;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {1 BETWEEN  0} {10 60  0} {6 AND  0} {10 84; 0}]\nSELECT * FROM film WHERE title LIKE 'AIR%';\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 title  0} {1 LIKE  0} {2 'AIR%' 0} {7 ; 0}]\nSELECT * FROM film WHERE title IS NOT NULL;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 title  0} {1 IS  0} {1 NOT  0} {1 NULL; 0}]\nSELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 114  0} {6 AND  0} {1 title  0} {7 = 0} {0   0} {2 'ALABAMA DEVIL' 0} {7 ; 0}]\nSELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 > 0} {0   0} {10 100  0} {6 AND  0} {1 title  0} {7 = 0} {0   0} {2 'ALABAMA DEVIL' 0} {7 ; 0}]\nSELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 > 0} {0   0} {10 100  0} {6 AND  0} {1 language_id  0} {7 < 0} {0   0} {10 10  0} {6 AND  0} {1 title  0} {7 = 0} {0   0} {2 'xyz' 0} {7 ; 0}]\nSELECT * FROM film WHERE length > 100 and language_id < 10;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 > 0} {0   0} {10 100  0} {6 AND  0} {1 language_id  0} {7 < 0} {0   0} {10 10; 0}]\nSELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;\n[{5 SELECT  0} {1 release_year, 0} {0   0} {4 SUM( 0} {4 LENGTH) 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 123  0} {6 AND  0} {1 language_id  0} {7 = 0} {0   0} {10 1  0} {5 GROUP BY  0} {1 release_year; 0}]\nSELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;\n[{5 SELECT  0} {1 release_year, 0} {0   0} {4 SUM( 0} {4 LENGTH) 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 >= 0} {0   0} {10 123  0} {5 GROUP BY  0} {1 release_year; 0}]\nSELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;\n[{5 SELECT  0} {1 release_year, 0} {0   0} {1 language_id, 0} {0   0} {4 SUM( 0} {4 LENGTH) 0} {0   0} {5 FROM  0} {1 film  0} {5 GROUP BY  0} {1 release_year, 0} {0   0} {1 language_id; 0}]\nSELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);\n[{5 SELECT  0} {1 release_year, 0} {0   0} {4 SUM( 0} {4 LENGTH) 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 123  0} {5 GROUP BY  0} {1 release_year, 0} {7 ( 0} {4 LENGTH+ 0} {1 language_id) 0} {7 ; 0}]\nSELECT release_year, sum(film_id) FROM film GROUP BY release_year;\n[{5 SELECT  0} {1 release_year, 0} {0   0} {4 SUM( 0} {1 film_id) 0} {0   0} {5 FROM  0} {1 film  0} {5 GROUP BY  0} {1 release_year; 0}]\nSELECT * FROM address GROUP BY address,district;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 address  0} {5 GROUP BY  0} {1 address, 0} {1 district; 0}]\nSELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;\n[{5 SELECT  0} {1 title  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 ABS( 0} {1 language_id) 0} {0   0} {7 = 0} {0   0} {10 3  0} {5 GROUP BY  0} {1 title; 0}]\nSELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;\n[{5 SELECT  0} {1 language_id  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 123  0} {5 GROUP BY  0} {1 release_year  0} {5 ORDER BY  0} {1 language_id; 0}]\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;\n[{5 SELECT  0} {1 release_year  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 123  0} {5 GROUP BY  0} {1 release_year  0} {5 ORDER BY  0} {1 release_year; 0}]\nSELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 123  0} {5 ORDER BY  0} {1 release_year  0} {1 ASC, 0} {0   0} {1 language_id  0} {1 DESC; 0}]\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\n[{5 SELECT  0} {1 release_year  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 123  0} {5 GROUP BY  0} {1 release_year  0} {5 ORDER BY  0} {1 release_year  0} {5 LIMIT  0} {10 10; 0}]\nSELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 123  0} {5 ORDER BY  0} {1 release_year  0} {5 LIMIT  0} {10 10; 0}]\nSELECT * FROM film ORDER BY release_year LIMIT 10;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 ORDER BY  0} {1 release_year  0} {5 LIMIT  0} {10 10; 0}]\nSELECT film_id FROM film ORDER BY release_year LIMIT 10;\n[{5 SELECT  0} {1 film_id  0} {5 FROM  0} {1 film  0} {5 ORDER BY  0} {1 release_year  0} {5 LIMIT  0} {10 10; 0}]\nSELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 > 0} {0   0} {10 100  0} {5 ORDER BY  0} {4 LENGTH  0} {5 LIMIT  0} {10 10; 0}]\nSELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 < 0} {0   0} {10 100  0} {5 ORDER BY  0} {4 LENGTH  0} {5 LIMIT  0} {10 10; 0}]\nSELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 customer  0} {5 WHERE  0} {1 address_id  0} {1 in  0} {7 ( 0} {10 224, 0} {10 510) 0} {0   0} {5 ORDER BY  0} {1 last_name; 0}]\nSELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 release_year  0} {7 = 0} {0   0} {10 2016  0} {6 AND  0} {4 LENGTH  0} {7 != 0} {0   0} {10 1  0} {5 ORDER BY  0} {1 title; 0}]\nSELECT title FROM film WHERE release_year = 1995;\n[{5 SELECT  0} {1 title  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 release_year  0} {7 = 0} {0   0} {10 1995; 0}]\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;\n[{5 SELECT  0} {1 title, 0} {0   0} {1 replacement_cost  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 language_id  0} {7 = 0} {0   0} {10 5  0} {6 AND  0} {4 LENGTH  0} {7 = 0} {0   0} {10 70; 0}]\nSELECT title FROM film WHERE language_id > 5 AND length > 70;\n[{5 SELECT  0} {1 title  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 language_id  0} {7 > 0} {0   0} {10 5  0} {6 AND  0} {4 LENGTH  0} {7 > 0} {0   0} {10 70; 0}]\nSELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 = 0} {0   0} {10 100  0} {6 AND  0} {1 title  0} {7 = 0} {0   0} {2 'xyz' 0} {0   0} {5 ORDER BY  0} {1 release_year; 0}]\nSELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 > 0} {0   0} {10 100  0} {6 AND  0} {1 title  0} {7 = 0} {0   0} {2 'xyz' 0} {0   0} {5 ORDER BY  0} {1 release_year; 0}]\nSELECT * FROM film WHERE length > 100 ORDER BY release_year;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 > 0} {0   0} {10 100  0} {5 ORDER BY  0} {1 release_year; 0}]\nSELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 INNER JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id; 0}]\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 LEFT JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id; 0}]\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 RIGHT JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id; 0}]\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 LEFT JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id  0} {5 WHERE  0} {1 b. 0} {1 last_update  0} {1 IS  0} {1 NULL; 0}]\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 RIGHT JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id  0} {5 WHERE  0} {1 a. 0} {1 last_update  0} {1 IS  0} {1 NULL; 0}]\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 LEFT JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id  0} {5 UNION  0} {5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 RIGHT JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id; 0}]\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 RIGHT JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id  0} {5 WHERE  0} {1 a. 0} {1 last_update  0} {1 IS  0} {1 NULL  0} {5 UNION  0} {5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 a  0} {6 LEFT JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id  0} {5 WHERE  0} {1 b. 0} {1 last_update  0} {1 IS  0} {1 NULL; 0}]\nSELECT country_id, last_update FROM city NATURAL JOIN country;\n[{5 SELECT  0} {1 country_id, 0} {0   0} {1 last_update  0} {5 FROM  0} {1 city  0} {1 NATURAL  0} {6 JOIN  0} {1 country; 0}]\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\n[{5 SELECT  0} {1 country_id, 0} {0   0} {1 last_update  0} {5 FROM  0} {1 city  0} {1 NATURAL  0} {6 LEFT JOIN  0} {1 country; 0}]\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\n[{5 SELECT  0} {1 country_id, 0} {0   0} {1 last_update  0} {5 FROM  0} {1 city  0} {1 NATURAL  0} {6 RIGHT JOIN  0} {1 country; 0}]\nSELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;\n[{5 SELECT  0} {1 a. 0} {1 country_id, 0} {0   0} {1 a. 0} {1 last_update  0} {5 FROM  0} {1 city  0} {1 a  0} {1 STRAIGHT_JOIN  0} {1 country  0} {1 b  0} {1 ON  0} {1 a. 0} {1 country_id= 0} {1 b. 0} {1 country_id; 0}]\nSELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN  (SELECT c.city_id FROM sakila.city c);\n[{5 SELECT  0} {1 a. 0} {1 address, 0} {0   0} {1 a. 0} {1 postal_code  0} {5 FROM  0} {1 sakila. 0} {1 address  0} {1 a  0} {5 WHERE  0} {1 a. 0} {1 city_id  0} {1 IN  0} {0   0} {7 ( 0} {5 SELECT  0} {1 c. 0} {1 city_id  0} {5 FROM  0} {1 sakila. 0} {1 city  0} {1 c) 0} {7 ; 0}]\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC;\n[{5 SELECT  0} {1 city  0} {5 FROM( 0} {0   0} {5 SELECT  0} {1 city_id  0} {5 FROM  0} {1 city  0} {5 WHERE  0} {1 city  0} {7 = 0} {0   0} {2 \"A Corua (La Corua)\" 0} {0   0} {5 ORDER BY  0} {1 last_update  0} {1 DESC  0} {5 LIMIT  0} {10 50, 0} {0   0} {10 10) 0} {0   0} {1 I  0} {6 JOIN  0} {1 city  0} {1 ON  0} {7 ( 0} {1 I. 0} {1 city_id  0} {7 = 0} {0   0} {1 city. 0} {1 city_id) 0} {0   0} {6 JOIN  0} {1 country  0} {1 ON  0} {7 ( 0} {1 country. 0} {1 country_id  0} {7 = 0} {0   0} {1 city. 0} {1 country_id) 0} {0   0} {5 ORDER BY  0} {1 city  0} {1 DESC; 0}]\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\n[{1 DELETE  0} {1 city, 0} {0   0} {1 country  0} {5 FROM  0} {1 city  0} {6 INNER JOIN  0} {1 country  0} {1 using  0} {7 ( 0} {1 country_id) 0} {0   0} {5 WHERE  0} {1 city. 0} {1 city_id  0} {7 = 0} {0   0} {10 1; 0}]\nDELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\n[{1 DELETE  0} {1 city  0} {5 FROM  0} {1 city  0} {6 LEFT JOIN  0} {1 country  0} {1 ON  0} {1 city. 0} {1 country_id  0} {7 = 0} {0   0} {1 country. 0} {1 country_id  0} {5 WHERE  0} {1 country. 0} {1 country  0} {1 IS  0} {1 NULL; 0}]\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\n[{1 DELETE  0} {1 a1, 0} {0   0} {1 a2  0} {5 FROM  0} {1 city  0} {1 AS  0} {1 a1  0} {6 INNER JOIN  0} {1 country  0} {1 AS  0} {1 a2  0} {5 WHERE  0} {1 a1. 0} {1 country_id= 0} {1 a2. 0} {1 country_id; 0}]\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\n[{5 DELETE FROM  0} {1 a1, 0} {0   0} {1 a2  0} {1 USING  0} {1 city  0} {1 AS  0} {1 a1  0} {6 INNER JOIN  0} {1 country  0} {1 AS  0} {1 a2  0} {5 WHERE  0} {1 a1. 0} {1 country_id= 0} {1 a2. 0} {1 country_id; 0}]\nDELETE FROM film WHERE length > 100;\n[{5 DELETE FROM  0} {1 film  0} {5 WHERE  0} {4 LENGTH  0} {7 > 0} {0   0} {10 100; 0}]\nUPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\n[{5 UPDATE  0} {1 city  0} {6 INNER JOIN  0} {1 country  0} {1 USING( 0} {1 country_id) 0} {0   0} {5 SET  0} {1 city. 0} {1 city  0} {7 = 0} {0   0} {2 'Abha' 0} {7 , 0} {0   0} {1 city. 0} {1 last_update  0} {7 = 0} {0   0} {2 '2006-02-15 04:45:25' 0} {7 , 0} {0   0} {1 country. 0} {1 country  0} {7 = 0} {0   0} {2 'Afghanistan' 0} {0   0} {5 WHERE  0} {1 city. 0} {1 city_id= 0} {10 10; 0}]\nUPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\n[{5 UPDATE  0} {1 city  0} {6 INNER JOIN  0} {1 country  0} {1 ON  0} {1 city. 0} {1 country_id  0} {7 = 0} {0   0} {1 country. 0} {1 country_id  0} {6 INNER JOIN  0} {1 address  0} {1 ON  0} {1 city. 0} {1 city_id  0} {7 = 0} {0   0} {1 address. 0} {1 city_id  0} {5 SET  0} {1 city. 0} {1 city  0} {7 = 0} {0   0} {2 'Abha' 0} {7 , 0} {0   0} {1 city. 0} {1 last_update  0} {7 = 0} {0   0} {2 '2006-02-15 04:45:25' 0} {7 , 0} {0   0} {1 country. 0} {1 country  0} {7 = 0} {0   0} {2 'Afghanistan' 0} {0   0} {5 WHERE  0} {1 city. 0} {1 city_id= 0} {10 10; 0}]\nUPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\n[{5 UPDATE  0} {1 city, 0} {0   0} {1 country  0} {5 SET  0} {1 city. 0} {1 city  0} {7 = 0} {0   0} {2 'Abha' 0} {7 , 0} {0   0} {1 city. 0} {1 last_update  0} {7 = 0} {0   0} {2 '2006-02-15 04:45:25' 0} {7 , 0} {0   0} {1 country. 0} {1 country  0} {7 = 0} {0   0} {2 'Afghanistan' 0} {0   0} {5 WHERE  0} {1 city. 0} {1 country_id  0} {7 = 0} {0   0} {1 country. 0} {1 country_id  0} {6 AND  0} {1 city. 0} {1 city_id= 0} {10 10; 0}]\nUPDATE film SET length = 10 WHERE language_id = 20;\n[{5 UPDATE  0} {1 film  0} {5 SET  0} {4 LENGTH  0} {7 = 0} {0   0} {10 10  0} {5 WHERE  0} {1 language_id  0} {7 = 0} {0   0} {10 20; 0}]\nINSERT INTO city (country_id) SELECT country_id FROM country;\n[{4 INSERT  0} {1 INTO  0} {1 city  0} {7 ( 0} {1 country_id) 0} {0   0} {5 SELECT  0} {1 country_id  0} {5 FROM  0} {1 country; 0}]\nINSERT INTO city (country_id) VALUES (1),(2),(3);\n[{4 INSERT  0} {1 INTO  0} {1 city  0} {7 ( 0} {1 country_id) 0} {0   0} {5 VALUES  0} {7 ( 0} {10 1) 0} {7 , 0} {7 ( 0} {10 2) 0} {7 , 0} {7 ( 0} {10 3) 0} {7 ; 0}]\nINSERT INTO city (country_id) VALUES (10);\n[{4 INSERT  0} {1 INTO  0} {1 city  0} {7 ( 0} {1 country_id) 0} {0   0} {5 VALUES  0} {7 ( 0} {10 10) 0} {7 ; 0}]\nINSERT INTO city (country_id) SELECT 10 FROM DUAL;\n[{4 INSERT  0} {1 INTO  0} {1 city  0} {7 ( 0} {1 country_id) 0} {0   0} {5 SELECT  0} {10 10  0} {5 FROM  0} {1 DUAL; 0}]\nREPLACE INTO city (country_id) SELECT country_id FROM country;\n[{4 REPLACE  0} {1 INTO  0} {1 city  0} {7 ( 0} {1 country_id) 0} {0   0} {5 SELECT  0} {1 country_id  0} {5 FROM  0} {1 country; 0}]\nREPLACE INTO city (country_id) VALUES (1),(2),(3);\n[{4 REPLACE  0} {1 INTO  0} {1 city  0} {7 ( 0} {1 country_id) 0} {0   0} {5 VALUES  0} {7 ( 0} {10 1) 0} {7 , 0} {7 ( 0} {10 2) 0} {7 , 0} {7 ( 0} {10 3) 0} {7 ; 0}]\nREPLACE INTO city (country_id) VALUES (10);\n[{4 REPLACE  0} {1 INTO  0} {1 city  0} {7 ( 0} {1 country_id) 0} {0   0} {5 VALUES  0} {7 ( 0} {10 10) 0} {7 ; 0}]\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL;\n[{4 REPLACE  0} {1 INTO  0} {1 city  0} {7 ( 0} {1 country_id) 0} {0   0} {5 SELECT  0} {10 10  0} {5 FROM  0} {1 DUAL; 0}]\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM  film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\n[{5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {7 ( 0} {0   0} {5 SELECT  0} {1 film_id  0} {5 FROM  0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film  0} {7 ) 0} {0   0} {1 film; 0}]\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 language_id  0} {7 = 0} {0   0} {7 ( 0} {5 SELECT  0} {1 language_id  0} {5 FROM  0} {1 language  0} {5 LIMIT  0} {10 1) 0} {7 ; 0}]\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 i  0} {6 LEFT JOIN  0} {1 country  0} {1 o  0} {1 ON  0} {1 i. 0} {1 city_id= 0} {1 o. 0} {1 country_id  0} {5 UNION  0} {5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 i  0} {6 RIGHT JOIN  0} {1 country  0} {1 o  0} {1 ON  0} {1 i. 0} {1 city_id= 0} {1 o. 0} {1 country_id; 0}]\nSELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {7 ( 0} {5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 actor  0} {5 WHERE  0} {1 last_update= 0} {2 '2006-02-15 04:34:33' 0} {0   0} {6 AND  0} {1 last_name= 0} {2 'CHASE' 0} {7 ) 0} {0   0} {1 t  0} {5 WHERE  0} {1 last_update= 0} {2 '2006-02-15 04:34:33' 0} {0   0} {6 AND  0} {1 last_name= 0} {2 'CHASE' 0} {0   0} {5 GROUP BY  0} {1 first_name; 0}]\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 i  0} {6 LEFT JOIN  0} {1 country  0} {1 o  0} {1 ON  0} {1 i. 0} {1 city_id= 0} {1 o. 0} {1 country_id  0} {5 UNION  0} {5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 i  0} {6 RIGHT JOIN  0} {1 country  0} {1 o  0} {1 ON  0} {1 i. 0} {1 city_id= 0} {1 o. 0} {1 country_id; 0}]\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 i  0} {6 LEFT JOIN  0} {1 country  0} {1 o  0} {1 ON  0} {1 i. 0} {1 city_id= 0} {1 o. 0} {1 country_id  0} {5 WHERE  0} {1 o. 0} {1 country_id  0} {1 is  0} {1 null  0} {5 UNION  0} {5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 city  0} {1 i  0} {6 RIGHT JOIN  0} {1 country  0} {1 o  0} {1 ON  0} {1 i. 0} {1 city_id= 0} {1 o. 0} {1 country_id  0} {5 WHERE  0} {1 i. 0} {1 city_id  0} {1 is  0} {1 null; 0}]\nSELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;\n[{5 SELECT  0} {1 first_name, 0} {1 last_name, 0} {1 email  0} {5 FROM  0} {1 customer  0} {1 STRAIGHT_JOIN  0} {1 address  0} {1 ON  0} {1 customer. 0} {1 address_id= 0} {1 address. 0} {1 address_id; 0}]\nSELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;\n[{5 SELECT  0} {1 ID, 0} {1 name  0} {5 FROM  0} {7 ( 0} {5 SELECT  0} {1 address  0} {5 FROM  0} {1 customer_list  0} {5 WHERE  0} {1 SID= 0} {10 1  0} {5 ORDER BY  0} {1 phone  0} {5 LIMIT  0} {10 50, 0} {10 10) 0} {0   0} {1 a  0} {6 JOIN  0} {1 customer_list  0} {1 l  0} {1 ON  0} {7 ( 0} {1 a. 0} {1 address= 0} {1 l. 0} {1 address) 0} {0   0} {6 JOIN  0} {1 city  0} {1 c  0} {1 ON  0} {7 ( 0} {1 c. 0} {1 city= 0} {1 l. 0} {1 city) 0} {0   0} {5 ORDER BY  0} {1 phone  0} {1 desc; 0}]\nSELECT * FROM film WHERE date(last_update)='2006-02-15';\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 film  0} {5 WHERE  0} {4 DATE( 0} {1 last_update) 0} {7 = 0} {2 '2006-02-15' 0} {7 ; 0}]\nSELECT last_update FROM film GROUP BY date(last_update);\n[{5 SELECT  0} {1 last_update  0} {5 FROM  0} {1 film  0} {5 GROUP BY  0} {4 DATE( 0} {1 last_update) 0} {7 ; 0}]\nSELECT last_update FROM film order by date(last_update);\n[{5 SELECT  0} {1 last_update  0} {5 FROM  0} {1 film  0} {5 ORDER BY  0} {4 DATE( 0} {1 last_update) 0} {7 ; 0}]\nSELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;\n[{5 SELECT  0} {1 description  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 description  0} {1 IN( 0} {2 'NEWS' 0} {7 , 0} {2 'asd' 0} {7 ) 0} {0   0} {5 GROUP BY  0} {1 description; 0}]\nalter table address add index idx_city_id(city_id);\n[{5 ALTER TABLE  0} {1 address  0} {5 ADD  0} {1 index  0} {1 idx_city_id( 0} {1 city_id) 0} {7 ; 0}]\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`);\n[{5 ALTER TABLE  0} {1 inventory  0} {5 ADD  0} {1 index  0} {3 `idx_store_film` 0} {0   0} {7 ( 0} {3 `store_id` 0} {7 , 0} {3 `film_id` 0} {7 ) 0} {7 ; 0}]\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);\n[{5 ALTER TABLE  0} {1 inventory  0} {5 ADD  0} {1 index  0} {3 `idx_store_film` 0} {0   0} {7 ( 0} {3 `store_id` 0} {7 , 0} {3 `film_id` 0} {7 ) 0} {7 , 0} {5 ADD  0} {1 index  0} {3 `idx_store_film` 0} {0   0} {7 ( 0} {3 `store_id` 0} {7 , 0} {3 `film_id` 0} {7 ) 0} {7 , 0} {5 ADD  0} {1 index  0} {3 `idx_store_film` 0} {0   0} {7 ( 0} {3 `store_id` 0} {7 , 0} {3 `film_id` 0} {7 ) 0} {7 ; 0}]\nSELECT\tDATE_FORMAT(t.last_update, '%Y-%m-%d'),\tCOUNT(DISTINCT (t.city))\tFROM city t WHERE t.last_update > '2018-10-22 00:00:00'\tAND t.city LIKE '%Chrome%'\tAND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d');\n[{5 SELECT\t 0} {4 DATE_FORMAT( 0} {1 t. 0} {1 last_update, 0} {0   0} {2 '%Y-%m-%d' 0} {7 ) 0} {7 , 0} {0   0} {4 COUNT( 0} {1 DISTINCT  0} {7 ( 0} {1 t. 0} {1 city) 0} {7 ) 0} {0   0} {5 FROM  0} {1 city  0} {1 t  0} {5 WHERE  0} {1 t. 0} {1 last_update  0} {7 > 0} {0   0} {2 '2018-10-22 00:00:00' 0} {0   0} {6 AND  0} {1 t. 0} {1 city  0} {1 LIKE  0} {2 '%Chrome%' 0} {0   0} {6 AND  0} {1 t. 0} {1 city  0} {7 = 0} {0   0} {2 'eip' 0} {0   0} {5 GROUP BY  0} {4 DATE_FORMAT( 0} {1 t. 0} {1 last_update, 0} {0   0} {2 '%Y-%m-%d' 0} {7 ) 0} {0   0} {5 ORDER BY  0} {4 DATE_FORMAT( 0} {1 t. 0} {1 last_update, 0} {0   0} {2 '%Y-%m-%d' 0} {7 ) 0} {7 ; 0}]\ncreate table hello.t (id int unsigned);\n[{1 create  0} {1 table  0} {1 hello. 0} {1 t  0} {7 ( 0} {1 id  0} {1 int  0} {1 unsigned) 0} {7 ; 0}]\nselect * from tb where data >= '';\n[{5 SELECT  0} {7 * 0} {0   0} {5 FROM  0} {1 tb  0} {5 WHERE  0} {1 data  0} {7 >= 0} {0   0} {2 '' 0} {7 ; 0}]\nalter table tb alter column id drop default;\n[{5 ALTER TABLE  0} {1 tb  0} {1 alter  0} {1 column  0} {1 id  0} {5 DROP  0} {4 DEFAULT; 0}]\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d;\n[{5 SELECT  0} {1 maxId, 0} {0   0} {1 minId  0} {5 FROM  0} {7 ( 0} {5 SELECT  0} {4 MAX( 0} {1 film_id) 0} {0   0} {1 maxId, 0} {0   0} {4 MIN( 0} {1 film_id) 0} {0   0} {1 minId  0} {5 FROM  0} {1 film  0} {5 WHERE  0} {1 last_update  0} {7 > 0} {0   0} {2 '2016-03-27 02:01:01' 0} {7 ) 0} {0   0} {1 as  0} {1 d; 0}]\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d;\n[{5 SELECT  0} {1 maxId, 0} {0   0} {1 minId  0} {5 FROM  0} {7 ( 0} {5 SELECT  0} {4 MAX( 0} {1 film_id) 0} {0   0} {1 maxId, 0} {0   0} {4 MIN( 0} {1 film_id) 0} {0   0} {1 minId  0} {5 FROM  0} {1 film) 0} {0   0} {1 as  0} {1 d; 0}]\n"
  },
  {
    "path": "ast/testdata/TestTokenizer.golden",
    "content": "[]ast.Token{\n    {Type:57348, Val:\"select\", i:0},\n    {Type:57407, Val:\"-- comment 1\", i:0},\n}\n[]ast.Token{\n    {Type:57348, Val:\"select\", i:0},\n    {Type:57397, Val:\"c1\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"c2\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"c3\", i:0},\n    {Type:57353, Val:\"from\", i:0},\n    {Type:57397, Val:\"t1\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"t2\", i:0},\n    {Type:57385, Val:\"join\", i:0},\n    {Type:57397, Val:\"t3\", i:0},\n    {Type:57395, Val:\"on\", i:0},\n    {Type:57397, Val:\"t1\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"c1\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57397, Val:\"t2\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"c1\", i:0},\n    {Type:57415, Val:\"and\", i:0},\n    {Type:57397, Val:\"t1\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"c3\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57397, Val:\"t3\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"c1\", i:0},\n    {Type:57354, Val:\"where\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:62, Val:\">\", i:0},\n    {Type:57402, Val:\"1000\", i:0},\n}\n[]ast.Token{\n    {Type:57348, Val:\"select\", i:0},\n    {Type:57397, Val:\"sourcetable\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57456, Val:\"if\", i:0},\n    {Type:40, Val:\"(\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"lastcontent\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57405, Val:\":v1\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"lastupdate\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"lastcontent\", i:0},\n    {Type:41, Val:\")\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"lastactivity\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"totalcount\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"activity\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"type\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"class\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"type\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:40, Val:\"(\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"nodeoptions\", i:0},\n    {Type:38, Val:\"&\", i:0},\n    {Type:57405, Val:\":v2\", i:0},\n    {Type:41, Val:\")\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"nounsubscribe\", i:0},\n    {Type:57353, Val:\"from\", i:0},\n    {Type:57397, Val:\"node\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:57389, Val:\"inner\", i:0},\n    {Type:57385, Val:\"join\", i:0},\n    {Type:57397, Val:\"contenttype\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"type\", i:0},\n    {Type:57395, Val:\"on\", i:0},\n    {Type:57397, Val:\"type\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"contenttypeid\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"contenttypeid\", i:0},\n    {Type:57389, Val:\"inner\", i:0},\n    {Type:57385, Val:\"join\", i:0},\n    {Type:57397, Val:\"subscribed\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"sd\", i:0},\n    {Type:57395, Val:\"on\", i:0},\n    {Type:57397, Val:\"sd\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"did\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"nodeid\", i:0},\n    {Type:57415, Val:\"and\", i:0},\n    {Type:57397, Val:\"sd\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"userid\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57405, Val:\":v3\", i:0},\n    {Type:57347, Val:\"union\", i:0},\n    {Type:57362, Val:\"all\", i:0},\n    {Type:57348, Val:\"select\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"name\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"title\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"userid\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"keyval\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57405, Val:\":v4\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"sourcetable\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"ifnull\", i:0},\n    {Type:40, Val:\"(\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"lastpost\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"joindate\", i:0},\n    {Type:41, Val:\")\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"lastactivity\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"posts\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"activity\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57405, Val:\":v5\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"type\", i:0},\n    {Type:44, Val:\",\", i:0},\n    {Type:57405, Val:\":v6\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"nounsubscribe\", i:0},\n    {Type:57353, Val:\"from\", i:0},\n    {Type:57397, Val:\"user\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:57389, Val:\"inner\", i:0},\n    {Type:57385, Val:\"join\", i:0},\n    {Type:57397, Val:\"userlist\", i:0},\n    {Type:57364, Val:\"as\", i:0},\n    {Type:57397, Val:\"ul\", i:0},\n    {Type:57395, Val:\"on\", i:0},\n    {Type:57397, Val:\"ul\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"relationid\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57397, Val:\"f\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"userid\", i:0},\n    {Type:57415, Val:\"and\", i:0},\n    {Type:57397, Val:\"ul\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"userid\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57405, Val:\":v7\", i:0},\n    {Type:57354, Val:\"where\", i:0},\n    {Type:57397, Val:\"ul\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"type\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57405, Val:\":v8\", i:0},\n    {Type:57415, Val:\"and\", i:0},\n    {Type:57397, Val:\"ul\", i:0},\n    {Type:46, Val:\".\", i:0},\n    {Type:57397, Val:\"aq\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57405, Val:\":v9\", i:0},\n    {Type:57357, Val:\"order\", i:0},\n    {Type:57358, Val:\"by\", i:0},\n    {Type:57397, Val:\"title\", i:0},\n    {Type:57359, Val:\"limit\", i:0},\n    {Type:57405, Val:\":v10\", i:0},\n}\n[]ast.Token{\n    {Type:57348, Val:\"select\", i:0},\n    {Type:57397, Val:\"c1\", i:0},\n    {Type:57353, Val:\"from\", i:0},\n    {Type:57397, Val:\"t1\", i:0},\n    {Type:57354, Val:\"where\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:57424, Val:\">=\", i:0},\n    {Type:57402, Val:\"1000\", i:0},\n}\n[]ast.Token{\n    {Type:57348, Val:\"select\", i:0},\n    {Type:57593, Val:\"SQL_CALC_FOUND_ROWS\", i:0},\n    {Type:57397, Val:\"col\", i:0},\n    {Type:57353, Val:\"from\", i:0},\n    {Type:57397, Val:\"tbl\", i:0},\n    {Type:57354, Val:\"where\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:62, Val:\">\", i:0},\n    {Type:57402, Val:\"1000\", i:0},\n}\n[]ast.Token{\n    {Type:57348, Val:\"SELECT\", i:0},\n    {Type:42, Val:\"*\", i:0},\n    {Type:57353, Val:\"FROM\", i:0},\n    {Type:57397, Val:\"tb\", i:0},\n    {Type:57354, Val:\"WHERE\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57405, Val:\":v1\", i:0},\n    {Type:59, Val:\";\", i:0},\n}\n[]ast.Token{\n    {Type:57348, Val:\"SELECT\", i:0},\n    {Type:42, Val:\"*\", i:0},\n    {Type:57353, Val:\"FROM\", i:0},\n    {Type:57397, Val:\"tb\", i:0},\n    {Type:57354, Val:\"WHERE\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:57427, Val:\"is\", i:0},\n    {Type:57410, Val:\"null\", i:0},\n    {Type:59, Val:\";\", i:0},\n}\n[]ast.Token{\n    {Type:57348, Val:\"SELECT\", i:0},\n    {Type:42, Val:\"*\", i:0},\n    {Type:57353, Val:\"FROM\", i:0},\n    {Type:57397, Val:\"tb\", i:0},\n    {Type:57354, Val:\"WHERE\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:57427, Val:\"is\", i:0},\n    {Type:57416, Val:\"not\", i:0},\n    {Type:57410, Val:\"null\", i:0},\n    {Type:59, Val:\";\", i:0},\n}\n[]ast.Token{\n    {Type:57348, Val:\"SELECT\", i:0},\n    {Type:42, Val:\"*\", i:0},\n    {Type:57353, Val:\"FROM\", i:0},\n    {Type:57397, Val:\"tb\", i:0},\n    {Type:57354, Val:\"WHERE\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:57417, Val:\"between\", i:0},\n    {Type:57402, Val:\"1\", i:0},\n    {Type:57415, Val:\"and\", i:0},\n    {Type:57402, Val:\"3\", i:0},\n    {Type:59, Val:\";\", i:0},\n}\n[]ast.Token{\n    {Type:57444, Val:\"alter\", i:0},\n    {Type:57451, Val:\"table\", i:0},\n    {Type:57397, Val:\"inventory\", i:0},\n    {Type:57448, Val:\"add\", i:0},\n    {Type:57452, Val:\"index\", i:0},\n    {Type:57397, Val:\"idx_store_film\", i:0},\n    {Type:57397, Val:\" (\", i:0},\n    {Type:57397, Val:\"store_id\", i:0},\n    {Type:57397, Val:\",\", i:0},\n    {Type:57397, Val:\"film_id\", i:0},\n    {Type:57346, Val:\");\", i:0},\n}\n[]ast.Token{\n    {Type:57351, Val:\"UPDATE\", i:0},\n    {Type:57397, Val:\"xxx\", i:0},\n    {Type:57372, Val:\"SET\", i:0},\n    {Type:57397, Val:\"c1\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57401, Val:\" LOGGER.error(\\\"\\\"); }\", i:0},\n    {Type:57354, Val:\"WHERE\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57402, Val:\"2\", i:0},\n    {Type:59, Val:\";\", i:0},\n}\n[]ast.Token{\n    {Type:57351, Val:\"update\", i:0},\n    {Type:57346, Val:\"\\xc2\", i:0},\n    {Type:57346, Val:\"\\xa0\", i:0},\n    {Type:57397, Val:\"tb\", i:0},\n    {Type:57372, Val:\"set\", i:0},\n    {Type:57346, Val:\"\\xc2\", i:0},\n    {Type:57346, Val:\"\\xa0\", i:0},\n    {Type:57488, Val:\"status\", i:0},\n    {Type:57346, Val:\"\\xc2\", i:0},\n    {Type:57346, Val:\"\\xa0\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57346, Val:\"\\xc2\", i:0},\n    {Type:57346, Val:\"\\xa0\", i:0},\n    {Type:57402, Val:\"1\", i:0},\n    {Type:57354, Val:\"where\", i:0},\n    {Type:57346, Val:\"\\xc2\", i:0},\n    {Type:57346, Val:\"\\xa0\", i:0},\n    {Type:57397, Val:\"id\", i:0},\n    {Type:57346, Val:\"\\xc2\", i:0},\n    {Type:57346, Val:\"\\xa0\", i:0},\n    {Type:61, Val:\"=\", i:0},\n    {Type:57346, Val:\"\\xc2\", i:0},\n    {Type:57346, Val:\"\\xa0\", i:0},\n    {Type:57402, Val:\"1\", i:0},\n    {Type:59, Val:\";\", i:0},\n}\n"
  },
  {
    "path": "ast/testdata/TestVitessStmtNode2JSON.golden",
    "content": "{\n  \"Cache\": \"\",\n  \"Comments\": null,\n  \"Distinct\": \"\",\n  \"Hints\": \"\",\n  \"SelectExprs\": [\n    {\n      \"Expr\": {\n        \"Type\": 1,\n        \"Val\": \"MQ==\"\n      },\n      \"As\": \"\"\n    }\n  ],\n  \"From\": [\n    {\n      \"Expr\": {\n        \"Name\": \"dual\",\n        \"Qualifier\": \"\"\n      },\n      \"Partitions\": null,\n      \"As\": \"\",\n      \"Hints\": null\n    }\n  ],\n  \"Where\": null,\n  \"GroupBy\": null,\n  \"Having\": null,\n  \"OrderBy\": null,\n  \"Limit\": null,\n  \"Lock\": \"\"\n}\n\n"
  },
  {
    "path": "ast/tidb.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\tjson \"github.com/CorgiMan/json2\"\n\t\"github.com/kr/pretty\"\n\t\"github.com/pingcap/parser\"\n\t\"github.com/pingcap/parser/ast\"\n\t\"github.com/tidwall/gjson\"\n\n\t// for pingcap parser\n\t_ \"github.com/pingcap/tidb/types/parser_driver\"\n)\n\n// TiParse TiDB 语法解析\nfunc TiParse(sql, charset, collation string) ([]ast.StmtNode, error) {\n\tp := parser.New()\n\tsql = removeIncompatibleWords(sql)\n\tstmt, warn, err := p.Parse(sql, charset, collation)\n\tif err != nil {\n\t\t// issue: https://github.com/XiaoMi/soar/issues/235\n\t\t// TODO: bypass charset error, pingcap/parser not support so much charsets\n\t\tif strings.Contains(err.Error(), \"Unknown character set\") {\n\t\t\terr = nil\n\t\t}\n\t}\n\n\t// TODO: bypass warning info\n\tfor _, w := range warn {\n\t\tcommon.Log.Warn(w.Error())\n\t}\n\treturn stmt, err\n}\n\n// removeIncompatibleWords remove pingcap/parser not support words from schema\nfunc removeIncompatibleWords(sql string) string {\n\tfields := strings.Fields(strings.TrimSpace(sql))\n\tif len(fields) == 0 {\n\t\treturn sql\n\t}\n\tswitch strings.ToLower(fields[0]) {\n\tcase \"create\", \"alter\":\n\tdefault:\n\t\treturn sql\n\t}\n\t// CONSTRAINT col_fk FOREIGN KEY (col) REFERENCES tb (id) ON UPDATE CASCADE\n\tre := regexp.MustCompile(`(?i) ON UPDATE CASCADE`)\n\tsql = re.ReplaceAllString(sql, \"\")\n\n\t// FULLTEXT KEY col_fk (col) /*!50100 WITH PARSER `ngram` */\n\t// /*!50100 PARTITION BY LIST (col)\n\tre = regexp.MustCompile(`/\\*!5`)\n\tsql = re.ReplaceAllString(sql, \"/* 5\")\n\n\t// col varchar(10) CHARACTER SET gbk DEFAULT NULL\n\tre = regexp.MustCompile(`(?i)CHARACTER SET [a-z_0-9]* `)\n\tsql = re.ReplaceAllString(sql, \"\")\n\n\t// CREATE TEMPORARY TABLE IF NOT EXISTS t_film AS (SELECT * FROM film);\n\tre = regexp.MustCompile(`(?i)CREATE TEMPORARY TABLE`)\n\tsql = re.ReplaceAllString(sql, \"CREATE TABLE\")\n\n\treturn sql\n}\n\n// PrintPrettyStmtNode 打印TiParse语法树\nfunc PrintPrettyStmtNode(sql, charset, collation string) {\n\ttree, err := TiParse(sql, charset, collation)\n\tif err != nil {\n\t\tcommon.Log.Warning(err.Error())\n\t} else {\n\t\t_, err = pretty.Println(tree)\n\t\tcommon.LogIfWarn(err, \"\")\n\t}\n}\n\n// StmtNode2JSON TiParse AST tree into json format\nfunc StmtNode2JSON(sql, charset, collation string) string {\n\tvar str string\n\ttree, err := TiParse(sql, charset, collation)\n\tif err != nil {\n\t\tcommon.Log.Warning(err.Error())\n\t} else {\n\t\tb, err := json.MarshalIndent(tree, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(err.Error())\n\t\t} else {\n\t\t\tstr = string(b)\n\t\t}\n\t}\n\treturn str\n}\n\n// SchemaMetaInfo get used database, table name from SQL\nfunc SchemaMetaInfo(sql string, defaultDatabase string) []string {\n\tvar tables []string\n\ttree, err := TiParse(sql, \"\", \"\")\n\tif err != nil {\n\t\treturn tables\n\t}\n\n\tjsonString := StmtNode2JSON(sql, \"\", \"\")\n\n\tfor _, node := range tree {\n\t\tswitch n := node.(type) {\n\t\tcase *ast.UseStmt:\n\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`dual`\", n.DBName))\n\t\t// SetOprStmt represents \"union/except/intersect statement\"\n\t\tcase *ast.InsertStmt, *ast.SelectStmt, *ast.SetOprStmt, *ast.UpdateStmt, *ast.DeleteStmt:\n\t\t\t// DML/DQL: INSERT, SELECT, UPDATE, DELETE\n\t\t\tfor _, tableRef := range common.JSONFind(jsonString, \"TableRefs\") {\n\t\t\t\tfor _, source := range common.JSONFind(tableRef, \"Source\") {\n\t\t\t\t\tdatabase := gjson.Get(source, \"Schema.O\")\n\t\t\t\t\ttable := gjson.Get(source, \"Name.O\")\n\t\t\t\t\tif database.String() == \"\" {\n\t\t\t\t\t\tif table.String() != \"\" {\n\t\t\t\t\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`%s`\", defaultDatabase, table.String()))\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif table.String() != \"\" {\n\t\t\t\t\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`%s`\", database.String(), table.String()))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`dual`\", database.String()))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase *ast.DropTableStmt:\n\t\t\t// DDL: DROP TABLE|VIEW\n\t\t\tschemas := common.JSONFind(jsonString, \"Tables\")\n\t\t\tfor _, tabs := range schemas {\n\t\t\t\tfor _, table := range gjson.Parse(tabs).Array() {\n\t\t\t\t\tdb := gjson.Get(table.String(), \"Schema.O\")\n\t\t\t\t\ttb := gjson.Get(table.String(), \"Name.O\")\n\t\t\t\t\tif db.String() == \"\" {\n\t\t\t\t\t\tif tb.String() != \"\" {\n\t\t\t\t\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`%s`\", defaultDatabase, tb.String()))\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif tb.String() != \"\" {\n\t\t\t\t\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`%s`\", db.String(), tb.String()))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase *ast.DropDatabaseStmt, *ast.CreateDatabaseStmt:\n\t\t\t// DDL: DROP|CREATE DATABASE\n\t\t\tschemas := common.JSONFind(jsonString, \"Name\")\n\t\t\tfor _, schema := range schemas {\n\t\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`dual`\", schema))\n\t\t\t}\n\t\tdefault:\n\t\t\t// DDL: CREATE TABLE|DATABASE|INDEX|VIEW, DROP INDEX\n\t\t\tschemas := common.JSONFind(jsonString, \"Table\")\n\t\t\tfor _, table := range schemas {\n\t\t\t\tdb := gjson.Get(table, \"Schema.O\")\n\t\t\t\ttb := gjson.Get(table, \"Name.O\")\n\t\t\t\tif db.String() == \"\" {\n\t\t\t\t\tif tb.String() != \"\" {\n\t\t\t\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`%s`\", defaultDatabase, tb.String()))\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif tb.String() != \"\" {\n\t\t\t\t\t\ttables = append(tables, fmt.Sprintf(\"`%s`.`%s`\", db.String(), tb.String()))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn common.RemoveDuplicatesItem(tables)\n}\n"
  },
  {
    "path": "ast/tidb_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\nfunc TestPrintPrettyStmtNode(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select 1`,\n\t\t`select * f`, // syntax error case\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range sqls {\n\t\t\tPrintPrettyStmtNode(sql, \"\", \"\")\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestStmtNode2JSON(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select 1`,\n\t\t`select * f`, // syntax error case\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range sqls {\n\t\t\tfmt.Println(StmtNode2JSON(sql, \"\", \"\"))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestSchemaMetaInfo(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"use world_x;\",\n\t\t\"select 1;\",\n\t\t\"syntax error case\",\n\t\t\"select * from ta join tb using (id)\",\n\t\t\"select * from ta, tb limit 1\",\n\t\t\"drop table tb\",\n\t\t\"drop table db.tb\",\n\t\t\"drop database db\",\n\t\t\"create database db\",\n\t\t\"create index idx_col on tbl (col)\",\n\t\t\"DROP INDEX idx_col on tbl\",\n\t}\n\t// fmt.Println(sqls[len(sqls)-1])\n\t// fmt.Println(SchemaMetaInfo(sqls[len(sqls)-1], \"sakila\"))\n\t// return\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range append(sqls, common.TestSQLs...) {\n\t\t\tfmt.Println(sql)\n\t\t\tfmt.Println(SchemaMetaInfo(sql, \"sakila\"))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRemoveIncompatibleWords(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := [][]string{\n\t\t{\n\t\t\t`CREATE TEMPORARY TABLE IF NOT EXISTS t_film AS (SELECT * FROM film)`,\n\t\t\t`CREATE CONSTRAINT col_fk FOREIGN KEY (col) REFERENCES tb (id) ON UPDATE CASCADE`,\n\t\t\t\"CREATE FULLTEXT KEY col_fk (col) /*!50100 WITH PARSER `ngram` */\",\n\t\t\t`CREATE /*!50100 PARTITION BY LIST (col)`,\n\t\t\t`CREATE col varchar(10) CHARACTER SET gbk DEFAULT NULL`,\n\t\t},\n\t\t{\n\t\t\t`CREATE TABLE IF NOT EXISTS t_film AS (SELECT * FROM film)`,\n\t\t\t`CREATE CONSTRAINT col_fk FOREIGN KEY (col) REFERENCES tb (id)`,\n\t\t\t\"CREATE FULLTEXT KEY col_fk (col) /* 50100 WITH PARSER `ngram` */\",\n\t\t\t`CREATE /* 50100 PARTITION BY LIST (col)`,\n\t\t\t`CREATE col varchar(10) DEFAULT NULL`,\n\t\t},\n\t}\n\tfor k, sql := range sqls[0] {\n\t\tsql = removeIncompatibleWords(sql)\n\t\tif sqls[1][k] != sql {\n\t\t\tfmt.Println(sql)\n\t\t\tt.Fatal(sql)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "ast/token.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// TokenType\nconst (\n\tTokenTypeWhitespace       = 0\n\tTokenTypeWord             = 1\n\tTokenTypeQuote            = 2\n\tTokenTypeBacktickQuote    = 3\n\tTokenTypeReserved         = 4\n\tTokenTypeReservedToplevel = 5\n\tTokenTypeReservedNewline  = 6\n\tTokenTypeBoundary         = 7\n\tTokenTypeComment          = 8\n\tTokenTypeBlockComment     = 9\n\tTokenTypeNumber           = 10\n\tTokenTypeError            = 11\n\tTokenTypeVariable         = 12\n)\n\nvar maxCachekeySize = 15\nvar cacheHits int\nvar cacheMisses int\n\nvar tokenBoundaries = []string{\n\t// multi character\n\t\"(>=)\", \"(<=)\", \"(!=)\", \"(<>)\",\n\t// single characters\n\t\",\", \";\", \":\", \"\\\\)\", \"\\\\(\", \"\\\\.\", \"=\", \"<\", \">\", \"\\\\+\", \"-\", \"\\\\*\", \"/\", \"!\", \"\\\\^\", \"%\", \"\\\\|\", \"&\", \"#\",\n}\n\nvar tokenReserved = []string{\n\t\"ACCESSIBLE\", \"ACTION\", \"AGAINST\", \"AGGREGATE\", \"ALGORITHM\", \"ALL\", \"ALTER\", \"ANALYSE\", \"ANALYZE\", \"AS\", \"ASC\",\n\t\"AUTOCOMMIT\", \"AUTO_INCREMENT\", \"BACKUP\", \"BEGIN\", \"BETWEEN\", \"BINLOG\", \"BOTH\", \"CASCADE\", \"CASE\", \"CHANGE\", \"CHANGED\", \"CHARACTER SET\",\n\t\"CHARSET\", \"CHECK\", \"CHECKSUM\", \"COLLATE\", \"COLLATION\", \"COLUMN\", \"COLUMNS\", \"COMMENT\", \"COMMIT\", \"COMMITTED\", \"COMPRESSED\", \"CONCURRENT\",\n\t\"CONSTRAINT\", \"CONTAINS\", \"CONVERT\", \"CREATE\", \"CROSS\", \"CURRENT_TIMESTAMP\", \"DATABASE\", \"DATABASES\", \"DAY\", \"DAY_HOUR\", \"DAY_MINUTE\",\n\t\"DAY_SECOND\", \"DEFAULT\", \"DEFINER\", \"DELAYED\", \"DELETE\", \"DESC\", \"DESCRIBE\", \"DETERMINISTIC\", \"DISTINCT\", \"DISTINCTROW\", \"DIV\",\n\t\"DO\", \"DUMPFILE\", \"DUPLICATE\", \"DYNAMIC\", \"ELSE\", \"ENCLOSED\", \"END\", \"ENGINE\", \"ENGINE_TYPE\", \"ENGINES\", \"ESCAPE\", \"ESCAPED\", \"EVENTS\", \"EXEC\",\n\t\"EXECUTE\", \"EXISTS\", \"EXPLAIN\", \"EXTENDED\", \"FAST\", \"FIELDS\", \"FILE\", \"FIRST\", \"FIXED\", \"FLUSH\", \"FOR\", \"FORCE\", \"FOREIGN\", \"FULL\", \"FULLTEXT\",\n\t\"FUNCTION\", \"GLOBAL\", \"GRANT\", \"GRANTS\", \"GROUP_CONCAT\", \"HEAP\", \"HIGH_PRIORITY\", \"HOSTS\", \"HOUR\", \"HOUR_MINUTE\",\n\t\"HOUR_SECOND\", \"IDENTIFIED\", \"IF\", \"IFNULL\", \"IGNORE\", \"IN\", \"INDEX\", \"INDEXES\", \"INFILE\", \"INSERT\", \"INSERT_ID\", \"INSERT_METHOD\", \"INTERVAL\",\n\t\"INTO\", \"INVOKER\", \"IS\", \"ISOLATION\", \"KEY\", \"KEYS\", \"KILL\", \"LAST_INSERT_ID\", \"LEADING\", \"LEVEL\", \"LIKE\", \"LINEAR\",\n\t\"LINES\", \"LOAD\", \"LOCAL\", \"LOCK\", \"LOCKS\", \"LOGS\", \"LOW_PRIORITY\", \"MARIA\", \"MASTER\", \"MASTER_CONNECT_RETRY\", \"MASTER_HOST\", \"MASTER_LOG_FILE\",\n\t\"MATCH\", \"MAX_CONNECTIONS_PER_HOUR\", \"MAX_QUERIES_PER_HOUR\", \"MAX_ROWS\", \"MAX_UPDATES_PER_HOUR\", \"MAX_USER_CONNECTIONS\",\n\t\"MEDIUM\", \"MERGE\", \"MINUTE\", \"MINUTE_SECOND\", \"MIN_ROWS\", \"MODE\", \"MODIFY\",\n\t\"MONTH\", \"MRG_MYISAM\", \"MYISAM\", \"NAMES\", \"NATURAL\", \"NOT\", \"NOW()\", \"NULL\", \"OFFSET\", \"ON\", \"OPEN\", \"OPTIMIZE\", \"OPTION\", \"OPTIONALLY\",\n\t\"ON UPDATE\", \"ON DELETE\", \"OUTFILE\", \"PACK_KEYS\", \"PAGE\", \"PARTIAL\", \"PARTITION\", \"PARTITIONS\", \"PASSWORD\", \"PRIMARY\", \"PRIVILEGES\", \"PROCEDURE\",\n\t\"PROCESS\", \"PROCESSLIST\", \"PURGE\", \"QUICK\", \"RANGE\", \"RAID0\", \"RAID_CHUNKS\", \"RAID_CHUNKSIZE\", \"RAID_TYPE\", \"READ\", \"READ_ONLY\",\n\t\"READ_WRITE\", \"REFERENCES\", \"REGEXP\", \"RELOAD\", \"RENAME\", \"REPAIR\", \"REPEATABLE\", \"REPLACE\", \"REPLICATION\", \"RESET\", \"RESTORE\", \"RESTRICT\",\n\t\"RETURN\", \"RETURNS\", \"REVOKE\", \"RLIKE\", \"ROLLBACK\", \"ROW\", \"ROWS\", \"ROW_FORMAT\", \"SECOND\", \"SECURITY\", \"SEPARATOR\",\n\t\"SERIALIZABLE\", \"SESSION\", \"SHARE\", \"SHOW\", \"SHUTDOWN\", \"SLAVE\", \"SONAME\", \"SOUNDS\", \"SQL\", \"SQL_AUTO_IS_NULL\", \"SQL_BIG_RESULT\",\n\t\"SQL_BIG_SELECTS\", \"SQL_BIG_TABLES\", \"SQL_BUFFER_RESULT\", \"SQL_CALC_FOUND_ROWS\", \"SQL_LOG_BIN\", \"SQL_LOG_OFF\", \"SQL_LOG_UPDATE\",\n\t\"SQL_LOW_PRIORITY_UPDATES\", \"SQL_MAX_JOIN_SIZE\", \"SQL_QUOTE_SHOW_CREATE\", \"SQL_SAFE_UPDATES\", \"SQL_SELECT_LIMIT\", \"SQL_SLAVE_SKIP_COUNTER\",\n\t\"SQL_SMALL_RESULT\", \"SQL_WARNINGS\", \"SQL_CACHE\", \"SQL_NO_CACHE\", \"START\", \"STARTING\", \"STATUS\", \"STOP\", \"STORAGE\",\n\t\"STRAIGHT_JOIN\", \"STRING\", \"STRIPED\", \"SUPER\", \"TABLE\", \"TABLES\", \"TEMPORARY\", \"TERMINATED\", \"THEN\", \"TO\", \"TRAILING\", \"TRANSACTIONAL\", \"TRUE\",\n\t\"TRUNCATE\", \"TYPE\", \"TYPES\", \"UNCOMMITTED\", \"UNIQUE\", \"UNLOCK\", \"UNSIGNED\", \"USAGE\", \"USE\", \"USING\", \"VARIABLES\",\n\t\"VIEW\", \"WHEN\", \"WITH\", \"WORK\", \"WRITE\", \"YEAR_MONTH\",\n}\n\nvar tokenReservedTopLevel = []string{\n\t\"SELECT\", \"FROM\", \"WHERE\", \"SET\", \"ORDER BY\", \"GROUP BY\", \"LIMIT\", \"DROP\",\n\t\"VALUES\", \"UPDATE\", \"HAVING\", \"ADD\", \"AFTER\", \"ALTER TABLE\", \"DELETE FROM\", \"UNION ALL\", \"UNION\", \"EXCEPT\", \"INTERSECT\",\n}\n\nvar tokenFunction = []string{\n\t\"ABS\", \"ACOS\", \"ADDDATE\", \"ADDTIME\", \"AES_DECRYPT\", \"AES_ENCRYPT\", \"AREA\", \"ASBINARY\", \"ASCII\", \"ASIN\", \"ASTEXT\", \"ATAN\", \"ATAN2\",\n\t\"AVG\", \"BDMPOLYFROMTEXT\", \"BDMPOLYFROMWKB\", \"BDPOLYFROMTEXT\", \"BDPOLYFROMWKB\", \"BENCHMARK\", \"BIN\", \"BIT_AND\", \"BIT_COUNT\", \"BIT_LENGTH\",\n\t\"BIT_OR\", \"BIT_XOR\", \"BOUNDARY\", \"BUFFER\", \"CAST\", \"CEIL\", \"CEILING\", \"CENTROID\", \"CHAR\", \"CHARACTER_LENGTH\", \"CHARSET\", \"CHAR_LENGTH\",\n\t\"COALESCE\", \"COERCIBILITY\", \"COLLATION\", \"COMPRESS\", \"CONCAT\", \"CONCAT_WS\", \"CONNECTION_ID\", \"CONTAINS\", \"CONV\", \"CONVERT\", \"CONVERT_TZ\",\n\t\"CONVEXHULL\", \"COS\", \"COT\", \"COUNT\", \"CRC32\", \"CROSSES\", \"CURDATE\", \"CURRENT_DATE\", \"CURRENT_TIME\", \"CURRENT_TIMESTAMP\", \"CURRENT_USER\",\n\t\"CURTIME\", \"DATABASE\", \"DATE\", \"DATEDIFF\", \"DATE_ADD\", \"DATE_DIFF\", \"DATE_FORMAT\", \"DATE_SUB\", \"DAY\", \"DAYNAME\", \"DAYOFMONTH\", \"DAYOFWEEK\",\n\t\"DAYOFYEAR\", \"DECODE\", \"DEFAULT\", \"DEGREES\", \"DES_DECRYPT\", \"DES_ENCRYPT\", \"DIFFERENCE\", \"DIMENSION\", \"DISJOINT\", \"DISTANCE\", \"ELT\", \"ENCODE\",\n\t\"ENCRYPT\", \"ENDPOINT\", \"ENVELOPE\", \"EQUALS\", \"EXP\", \"EXPORT_SET\", \"EXTERIORRING\", \"EXTRACT\", \"EXTRACTVALUE\", \"FIELD\", \"FIND_IN_SET\", \"FLOOR\",\n\t\"FORMAT\", \"FOUND_ROWS\", \"FROM_DAYS\", \"FROM_UNIXTIME\", \"GEOMCOLLFROMTEXT\", \"GEOMCOLLFROMWKB\", \"GEOMETRYCOLLECTION\", \"GEOMETRYCOLLECTIONFROMTEXT\",\n\t\"GEOMETRYCOLLECTIONFROMWKB\", \"GEOMETRYFROMTEXT\", \"GEOMETRYFROMWKB\", \"GEOMETRYN\", \"GEOMETRYTYPE\", \"GEOMFROMTEXT\", \"GEOMFROMWKB\", \"GET_FORMAT\",\n\t\"GET_LOCK\", \"GLENGTH\", \"GREATEST\", \"GROUP_CONCAT\", \"GROUP_UNIQUE_USERS\", \"HEX\", \"HOUR\", \"IF\", \"IFNULL\", \"INET_ATON\", \"INET_NTOA\", \"INSERT\", \"INSTR\",\n\t\"INTERIORRINGN\", \"INTERSECTION\", \"INTERSECTS\", \"INTERVAL\", \"ISCLOSED\", \"ISEMPTY\", \"ISNULL\", \"ISRING\", \"ISSIMPLE\", \"IS_FREE_LOCK\", \"IS_USED_LOCK\",\n\t\"LAST_DAY\", \"LAST_INSERT_ID\", \"LCASE\", \"LEAST\", \"LEFT\", \"LENGTH\", \"LINEFROMTEXT\", \"LINEFROMWKB\", \"LINESTRING\", \"LINESTRINGFROMTEXT\", \"LINESTRINGFROMWKB\",\n\t\"LN\", \"LOAD_FILE\", \"LOCALTIME\", \"LOCALTIMESTAMP\", \"LOCATE\", \"LOG\", \"LOG10\", \"LOG2\", \"LOWER\", \"LPAD\", \"LTRIM\", \"MAKEDATE\", \"MAKETIME\", \"MAKE_SET\",\n\t\"MASTER_POS_WAIT\", \"MAX\", \"MBRCONTAINS\", \"MBRDISJOINT\", \"MBREQUAL\", \"MBRINTERSECTS\", \"MBROVERLAPS\", \"MBRTOUCHES\", \"MBRWITHIN\", \"MD5\", \"MICROSECOND\",\n\t\"MID\", \"MIN\", \"MINUTE\", \"MLINEFROMTEXT\", \"MLINEFROMWKB\", \"MOD\", \"MONTH\", \"MONTHNAME\", \"MPOINTFROMTEXT\", \"MPOINTFROMWKB\", \"MPOLYFROMTEXT\", \"MPOLYFROMWKB\",\n\t\"MULTILINESTRING\", \"MULTILINESTRINGFROMTEXT\", \"MULTILINESTRINGFROMWKB\", \"MULTIPOINT\", \"MULTIPOINTFROMTEXT\", \"MULTIPOINTFROMWKB\", \"MULTIPOLYGON\",\n\t\"MULTIPOLYGONFROMTEXT\", \"MULTIPOLYGONFROMWKB\", \"NAME_CONST\", \"NULLIF\", \"NUMGEOMETRIES\", \"NUMINTERIORRINGS\", \"NUMPOINTS\", \"OCT\", \"OCTET_LENGTH\",\n\t\"OLD_PASSWORD\", \"ORD\", \"OVERLAPS\", \"PASSWORD\", \"PERIOD_ADD\", \"PERIOD_DIFF\", \"PI\", \"POINT\", \"POINTFROMTEXT\", \"POINTFROMWKB\", \"POINTN\", \"POINTONSURFACE\",\n\t\"POLYFROMTEXT\", \"POLYFROMWKB\", \"POLYGON\", \"POLYGONFROMTEXT\", \"POLYGONFROMWKB\", \"POSITION\", \"POW\", \"POWER\", \"QUARTER\", \"QUOTE\", \"RADIANS\", \"RAND\",\n\t\"RELATED\", \"RELEASE_LOCK\", \"REPEAT\", \"REPLACE\", \"REVERSE\", \"RIGHT\", \"ROUND\", \"ROW_COUNT\", \"RPAD\", \"RTRIM\", \"SCHEMA\", \"SECOND\", \"SEC_TO_TIME\",\n\t\"SESSION_USER\", \"SHA\", \"SHA1\", \"SIGN\", \"SIN\", \"SLEEP\", \"SOUNDEX\", \"SPACE\", \"SQRT\", \"SRID\", \"STARTPOINT\", \"STD\", \"STDDEV\", \"STDDEV_POP\", \"STDDEV_SAMP\",\n\t\"STRCMP\", \"STR_TO_DATE\", \"SUBDATE\", \"SUBSTR\", \"SUBSTRING\", \"SUBSTRING_INDEX\", \"SUBTIME\", \"SUM\", \"SYMDIFFERENCE\", \"SYSDATE\", \"SYSTEM_USER\", \"TAN\",\n\t\"TIME\", \"TIMEDIFF\", \"TIMESTAMP\", \"TIMESTAMPADD\", \"TIMESTAMPDIFF\", \"TIME_FORMAT\", \"TIME_TO_SEC\", \"TOUCHES\", \"TO_DAYS\", \"TRIM\", \"TRUNCATE\", \"UCASE\",\n\t\"UNCOMPRESS\", \"UNCOMPRESSED_LENGTH\", \"UNHEX\", \"UNIQUE_USERS\", \"UNIX_TIMESTAMP\", \"UPDATEXML\", \"UPPER\", \"USER\", \"UTC_DATE\", \"UTC_TIME\", \"UTC_TIMESTAMP\",\n\t\"UUID\", \"VARIANCE\", \"VAR_POP\", \"VAR_SAMP\", \"VERSION\", \"WEEK\", \"WEEKDAY\", \"WEEKOFYEAR\", \"WITHIN\", \"X\", \"Y\", \"YEAR\", \"YEARWEEK\",\n}\n\nvar tokenReservedNewLine = []string{\n\t\"LEFT OUTER JOIN\", \"RIGHT OUTER JOIN\", \"LEFT JOIN\", \"RIGHT JOIN\", \"OUTER JOIN\", \"INNER JOIN\", \"JOIN\", \"XOR\", \"OR\", \"AND\",\n}\n\nvar regBoundariesString string\nvar regReservedToplevelString string\nvar regReservedNewlineString string\nvar regReservedString string\nvar regFunctionString string\n\nfunc init() {\n\tvar regs []string\n\tregs = append(regs, tokenBoundaries...)\n\tregBoundariesString = \"(\" + strings.Join(regs, \"|\") + \")\"\n\n\tregs = make([]string, 0)\n\tfor _, reg := range tokenReservedTopLevel {\n\t\tregs = append(regs, regexp.QuoteMeta(reg))\n\t}\n\tregReservedToplevelString = \"(\" + strings.Join(regs, \"|\") + \")\"\n\n\tregs = make([]string, 0)\n\tfor _, reg := range tokenReservedNewLine {\n\t\tregs = append(regs, regexp.QuoteMeta(reg))\n\t}\n\tregReservedNewlineString = \"(\" + strings.Join(regs, \"|\") + \")\"\n\n\tregs = make([]string, 0)\n\tfor _, reg := range tokenReserved {\n\t\tregs = append(regs, regexp.QuoteMeta(reg))\n\t}\n\tregReservedString = \"(\" + strings.Join(regs, \"|\") + \")\"\n\n\tregs = make([]string, 0)\n\tfor _, reg := range tokenFunction {\n\t\tregs = append(regs, regexp.QuoteMeta(reg))\n\t}\n\tregFunctionString = \"(\" + strings.Join(regs, \"|\") + \")\"\n}\n\n// TokenString sqlparser tokens\nvar TokenString = map[int]string{\n\tsqlparser.LEX_ERROR:               \"\",\n\tsqlparser.UNION:                   \"union\",\n\tsqlparser.SELECT:                  \"select\",\n\tsqlparser.STREAM:                  \"stream\",\n\tsqlparser.INSERT:                  \"insert\",\n\tsqlparser.UPDATE:                  \"update\",\n\tsqlparser.DELETE:                  \"delete\",\n\tsqlparser.FROM:                    \"from\",\n\tsqlparser.WHERE:                   \"where\",\n\tsqlparser.GROUP:                   \"group\",\n\tsqlparser.HAVING:                  \"having\",\n\tsqlparser.ORDER:                   \"order\",\n\tsqlparser.BY:                      \"by\",\n\tsqlparser.LIMIT:                   \"limit\",\n\tsqlparser.OFFSET:                  \"offset\",\n\tsqlparser.FOR:                     \"for\",\n\tsqlparser.ALL:                     \"all\",\n\tsqlparser.DISTINCT:                \"distinct\",\n\tsqlparser.AS:                      \"as\",\n\tsqlparser.EXISTS:                  \"exists\",\n\tsqlparser.ASC:                     \"asc\",\n\tsqlparser.DESC:                    \"desc\",\n\tsqlparser.INTO:                    \"into\",\n\tsqlparser.DUPLICATE:               \"duplicate\",\n\tsqlparser.KEY:                     \"key\",\n\tsqlparser.DEFAULT:                 \"default\",\n\tsqlparser.SET:                     \"set\",\n\tsqlparser.LOCK:                    \"lock\",\n\tsqlparser.KEYS:                    \"keys\",\n\tsqlparser.VALUES:                  \"values\",\n\tsqlparser.LAST_INSERT_ID:          \"last_insert_id\",\n\tsqlparser.NEXT:                    \"next\",\n\tsqlparser.VALUE:                   \"value\",\n\tsqlparser.SHARE:                   \"share\",\n\tsqlparser.MODE:                    \"mode\",\n\tsqlparser.SQL_NO_CACHE:            \"sql_no_cache\",\n\tsqlparser.SQL_CACHE:               \"sql_cache\",\n\tsqlparser.JOIN:                    \"join\",\n\tsqlparser.STRAIGHT_JOIN:           \"straight_join\",\n\tsqlparser.LEFT:                    \"left\",\n\tsqlparser.RIGHT:                   \"right\",\n\tsqlparser.INNER:                   \"inner\",\n\tsqlparser.OUTER:                   \"outer\",\n\tsqlparser.CROSS:                   \"cross\",\n\tsqlparser.NATURAL:                 \"natural\",\n\tsqlparser.USE:                     \"use\",\n\tsqlparser.FORCE:                   \"force\",\n\tsqlparser.ON:                      \"on\",\n\tsqlparser.USING:                   \"using\",\n\tsqlparser.ID:                      \"id\",\n\tsqlparser.HEX:                     \"hex\",\n\tsqlparser.STRING:                  \"string\",\n\tsqlparser.INTEGRAL:                \"integral\",\n\tsqlparser.FLOAT:                   \"float\",\n\tsqlparser.HEXNUM:                  \"hexnum\",\n\tsqlparser.VALUE_ARG:               \"?\",\n\tsqlparser.LIST_ARG:                \":\",\n\tsqlparser.COMMENT:                 \"\",\n\tsqlparser.COMMENT_KEYWORD:         \"comment\",\n\tsqlparser.BIT_LITERAL:             \"bit_literal\",\n\tsqlparser.NULL:                    \"null\",\n\tsqlparser.TRUE:                    \"true\",\n\tsqlparser.FALSE:                   \"false\",\n\tsqlparser.OR:                      \"||\",\n\tsqlparser.AND:                     \"&&\",\n\tsqlparser.NOT:                     \"not\",\n\tsqlparser.BETWEEN:                 \"between\",\n\tsqlparser.CASE:                    \"case\",\n\tsqlparser.WHEN:                    \"when\",\n\tsqlparser.THEN:                    \"then\",\n\tsqlparser.ELSE:                    \"else\",\n\tsqlparser.END:                     \"end\",\n\tsqlparser.LE:                      \"<\",\n\tsqlparser.GE:                      \">=\",\n\tsqlparser.NE:                      \"<>\",\n\tsqlparser.NULL_SAFE_EQUAL:         \"<=>\",\n\tsqlparser.IS:                      \"is\",\n\tsqlparser.LIKE:                    \"like\",\n\tsqlparser.REGEXP:                  \"regexp\",\n\tsqlparser.IN:                      \"in\",\n\tsqlparser.SHIFT_LEFT:              \"<<\",\n\tsqlparser.SHIFT_RIGHT:             \">>\",\n\tsqlparser.DIV:                     \"div\",\n\tsqlparser.MOD:                     \"mod\",\n\tsqlparser.UNARY:                   \"unary\",\n\tsqlparser.COLLATE:                 \"collate\",\n\tsqlparser.BINARY:                  \"binary\",\n\tsqlparser.UNDERSCORE_BINARY:       \"_binary\",\n\tsqlparser.INTERVAL:                \"interval\",\n\tsqlparser.JSON_EXTRACT_OP:         \"->>\",\n\tsqlparser.JSON_UNQUOTE_EXTRACT_OP: \"->\",\n\tsqlparser.CREATE:                  \"create\",\n\tsqlparser.ALTER:                   \"alter\",\n\tsqlparser.DROP:                    \"drop\",\n\tsqlparser.RENAME:                  \"rename\",\n\tsqlparser.ANALYZE:                 \"analyze\",\n\tsqlparser.ADD:                     \"add\",\n\tsqlparser.SCHEMA:                  \"schema\",\n\tsqlparser.TABLE:                   \"table\",\n\tsqlparser.INDEX:                   \"index\",\n\tsqlparser.VIEW:                    \"view\",\n\tsqlparser.TO:                      \"to\",\n\tsqlparser.IGNORE:                  \"ignore\",\n\tsqlparser.IF:                      \"if\",\n\tsqlparser.UNIQUE:                  \"unique\",\n\tsqlparser.PRIMARY:                 \"primary\",\n\tsqlparser.COLUMN:                  \"column\",\n\tsqlparser.CONSTRAINT:              \"constraint\",\n\tsqlparser.SPATIAL:                 \"spatial\",\n\tsqlparser.FULLTEXT:                \"fulltext\",\n\tsqlparser.FOREIGN:                 \"foreign\",\n\tsqlparser.SHOW:                    \"show\",\n\tsqlparser.DESCRIBE:                \"describe\",\n\tsqlparser.EXPLAIN:                 \"explain\",\n\tsqlparser.DATE:                    \"date\",\n\tsqlparser.ESCAPE:                  \"escape\",\n\tsqlparser.REPAIR:                  \"repair\",\n\tsqlparser.OPTIMIZE:                \"optimize\",\n\tsqlparser.TRUNCATE:                \"truncate\",\n\tsqlparser.MAXVALUE:                \"maxvalue\",\n\tsqlparser.PARTITION:               \"partition\",\n\tsqlparser.REORGANIZE:              \"reorganize\",\n\tsqlparser.LESS:                    \"less\",\n\tsqlparser.THAN:                    \"than\",\n\tsqlparser.PROCEDURE:               \"procedure\",\n\tsqlparser.TRIGGER:                 \"trigger\",\n\tsqlparser.VINDEX:                  \"vindex\",\n\tsqlparser.VINDEXES:                \"vindexes\",\n\tsqlparser.STATUS:                  \"status\",\n\tsqlparser.VARIABLES:               \"variables\",\n\tsqlparser.BEGIN:                   \"begin\",\n\tsqlparser.START:                   \"start\",\n\tsqlparser.TRANSACTION:             \"transaction\",\n\tsqlparser.COMMIT:                  \"commit\",\n\tsqlparser.ROLLBACK:                \"rollback\",\n\tsqlparser.BIT:                     \"bit\",\n\tsqlparser.TINYINT:                 \"tinyint\",\n\tsqlparser.SMALLINT:                \"smallint\",\n\tsqlparser.MEDIUMINT:               \"mediumint\",\n\tsqlparser.INT:                     \"int\",\n\tsqlparser.INTEGER:                 \"integer\",\n\tsqlparser.BIGINT:                  \"bigint\",\n\tsqlparser.INTNUM:                  \"intnum\",\n\tsqlparser.REAL:                    \"real\",\n\tsqlparser.DOUBLE:                  \"double\",\n\tsqlparser.FLOAT_TYPE:              \"float_type\",\n\tsqlparser.DECIMAL:                 \"decimal\",\n\tsqlparser.NUMERIC:                 \"numeric\",\n\tsqlparser.TIME:                    \"time\",\n\tsqlparser.TIMESTAMP:               \"timestamp\",\n\tsqlparser.DATETIME:                \"datetime\",\n\tsqlparser.YEAR:                    \"year\",\n\tsqlparser.CHAR:                    \"char\",\n\tsqlparser.VARCHAR:                 \"varchar\",\n\tsqlparser.BOOL:                    \"bool\",\n\tsqlparser.CHARACTER:               \"character\",\n\tsqlparser.VARBINARY:               \"varbinary\",\n\tsqlparser.NCHAR:                   \"nchar\",\n\tsqlparser.TEXT:                    \"text\",\n\tsqlparser.TINYTEXT:                \"tinytext\",\n\tsqlparser.MEDIUMTEXT:              \"mediumtext\",\n\tsqlparser.LONGTEXT:                \"longtext\",\n\tsqlparser.BLOB:                    \"blob\",\n\tsqlparser.TINYBLOB:                \"tinyblob\",\n\tsqlparser.MEDIUMBLOB:              \"mediumblob\",\n\tsqlparser.LONGBLOB:                \"longblob\",\n\tsqlparser.JSON:                    \"json\",\n\tsqlparser.ENUM:                    \"enum\",\n\tsqlparser.GEOMETRY:                \"geometry\",\n\tsqlparser.POINT:                   \"point\",\n\tsqlparser.LINESTRING:              \"linestring\",\n\tsqlparser.POLYGON:                 \"polygon\",\n\tsqlparser.GEOMETRYCOLLECTION:      \"geometrycollection\",\n\tsqlparser.MULTIPOINT:              \"multipoint\",\n\tsqlparser.MULTILINESTRING:         \"multilinestring\",\n\tsqlparser.MULTIPOLYGON:            \"multipolygon\",\n\tsqlparser.NULLX:                   \"nullx\",\n\tsqlparser.AUTO_INCREMENT:          \"auto_increment\",\n\tsqlparser.APPROXNUM:               \"approxnum\",\n\tsqlparser.SIGNED:                  \"signed\",\n\tsqlparser.UNSIGNED:                \"unsigned\",\n\tsqlparser.ZEROFILL:                \"zerofill\",\n\tsqlparser.DATABASES:               \"databases\",\n\tsqlparser.TABLES:                  \"tables\",\n\tsqlparser.NAMES:                   \"names\",\n\tsqlparser.CHARSET:                 \"charset\",\n\tsqlparser.GLOBAL:                  \"global\",\n\tsqlparser.SESSION:                 \"session\",\n\tsqlparser.CURRENT_TIMESTAMP:       \"current_timestamp\",\n\tsqlparser.DATABASE:                \"database\",\n\tsqlparser.CURRENT_DATE:            \"current_date\",\n\tsqlparser.CURRENT_TIME:            \"current_time\",\n\tsqlparser.LOCALTIME:               \"localtime\",\n\tsqlparser.LOCALTIMESTAMP:          \"localtimestamp\",\n\tsqlparser.UTC_DATE:                \"utc_date\",\n\tsqlparser.UTC_TIME:                \"utc_time\",\n\tsqlparser.UTC_TIMESTAMP:           \"utc_timestamp\",\n\tsqlparser.REPLACE:                 \"replace\",\n\tsqlparser.CONVERT:                 \"convert\",\n\tsqlparser.CAST:                    \"cast\",\n\tsqlparser.SUBSTR:                  \"substr\",\n\tsqlparser.SUBSTRING:               \"substring\",\n\tsqlparser.GROUP_CONCAT:            \"group_concat\",\n\tsqlparser.SEPARATOR:               \"separator\",\n\tsqlparser.VSCHEMA:                 \"vschema\",\n\tsqlparser.SEQUENCE:                \"sequence\",\n\tsqlparser.MATCH:                   \"match\",\n\tsqlparser.AGAINST:                 \"against\",\n\tsqlparser.BOOLEAN:                 \"boolean\",\n\tsqlparser.LANGUAGE:                \"language\",\n\tsqlparser.WITH:                    \"with\",\n\tsqlparser.QUERY:                   \"query\",\n\tsqlparser.EXPANSION:               \"expansion\",\n\tsqlparser.UNUSED:                  \"\",\n}\n\n// 这个变更从vitess更新过来，如果vitess新开了一个关键字这里也要同步开\nvar mySQLKeywords = map[string]string{\n\t\"add\":                \"ADD\",\n\t\"against\":            \"AGAINST\",\n\t\"all\":                \"ALL\",\n\t\"alter\":              \"ALTER\",\n\t\"analyze\":            \"ANALYZE\",\n\t\"and\":                \"AND\",\n\t\"as\":                 \"AS\",\n\t\"asc\":                \"ASC\",\n\t\"auto_increment\":     \"AUTO_INCREMENT\",\n\t\"begin\":              \"BEGIN\",\n\t\"between\":            \"BETWEEN\",\n\t\"bigint\":             \"BIGINT\",\n\t\"binary\":             \"BINARY\",\n\t\"_binary\":            \"UNDERSCORE_BINARY\",\n\t\"bit\":                \"BIT\",\n\t\"blob\":               \"BLOB\",\n\t\"bool\":               \"BOOL\",\n\t\"boolean\":            \"BOOLEAN\",\n\t\"by\":                 \"BY\",\n\t\"case\":               \"CASE\",\n\t\"cast\":               \"CAST\",\n\t\"char\":               \"CHAR\",\n\t\"character\":          \"CHARACTER\",\n\t\"charset\":            \"CHARSET\",\n\t\"collate\":            \"COLLATE\",\n\t\"column\":             \"COLUMN\",\n\t\"comment\":            \"COMMENT_KEYWORD\",\n\t\"commit\":             \"COMMIT\",\n\t\"constraint\":         \"CONSTRAINT\",\n\t\"convert\":            \"CONVERT\",\n\t\"substr\":             \"SUBSTR\",\n\t\"substring\":          \"SUBSTRING\",\n\t\"create\":             \"CREATE\",\n\t\"cross\":              \"CROSS\",\n\t\"current_date\":       \"CURRENT_DATE\",\n\t\"current_time\":       \"CURRENT_TIME\",\n\t\"current_timestamp\":  \"CURRENT_TIMESTAMP\",\n\t\"database\":           \"DATABASE\",\n\t\"databases\":          \"DATABASES\",\n\t\"date\":               \"DATE\",\n\t\"datetime\":           \"DATETIME\",\n\t\"decimal\":            \"DECIMAL\",\n\t\"default\":            \"DEFAULT\",\n\t\"delete\":             \"DELETE\",\n\t\"desc\":               \"DESC\",\n\t\"describe\":           \"DESCRIBE\",\n\t\"distinct\":           \"DISTINCT\",\n\t\"div\":                \"DIV\",\n\t\"double\":             \"DOUBLE\",\n\t\"drop\":               \"DROP\",\n\t\"duplicate\":          \"DUPLICATE\",\n\t\"else\":               \"ELSE\",\n\t\"end\":                \"END\",\n\t\"enum\":               \"ENUM\",\n\t\"escape\":             \"ESCAPE\",\n\t\"exists\":             \"EXISTS\",\n\t\"explain\":            \"EXPLAIN\",\n\t\"expansion\":          \"EXPANSION\",\n\t\"false\":              \"FALSE\",\n\t\"float\":              \"FLOAT_TYPE\",\n\t\"for\":                \"FOR\",\n\t\"force\":              \"FORCE\",\n\t\"foreign\":            \"FOREIGN\",\n\t\"from\":               \"FROM\",\n\t\"fulltext\":           \"FULLTEXT\",\n\t\"geometry\":           \"GEOMETRY\",\n\t\"geometrycollection\": \"GEOMETRYCOLLECTION\",\n\t\"global\":             \"GLOBAL\",\n\t\"grant\":              \"GRANT\",\n\t\"group\":              \"GROUP\",\n\t\"group_concat\":       \"GROUP_CONCAT\",\n\t\"having\":             \"HAVING\",\n\t\"if\":                 \"IF\",\n\t\"ignore\":             \"IGNORE\",\n\t\"in\":                 \"IN\",\n\t\"index\":              \"INDEX\",\n\t\"inner\":              \"INNER\",\n\t\"insert\":             \"INSERT\",\n\t\"int\":                \"INT\",\n\t\"integer\":            \"INTEGER\",\n\t\"interval\":           \"INTERVAL\",\n\t\"into\":               \"INTO\",\n\t\"is\":                 \"IS\",\n\t\"join\":               \"JOIN\",\n\t\"json\":               \"JSON\",\n\t\"key\":                \"KEY\",\n\t\"keys\":               \"KEYS\",\n\t\"key_block_size\":     \"KEY_BLOCK_SIZE\",\n\t\"language\":           \"LANGUAGE\",\n\t\"last_insert_id\":     \"LAST_INSERT_ID\",\n\t\"left\":               \"LEFT\",\n\t\"less\":               \"LESS\",\n\t\"like\":               \"LIKE\",\n\t\"limit\":              \"LIMIT\",\n\t\"linestring\":         \"LINESTRING\",\n\t\"localtime\":          \"LOCALTIME\",\n\t\"localtimestamp\":     \"LOCALTIMESTAMP\",\n\t\"lock\":               \"LOCK\",\n\t\"longblob\":           \"LONGBLOB\",\n\t\"longtext\":           \"LONGTEXT\",\n\t\"match\":              \"MATCH\",\n\t\"maxvalue\":           \"MAXVALUE\",\n\t\"mediumblob\":         \"MEDIUMBLOB\",\n\t\"mediumint\":          \"MEDIUMINT\",\n\t\"mediumtext\":         \"MEDIUMTEXT\",\n\t\"mod\":                \"MOD\",\n\t\"mode\":               \"MODE\",\n\t\"multilinestring\":    \"MULTILINESTRING\",\n\t\"multipoint\":         \"MULTIPOINT\",\n\t\"multipolygon\":       \"MULTIPOLYGON\",\n\t\"names\":              \"NAMES\",\n\t\"natural\":            \"NATURAL\",\n\t\"nchar\":              \"NCHAR\",\n\t\"next\":               \"NEXT\",\n\t\"not\":                \"NOT\",\n\t\"null\":               \"NULL\",\n\t\"numeric\":            \"NUMERIC\",\n\t\"offset\":             \"OFFSET\",\n\t\"on\":                 \"ON\",\n\t\"optimize\":           \"OPTIMIZE\",\n\t\"or\":                 \"OR\",\n\t\"order\":              \"ORDER\",\n\t\"outer\":              \"OUTER\",\n\t\"partition\":          \"PARTITION\",\n\t\"point\":              \"POINT\",\n\t\"polygon\":            \"POLYGON\",\n\t\"primary\":            \"PRIMARY\",\n\t\"procedure\":          \"PROCEDURE\",\n\t\"query\":              \"QUERY\",\n\t\"real\":               \"REAL\",\n\t\"regexp\":             \"REGEXP\",\n\t\"rename\":             \"RENAME\",\n\t\"reorganize\":         \"REORGANIZE\",\n\t\"repair\":             \"REPAIR\",\n\t\"replace\":            \"REPLACE\",\n\t\"revoke\":             \"REVOKE\",\n\t\"right\":              \"RIGHT\",\n\t\"rlike\":              \"REGEXP\",\n\t\"rollback\":           \"ROLLBACK\",\n\t\"schema\":             \"SCHEMA\",\n\t\"select\":             \"SELECT\",\n\t\"separator\":          \"SEPARATOR\",\n\t\"session\":            \"SESSION\",\n\t\"set\":                \"SET\",\n\t\"share\":              \"SHARE\",\n\t\"show\":               \"SHOW\",\n\t\"signed\":             \"SIGNED\",\n\t\"smallint\":           \"SMALLINT\",\n\t\"spatial\":            \"SPATIAL\",\n\t\"sql_cache\":          \"SQL_CACHE\",\n\t\"sql_no_cache\":       \"SQL_NO_CACHE\",\n\t\"start\":              \"START\",\n\t\"status\":             \"STATUS\",\n\t\"straight_join\":      \"STRAIGHT_JOIN\",\n\t\"stream\":             \"STREAM\",\n\t\"table\":              \"TABLE\",\n\t\"tables\":             \"TABLES\",\n\t\"text\":               \"TEXT\",\n\t\"than\":               \"THAN\",\n\t\"then\":               \"THEN\",\n\t\"time\":               \"TIME\",\n\t\"timestamp\":          \"TIMESTAMP\",\n\t\"tinyblob\":           \"TINYBLOB\",\n\t\"tinyint\":            \"TINYINT\",\n\t\"tinytext\":           \"TINYTEXT\",\n\t\"to\":                 \"TO\",\n\t\"transaction\":        \"TRANSACTION\",\n\t\"trigger\":            \"TRIGGER\",\n\t\"true\":               \"TRUE\",\n\t\"truncate\":           \"TRUNCATE\",\n\t\"union\":              \"UNION\",\n\t\"unique\":             \"UNIQUE\",\n\t\"unsigned\":           \"UNSIGNED\",\n\t\"update\":             \"UPDATE\",\n\t\"use\":                \"USE\",\n\t\"using\":              \"USING\",\n\t\"utc_date\":           \"UTC_DATE\",\n\t\"utc_time\":           \"UTC_TIME\",\n\t\"utc_timestamp\":      \"UTC_TIMESTAMP\",\n\t\"values\":             \"VALUES\",\n\t\"variables\":          \"VARIABLES\",\n\t\"varbinary\":          \"VARBINARY\",\n\t\"varchar\":            \"VARCHAR\",\n\t\"vindex\":             \"VINDEX\",\n\t\"vindexes\":           \"VINDEXES\",\n\t\"view\":               \"VIEW\",\n\t\"vitess_keyspaces\":   \"VITESS_KEYSPACES\",\n\t\"vitess_shards\":      \"VITESS_SHARDS\",\n\t\"vitess_tablets\":     \"VITESS_TABLETS\",\n\t\"vschema_tables\":     \"VSCHEMA_TABLES\",\n\t\"when\":               \"WHEN\",\n\t\"where\":              \"WHERE\",\n\t\"with\":               \"WITH\",\n\t\"year\":               \"YEAR\",\n\t\"zerofill\":           \"ZEROFILL\",\n}\n\n// Token 基本定义\ntype Token struct {\n\tType int\n\tVal  string\n\ti    int\n}\n\n// Tokenizer 用于初始化 token，区别于 Tokenize 函数，这个函数使用 vitess 的切词方式\nfunc Tokenizer(sql string) []Token {\n\tvar tokens []Token\n\ttkn := sqlparser.NewStringTokenizer(sql)\n\ttyp, val := tkn.Scan()\n\tfor typ != 0 {\n\t\tif val != nil {\n\t\t\ttokens = append(tokens, Token{Type: typ, Val: string(val)})\n\t\t} else {\n\t\t\tif typ > 255 {\n\t\t\t\tif v, ok := TokenString[typ]; ok {\n\t\t\t\t\ttokens = append(tokens, Token{\n\t\t\t\t\t\tType: typ,\n\t\t\t\t\t\tVal:  v,\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\ttokens = append(tokens, Token{\n\t\t\t\t\t\tType: typ,\n\t\t\t\t\t\tVal:  \"\",\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttokens = append(tokens, Token{\n\t\t\t\t\tType: typ,\n\t\t\t\t\tVal:  fmt.Sprintf(\"%c\", typ),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\ttyp, val = tkn.Scan()\n\t}\n\treturn tokens\n}\n\n// IsMysqlKeyword 判断是否是关键字\nfunc IsMysqlKeyword(name string) bool {\n\t_, ok := mySQLKeywords[strings.ToLower(strings.TrimSpace(name))]\n\treturn ok\n}\n\n// getNextToken 从 buf 中获取 token\nfunc getNextToken(buf string, previous Token) Token {\n\tvar typ int // TOKEN_TYPE\n\n\t// Whitespace\n\twhiteSpaceReg := regexp.MustCompile(`^\\s+`)\n\tif whiteSpaceReg.MatchString(buf) {\n\t\treturn Token{\n\t\t\tType: TokenTypeWhitespace,\n\t\t\tVal:  \" \",\n\t\t}\n\t}\n\n\t// Comment (#, --, /**/)\n\tif buf[0] == '#' || (len(buf) > 1 && (buf[:2] == \"--\" || buf[:2] == \"/*\")) {\n\t\tvar last int\n\t\tif buf[0] == '-' || buf[0] == '#' {\n\t\t\t// Comment until end of line\n\t\t\tlast = strings.Index(buf, \"\\n\")\n\t\t\ttyp = TokenTypeComment\n\t\t} else {\n\t\t\t// Comment until closing comment tag\n\t\t\tlast = strings.Index(buf[2:], \"*/\") + 2\n\t\t\ttyp = TokenTypeBlockComment\n\t\t}\n\t\tif last <= 0 {\n\t\t\tlast = len(buf)\n\t\t}\n\t\treturn Token{\n\t\t\tType: typ,\n\t\t\tVal:  buf[:last],\n\t\t}\n\t}\n\n\t// Quoted String\n\tif buf[0] == '\"' || buf[0] == '\\'' || buf[0] == '`' || buf[0] == '[' {\n\t\tvar typ int\n\t\tswitch buf[0] {\n\t\tcase '`', '[':\n\t\t\ttyp = TokenTypeBacktickQuote\n\t\tdefault:\n\t\t\ttyp = TokenTypeQuote\n\t\t}\n\t\treturn Token{\n\t\t\tType: typ,\n\t\t\tVal:  getQuotedString(buf),\n\t\t}\n\t}\n\n\t// User-defined Variable\n\tif (buf[0] == '@' || buf[0] == ':') && len(buf) > 1 {\n\t\tret := Token{\n\t\t\tType: TokenTypeVariable,\n\t\t\tVal:  \"\",\n\t\t}\n\n\t\tif buf[1] == '\"' || buf[1] == '\\'' || buf[1] == '`' {\n\t\t\t// If the variable name is quoted\n\t\t\tret.Val = string(buf[0]) + getQuotedString(buf[1:])\n\t\t} else {\n\t\t\t// Non-quoted variable name\n\t\t\tvarReg := regexp.MustCompile(`^(` + string(buf[0]) + `[a-zA-Z0-9\\._\\$]+)`)\n\t\t\tif varReg.MatchString(buf) {\n\t\t\t\tret.Val = varReg.FindString(buf)\n\t\t\t}\n\t\t}\n\n\t\tif ret.Val != \"\" {\n\t\t\treturn ret\n\t\t}\n\t}\n\n\t// Number(decimal, binary, hex...)\n\tnumReg := regexp.MustCompile(`^([0-9]+(\\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)($|\\s|\"'` + \"`|\" + regBoundariesString + \")\")\n\tif numReg.MatchString(buf) {\n\t\treturn Token{\n\t\t\tType: TokenTypeNumber,\n\t\t\tVal:  numReg.FindString(buf),\n\t\t}\n\t}\n\n\t// Boundary Character(punctuation and symbols)\n\tboundaryReg := regexp.MustCompile(`^(` + regBoundariesString + `)`)\n\tif boundaryReg.MatchString(buf) {\n\t\treturn Token{\n\t\t\tType: TokenTypeBoundary,\n\t\t\tVal:  boundaryReg.FindString(buf),\n\t\t}\n\t}\n\tsqlUpper := strings.ToUpper(buf)\n\t// A reserved word cannot be preceded by a '.'\n\t// this makes it so in \"mytable.from\", \"from\" is not considered a reserved word\n\tif previous.Val != \".\" {\n\t\t// Top Level Reserved Word\n\t\treservedToplevelReg := regexp.MustCompile(`^(` + regReservedToplevelString + `)($|\\s|` + regBoundariesString + `)`)\n\t\tif reservedToplevelReg.MatchString(sqlUpper) {\n\t\t\treturn Token{\n\t\t\t\tType: TokenTypeReservedToplevel,\n\t\t\t\tVal:  reservedToplevelReg.FindString(sqlUpper),\n\t\t\t}\n\t\t}\n\n\t\t// Newline Reserved Word\n\t\treservedNewlineReg := regexp.MustCompile(`^(` + regReservedNewlineString + `)($|\\s|` + regBoundariesString + `)`)\n\t\tif reservedNewlineReg.MatchString(sqlUpper) {\n\t\t\treturn Token{\n\t\t\t\tType: TokenTypeReservedNewline,\n\t\t\t\tVal:  reservedNewlineReg.FindString(sqlUpper),\n\t\t\t}\n\t\t}\n\n\t\t// Other Reserved Word\n\t\treservedReg := regexp.MustCompile(`^(` + regReservedString + `)($|\\s|` + regBoundariesString + `)`)\n\t\tif reservedNewlineReg.MatchString(sqlUpper) {\n\t\t\treturn Token{\n\t\t\t\tType: TokenTypeReserved,\n\t\t\t\tVal:  reservedReg.FindString(sqlUpper),\n\t\t\t}\n\t\t}\n\n\t}\n\n\t// function\n\t// A function must be succeeded by '('\n\t// this makes it so \"count(\" is considered a function, but \"count\" alone is not\n\tfunctionReg := regexp.MustCompile(`^(` + regFunctionString + `)($|\\s|` + regBoundariesString + `)`)\n\tif functionReg.MatchString(sqlUpper) {\n\t\treturn Token{\n\t\t\tType: TokenTypeReserved,\n\t\t\tVal:  functionReg.FindString(sqlUpper),\n\t\t}\n\t}\n\n\t// Non reserved word\n\tnoReservedReg := regexp.MustCompile(`(.*?)($|\\s|[\"'` + \"`]|\" + regBoundariesString + `)`)\n\tif noReservedReg.MatchString(buf) {\n\t\treturn Token{\n\t\t\tType: TokenTypeWord,\n\t\t\tVal:  noReservedReg.FindString(buf),\n\t\t}\n\t}\n\treturn Token{}\n}\n\n// getQuotedString 获取quote\nfunc getQuotedString(buf string) string {\n\t// This checks for the following patterns:\n\t// 1. backtick quoted string using `` to escape\n\t// 2. double quoted string using \"\" or \\\" to escape\n\t// 3. single quoted string using '' or \\' to escape\n\tstart := string(buf[0])\n\tswitch start {\n\tcase \"\\\"\", \"`\", \"'\":\n\t\treg := fmt.Sprintf(`(^%s[^%s\\\\]*(?:\\\\.[^%s\\\\]*)*(%s|$))+`, start, start, start, start)\n\t\tquotedReg := regexp.MustCompile(reg)\n\t\tif quotedReg.MatchString(buf) {\n\t\t\tbuf = quotedReg.FindString(buf)\n\t\t} else {\n\t\t\tbuf = \"\"\n\t\t}\n\tdefault:\n\t\tbuf = \"\"\n\t}\n\treturn buf\n}\n\n// Tokenize 序列化 token，区别于 Tokenizer 函数，这个函数是 soar built-in 实现的切词\nfunc Tokenize(sql string) []Token {\n\tvar token Token\n\tvar tokenLength int\n\tvar tokens []Token\n\ttokenCache := make(map[string]Token)\n\n\t// Used to make sure the string keeps shrinking on each iteration\n\toldStringLen := len(sql) + 1\n\n\tcurrentLength := len(sql)\n\tfor currentLength > 0 {\n\t\t// If the string stopped shrinking, there was a problem\n\t\tif oldStringLen <= currentLength {\n\t\t\ttokens = []Token{\n\t\t\t\t{\n\t\t\t\t\tType: TokenTypeError,\n\t\t\t\t\tVal:  sql,\n\t\t\t\t},\n\t\t\t}\n\t\t\treturn tokens\n\t\t}\n\n\t\toldStringLen = currentLength\n\t\tcacheKey := \"\"\n\t\t// Determine if we can use caching\n\t\tif currentLength >= maxCachekeySize {\n\t\t\tcacheKey = sql[:maxCachekeySize]\n\t\t}\n\n\t\t// See if the token is already cached\n\t\tif _, ok := tokenCache[cacheKey]; ok {\n\t\t\t// Retrieve from cache\n\t\t\ttoken = tokenCache[cacheKey]\n\t\t\ttokenLength = len(token.Val)\n\t\t\tcacheHits = cacheHits + 1\n\t\t} else {\n\t\t\t// Get the next token and the token type\n\t\t\ttoken = getNextToken(sql, token)\n\t\t\ttokenLength = len(token.Val)\n\t\t\tcacheMisses = cacheMisses + 1\n\t\t\t// If the token is shorter than the max length, store it in cache\n\t\t\tif cacheKey != \"\" && tokenLength < maxCachekeySize {\n\t\t\t\ttokenCache[cacheKey] = token\n\t\t\t}\n\t\t}\n\n\t\ttokens = append(tokens, token)\n\n\t\t// Advance the string\n\t\tsql = sql[tokenLength:]\n\t\tcurrentLength = currentLength - tokenLength\n\t}\n\treturn tokens\n}\n\n// Compress compress sql\n// this method is inspired by eversql.com\nfunc Compress(sql string) string {\n\tregLineTab := regexp.MustCompile(`(?i)([\\n\\t])`)\n\tregSpace := regexp.MustCompile(`\\s\\s+`)\n\tsql = regSpace.ReplaceAllString(regLineTab.ReplaceAllString(sql, \" \"), \" \")\n\treturn sql\n}\n\n// SplitStatement SQL切分\n// return 1. original sql, 2. remove comment sql, 3. left over buf\nfunc SplitStatement(buf []byte, delimiter []byte) (string, string, []byte) {\n\tvar singleLineComment bool\n\tvar multiLineComment bool\n\tvar quoted bool\n\tvar quoteRune byte\n\tvar sql string\n\n\tfor i := 0; i < len(buf); i++ {\n\t\tb := buf[i]\n\t\t// single line comment\n\t\tif b == '-' {\n\t\t\tif !quoted && i+2 < len(buf) && buf[i+1] == '-' && buf[i+2] == ' ' {\n\t\t\t\tsingleLineComment = true\n\t\t\t\ti = i + 2\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !quoted && i+2 < len(buf) && i == 0 && buf[i+1] == '-' && (buf[i+2] == '\\n' || buf[i+2] == '\\r') {\n\t\t\t\tsql = \"--\\n\"\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif b == '#' {\n\t\t\tif !multiLineComment && !quoted && !singleLineComment {\n\t\t\t\tsingleLineComment = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// new line end single line comment\n\t\tif b == '\\r' || b == '\\n' {\n\t\t\tif singleLineComment {\n\t\t\t\tsql = string(buf[:i])\n\t\t\t\tsingleLineComment = false\n\t\t\t\tif strings.HasPrefix(strings.TrimSpace(sql), \"--\") ||\n\t\t\t\t\tstrings.HasPrefix(strings.TrimSpace(sql), \"#\") {\n\t\t\t\t\t// just comment, query start with '--', '#'\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\t// comment in multi-line sql\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// multi line comment\n\t\t// https://dev.mysql.com/doc/refman/8.0/en/comments.html\n\t\t// https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html\n\t\tif b == '/' && i+1 < len(buf) && buf[i+1] == '*' {\n\t\t\tif !multiLineComment && !singleLineComment && !quoted &&\n\t\t\t\t(buf[i+2] != '!' && buf[i+2] != '+') {\n\t\t\t\ti = i + 2\n\t\t\t\tmultiLineComment = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif b == '*' && i+1 < len(buf) && buf[i+1] == '/' {\n\t\t\tif multiLineComment && !quoted && !singleLineComment {\n\t\t\t\ti = i + 2\n\t\t\t\tmultiLineComment = false\n\t\t\t\t// '/*comment*/'\n\t\t\t\tif i == len(buf) {\n\t\t\t\t\tsql = string(buf[:i])\n\t\t\t\t}\n\t\t\t\t// '/*comment*/;', 'select 1/*comment*/;'\n\t\t\t\tif string(buf[i:]) == string(delimiter) {\n\t\t\t\t\tsql = string(buf)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// quoted string\n\t\tswitch b {\n\t\tcase '`', '\\'', '\"':\n\t\t\tif i > 1 && buf[i-1] != '\\\\' {\n\t\t\t\tif quoted && b == quoteRune {\n\t\t\t\t\tquoted = false\n\t\t\t\t\tquoteRune = 0\n\t\t\t\t} else {\n\t\t\t\t\t// check if first time found quote\n\t\t\t\t\tif quoteRune == 0 {\n\t\t\t\t\t\tquoted = true\n\t\t\t\t\t\tquoteRune = b\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// delimiter\n\t\tif !quoted && !singleLineComment && !multiLineComment {\n\t\t\teof := true\n\t\t\tfor k, c := range delimiter {\n\t\t\t\tif len(buf) > i+k && buf[i+k] != c {\n\t\t\t\t\teof = false\n\t\t\t\t}\n\t\t\t}\n\t\t\tif eof {\n\t\t\t\ti = i + len(delimiter)\n\t\t\t\tsql = string(buf[:i])\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// ended of buf\n\t\tif i == len(buf)-1 {\n\t\t\tsql = string(buf)\n\t\t}\n\t}\n\torgSQL := string(buf[:len(sql)])\n\tbuf = buf[len(sql):]\n\treturn orgSQL, strings.TrimSuffix(sql, string(delimiter)), buf\n}\n\n// LeftNewLines cal left new lines in space\nfunc LeftNewLines(buf []byte) int {\n\tnewLines := 0\n\tfor _, b := range buf {\n\t\tif !unicode.IsSpace(rune(b)) {\n\t\t\tbreak\n\t\t}\n\t\tif b == byte('\\n') {\n\t\t\tnewLines++\n\t\t}\n\t}\n\treturn newLines\n}\n\n// NewLines cal all new lines\nfunc NewLines(buf []byte) int {\n\tnewLines := 0\n\tfor _, b := range buf {\n\t\tif b == byte('\\n') {\n\t\t\tnewLines++\n\t\t}\n\t}\n\treturn newLines\n}\n\n// QueryType get query type such as SELECT/INSERT/DELETE/CREATE/ALTER\nfunc QueryType(sql string) string {\n\ttokens := Tokenize(sql)\n\tfor _, token := range tokens {\n\t\t// use strings.Fields for 'ALTER TABLE' token split\n\t\tfor _, tk := range strings.Fields(strings.TrimSpace(token.Val)) {\n\t\t\tif val, ok := mySQLKeywords[strings.ToLower(tk)]; ok {\n\t\t\t\treturn val\n\t\t\t}\n\t\t}\n\t}\n\treturn \"UNKNOWN\"\n}\n"
  },
  {
    "path": "ast/token_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n)\n\nfunc TestTokenize(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range common.TestSQLs {\n\t\t\tfmt.Println(sql)\n\t\t\tfmt.Println(Tokenize(sql))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestTokenizer(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select -- comment 1\",\n\t\t\"select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1 and t1.c3=t3.c1 where id>1000\",\n\t\t\"select sourcetable, if(f.lastcontent = ?, f.lastupdate, f.lastcontent) as lastactivity, f.totalcount as activity, type.class as type, (f.nodeoptions & ?) as nounsubscribe from node as f inner join contenttype as type on type.contenttypeid = f.contenttypeid inner join subscribed as sd on sd.did = f.nodeid and sd.userid = ? union all select f.name as title, f.userid as keyval, ? as sourcetable, ifnull(f.lastpost, f.joindate) as lastactivity, f.posts as activity, ? as type, ? as nounsubscribe from user as f inner join userlist as ul on ul.relationid = f.userid and ul.userid = ? where ul.type = ? and ul.aq = ? order by title limit ?\",\n\t\t\"select c1 from t1 where id>=1000\", // test \">=\"\n\t\t\"select SQL_CALC_FOUND_ROWS col from tbl where id>1000\",\n\t\t\"SELECT * FROM tb WHERE id=?;\",\n\t\t\"SELECT * FROM tb WHERE id is null;\",\n\t\t\"SELECT * FROM tb WHERE id is not null;\",\n\t\t\"SELECT * FROM tb WHERE id between 1 and 3;\",\n\t\t\"alter table inventory add index idx_store_film` (`store_id`,`film_id`);\",\n\t\t`UPDATE xxx SET c1=' LOGGER.error(\"\"); }' WHERE id = 2 ;`,\n\t\t`update tb set status = 1 where id = 1;`, // SQL 中包含 non-broken-space\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range sqls {\n\t\t\tpretty.Println(Tokenizer(sql))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestGetQuotedString(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tvar str = []string{\n\t\t`\"hello world\"`,\n\t\t\"`hello world`\",\n\t\t`'hello world'`,\n\t\t\"hello world\",\n\t\t`'hello \\'world'`,\n\t\t`\"hello \\\"wor\\\"ld\"`,\n\t\t`\"hello \\\"world\"`,\n\t\t`\"\"`,\n\t\t`''`,\n\t\t\"``\",\n\t\t`'hello 'world'`,\n\t\t`\"hello \"world\"`,\n\t\t`' LOGGER.error(\"\"); }'`,\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, s := range str {\n\t\t\tfmt.Printf(\"orignal: %s\\nquoted: %s\\n\", s, getQuotedString(s))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestCompress(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range common.TestSQLs {\n\t\t\tfmt.Println(sql)\n\t\t\tfmt.Println(Compress(sql))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFormat(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range common.TestSQLs {\n\t\t\tfmt.Println(sql)\n\t\t\tfmt.Println(format(sql))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestSplitStatement(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tbufs := [][]byte{\n\t\t[]byte(\"select * from test;hello\"),              // 0\n\t\t[]byte(\"select 'asd;fas', col from test;hello\"), // 1\n\t\t[]byte(\"-- select * from test;hello\"),           // 2\n\t\t[]byte(\"#select * from test;hello\"),             // 3\n\t\t[]byte(\"select * /*comment*/from test;hello\"),   // 4\n\t\t[]byte(\"select * /*comment;*/from test;hello\"),  // 5\n\t\t[]byte(`select * /*comment\n\t\t;*/\n\t\tfrom test;hello`), // 6\n\t\t[]byte(`select * from test`), // 7\n\t\t// https://github.com/XiaoMi/soar/issues/66\n\t\t[]byte(`/*comment*/`),  // 8\n\t\t[]byte(`/*comment*/;`), // 9\n\t\t[]byte(`--`),           // 10\n\t\t[]byte(`-- comment`),   // 11\n\t\t[]byte(`# comment`),    // 12\n\t\t// https://github.com/XiaoMi/soar/issues/116\n\t\t[]byte(`select\n*\n-- comment\nfrom tb\nwhere col = 1`), // 13\n\t\t[]byte(`select\n* --\nfrom tb\nwhere col = 1`), // 14\n\t\t[]byte(`select\n* #\nfrom tb\nwhere col = 1`), // 15\n\t\t[]byte(`select\n*\n--\nfrom tb\nwhere col = 1`), // 16\n\t\t[]byte(`select * from\n-- comment\ntb;\nselect col from tb where col = 1;`), // 17\n\t\t// https://github.com/XiaoMi/soar/issues/120\n\t\t[]byte(`\n-- comment\nselect col from tb;\nselect col from tb;\n`), // 18\n\t\t[]byte(`INSERT /*+ SET_VAR(foreign_key_checks=OFF) */ INTO t2 VALUES(2);`),                              // 19\n\t\t[]byte(`select /*!50000 1,*/ 1;`),                                                                       // 20\n\t\t[]byte(`UPDATE xxx SET c1=' LOGGER.error(\"\"); }' WHERE id = 2 ;`),                                       // 21\n\t\t[]byte(\"UPDATE `xxx` SET aaa='a;' WHERE `id` = 15;\"),                                                    // 22\n\t\t[]byte(\"UPDATE `xxx` SET aaa='a -- b' WHERE `id` = 15; UPDATE `xxx` SET aaa='c -- d' WHERE `id` = 16;\"), // 23\n\t\t// []byte(`/* comment here */ SET MAX_JOIN_SIZE=#`),                        // 24\n\t}\n\t// \\G 分隔符\n\tbuf2s := [][]byte{\n\t\t[]byte(\"select * from test\\\\Ghello\"),                    // 0\n\t\t[]byte(\"select 'hello\\\\Gworld', col from test\\\\Ghello\"), // 1\n\t\t[]byte(\"-- select * from test\\\\Ghello\"),                 // 2\n\t\t[]byte(\"#select * from test\\\\Ghello\"),                   // 3\n\t\t[]byte(\"select * /*comment*/from test\\\\Ghello\"),         // 4\n\t\t[]byte(\"select * /*comment;*/from test\\\\Ghello\"),        // 5\n\t\t[]byte(`select * /*comment\n        \\\\G*/\n        from test\\\\Ghello`), // 6\n\t}\n\t//// single sql test\n\t//b, t1, t2 := SplitStatement(bufs[23], []byte(\";\"))\n\t//fmt.Println(\"buf: \", b, \"sql: \", t1, \"left: \", string(t2))\n\t//return\n\terr := common.GoldenDiff(func() {\n\t\tfor i, buf := range bufs {\n\t\t\tsql, _, _ := SplitStatement(buf, []byte(\";\"))\n\t\t\tfmt.Println(i, sql)\n\t\t}\n\n\t\tfor i, buf := range buf2s {\n\t\t\tsql, _, _ := SplitStatement(buf, []byte(\"\\\\G\"))\n\t\t\tfmt.Println(i, sql)\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestLeftNewLines(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tbufs := [][]byte{\n\t\t[]byte(`\n\t\tselect * from test;hello`),\n\t\t[]byte(`select * /*comment\n        ;*/\n        from test;hello`),\n\t\t[]byte(`select * from test`),\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, buf := range bufs {\n\t\t\tfmt.Println(LeftNewLines(buf))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestNewLines(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tbufs := [][]byte{\n\t\t[]byte(`\n\t\tselect * from test;hello`),\n\t\t[]byte(`select * /*comment\n        ;*/\n        from test;hello`),\n\t\t[]byte(`select * from test`),\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, buf := range bufs {\n\t\t\tfmt.Println(NewLines(buf))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestQueryType(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tvar testSQLs = []string{\n\t\t` select 1`,\n\t\t`/*comment*/ select 1`,\n\t\t`(select 1)`,\n\t\t`grant select on *.* to user@'localhost'`,\n\t\t`REVOKE INSERT ON *.* FROM 'jeffrey'@'localhost';`,\n\t}\n\t// fmt.Println(testSQLs[len(testSQLs)-1])\n\t// fmt.Println(QueryType(testSQLs[len(testSQLs)-1]))\n\t// return\n\terr := common.GoldenDiff(func() {\n\t\tfor _, buf := range append(testSQLs, common.TestSQLs...) {\n\t\t\tfmt.Println(QueryType(buf))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "ast/vitess.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// PrintPrettyVitessStmtNode print vitess AST struct data\nfunc PrintPrettyVitessStmtNode(sql string) {\n\ttree, err := sqlparser.Parse(sql)\n\tif err != nil {\n\t\tcommon.Log.Warning(err.Error())\n\t} else {\n\t\t_, err = pretty.Println(tree)\n\t\tcommon.LogIfWarn(err, \"\")\n\t}\n}\n\n// VitessStmtNode2JSON vitess AST tree into json format\nfunc VitessStmtNode2JSON(sql string) string {\n\tvar str string\n\ttree, err := sqlparser.Parse(sql)\n\tif err != nil {\n\t\tcommon.Log.Warning(err.Error())\n\t} else {\n\t\tb, err := json.MarshalIndent(tree, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(err.Error())\n\t\t} else {\n\t\t\tstr = string(b)\n\t\t}\n\t}\n\treturn str\n}\n"
  },
  {
    "path": "ast/vitess_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\nfunc TestPrintPrettyVitessStmtNode(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select 1`,\n\t\t`select * f`, // syntax error case\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range sqls {\n\t\t\tPrintPrettyVitessStmtNode(sql)\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestVitessStmtNode2JSON(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t`select 1`,\n\t\t`select * f`, // syntax error case\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range sqls {\n\t\t\tfmt.Println(VitessStmtNode2JSON(sql))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "cmd/soar/doc.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package soar is the main program of SOAR\npackage main\n"
  },
  {
    "path": "cmd/soar/soar.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/advisor\"\n\t\"github.com/XiaoMi/soar/ast\"\n\t\"github.com/XiaoMi/soar/common\"\n\t\"github.com/XiaoMi/soar/database\"\n\t\"github.com/XiaoMi/soar/env\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/kr/pretty\"\n\t\"github.com/percona/go-mysql/query\"\n)\n\nfunc main() {\n\t// 全局变量\n\tvar err error\n\tvar sql string                                            // 单条评审指定的 sql 或 explain\n\tvar currentDB string                                      // 当前 SQL 使用的 database\n\tsqlCounter := 1                                           // SQL 计数器\n\tlineCounter := 1                                          // 行计数器\n\tvar alterSQLs []string                                    // 待评审的 SQL 中所有 ALTER 请求\n\talterTableTimes := make(map[string]int)                   // 待评审的 SQL 中同一经表 ALTER 请求计数器\n\tsuggestMerged := make(map[string]map[string]advisor.Rule) // 优化建议去重, key 为 sql 的 fingerprint.ID\n\tvar suggestStr []string                                   // string 形式格式化之后的优化建议，用于 -report-type json\n\ttables := make(map[string][]string)                       // SQL 使用的库表名\n\n\t// 配置文件&命令行参数解析\n\tinitConfig()\n\n\t// 命令行帮助工具，如 -list-report-types, -check-config等。\n\tif isContinue, exitCode := helpTools(); !isContinue {\n\t\tos.Exit(exitCode)\n\t}\n\n\t// 环境初始化，连接检查线上环境+构建测试环境\n\tvEnv, rEnv := env.BuildEnv()\n\n\t// 使用 -cleanup-test-database 命令手动清理残余的 optimizer_xxx 数据库\n\tif common.Config.CleanupTestDatabase {\n\t\tvEnv.CleanupTestDatabase()\n\t\treturn\n\t}\n\n\t// 如果使用到测试环境，在这里环境清理\n\tif common.Config.DropTestTemporary {\n\t\tdefer vEnv.CleanUp()\n\t}\n\n\t// 当程序卡死的时候，或者由于某些原因程序没有退出，可以通过捕获信号量的形式让程序优雅退出并且清理测试环境\n\tcommon.HandleSignal(func() {\n\t\tshutdown(vEnv, rEnv)\n\t})\n\n\t// 对指定的库表进行索引重复检查\n\tif common.Config.ReportType == \"duplicate-key-checker\" {\n\t\tdupKeySuggest := advisor.DuplicateKeyChecker(rEnv)\n\t\t_, str := advisor.FormatSuggest(\"\", currentDB, common.Config.ReportType, dupKeySuggest)\n\t\tif str == \"\" {\n\t\t\tfmt.Printf(\"%s/%s 未发现重复索引\\n\", common.Config.OnlineDSN.Addr, common.Config.OnlineDSN.Schema)\n\t\t} else {\n\t\t\tfmt.Println(str)\n\t\t}\n\t\treturn\n\t}\n\n\t// 读入待优化 SQL ，当配置文件或命令行参数未指定 SQL 时从管道读取\n\tbuf := initQuery(common.Config.Query)\n\tlineCounter += ast.LeftNewLines([]byte(buf))\n\tbuf = strings.TrimSpace(buf)\n\n\t// remove bom from file header\n\tvar bom []byte\n\tbuf, bom = common.RemoveBOM([]byte(buf))\n\n\tif isContinue, exitCode := reportTool(buf, bom); !isContinue {\n\t\tos.Exit(exitCode)\n\t}\n\n\t// 逐条SQL给出优化建议\n\tfor ; ; sqlCounter++ {\n\t\tvar id string                                     // fingerprint.ID\n\t\theuristicSuggest := make(map[string]advisor.Rule) // 启发式建议\n\t\texpSuggest := make(map[string]advisor.Rule)       // EXPLAIN 解读\n\t\tidxSuggest := make(map[string]advisor.Rule)       // 索引建议\n\t\tproSuggest := make(map[string]advisor.Rule)       // Profiling 信息\n\t\ttraceSuggest := make(map[string]advisor.Rule)     // Trace 信息\n\t\tmysqlSuggest := make(map[string]advisor.Rule)     // MySQL 返回的 ERROR 信息\n\n\t\tif buf == \"\" {\n\t\t\tcommon.Log.Debug(\"Ending, buf: '%s', sql: '%s'\", buf, sql)\n\t\t\tbreak\n\t\t}\n\t\t// 查询请求切分\n\t\torgSQL, sql, bufBytes := ast.SplitStatement([]byte(buf), []byte(common.Config.Delimiter))\n\t\t// lineCounter\n\t\tlc := ast.NewLines([]byte(orgSQL))\n\t\t// leftLineCounter\n\t\tllc := ast.LeftNewLines([]byte(orgSQL))\n\t\tlineCounter += llc\n\t\tif len(buf) == len(bufBytes) {\n\t\t\t// 防止切分死循环，当剩余的内容和原 SQL 相同时直接清空 buf\n\t\t\tbuf = \"\"\n\t\t\torgSQL = string(bufBytes)\n\t\t\tsql = orgSQL\n\t\t} else {\n\t\t\tbuf = string(bufBytes)\n\t\t}\n\n\t\t// 去除无用的备注和空格\n\t\tsql = database.RemoveSQLComments(sql)\n\t\tif sql == \"\" {\n\t\t\tcommon.Log.Debug(\"empty query or comment, buf: %s\", buf)\n\t\t\tcontinue\n\t\t}\n\t\tcommon.Log.Debug(\"main loop SQL: %s\", sql)\n\n\t\t// +++++++++++++++++++++小工具集[开始]+++++++++++++++++++++++{\n\t\tfingerprint := strings.TrimSpace(query.Fingerprint(sql))\n\t\t// SQL 签名\n\t\tid = query.Id(fingerprint)\n\t\tcurrentDB = env.CurrentDB(sql, currentDB)\n\t\tswitch common.Config.ReportType {\n\t\tcase \"fingerprint\":\n\t\t\t// SQL 指纹\n\t\t\tif common.Config.Verbose {\n\t\t\t\tfmt.Printf(\"-- ID: %s\\n\", id)\n\t\t\t}\n\t\t\tfmt.Println(fingerprint)\n\t\t\tcontinue\n\t\tcase \"pretty\":\n\t\t\t// SQL 美化\n\t\t\tfmt.Println(ast.Pretty(sql, \"builtin\") + common.Config.Delimiter)\n\t\t\tcontinue\n\t\tcase \"compress\":\n\t\t\t// SQL 压缩\n\t\t\tfmt.Println(ast.Compress(sql) + common.Config.Delimiter)\n\t\t\tcontinue\n\t\tcase \"ast\":\n\t\t\t// print vitess AST data struct\n\t\t\tast.PrintPrettyVitessStmtNode(sql)\n\t\t\tcontinue\n\t\tcase \"ast-json\":\n\t\t\t// print vitess SQL AST into json format\n\t\t\tfmt.Println(ast.VitessStmtNode2JSON(sql))\n\t\t\tcontinue\n\t\tcase \"tiast\":\n\t\t\t// print TiDB AST data struct\n\t\t\tast.PrintPrettyStmtNode(sql, \"\", \"\")\n\t\t\tcontinue\n\t\tcase \"tiast-json\":\n\t\t\t// print TiDB SQL AST into json format\n\t\t\tfmt.Println(ast.StmtNode2JSON(sql, \"\", \"\"))\n\t\t\tcontinue\n\t\tcase \"tokenize\":\n\t\t\t// SQL 切词\n\t\t\t_, err = pretty.Println(ast.Tokenize(sql))\n\t\t\tcommon.LogIfWarn(err, \"\")\n\t\t\tcontinue\n\t\tdefault:\n\t\t\t// 建议去重，减少评审整个文件耗时\n\t\t\t// TODO: 由于 a = 11 和 a = '11' 的 fingerprint 相同，这里一旦跳过即无法检查有些建议了，如： ARG.003\n\t\t\tif _, ok := suggestMerged[id]; ok {\n\t\t\t\t// `use ?` 不可以去重，去重后将导致无法切换数据库\n\t\t\t\tif !strings.HasPrefix(fingerprint, \"use\") {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 黑名单中的SQL不给建议\n\t\t\tif advisor.InBlackList(fingerprint) {\n\t\t\t\t// `use ?` 不可以出现在黑名单中\n\t\t\t\tif !strings.HasPrefix(fingerprint, \"use\") {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ttables[id] = ast.SchemaMetaInfo(sql, currentDB)\n\t\t// +++++++++++++++++++++小工具集[结束]+++++++++++++++++++++++}\n\n\t\t// +++++++++++++++++++++语法检查[开始]+++++++++++++++++++++++{\n\t\tq, syntaxErr := advisor.NewQuery4Audit(sql)\n\t\tstmt := q.Stmt\n\n\t\t// 如果语法检查出错则不需要给优化建议\n\t\tif syntaxErr != nil {\n\t\t\terrContent := fmt.Sprintf(\"At SQL %d : %v\", sqlCounter, syntaxErr)\n\t\t\tcommon.Log.Warning(errContent)\n\t\t\tif common.Config.OnlySyntaxCheck || common.Config.ReportType == \"rewrite\" ||\n\t\t\t\tcommon.Config.ReportType == \"query-type\" {\n\t\t\t\tfmt.Println(errContent)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\t// tidb parser 语法检查给出的建议 ERR.000\n\t\t\tmysqlSuggest[\"ERR.000\"] = advisor.RuleMySQLError(\"ERR.000\", syntaxErr)\n\t\t}\n\t\t// 如果只想检查语法直接跳过后面的步骤\n\t\tif common.Config.OnlySyntaxCheck {\n\t\t\tcontinue\n\t\t}\n\t\t// +++++++++++++++++++++语法检查[结束]+++++++++++++++++++++++}\n\n\t\tswitch common.Config.ReportType {\n\t\tcase \"tables\":\n\t\t\tcontinue\n\t\tcase \"query-type\":\n\t\t\t// query type by first key word\n\t\t\tfmt.Println(ast.QueryType(sql))\n\t\t\tcontinue\n\t\t}\n\n\t\t// +++++++++++++++++++++启发式规则建议[开始]+++++++++++++++++++++++{\n\t\tcommon.Log.Debug(\"start of heuristic advisor Query: %s\", q.Query)\n\t\tfor item, rule := range advisor.HeuristicRules {\n\t\t\t// 去除忽略的建议检查\n\t\t\tokFunc := (*advisor.Query4Audit).RuleOK\n\t\t\tif !advisor.IsIgnoreRule(item) && &rule.Func != &okFunc {\n\t\t\t\tr := rule.Func(q)\n\t\t\t\tif r.Item == item {\n\t\t\t\t\theuristicSuggest[item] = r\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcommon.Log.Debug(\"end of heuristic advisor Query: %s\", q.Query)\n\t\t// +++++++++++++++++++++启发式规则建议[结束]+++++++++++++++++++++++}\n\n\t\t// +++++++++++++++++++++索引优化建议[开始]+++++++++++++++++++++++{\n\t\t// 如果配置了索引建议过滤规则，不进行索引优化建议\n\t\t// 在配置文件 ignore-rules 中添加 'IDX.*' 即可屏蔽索引优化建议\n\t\tcommon.Log.Debug(\"start of index advisor Query: %s\", q.Query)\n\t\tif !advisor.IsIgnoreRule(\"IDX.\") {\n\t\t\tif vEnv.BuildVirtualEnv(rEnv, q.Query) {\n\t\t\t\tidxAdvisor, err := advisor.NewAdvisor(vEnv, *rEnv, *q)\n\t\t\t\tif err != nil || (idxAdvisor == nil && vEnv.Error == nil) {\n\t\t\t\t\tif idxAdvisor == nil {\n\t\t\t\t\t\t// 如果 SQL 是 DDL 语句，则返回的 idxAdvisor 为 nil，可以忽略不处理\n\t\t\t\t\t\t// TODO alter table add index 语句检查索引是否已经存在\n\t\t\t\t\t\tcommon.Log.Debug(\"idxAdvisor by pass Query: %s\", q.Query)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcommon.Log.Warning(\"advisor.NewAdvisor Error: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// 创建环境时没有出现错误，生成索引建议\n\t\t\t\t\tif vEnv.Error == nil {\n\t\t\t\t\t\tidxSuggest = idxAdvisor.IndexAdvise().Format()\n\n\t\t\t\t\t\t// 依赖数据字典的启发式建议\n\t\t\t\t\t\tfor i, r := range idxAdvisor.HeuristicCheck(*q) {\n\t\t\t\t\t\t\theuristicSuggest[i] = r\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// 根据错误号输出建议\n\t\t\t\t\t\tswitch vEnv.Error.(*mysql.MySQLError).Number {\n\t\t\t\t\t\tcase 1061:\n\t\t\t\t\t\t\tidxSuggest[\"IDX.001\"] = advisor.Rule{\n\t\t\t\t\t\t\t\tItem:     \"IDX.001\",\n\t\t\t\t\t\t\t\tSeverity: \"L2\",\n\t\t\t\t\t\t\t\tSummary:  \"索引名称已存在\",\n\t\t\t\t\t\t\t\tContent:  strings.Trim(strings.Split(vEnv.Error.Error(), \":\")[1], \" \"),\n\t\t\t\t\t\t\t\tCase:     sql,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t// vEnv.VEnvBuild 阶段给出的 ERROR 是 ERR.001\n\t\t\t\t\t\t\tdelete(mysqlSuggest, \"ERR.000\")\n\t\t\t\t\t\t\tmysqlSuggest[\"ERR.001\"] = advisor.RuleMySQLError(\"ERR.001\", vEnv.Error)\n\t\t\t\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv DDL Execute Error : %v\", vEnv.Error)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcommon.Log.Error(\"vEnv.BuildVirtualEnv Error: prepare SQL '%s' in vEnv failed.\", q.Query)\n\t\t\t}\n\t\t}\n\t\tcommon.Log.Debug(\"end of index advisor Query: %s\", q.Query)\n\t\t// +++++++++++++++++++++索引优化建议[结束]+++++++++++++++++++++++}\n\n\t\t// +++++++++++++++++++++EXPLAIN 建议[开始]+++++++++++++++++++++++{\n\t\t// 如果未配置 Online 或 Test 无法给 Explain 建议\n\t\tcommon.Log.Debug(\"start of explain Query: %s\", q.Query)\n\t\tif !common.Config.OnlineDSN.Disable && !common.Config.TestDSN.Disable {\n\t\t\t// 因为 EXPLAIN 依赖数据库环境，所以把这段逻辑放在启发式建议和索引建议后面\n\t\t\tif common.Config.Explain {\n\t\t\t\t// 执行 EXPLAIN\n\t\t\t\texplainInfo, err := rEnv.Explain(q.Query,\n\t\t\t\t\tdatabase.ExplainType[common.Config.ExplainType],\n\t\t\t\t\tdatabase.ExplainFormatType[common.Config.ExplainFormat])\n\t\t\t\tif err != nil {\n\t\t\t\t\t// 线上环境执行失败才到测试环境 EXPLAIN，比如在用户提供建表语句及查询语句的场景\n\t\t\t\t\tcommon.Log.Warn(\"rEnv.Explain Warn: %v\", err)\n\t\t\t\t\texplainInfo, err = vEnv.Explain(q.Query,\n\t\t\t\t\t\tdatabase.ExplainType[common.Config.ExplainType],\n\t\t\t\t\t\tdatabase.ExplainFormatType[common.Config.ExplainFormat])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t// EXPLAIN 阶段给出的 ERROR 是 ERR.002\n\t\t\t\t\t\tmysqlSuggest[\"ERR.002\"] = advisor.RuleMySQLError(\"ERR.002\", err)\n\t\t\t\t\t\tcommon.Log.Error(\"vEnv.Explain Error: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// 分析 EXPLAIN 结果\n\t\t\t\tif explainInfo != nil {\n\t\t\t\t\texpSuggest = advisor.ExplainAdvisor(explainInfo)\n\t\t\t\t} else {\n\t\t\t\t\tcommon.Log.Warn(\"rEnv&vEnv.Explain explainInfo nil, SQL: %s\", q.Query)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcommon.Log.Debug(\"end of explain Query: %s\", q.Query)\n\t\t// +++++++++++++++++++++ EXPLAIN 建议[结束]+++++++++++++++++++++++}\n\n\t\t// +++++++++++++++++++++ Profiling [开始]+++++++++++++++++++++++++{\n\t\tcommon.Log.Debug(\"start of profiling Query: %s\", q.Query)\n\t\tif common.Config.Profiling {\n\t\t\tres, err := vEnv.Profiling(q.Query)\n\t\t\tif err == nil {\n\t\t\t\tproSuggest[\"PRO.001\"] = advisor.Rule{\n\t\t\t\t\tItem:     \"PRO.001\",\n\t\t\t\t\tSeverity: \"L0\",\n\t\t\t\t\tContent:  database.FormatProfiling(res),\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcommon.Log.Error(\"Profiling Error: %v\", err)\n\t\t\t}\n\t\t}\n\t\tcommon.Log.Debug(\"end of profiling Query: %s\", q.Query)\n\t\t// +++++++++++++++++++++ Profiling [结束]++++++++++++++++++++++++++}\n\n\t\t// +++++++++++++++++++++ Trace [开始]+++++++++++++++++++++++++{\n\t\tcommon.Log.Debug(\"start of trace Query: %s\", q.Query)\n\t\tif common.Config.Trace {\n\t\t\tres, err := vEnv.Trace(q.Query)\n\t\t\tif err == nil {\n\t\t\t\ttraceSuggest[\"TRA.001\"] = advisor.Rule{\n\t\t\t\t\tItem:     \"TRA.001\",\n\t\t\t\t\tSeverity: \"L0\",\n\t\t\t\t\tContent:  database.FormatTrace(res),\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcommon.Log.Error(\"Trace Error: %v\", err)\n\t\t\t}\n\t\t}\n\t\tcommon.Log.Debug(\"end of trace Query: %s\", q.Query)\n\t\t// +++++++++++++++++++++Trace [结束]++++++++++++++++++++++++++}\n\n\t\t// +++++++++++++++++++++SQL 重写[开始]+++++++++++++++++++++++++{\n\t\tcommon.Log.Debug(\"start of rewrite Query: %s\", q.Query)\n\t\tif common.Config.ReportType == \"rewrite\" {\n\t\t\tif strings.HasPrefix(strings.TrimSpace(strings.ToLower(sql)), \"create\") ||\n\t\t\t\tstrings.HasPrefix(strings.TrimSpace(strings.ToLower(sql)), \"alter\") ||\n\t\t\t\tstrings.HasPrefix(strings.TrimSpace(strings.ToLower(sql)), \"rename\") {\n\t\t\t\t// 依赖上下文件的 SQL 重写，如：多条 ALTER SQL 合并\n\t\t\t\t// vitess 对 DDL 语法的支持不好，大部分 DDL 会语法解析出错，但即使出错了还是会生成一个 stmt 而且里面的 db.table 还是准确的。\n\n\t\t\t\talterSQLs = append(alterSQLs, sql)\n\t\t\t\talterTbl := ast.AlterAffectTable(stmt)\n\t\t\t\tif alterTbl != \"\" && alterTbl != \"dual\" {\n\t\t\t\t\tif _, ok := alterTableTimes[alterTbl]; ok {\n\t\t\t\t\t\theuristicSuggest[\"ALT.002\"] = advisor.HeuristicRules[\"ALT.002\"]\n\t\t\t\t\t\talterTableTimes[alterTbl] = alterTableTimes[alterTbl] + 1\n\t\t\t\t\t} else {\n\t\t\t\t\t\talterTableTimes[alterTbl] = 1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// 其他不依赖上下文件的 SQL 重写\n\t\t\t\trw := ast.NewRewrite(sql)\n\t\t\t\tif rw == nil {\n\t\t\t\t\t// 都到这一步了 sql 不会语法不正确，因此 rw 一般不会为 nil\n\t\t\t\t\tcommon.Log.Critical(\"NewRewrite nil point error, SQL: %s\", sql)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\t// SQL 转写需要的源信息采集，如果没有配置环境则只做有限改写\n\t\t\t\tmeta := ast.GetMeta(rw.Stmt, nil)\n\t\t\t\trw.Columns = vEnv.GenTableColumns(meta)\n\t\t\t\t// 执行定义好的 SQL 重写规则\n\t\t\t\trw.Rewrite()\n\t\t\t\tfmt.Println(strings.TrimSpace(rw.NewSQL))\n\t\t\t}\n\t\t}\n\t\tcommon.Log.Debug(\"end of rewrite Query: %s\", q.Query)\n\t\t// +++++++++++++++++++++ SQL 重写[结束]++++++++++++++++++++++++++}\n\n\t\t// +++++++++++++++++++++打印单条 SQL 优化建议[开始]++++++++++++++++++++++++++{\n\t\tcommon.Log.Debug(\"start of print suggestions, Query: %s\", q.Query)\n\t\tif strings.HasPrefix(fingerprint, \"use\") {\n\t\t\tcontinue\n\t\t}\n\t\tsug, str := advisor.FormatSuggest(q.Query, currentDB, common.Config.ReportType, heuristicSuggest, idxSuggest, expSuggest, proSuggest, traceSuggest, mysqlSuggest)\n\t\tsuggestMerged[id] = sug\n\t\tswitch common.Config.ReportType {\n\t\tcase \"json\":\n\t\t\tsuggestStr = append(suggestStr, str)\n\t\tcase \"tables\":\n\t\tcase \"duplicate-key-checker\":\n\t\tcase \"rewrite\":\n\t\tcase \"lint\":\n\t\t\tfor _, s := range strings.Split(str, \"\\n\") {\n\t\t\t\t// ignore empty output\n\t\t\t\tif strings.TrimSpace(s) == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif common.Config.Query != \"\" {\n\t\t\t\t\tif _, err = os.Stat(common.Config.Query); err == nil {\n\t\t\t\t\t\tfmt.Printf(\"%s:%d:%s\\n\", common.Config.Query, lineCounter, s)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Printf(\"null:%d:%s\\n\", lineCounter, s)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Printf(\"stdin:%d:%s\\n\", lineCounter, s)\n\t\t\t\t}\n\t\t\t}\n\t\t\tlineCounter += lc - llc\n\t\tcase \"html\":\n\t\t\tfmt.Println(common.Markdown2HTML(str))\n\t\tdefault:\n\t\t\tfmt.Println(str)\n\t\t}\n\t\tcommon.Log.Debug(\"end of print suggestions, Query: %s\", q.Query)\n\t\t// +++++++++++++++++++++打印单条 SQL 优化建议[结束]++++++++++++++++++++++++++}\n\t}\n\n\t// 同一张表的多条 ALTER 语句合并为一条\n\tif ast.RewriteRuleMatch(\"mergealter\") {\n\t\tfor _, v := range ast.MergeAlterTables(alterSQLs...) {\n\t\t\tfmt.Println(strings.TrimSpace(v))\n\t\t}\n\t\treturn\n\t}\n\n\t// 以 JSON 格式化输出\n\tif common.Config.ReportType == \"json\" {\n\t\tfmt.Println(\"[\\n\", strings.Join(suggestStr, \",\\n\"), \"\\n]\")\n\t}\n\n\t// 以 JSON 格式输出 SQL 影响的库表名\n\tif common.Config.ReportType == \"tables\" {\n\t\tjs, err := json.MarshalIndent(tables, \"\", \"  \")\n\t\tif err == nil {\n\t\t\tfmt.Println(string(js))\n\t\t} else {\n\t\t\tcommon.Log.Error(\"FormatSuggest json.Marshal Error: %v\", err)\n\t\t}\n\t\treturn\n\t}\n\n\tverboseInfo()\n}\n"
  },
  {
    "path": "cmd/soar/soar_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\nvar update = flag.Bool(\"update\", false, \"update .golden files\")\n\nfunc TestMain(m *testing.M) {\n\t// 初始化 init\n\tif common.DevPath == \"\" {\n\t\t_, file, _, _ := runtime.Caller(0)\n\t\tcommon.DevPath, _ = filepath.Abs(filepath.Dir(filepath.Join(file, \"..\"+string(filepath.Separator))))\n\t}\n\tcommon.BaseDir = common.DevPath\n\terr := common.ParseConfig(\"\")\n\tcommon.LogIfError(err, \"init ParseConfig\")\n\tcommon.Log.Debug(\"mysql_test init\")\n\t_ = update // check if var success init\n\n\t// 分割线\n\tflag.Parse()\n\tm.Run()\n\n\t// 环境清理\n\t//\n}\n\nfunc Test_Main(_ *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tcommon.Config.OnlineDSN.Disable = true\n\tcommon.Config.LogLevel = 0\n\tcommon.Config.Query = \"select * syntaxError\"\n\tmain()\n\tcommon.Config.Query = \"select * from film;alter table city add index idx_country_id(country_id);\"\n\tmain()\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc Test_Main_More(_ *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tcommon.Config.LogLevel = 0\n\tcommon.Config.Profiling = true\n\tcommon.Config.Explain = true\n\tcommon.Config.Query = \"select * from film where country_id = 1;use sakila;alter table city add index idx_country_id(country_id);\"\n\torgRerportType := common.Config.ReportType\n\tfor _, typ := range []string{\n\t\t\"json\", \"html\", \"markdown\", \"fingerprint\", \"compress\", \"pretty\", \"rewrite\",\n\t\t\"ast\", \"tiast\", \"ast-json\", \"tiast-json\", \"tokenize\", \"lint\", \"tables\", \"query-type\",\n\t} {\n\t\tcommon.Config.ReportType = typ\n\t\tmain()\n\t}\n\tcommon.Config.ReportType = orgRerportType\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc Test_Main_initQuery(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\t// direct query\n\tquery := initQuery(\"select 1\")\n\tif query != \"select 1\" {\n\t\tt.Errorf(\"want 'select 1', got %s\", query)\n\t}\n\n\t// read from file\n\tinitQuery(common.DevPath + \"/README.md\")\n\n\torgStdin := os.Stdin\n\ttmpStdin, err := os.Open(common.DevPath + \"/VERSION\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tos.Stdin = tmpStdin\n\tfmt.Println(initQuery(\"\"))\n\tos.Stdin = orgStdin\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc Test_Main_reportTool(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgRerportType := common.Config.ReportType\n\ttypes := []string{\"html\", \"md2html\", \"explain-digest\", \"chardet\", \"remove-comment\"}\n\tfor _, tp := range types {\n\t\tcommon.Config.ReportType = tp\n\t\tfmt.Println(reportTool(tp, []byte{}))\n\t}\n\tcommon.Config.ReportType = orgRerportType\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc Test_Main_helpTools(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\n\torgConfig := common.CheckConfig\n\tcommon.CheckConfig = true\n\thelpTools()\n\tcommon.CheckConfig = orgConfig\n\n\torgConfig = common.PrintVersion\n\tcommon.PrintVersion = true\n\thelpTools()\n\tcommon.PrintVersion = orgConfig\n\n\torgConfig = common.PrintConfig\n\tcommon.PrintConfig = true\n\thelpTools()\n\tcommon.PrintConfig = orgConfig\n\n\torgConfig = common.Config.ListHeuristicRules\n\tcommon.Config.ListHeuristicRules = true\n\thelpTools()\n\tcommon.Config.ListHeuristicRules = orgConfig\n\n\torgConfig = common.Config.ListRewriteRules\n\tcommon.Config.ListRewriteRules = true\n\thelpTools()\n\tcommon.Config.ListRewriteRules = orgConfig\n\n\torgConfig = common.Config.ListTestSqls\n\tcommon.Config.ListTestSqls = true\n\thelpTools()\n\tcommon.Config.ListTestSqls = orgConfig\n\n\torgConfig = common.Config.ListReportTypes\n\tcommon.Config.ListReportTypes = true\n\thelpTools()\n\tcommon.Config.ListReportTypes = orgConfig\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc Test_Main_verboseInfo(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgVerbose := common.Config.Verbose\n\tcommon.Config.Verbose = true\n\terr := common.GoldenDiff(func() {\n\t\t// Syntax check OK\n\t\torgSyntaxCheck := common.Config.OnlySyntaxCheck\n\t\tcommon.Config.OnlySyntaxCheck = true\n\t\tverboseInfo()\n\t\tcommon.Config.OnlySyntaxCheck = orgSyntaxCheck\n\n\t\t// MySQL environment verbose info\n\t\torgTestDSNDisable := common.Config.TestDSN.Disable\n\t\tcommon.Config.TestDSN.Disable = true\n\t\tverboseInfo()\n\t\tcommon.Config.TestDSN.Disable = orgTestDSNDisable\n\n\t\torgOnlineDSNDisable := common.Config.OnlineDSN.Disable\n\t\tcommon.Config.OnlineDSN.Disable = true\n\t\tverboseInfo()\n\t\tcommon.Config.OnlineDSN.Disable = orgOnlineDSNDisable\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tcommon.Config.Verbose = orgVerbose\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "cmd/soar/testdata/Test_Main.golden",
    "content": "# 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",
    "content": "Syntax check OK!\nMySQL environment verbose info\n* test-dsn: 127.0.0.1:3306 is disable, please check log.\n* online-dsn: 127.0.0.1:3306 is disable, please check log.\nMySQL environment verbose info\n* test-dsn: 127.0.0.1:3306 is disable, please check log.\n* online-dsn: 127.0.0.1:3306 is disable, please check log.\n"
  },
  {
    "path": "cmd/soar/tool.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/advisor\"\n\t\"github.com/XiaoMi/soar/ast\"\n\t\"github.com/XiaoMi/soar/common\"\n\t\"github.com/XiaoMi/soar/database\"\n\t\"github.com/XiaoMi/soar/env\"\n)\n\n// initConfig load config from default->file->cmdFlag\nfunc initConfig() {\n\t// 更新 binary 文件所在路径为 BaseDir\n\tex, err := os.Executable()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tcommon.BaseDir = filepath.Dir(ex)\n\n\tfor i, c := range os.Args {\n\t\t// 如果指定了 -config, 它必须是第一个参数\n\t\tif strings.HasPrefix(c, \"-config\") && i != 1 {\n\t\t\tfmt.Println(\"-config must be the first arg\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\t// 等号两边请不要加空格\n\t\tif c == \"=\" {\n\t\t\t// -config = soar.yaml not support\n\t\t\tfmt.Println(\"wrong format, no space between '=', eg: -config=soar.yaml\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\t// 加载配置文件，处理命令行参数\n\terr = common.ParseConfig(common.ArgConfig())\n\t// 检查配置文件及命令行参数是否正确\n\tif common.CheckConfig && err != nil {\n\t\tfmt.Println(err.Error())\n\t\tos.Exit(1)\n\t}\n\n\t// 更新 HeuristicRules 中与配置相关的文字\n\tadvisor.InitHeuristicRules()\n\n\tcommon.LogIfWarn(err, \"\")\n}\n\n// checkConfig for `-check-config` flag\n// if error found return non-zero, no error return zero\nfunc checkConfig() int {\n\t// TestDSN connection check\n\tconnTest, err := database.NewConnector(common.Config.TestDSN)\n\tif err != nil {\n\t\tfmt.Println(\"test-dsn:\", common.Config.TestDSN.Addr, err.Error())\n\t\treturn 1\n\t}\n\ttestVersion, err := connTest.Version()\n\tif err != nil && !common.Config.TestDSN.Disable {\n\t\tfmt.Println(\"test-dsn:\", connTest, err.Error())\n\t\treturn 1\n\t}\n\tif common.Config.Verbose {\n\t\tif err == nil {\n\t\t\tfmt.Println(\"test-dsn\", connTest, \"Version:\", testVersion)\n\t\t} else {\n\t\t\tfmt.Println(\"test-dsn\", common.Config.TestDSN)\n\t\t}\n\t}\n\n\tif !connTest.HasAllPrivilege() {\n\t\tfmt.Printf(\"test-dsn: %s, need all privileges\", common.FormatDSN(common.Config.TestDSN))\n\t\treturn 1\n\t}\n\t// OnlineDSN connection check\n\tconnOnline, err := database.NewConnector(common.Config.OnlineDSN)\n\tif err != nil {\n\t\tfmt.Println(\"test-dsn:\", common.Config.OnlineDSN.Addr, err.Error())\n\t\treturn 1\n\t}\n\tonlineVersion, err := connOnline.Version()\n\tif err != nil && !common.Config.OnlineDSN.Disable {\n\t\tfmt.Println(\"online-dsn:\", connOnline, err.Error())\n\t\treturn 1\n\t}\n\tif common.Config.Verbose {\n\t\tif err == nil {\n\t\t\tfmt.Println(\"online-dsn\", connOnline, \"Version:\", onlineVersion)\n\t\t} else {\n\t\t\tfmt.Println(\"online-dsn\", common.Config.OnlineDSN)\n\t\t}\n\t}\n\n\tif !connOnline.HasSelectPrivilege() {\n\t\tfmt.Printf(\"online-dsn: %s, need all privileges\", common.FormatDSN(common.Config.OnlineDSN))\n\t\treturn 1\n\t}\n\treturn 0\n}\n\n// helpTools help tools in cmd flags\nfunc helpTools() (isContinue bool, exitCode int) {\n\t// environment error check, eg. MySQL password error\n\tif common.CheckConfig {\n\t\treturn false, checkConfig()\n\t}\n\t// 打印 SOAR 版本信息\n\tif common.PrintVersion {\n\t\tcommon.SoarVersion()\n\t\treturn false, 0\n\t}\n\t// 打印已加载配置的各配置项，检查配置是否生效\n\tif common.PrintConfig {\n\t\tcommon.PrintConfiguration()\n\t\treturn false, 0\n\t}\n\t// 打印支持启发式建议\n\tif common.Config.ListHeuristicRules {\n\t\tadvisor.ListHeuristicRules(advisor.HeuristicRules)\n\t\treturn false, 0\n\t}\n\t// 打印支持的 SQL 重写规则\n\tif common.Config.ListRewriteRules {\n\t\tast.ListRewriteRules(ast.RewriteRules)\n\t\treturn false, 0\n\t}\n\t// 打印所有的测试 SQL\n\tif common.Config.ListTestSqls {\n\t\tadvisor.ListTestSQLs()\n\t\treturn false, 0\n\t}\n\t// 打印支持的 report-type\n\tif common.Config.ListReportTypes {\n\t\tcommon.ListReportTypes()\n\t\treturn false, 0\n\t}\n\n\treturn true, 0\n}\n\n// reportTool tools in report type\nfunc reportTool(sql string, bom []byte) (isContinue bool, exitCode int) {\n\tswitch common.Config.ReportType {\n\tcase \"html\":\n\t\t// HTML 格式输入 CSS 加载\n\t\tfmt.Println(common.MarkdownHTMLHeader())\n\t\treturn true, 0\n\tcase \"md2html\":\n\t\t// markdown2html 转换小工具\n\t\tfmt.Println(common.MarkdownHTMLHeader())\n\t\tfmt.Println(common.Markdown2HTML(sql))\n\t\treturn false, 0\n\tcase \"explain-digest\":\n\t\t// 当用户输入为 EXPLAIN 信息，只对 Explain 信息进行分析\n\t\t// 注意： 这里只能处理一条 SQL 的 EXPLAIN 信息，用户一次反馈多条 SQL 的 EXPLAIN 信息无法处理\n\t\tadvisor.DigestExplainText(sql)\n\t\treturn false, 0\n\tcase \"chardet\":\n\t\t// Get charset of input\n\t\tcharset := common.CheckCharsetByBOM(bom)\n\t\tif charset == \"\" {\n\t\t\tcharset = common.Chardet([]byte(sql))\n\t\t}\n\t\tfmt.Println(charset)\n\t\treturn false, 0\n\tcase \"remove-comment\":\n\t\tfmt.Println(database.RemoveSQLComments(sql))\n\t\treturn false, 0\n\tdefault:\n\t\treturn true, 0\n\t}\n}\n\n// initQuery\nfunc initQuery(query string) string {\n\t// 读入待优化 SQL ，当配置文件或命令行参数未指定 SQL 时从管道读取\n\tif query == \"\" {\n\t\t// check stdin is pipe or terminal\n\t\t// https://stackoverflow.com/questions/22744443/check-if-there-is-something-to-read-on-stdin-in-golang\n\t\tstat, err := os.Stdin.Stat()\n\t\tif stat == nil {\n\t\t\tcommon.Log.Critical(\"os.Stdin.Stat Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif (stat.Mode() & os.ModeCharDevice) != 0 {\n\t\t\t// stdin is from a terminal\n\t\t\tfmt.Println(\"Args format error, use --help see how to use it!\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\t// read from pipe\n\t\tvar data []byte\n\t\tdata, err = ioutil.ReadAll(os.Stdin)\n\t\tif err != nil {\n\t\t\tcommon.Log.Critical(\"ioutil.ReadAll Error: %v\", err)\n\t\t}\n\t\tcommon.Log.Debug(\"initQuery get query from os.Stdin\")\n\t\treturn string(data)\n\t}\n\n\tif _, err := os.Stat(query); err == nil {\n\t\tvar data []byte\n\t\tdata, err = ioutil.ReadFile(query)\n\t\tif err != nil {\n\t\t\tcommon.Log.Critical(\"ioutil.ReadFile Error: %v\", err)\n\t\t}\n\t\tcommon.Log.Debug(\"initQuery get query from file: %s\", query)\n\t\treturn string(data)\n\t}\n\n\treturn query\n}\n\nfunc shutdown(vEnv *env.VirtualEnv, rEnv *database.Connector) {\n\tif common.Config.DropTestTemporary {\n\t\tvEnv.CleanUp()\n\t}\n\terr := vEnv.Conn.Close()\n\tcommon.LogIfWarn(err, \"\")\n\terr = rEnv.Conn.Close()\n\tcommon.LogIfWarn(err, \"\")\n\tos.Exit(0)\n}\n\nfunc verboseInfo() {\n\tif !common.Config.Verbose {\n\t\treturn\n\t}\n\t// syntax check verbose mode, add output for success!\n\tif common.Config.OnlySyntaxCheck {\n\t\tfmt.Println(\"Syntax check OK!\")\n\t\treturn\n\t}\n\tswitch common.Config.ReportType {\n\tcase \"markdown\":\n\t\tif common.Config.TestDSN.Disable || common.Config.OnlineDSN.Disable {\n\t\t\tfmt.Println(\"MySQL environment verbose info\")\n\t\t\t// TestDSN\n\t\t\tif common.Config.TestDSN.Disable {\n\t\t\t\tfmt.Println(\"* test-dsn:\", common.Config.TestDSN.Addr, \"is disable, please check log.\")\n\t\t\t}\n\t\t\t// OnlineDSN\n\t\t\tif common.Config.OnlineDSN.Disable {\n\t\t\t\tfmt.Println(\"* online-dsn:\", common.Config.OnlineDSN.Addr, \"is disable, please check log.\")\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/cases.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\n// TestSQLs 测试SQL大集合\nvar TestSQLs []string\n\nfunc init() {\n\t// 所有的SQL都要以分号结尾，-list-test-sqls 参数会打印这个 list，以分号结尾可方便测试\n\t// 如：./soar -list-test-sql | ./soar\n\tTestSQLs = []string{\n\t\t// single equality\n\t\t\"SELECT * FROM film WHERE length = 86;\",    // index(length)\n\t\t\"SELECT * FROM film WHERE length IS NULL;\", // index(length)\n\t\t\"SELECT * FROM film HAVING title = 'abc';\", // 无法使用索引\n\n\t\t// single inequality\n\t\t\"SELECT * FROM sakila.film WHERE length >= 60;\",   // any of <, <=, >=, >; but not <>, !=, IS NOT NULL\"\n\t\t\"SELECT * FROM sakila.film WHERE length >= '60';\", // Implicit Conversion\n\t\t\"SELECT * FROM film WHERE length BETWEEN 60 AND 84;\",\n\t\t\"SELECT * FROM film WHERE title LIKE 'AIR%';\", // but not LIKE '%blah'\",\n\t\t\"SELECT * FROM film WHERE title IS NOT NULL;\",\n\n\t\t// multiple equalities\n\t\t\"SELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';\", // index(title,length) or index(length,title)\",\n\n\t\t// equality and inequality\n\t\t\"SELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';\", // index(title, length)\",\n\n\t\t// multiple inequality\n\t\t\"SELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';\", // index(d, b) or index(d, c) 依赖数据\",\n\t\t\"SELECT * FROM film WHERE length > 100 and language_id < 10;\",                   // index(b) or index(c)\",\n\n\t\t// GROUP BY\n\t\t\"SELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;\",  // INDEX(length, language_id, release_year) or INDEX(language_id, length, release_year)\",\n\t\t\"SELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;\",                     // INDEX(length)\",\n\t\t\"SELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;\",               // INDEX(release_year, language_id) (no WHERE)\",\n\t\t\"SELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);\", // INDEX(length) expression in GROUP BY, so no use including even release_year.\",\n\t\t\"SELECT release_year, sum(film_id) FROM film GROUP BY release_year;\",                                        // INDEX(`release_year`)\n\t\t\"SELECT * FROM address GROUP BY address,district;\",                                                          // INDEX(address, district)\n\t\t\"SELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;\",                                         // 无法使用索引\n\n\t\t// ORDER BY\n\t\t\"SELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;\",            //  INDEX(length, release_year) should have stopped with Step 2b\",\n\t\t\"SELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;\",          //  INDEX(length, release_year) the release_year will be used for both GROUP BY and ORDER BY\",\n\t\t\"SELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;\",                     //  INDEX(length) mixture of ASC and DESC.\",\n\t\t\"SELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\", //  INDEX(length, release_year)\",\n\t\t\"SELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;\",                                  //  INDEX(length, release_year)\",\n\t\t\"SELECT * FROM film ORDER BY release_year LIMIT 10;\",                                                     //  不能单独给release_year加索引\n\t\t\"SELECT film_id FROM film ORDER BY release_year LIMIT 10;\",                                               //  TODO: INDEX(release_year)，film_id 是主键查询列满足索引覆盖的情况才会使用到 release_year 索引\n\t\t\"SELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;\",                                        //  INDEX(length) This \"range\" is compatible with ORDER BY\n\t\t\"SELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;\",                                        //  INDEX(length) also works\n\t\t\"SELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;\",                               //  INDEX(address_id)\n\t\t\"SELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;\",                           //  INDEX(`release_year`, `length`, `title`)\n\n\t\t// \"Covering\" Rows\n\t\t\"SELECT title FROM film WHERE release_year = 1995;\",                               //  INDEX(release_year, title)\",\n\t\t\"SELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;\", //  INDEX(language_id, length, title, replacement_cos film ), title, replacement_cost顺序无关，language_id, length顺序视散粒度情况.\n\t\t\"SELECT title FROM film WHERE language_id > 5 AND length > 70;\",                   //  INDEX(language_id, length, title) language_id or length first (that's as far as the Algorithm goes), then the other two fields afterwards.\n\n\t\t// equalities and sort\n\t\t\"SELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;\", // 依赖数据特征，index(length, title, release_year) or index(title, length, release_year)需要评估\n\n\t\t// inequality and sort\n\t\t\"SELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;\", // 依赖数据特征， index(title, release_year)，index(title, length)需要评估\n\t\t\"SELECT * FROM film WHERE length > 100 ORDER BY release_year;\",                   // 依赖数据特征， index(length)，index(release_year)需要评估\n\n\t\t// Join\n\t\t// 内连接 INNER JOIN\n\t\t// 在mysql中，inner join...on , join...on , 逗号...WHERE ，cross join...on是一样的含义。\n\t\t// 但是在标准SQL中，它们并不等价，标准 SQL 中 INNER JOIN 与 ON共同使用, CROSS JOIN用于其他情况。\n\t\t// 逗号不支持 on 和 using 语法, 逗号的优先级要低于INNER JOIN, CROSS JOIN, LEFT JOIN\n\t\t// ON子句的语法格式为：tb1.col1 = tb2.col2列名可以不同，筛选连接后的结果，两表的对应列值相同才在结果集中。\n\t\t// 当模式设计对联接表的列采用了相同的命名样式时，就可以使用 USING 语法来简化 ON 语法\n\n\t\t// join, inner join, cross join等价，优先选择小结果集条件表为驱动表\n\t\t// left [outer] join左表为驱动表\n\t\t// right [outer] join右表为驱动表\n\t\t// 驱动表连接列如果没其他条件可以不考虑加索引，反正是需要foreach\n\t\t// 被驱动表连接列需要加索引。即:left [outer] join的右表连接列需要加索引，right [outer] join的左表连接列需要加索引，inner join结果集较大表的连接列需要加索引\n\t\t// 其他索引添加算法与单表索引优化算法相同\n\t\t// 总结：被驱动表列需要添加索引\n\t\t// 建议：将无索引的表通常作为驱动表\n\n\t\t\"SELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;\",\n\n\t\t// 左外连接 LEFT [OUTER] JOIN\n\t\t\"SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\",\n\n\t\t// 右外连接 RIGHT [OUTER] JOIN\n\t\t\"SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\",\n\n\t\t// 左连接\n\t\t\"SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\",\n\n\t\t// 右连接\n\t\t\"SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;\",\n\n\t\t// 全连接 FULL JOIN 因为在mysql中并不支持，所以我们用union实现\n\t\t\"SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id \" +\n\t\t\t\"UNION \" +\n\t\t\t\"SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\",\n\n\t\t// 两张表中不共同满足的数据集\n\t\t\"SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL \" +\n\t\t\t\"UNION \" +\n\t\t\t\"SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;\",\n\n\t\t// NATURAL JOIN 默认是同名字段完全匹配的INNER JOIN\n\t\t\"SELECT country_id, last_update FROM city NATURAL JOIN country;\",\n\n\t\t// NATURAL LEFT JOIN\n\t\t\"SELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\",\n\n\t\t// NATURAL RIGHT JOIN\n\t\t\"SELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\",\n\n\t\t// STRAIGHT_JOIN 实际上与内连接 INNER JOIN 表现完全一致，\n\t\t// 不同的是使用了 STRAIGHT_JOIN 后指定表载入的顺序，city 先于 country 载入\n\t\t\"SELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;\",\n\n\t\t// SEMI JOIN\n\t\t// 半连接： 当一张表在另一张表找到匹配的记录之后，半连接（semi-join）返回第一张表中的记录。\n\t\t// 与条件连接相反，即使在右节点中找到几条匹配的记录，左节点的表也只会返回一条记录。\n\t\t// 另外，右节点的表一条记录也不会返回。半连接通常使用 IN 或 EXISTS 作为连接条件\n\t\t\"SELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN  (SELECT c.city_id FROM sakila.city c);\",\n\n\t\t// Delayed Join\n\t\t// https://www.percona.com/blog/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/\n\t\t`SELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC;`,\n\n\t\t// DELETE\n\t\t\"DELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;\",\n\t\t\"DELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;\",\n\t\t\"DELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\",\n\t\t\"DELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;\",\n\t\t\"DELETE FROM film WHERE length > 100;\",\n\n\t\t// UPDATE\n\t\t\"UPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\",\n\t\t\"UPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;\",\n\t\t\"UPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;\",\n\t\t\"UPDATE film SET length = 10 WHERE language_id = 20;\",\n\n\t\t// INSERT\n\t\t\"INSERT INTO city (country_id) SELECT country_id FROM country;\",\n\t\t\"INSERT INTO city (country_id) VALUES (1),(2),(3);\",\n\t\t\"INSERT INTO city (country_id) VALUES (10);\",\n\t\t\"INSERT INTO city (country_id) SELECT 10 FROM DUAL;\",\n\n\t\t// REPLACE\n\t\t\"REPLACE INTO city (country_id) SELECT country_id FROM country;\",\n\t\t\"REPLACE INTO city (country_id) VALUES (1),(2),(3);\",\n\t\t\"REPLACE INTO city (country_id) VALUES (10);\",\n\t\t\"REPLACE INTO city (country_id) SELECT 10 FROM DUAL;\",\n\n\t\t// DEPTH\n\t\t\"SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM  film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\",\n\n\t\t// SUBQUERY\n\t\t\"SELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\",\n\t\t// \"SELECT COUNT(*) /* no hint */ FROM t2 WHERE NOT EXISTS (SELECT * FROM t3 WHERE ROW(5 * t2.s1, 77) = (SELECT 50, 11 * s1 FROM t4 UNION SELECT 50, 77 FROM (SELECT * FROM t5) AS t5 ) ) ;\",\n\t\t\"SELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\",\n\t\t\"SELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;\",\n\t\t\"SELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;\",\n\t\t\"SELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;\",\n\t\t\"SELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;\",\n\t\t\"SELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;\",\n\n\t\t// function in conditions\n\t\t\"SELECT * FROM film WHERE date(last_update)='2006-02-15';\",\n\t\t\"SELECT last_update FROM film GROUP BY date(last_update);\",\n\t\t\"SELECT last_update FROM film order by date(last_update);\",\n\n\t\t// CLA.004\n\t\t\"SELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;\",\n\n\t\t// ALTER TABLE ADD INDEX\n\t\t// 已经存在索引的列应该提醒索引已存在\n\t\t\"alter table address add index idx_city_id(city_id);\",\n\t\t\"alter table inventory add index `idx_store_film` (`store_id`,`film_id`);\",\n\t\t\"alter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);\",\n\n\t\t// https://github.com/XiaoMi/soar/issues/47\n\t\t`SELECT\tDATE_FORMAT(t.last_update, '%Y-%m-%d'),\tCOUNT(DISTINCT (t.city))\tFROM city t WHERE t.last_update > '2018-10-22 00:00:00'\tAND t.city LIKE '%Chrome%'\tAND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d');`,\n\t\t// https://github.com/XiaoMi/soar/issues/17\n\t\t\"create table hello.t (id int unsigned);\",\n\n\t\t// https://github.com/XiaoMi/soar/issues/146\n\t\t\"select * from tb where data >= '';\",\n\n\t\t// https://github.com/XiaoMi/soar/issues/163\n\t\t\"alter table tb alter column id drop default;\",\n\n\t\t// explain extra info \"Select tables optimized away\"\n\t\t\"select maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d;\",\n\t\t\"select maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d;\",\n\t}\n}\n"
  },
  {
    "path": "common/chardet.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"github.com/kr/pretty\"\n\t\"github.com/saintfish/chardet\"\n)\n\n// Chardet get best match charset\nfunc Chardet(buf []byte) string {\n\t// check character set by file BOM\n\tcharset := CheckCharsetByBOM(buf)\n\tif charset != \"\" {\n\t\treturn charset\n\t}\n\n\t// use chardet pkg check file charset\n\tcharset = \"unknown\"\n\tvar confidence int\n\tdetector := chardet.NewTextDetector()\n\n\t// detector.DetectBest is unstable\n\t// when the confidence value are equally, the best detect charset will be random\n\tresult, err := detector.DetectAll(buf)\n\tif err != nil {\n\t\treturn charset\n\t}\n\tLog.Debug(\"Chardet DetectAll Result: %s\", pretty.Sprint(result))\n\n\t// SOAR's main user speak Chinese, GB-18030, UTF-8 are higher suggested\n\tfor _, r := range result {\n\t\tif confidence > r.Confidence && r.Confidence != 0 {\n\t\t\treturn charset\n\t\t}\n\t\tconfidence = r.Confidence\n\t\tif r.Charset == \"GB-18030\" || r.Charset == \"UTF-8\" {\n\t\t\treturn r.Charset\n\t\t}\n\t\tcharset = r.Charset\n\t}\n\treturn charset\n}\n\n// CheckCharsetByBOM ref: https://en.wikipedia.org/wiki/Byte_order_mark\nfunc CheckCharsetByBOM(buf []byte) string {\n\t// TODO: There are many kind of BOM\n\t// UTF-8\tEF BB BF\n\tif len(buf) >= 3 {\n\t\tif buf[0] == 0xef && buf[1] == 0xbb && buf[2] == 0xbf {\n\t\t\treturn \"UTF-8\"\n\t\t}\n\t}\n\t// GB-18030\t84 31 95 33\n\tif len(buf) >= 4 {\n\t\tif buf[0] == 0x84 && buf[1] == 0x31 && buf[2] == 0x95 && buf[3] == 0x33 {\n\t\t\treturn \"GB-18030\"\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// RemoveBOM remove bom from file\nfunc RemoveBOM(buf []byte) (string, []byte) {\n\t// ef bb bf, UTF-8 BOM\n\tif len(buf) > 3 {\n\t\tif buf[0] == 0xef && buf[1] == 0xbb && buf[2] == 0xbf {\n\t\t\treturn string(buf[3:]), buf[:3]\n\t\t}\n\t}\n\t// ff fe, UTF-16 (LE) BOM\n\tif len(buf) > 2 {\n\t\tif buf[0] == 0xff && buf[1] == 0xfe {\n\t\t\treturn string(buf[2:]), buf[:2]\n\t\t}\n\t}\n\treturn string(buf), []byte{}\n}\n"
  },
  {
    "path": "common/chardet_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"testing\"\n)\n\nfunc TestChardet(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tcharsets := []string{\n\t\t\"GB-18030\",\n\t\t\"UTF-8\",\n\t}\n\tfor _, c := range charsets {\n\t\tfileName := DevPath + \"/common/testdata/chardet_\" + c + \".txt\"\n\t\tbuf, err := ioutil.ReadFile(fileName)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ioutil.ReadFile %s, Error: %s\", fileName, err.Error())\n\t\t}\n\t\tname := Chardet(buf)\n\t\tif name != c {\n\t\t\tt.Errorf(\"file: %s, Want: %s, Get: %s\", fileName, c, name)\n\t\t}\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestRemoveBOM(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tfileName := DevPath + \"/common/testdata/UTF-8.bom.sql\"\n\tbuf, err := ioutil.ReadFile(fileName)\n\tif err != nil {\n\t\tt.Errorf(\"ioutil.ReadFile %s, Error: %s\", fileName, err.Error())\n\t}\n\tGoldenDiff(func() {\n\t\tfmt.Println(RemoveBOM(buf))\n\t}, t.Name(), update)\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestCheckCharsetByBOM(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tfileName := DevPath + \"/common/testdata/UTF-8.bom.sql\"\n\tbuf, err := ioutil.ReadFile(fileName)\n\tif err != nil {\n\t\tt.Errorf(\"ioutil.ReadFile %s, Error: %s\", fileName, err.Error())\n\t}\n\n\tif CheckCharsetByBOM(buf) != \"UTF-8\" {\n\t\tt.Errorf(\"checkCharsetByBOM Want: UTF-8, Get: %s\", CheckCharsetByBOM(buf))\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n"
  },
  {
    "path": "common/config.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\tyaml \"gopkg.in/yaml.v2\"\n)\n\nvar (\n\t// BlackList 黑名单中的SQL不会被评审\n\tBlackList []string\n\t// PrintConfig -print-config\n\tPrintConfig bool\n\t// PrintVersion -print-config\n\tPrintVersion bool\n\t// CheckConfig -check-config\n\tCheckConfig bool\n\t// 防止 readCmdFlags 函数重入\n\thasParsed bool\n)\n\n// Configuration 配置文件定义结构体\ntype Configuration struct {\n\t// +++++++++++++++测试环境+++++++++++++++++\n\tOnlineDSN               *Dsn   `yaml:\"online-dsn\"`                // 线上环境数据库配置\n\tTestDSN                 *Dsn   `yaml:\"test-dsn\"`                  // 测试环境数据库配置\n\tAllowOnlineAsTest       bool   `yaml:\"allow-online-as-test\"`      // 允许 Online 环境也可以当作 Test 环境\n\tDisableVersionCheck     bool   `yaml:\"disable-version-check\"`     // 是否禁用环境检测，开启后表示允许测试环境版本低于线上环境 不建议开启，可能会导致语句执行异常\n\tDropTestTemporary       bool   `yaml:\"drop-test-temporary\"`       // 是否清理Test环境产生的临时库表\n\tCleanupTestDatabase     bool   `yaml:\"cleanup-test-database\"`     // 清理残余的测试数据库（程序异常退出或未开启drop-test-temporary）  issue #48\n\tOnlySyntaxCheck         bool   `yaml:\"only-syntax-check\"`         // 只做语法检查不输出优化建议\n\tSamplingStatisticTarget int    `yaml:\"sampling-statistic-target\"` // 数据采样因子，对应 PostgreSQL 的 default_statistics_target\n\tSampling                bool   `yaml:\"sampling\"`                  // 数据采样开关\n\tSamplingCondition       string `yaml:\"sampling-condition\"`        // 指定采样条件，如：WHERE xxx LIMIT xxx;\n\tProfiling               bool   `yaml:\"profiling\"`                 // 在开启数据采样的情况下，在测试环境执行进行profile\n\tTrace                   bool   `yaml:\"trace\"`                     // 在开启数据采样的情况下，在测试环境执行进行Trace\n\tExplain                 bool   `yaml:\"explain\"`                   // Explain开关\n\tDelimiter               string `yaml:\"delimiter\"`                 // SQL分隔符\n\n\t// +++++++++++++++日志相关+++++++++++++++++\n\t// 日志级别，这里使用了 beego 的 log 包\n\t// [0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug]\n\tLogLevel int `yaml:\"log-level\"`\n\t// 日志输出位置，默认日志输出到控制台\n\t// 目前只支持['console', 'file']两种形式，如非console形式这里需要指定文件的路径，可以是相对路径\n\tLogOutput string `yaml:\"log-output\"`\n\t// 优化建议输出格式，目前支持: json, text, markdown格式，如指定其他格式会给 pretty.Println 的输出\n\tReportType string `yaml:\"report-type\"`\n\t// 当 ReportType 为 html 格式时使用的 css 风格，如不指定会提供一个默认风格。CSS可 以是本地文件，也可以是一个URL\n\tReportCSS string `yaml:\"report-css\"`\n\t// 当 ReportType 为 html 格式时使用的 javascript 脚本，如不指定默认会加载SQL pretty 使用的 javascript。像CSS一样可以是本地文件，也可以是一个URL\n\tReportJavascript string `yaml:\"report-javascript\"`\n\t// 当ReportType 为 html 格式时，HTML 的 title\n\tReportTitle string `yaml:\"report-title\"`\n\t// blackfriday markdown2html config\n\tMarkdownExtensions int `yaml:\"markdown-extensions\"` // markdown 转 html 支持的扩展包, 参考blackfriday\n\tMarkdownHTMLFlags  int `yaml:\"markdown-html-flags\"` // markdown 转 html 支持的 flag, 参考blackfriday, default 0\n\n\t// ++++++++++++++优化建议相关++++++++++++++\n\tIgnoreRules          []string `yaml:\"ignore-rules\"`              // 忽略的优化建议规则\n\tRewriteRules         []string `yaml:\"rewrite-rules\"`             // 生效的重写规则\n\tBlackList            string   `yaml:\"blacklist\"`                 // blacklist 中的 SQL 不会被评审，可以是指纹，也可以是正则\n\tMaxJoinTableCount    int      `yaml:\"max-join-table-count\"`      // 单条 SQL 中 JOIN 表的最大数量\n\tMaxGroupByColsCount  int      `yaml:\"max-group-by-cols-count\"`   // 单条 SQL 中 GroupBy 包含列的最大数量\n\tMaxDistinctCount     int      `yaml:\"max-distinct-count\"`        // 单条 SQL 中 Distinct 的最大数量\n\tMaxIdxColsCount      int      `yaml:\"max-index-cols-count\"`      // 复合索引中包含列的最大数量\n\tMaxTextColsCount     int      `yaml:\"max-text-cols-count\"`       // 表中含有的 text/blob 列的最大数量\n\tMaxTotalRows         uint64   `yaml:\"max-total-rows\"`            // 计算散粒度时，当数据行数大于 MaxTotalRows 即开启数据库保护模式，散粒度返回结果可信度下降\n\tMaxQueryCost         int64    `yaml:\"max-query-cost\"`            // last_query_cost 超过该值时将给予警告\n\tSpaghettiQueryLength int      `yaml:\"spaghetti-query-length\"`    // SQL最大长度警告，超过该长度会给警告\n\tAllowDropIndex       bool     `yaml:\"allow-drop-index\"`          // 允许输出删除重复索引的建议\n\tMaxInCount           int      `yaml:\"max-in-count\"`              // IN()最大数量\n\tMaxIdxBytesPerColumn int      `yaml:\"max-index-bytes-percolumn\"` // 索引中单列最大字节数，默认767\n\tMaxIdxBytes          int      `yaml:\"max-index-bytes\"`           // 索引总长度限制，默认3072\n\tAllowCharsets        []string `yaml:\"allow-charsets\"`            // 允许使用的 DEFAULT CHARSET\n\tAllowCollates        []string `yaml:\"allow-collates\"`            // 允许使用的 COLLATE\n\tAllowEngines         []string `yaml:\"allow-engines\"`             // 允许使用的存储引擎\n\tMaxIdxCount          int      `yaml:\"max-index-count\"`           // 单张表允许最多索引数\n\tMaxColCount          int      `yaml:\"max-column-count\"`          // 单张表允许最大列数\n\tMaxValueCount        int      `yaml:\"max-value-count\"`           // INSERT/REPLACE 单次允许批量写入的行数\n\tIdxPrefix            string   `yaml:\"index-prefix\"`              // 普通索引建议使用的前缀\n\tUkPrefix             string   `yaml:\"unique-key-prefix\"`         // 唯一键建议使用的前缀\n\tMaxSubqueryDepth     int      `yaml:\"max-subquery-depth\"`        // 子查询最大尝试\n\tMaxVarcharLength     int      `yaml:\"max-varchar-length\"`        // varchar最大长度\n\tColumnNotAllowType   []string `yaml:\"column-not-allow-type\"`     // 字段不允许使用的数据类型\n\tMinCardinality       float64  `yaml:\"min-cardinality\"`           // 添加索引散粒度阈值，范围 0~100\n\n\t// ++++++++++++++EXPLAIN检查项+++++++++++++\n\tExplainSQLReportType   string   `yaml:\"explain-sql-report-type\"`  // EXPLAIN markdown 格式输出 SQL 样式，支持 sample, fingerprint, pretty 等\n\tExplainType            string   `yaml:\"explain-type\"`             // EXPLAIN方式 [traditional, extended, partitions]\n\tExplainFormat          string   `yaml:\"explain-format\"`           // FORMAT=[json, traditional]\n\tExplainWarnSelectType  []string `yaml:\"explain-warn-select-type\"` // 哪些 select_type 不建议使用\n\tExplainWarnAccessType  []string `yaml:\"explain-warn-access-type\"` // 哪些 access type 不建议使用\n\tExplainMaxKeyLength    int      `yaml:\"explain-max-keys\"`         // 最大 key_len\n\tExplainMinPossibleKeys int      `yaml:\"explain-min-keys\"`         // 最小 possible_keys 警告\n\tExplainMaxRows         int64    `yaml:\"explain-max-rows\"`         // 最大扫描行数警告\n\tExplainWarnExtra       []string `yaml:\"explain-warn-extra\"`       // 哪些 extra 信息会给警告\n\tExplainMaxFiltered     float64  `yaml:\"explain-max-filtered\"`     // filtered 大于该配置给出警告\n\tExplainWarnScalability []string `yaml:\"explain-warn-scalability\"` // 复杂度警告名单\n\tShowWarnings           bool     `yaml:\"show-warnings\"`            // explain extended with show warnings\n\tShowLastQueryCost      bool     `yaml:\"show-last-query-cost\"`     // switch with show status like 'last_query_cost'\n\t// ++++++++++++++其他配置项+++++++++++++++\n\tQuery              string `yaml:\"query\"`                 // 需要进行调优的SQL\n\tListHeuristicRules bool   `yaml:\"list-heuristic-rules\"`  // 打印支持的评审规则列表\n\tListRewriteRules   bool   `yaml:\"list-rewrite-rules\"`    // 打印重写规则\n\tListTestSqls       bool   `yaml:\"list-test-sqls\"`        // 打印测试case用于测试\n\tListReportTypes    bool   `yaml:\"list-report-types\"`     // 打印支持的报告输出类型\n\tVerbose            bool   `yaml:\"verbose\"`               // verbose模式，会多输出一些信息\n\tDryRun             bool   `yaml:\"dry-run\"`               // 是否在预演环境执行\n\tMaxPrettySQLLength int    `yaml:\"max-pretty-sql-length\"` // 超出该长度的SQL会转换成指纹输出\n}\n\n// Config 默认设置\nvar Config = &Configuration{\n\tOnlineDSN:               newDSN(nil),\n\tTestDSN:                 newDSN(nil),\n\tAllowOnlineAsTest:       false,\n\tDropTestTemporary:       true,\n\tCleanupTestDatabase:     false,\n\tDryRun:                  true,\n\tOnlySyntaxCheck:         false,\n\tSamplingStatisticTarget: 100,\n\tSampling:                false,\n\tProfiling:               false,\n\tTrace:                   false,\n\tExplain:                 true,\n\tDelimiter:               \";\",\n\tMinCardinality:          0,\n\n\tMaxJoinTableCount:    5,\n\tMaxGroupByColsCount:  5,\n\tMaxDistinctCount:     5,\n\tMaxIdxColsCount:      5,\n\tMaxTextColsCount:     2,\n\tMaxIdxBytesPerColumn: 767,\n\tMaxIdxBytes:          3072,\n\tMaxTotalRows:         9999999,\n\tMaxQueryCost:         9999,\n\tSpaghettiQueryLength: 2048,\n\tAllowDropIndex:       false,\n\tLogLevel:             3,\n\tLogOutput:            \"soar.log\",\n\tReportType:           \"markdown\",\n\tReportCSS:            \"\",\n\tReportJavascript:     \"\",\n\tReportTitle:          \"SQL优化分析报告\",\n\tBlackList:            \"\",\n\tAllowCharsets:        []string{\"utf8\", \"utf8mb4\"},\n\tAllowCollates:        []string{},\n\tAllowEngines:         []string{\"innodb\"},\n\tMaxIdxCount:          10,\n\tMaxColCount:          40,\n\tMaxValueCount:        100,\n\tMaxInCount:           10,\n\tIdxPrefix:            \"idx_\",\n\tUkPrefix:             \"uk_\",\n\tMaxSubqueryDepth:     5,\n\tMaxVarcharLength:     1024,\n\tColumnNotAllowType:   []string{\"boolean\"},\n\n\tMarkdownExtensions: 94,\n\tMarkdownHTMLFlags:  0,\n\n\tExplainSQLReportType:   \"pretty\",\n\tExplainType:            \"extended\",\n\tExplainFormat:          \"traditional\",\n\tExplainWarnSelectType:  []string{\"\"},\n\tExplainWarnAccessType:  []string{\"ALL\"},\n\tExplainMaxKeyLength:    3,\n\tExplainMinPossibleKeys: 0,\n\tExplainMaxRows:         10000,\n\tExplainWarnExtra:       []string{\"Using temporary\", \"Using filesort\"},\n\tExplainMaxFiltered:     100.0,\n\tExplainWarnScalability: []string{\"O(n)\"},\n\tShowWarnings:           false,\n\tShowLastQueryCost:      false,\n\n\tIgnoreRules: []string{\n\t\t\"COL.011\",\n\t},\n\tRewriteRules: []string{\n\t\t\"delimiter\",\n\t\t\"orderbynull\",\n\t\t\"groupbyconst\",\n\t\t\"dmlorderby\",\n\t\t\"having\",\n\t\t\"star2columns\",\n\t\t\"insertcolumns\",\n\t\t\"distinctstar\",\n\t},\n\n\tListHeuristicRules: false,\n\tListRewriteRules:   false,\n\tListTestSqls:       false,\n\tListReportTypes:    false,\n\tMaxPrettySQLLength: 1024,\n}\n\n// Dsn Data source name\ntype Dsn struct {\n\tUser             string            `yaml:\"user\"`               // Usernames\n\tPassword         string            `yaml:\"password\"`           // Password (requires User)\n\tNet              string            `yaml:\"net\"`                // Network type\n\tAddr             string            `yaml:\"addr\"`               // Network address (requires Net)\n\tSchema           string            `yaml:\"schema\"`             // Database name\n\tCharset          string            `yaml:\"charset\"`            // SET NAMES charset\n\tCollation        string            `yaml:\"collation\"`          // Connection collation\n\tLoc              string            `yaml:\"loc\"`                // Location for time.Time values\n\tTLS              string            `yaml:\"tls\"`                // TLS configuration name\n\tServerPubKey     string            `yaml:\"server-public-key\"`  // Server public key name\n\tMaxAllowedPacket int               `yaml:\"max-allowed-packet\"` // Max packet size allowed\n\tParams           map[string]string `yaml:\"params\"`             // Other Connection parameters, `SET param=val`, `SET NAMES charset`\n\tTimeout          string            `yaml:\"timeout\"`            // Dial timeout\n\tReadTimeout      string            `yaml:\"read-timeout\"`       // I/O read timeout\n\tWriteTimeout     string            `yaml:\"write-timeout\"`      // I/O write timeout\n\n\tAllowNativePasswords bool `yaml:\"allow-native-passwords\"` // Allows the native password authentication method\n\tAllowOldPasswords    bool `yaml:\"allow-old-passwords\"`    // Allows the old insecure password method\n\n\tDisable bool `yaml:\"disable\"`\n\tVersion int  `yaml:\"-\"` // 版本自动检查，不可配置\n}\n\n// newDSN create default Dsn struct\nfunc newDSN(cfg *mysql.Config) *Dsn {\n\tdsn := &Dsn{\n\t\tNet:                  \"tcp\",\n\t\tSchema:               \"information_schema\",\n\t\tCharset:              \"utf8\",\n\t\tTimeout:              \"3s\",\n\t\tAllowNativePasswords: true,\n\t\tParams:               make(map[string]string),\n\t\tMaxAllowedPacket:     4 << 20, // 4 MiB\n\n\t\t// Disable: true,\n\t\tVersion: 99999,\n\t}\n\tif cfg == nil {\n\t\treturn dsn\n\t}\n\tdsn.User = cfg.User\n\tdsn.Password = cfg.Passwd\n\tdsn.Net = cfg.Net\n\tdsn.Addr = cfg.Addr\n\tdsn.Schema = cfg.DBName\n\tdsn.Params = make(map[string]string)\n\tfor k, v := range cfg.Params {\n\t\tdsn.Params[k] = v\n\t}\n\tif _, ok := cfg.Params[\"charset\"]; ok {\n\t\tdsn.Charset = cfg.Params[\"charset\"]\n\t}\n\tdsn.Collation = cfg.Collation\n\tdsn.Loc = cfg.Loc.String()\n\tdsn.MaxAllowedPacket = cfg.MaxAllowedPacket\n\tdsn.ServerPubKey = cfg.ServerPubKey\n\tdsn.TLS = cfg.TLSConfig\n\tdsn.Timeout = cfg.Timeout.String()\n\tdsn.ReadTimeout = cfg.ReadTimeout.String()\n\tdsn.WriteTimeout = cfg.WriteTimeout.String()\n\tdsn.AllowNativePasswords = cfg.AllowNativePasswords\n\tdsn.AllowOldPasswords = cfg.AllowOldPasswords\n\treturn dsn\n}\n\n// newMySQLConfig convert Dsn to go-sql-drive Config\nfunc (env *Dsn) newMySQLConfig() (*mysql.Config, error) {\n\tvar err error\n\tdsn := mysql.NewConfig()\n\n\tdsn.User = env.User\n\tdsn.Passwd = env.Password\n\tdsn.Net = env.Net\n\tdsn.Addr = env.Addr\n\tdsn.DBName = env.Schema\n\tdsn.Params = make(map[string]string)\n\tfor k, v := range env.Params {\n\t\tdsn.Params[k] = v\n\t}\n\tdsn.Params[\"charset\"] = env.Charset\n\tdsn.Collation = env.Collation\n\tdsn.Loc, err = time.LoadLocation(env.Loc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdsn.MaxAllowedPacket = env.MaxAllowedPacket\n\tdsn.ServerPubKey = env.ServerPubKey\n\tdsn.TLSConfig = env.TLS\n\tif env.Timeout != \"\" {\n\t\tdsn.Timeout, err = time.ParseDuration(env.Timeout)\n\t\tLogIfError(err, \"timeout: '%s'\", env.Timeout)\n\t}\n\tif env.WriteTimeout != \"\" {\n\t\tdsn.WriteTimeout, err = time.ParseDuration(env.WriteTimeout)\n\t\tLogIfError(err, \"writeTimeout: '%s'\", env.WriteTimeout)\n\t}\n\tif env.ReadTimeout != \"\" {\n\t\tdsn.ReadTimeout, err = time.ParseDuration(env.ReadTimeout)\n\t\tLogIfError(err, \"readTimeout: '%s'\", env.ReadTimeout)\n\t}\n\tdsn.AllowNativePasswords = env.AllowNativePasswords\n\tdsn.AllowOldPasswords = env.AllowOldPasswords\n\treturn dsn, err\n}\n\n// 解析命令行DSN输入\nfunc parseDSN(odbc string, d *Dsn) *Dsn {\n\tdsn := newDSN(nil)\n\tvar addr, user, password, schema, charset, timeout string\n\tif odbc == FormatDSN(d) {\n\t\treturn d\n\t}\n\n\tif d != nil {\n\t\t// 原来有个判断，后来判断条件被删除了就导致第一次addr无论如何都会被修改。所以这边先注释掉\n\t\t// addr = d.Addr\n\t\tuser = d.User\n\t\tpassword = d.Password\n\t\tschema = d.Schema\n\t\tcharset = d.Charset\n\t\ttimeout = d.Timeout\n\t}\n\n\t// 设置为空表示禁用环境\n\todbc = strings.TrimSpace(odbc)\n\tif odbc == \"\" {\n\t\treturn &Dsn{Disable: true}\n\t}\n\n\tvar userInfo, hostInfo, query string\n\n\t// DSN 格式匹配\n\tif res := regexp.MustCompile(`^(.*)@(.*?)/(.*?)($|\\?)(.*)`).FindStringSubmatch(odbc); len(res) > 5 {\n\t\t// userInfo@hostInfo/database\n\t\tuserInfo = res[1]\n\t\thostInfo = res[2]\n\t\tschema = res[3]\n\t\tquery = res[5]\n\t} else if res := regexp.MustCompile(`^(.*)/(.*?)($|\\?)(.*)`).FindStringSubmatch(odbc); len(res) > 4 {\n\t\t// hostInfo/database\n\t\thostInfo = res[1]\n\t\tschema = res[2]\n\t\tquery = res[4]\n\t} else if res := regexp.MustCompile(`^(.*)@(.*?)($|\\?)(.*)`).FindStringSubmatch(odbc); len(res) > 4 {\n\t\t// userInfo@hostInfo\n\t\tuserInfo = res[1]\n\t\thostInfo = res[2]\n\t\tquery = res[4]\n\t} else if res := regexp.MustCompile(`^(.*?)($|\\?)(.*)`).FindStringSubmatch(odbc); len(res) > 3 {\n\t\t// hostInfo\n\t\thostInfo = res[1]\n\t\tquery = res[3]\n\t}\n\n\t// 解析用户信息\n\tif userInfo != \"\" {\n\t\tuser = strings.Split(userInfo, \":\")[0]\n\t\t// 防止密码中含有与用户名相同的字符, 所以用正则替换, 剩下的就是密码\n\t\tpassword = strings.TrimLeft(regexp.MustCompile(\"^\"+user).ReplaceAllString(userInfo, \"\"), \":\")\n\t}\n\n\t// 解析主机信息\n\thost := strings.Split(hostInfo, \":\")[0]\n\tport := strings.TrimLeft(strings.Replace(hostInfo, host, \"\", 1), \":\")\n\tif host == \"\" {\n\t\thost = \"127.0.0.1\"\n\t}\n\tif port == \"\" {\n\t\tport = \"3306\"\n\t}\n\taddr = host + \":\" + port\n\n\t// 解析查询字符串\n\tif query != \"\" {\n\t\tparams := strings.Split(query, \"&\")\n\t\tfor _, f := range params {\n\t\t\tattr := strings.Split(f, \"=\")\n\t\t\tif len(attr) > 1 {\n\t\t\t\targ := strings.TrimSpace(attr[0])\n\t\t\t\tval := strings.TrimSpace(attr[1])\n\t\t\t\tswitch arg {\n\t\t\t\tcase \"charset\":\n\t\t\t\t\tcharset = val\n\t\t\t\tcase \"timeout\":\n\t\t\t\t\ttimeout = val\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 默认用information_schema库\n\tif schema == \"\" {\n\t\tschema = \"information_schema\"\n\t}\n\n\t// 默认 utf8 使用字符集\n\tif charset == \"\" {\n\t\tcharset = \"utf8\"\n\t}\n\n\t// 默认连接数据库超时时间 3s\n\tif timeout == \"\" {\n\t\ttimeout = \"3s\"\n\t}\n\n\tdsn.Addr = addr\n\tdsn.User = user\n\tdsn.Password = password\n\tdsn.Schema = schema\n\tdsn.Charset = charset\n\tdsn.Timeout = timeout\n\treturn dsn\n}\n\n// ParseDSN compatible with old version soar < 0.11.0\nfunc ParseDSN(odbc string, d *Dsn) *Dsn {\n\tcfg, err := mysql.ParseDSN(odbc)\n\tif err != nil {\n\t\t// Log.Debug(\"go-sql-driver/mysql.ParseDSN Error: %s, DSN: %s, try to use old version parseDSN\", err.Error(), odbc)\n\t\treturn parseDSN(odbc, d)\n\t}\n\treturn newDSN(cfg)\n}\n\n// FormatDSN 格式化打印DSN\nfunc FormatDSN(env *Dsn) string {\n\tif env == nil || env.Disable {\n\t\treturn \"\"\n\t}\n\tdsn, err := env.newMySQLConfig()\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn dsn.FormatDSN()\n}\n\n// SoarVersion soar version information\nfunc SoarVersion() {\n\tfmt.Println(\"Version:\", Version)\n\tfmt.Println(\"Branch:\", Branch)\n\tfmt.Println(\"Compile:\", Compile)\n\tfmt.Println(\"GitDirty:\", GitDirty)\n}\n\n// 因为vitess sqlparser 使用了 glog 中也会使用 flag，为了不让用户困扰我们单独写一个 usage\nfunc usage() {\n\tregPwd := regexp.MustCompile(`:.*@`)\n\tvitessHelp := []string{\n\t\t\"-alsologtostderr\",\n\t\t\"log to standard error as well as files\",\n\t\t\"-log_backtrace_at value\",\n\t\t\"when logging hits line file:N, emit a stack trace\",\n\t\t\"-log_dir string\",\n\t\t\"If non-empty, write log files in this directory\",\n\t\t\"-logtostderr\",\n\t\t\"log to standard error instead of files\",\n\t\t\"-sql-max-length-errors int\",\n\t\t\"truncate queries in error logs to the given length (default unlimited)\",\n\t\t\"-sql-max-length-ui int\",\n\t\t\"truncate queries in debug UIs to the given length (default 512) (default 512)\",\n\t\t\"-stderrthreshold value\",\n\t\t\"logs at or above this threshold go to stderr\",\n\t\t\"-v value\",\n\t\t\"log level for V logs\",\n\t\t\"-vmodule value\",\n\t\t\"comma-separated list of pattern=N settings for file-filtered logging\",\n\t}\n\n\t// io redirect\n\trestoreStdout := os.Stdout\n\trestoreStderr := os.Stderr\n\tstdin, stdout, _ := os.Pipe()\n\tos.Stderr = stdout\n\tos.Stdout = stdout\n\n\tflag.PrintDefaults()\n\n\t// copy the output in a separate goroutine so printing can't block indefinitely\n\toutC := make(chan string)\n\tgo func() {\n\t\tvar buf bytes.Buffer\n\t\t_, err := io.Copy(&buf, stdin)\n\t\tif err != nil {\n\t\t\tfmt.Println(err.Error())\n\t\t}\n\t\toutC <- buf.String()\n\t}()\n\n\t// back to normal state\n\tstdout.Close()\n\tos.Stdout = restoreStdout // restoring the real stderr\n\tos.Stderr = restoreStderr\n\n\tfmt.Printf(\"Usage of %s:\\n\", os.Args[0])\n\t// reading our temp stdout\n\tout := <-outC\n\tfor _, line := range strings.Split(out, \"\\n\") {\n\t\tfound := false\n\t\tfor _, ignore := range vitessHelp {\n\t\t\tif strings.TrimSpace(line) == strings.TrimSpace(ignore) {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t\tif regPwd.MatchString(line) && !Config.Verbose {\n\t\t\t\tline = regPwd.ReplaceAllString(line, \":********@\")\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tfmt.Println(line)\n\t\t}\n\t}\n}\n\n// PrintConfiguration for `-print-config` flag\nfunc PrintConfiguration() {\n\t// 打印配置的时候密码不显示\n\tif !Config.Verbose {\n\t\tConfig.OnlineDSN.Password = \"********\"\n\t\tConfig.TestDSN.Password = \"********\"\n\t}\n\tdata, _ := yaml.Marshal(Config)\n\tfmt.Print(string(data))\n}\n\n// 加载配置文件\nfunc (conf *Configuration) readConfigFile(path string) error {\n\tconfigFile, err := os.Open(path)\n\tif err != nil {\n\t\tLog.Warning(\"readConfigFile(%s) os.Open failed: %v\", path, err)\n\t\treturn err\n\t}\n\tdefer configFile.Close()\n\n\tcontent, err := ioutil.ReadAll(configFile)\n\tif err != nil {\n\t\tLog.Warning(\"readConfigFile(%s) ioutil.ReadAll failed: %v\", path, err)\n\t\treturn err\n\t}\n\n\terr = yaml.Unmarshal(content, Config)\n\tif err != nil {\n\t\tLog.Warning(\"readConfigFile(%s) yaml.Unmarshal failed: %v\", path, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// 从命令行参数读配置\nfunc readCmdFlags() error {\n\tif hasParsed {\n\t\tLog.Debug(\"Skip read cmd flags.\")\n\t\treturn nil\n\t}\n\n\t_ = flag.String(\"config\", \"\", \"Config file path\")\n\t// +++++++++++++++测试环境+++++++++++++++++\n\tonlineDSN := flag.String(\"online-dsn\", FormatDSN(Config.OnlineDSN), \"OnlineDSN, 线上环境数据库配置, username:password@tcp(ip:port)/schema\")\n\ttestDSN := flag.String(\"test-dsn\", FormatDSN(Config.TestDSN), \"TestDSN, 测试环境数据库配置, username:password@tcp(ip:port)/schema\")\n\tallowOnlineAsTest := flag.Bool(\"allow-online-as-test\", Config.AllowOnlineAsTest, \"AllowOnlineAsTest, 允许线上环境也可以当作测试环境\")\n\tdropTestTemporary := flag.Bool(\"drop-test-temporary\", Config.DropTestTemporary, \"DropTestTemporary, 是否清理测试环境产生的临时库表\")\n\tcleanupTestDatabase := flag.Bool(\"cleanup-test-database\", Config.CleanupTestDatabase, \"单次运行清理历史1小时前残余的测试库。\")\n\tonlySyntaxCheck := flag.Bool(\"only-syntax-check\", Config.OnlySyntaxCheck, \"OnlySyntaxCheck, 只做语法检查不输出优化建议\")\n\tprofiling := flag.Bool(\"profiling\", Config.Profiling, \"Profiling, 开启数据采样的情况下在测试环境执行Profile\")\n\ttrace := flag.Bool(\"trace\", Config.Trace, \"Trace, 开启数据采样的情况下在测试环境执行Trace\")\n\texplain := flag.Bool(\"explain\", Config.Explain, \"Explain, 是否开启Explain执行计划分析\")\n\tsampling := flag.Bool(\"sampling\", Config.Sampling, \"Sampling, 数据采样开关\")\n\tsamplingStatisticTarget := flag.Int(\"sampling-statistic-target\", Config.SamplingStatisticTarget, \"SamplingStatisticTarget, 数据采样因子，对应 PostgreSQL 的 default_statistics_target\")\n\tsamplingCondition := flag.String(\"sampling-condition\", Config.SamplingCondition, \"SamplingCondition, 数据采样条件，如： WHERE xxx LIMIT xxx\")\n\tdelimiter := flag.String(\"delimiter\", Config.Delimiter, \"Delimiter, SQL分隔符\")\n\tminCardinality := flag.Float64(\"min-cardinality\", Config.MinCardinality, \"MinCardinality，索引列散粒度最低阈值，散粒度低于该值的列不添加索引，建议范围0.0 ~ 100.0\")\n\t// +++++++++++++++日志相关+++++++++++++++++\n\tlogLevel := flag.Int(\"log-level\", Config.LogLevel, \"LogLevel, 日志级别, [0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug]\")\n\tlogOutput := flag.String(\"log-output\", Config.LogOutput, \"LogOutput, 日志输出位置\")\n\treportType := flag.String(\"report-type\", Config.ReportType, \"ReportType, 优化建议输出格式，目前支持: json, text, markdown, html等\")\n\treportCSS := flag.String(\"report-css\", Config.ReportCSS, \"ReportCSS, 当 ReportType 为 html 格式时使用的 css 风格，如不指定会提供一个默认风格。CSS可以是本地文件，也可以是一个URL\")\n\treportJavascript := flag.String(\"report-javascript\", Config.ReportJavascript, \"ReportJavascript, 当 ReportType 为 html 格式时使用的javascript脚本，如不指定默认会加载SQL pretty 使用的 javascript。像CSS一样可以是本地文件，也可以是一个URL\")\n\treportTitle := flag.String(\"report-title\", Config.ReportTitle, \"ReportTitle, 当 ReportType 为 html 格式时，HTML 的 title\")\n\t// +++++++++++++++markdown+++++++++++++++++\n\tmarkdownExtensions := flag.Int(\"markdown-extensions\", Config.MarkdownExtensions, \"MarkdownExtensions, markdown 转 html支持的扩展包, 参考blackfriday\")\n\tmarkdownHTMLFlags := flag.Int(\"markdown-html-flags\", Config.MarkdownHTMLFlags, \"MarkdownHTMLFlags, markdown 转 html 支持的 flag, 参考blackfriday\")\n\t// ++++++++++++++优化建议相关++++++++++++++\n\tignoreRules := flag.String(\"ignore-rules\", strings.Join(Config.IgnoreRules, \",\"), \"IgnoreRules, 忽略的优化建议规则\")\n\trewriteRules := flag.String(\"rewrite-rules\", strings.Join(Config.RewriteRules, \",\"), \"RewriteRules, 生效的重写规则\")\n\tblackList := flag.String(\"blacklist\", Config.BlackList, \"指定 blacklist 配置文件的位置，文件中的 SQL 不会被评审。一行一条SQL，可以是指纹，也可以是正则\")\n\tmaxJoinTableCount := flag.Int(\"max-join-table-count\", Config.MaxJoinTableCount, \"MaxJoinTableCount, 单条 SQL 中 JOIN 表的最大数量\")\n\tmaxGroupByColsCount := flag.Int(\"max-group-by-cols-count\", Config.MaxGroupByColsCount, \"MaxGroupByColsCount, 单条 SQL 中 GroupBy 包含列的最大数量\")\n\tmaxDistinctCount := flag.Int(\"max-distinct-count\", Config.MaxDistinctCount, \"MaxDistinctCount, 单条 SQL 中 Distinct 的最大数量\")\n\tmaxIdxColsCount := flag.Int(\"max-index-cols-count\", Config.MaxIdxColsCount, \"MaxIdxColsCount, 复合索引中包含列的最大数量\")\n\tmaxTextColsCount := flag.Int(\"max-text-cols-count\", Config.MaxTextColsCount, \"MaxTextColsCount, 表中含有的 text/blob 列的最大数量\")\n\tmaxTotalRows := flag.Uint64(\"max-total-rows\", Config.MaxTotalRows, \"MaxTotalRows, 计算散粒度时，当数据行数大于MaxTotalRows即开启数据库保护模式，不计算散粒度\")\n\tmaxQueryCost := flag.Int64(\"max-query-cost\", Config.MaxQueryCost, \"MaxQueryCost, last_query_cost 超过该值时将给予警告\")\n\tspaghettiQueryLength := flag.Int(\"spaghetti-query-length\", Config.SpaghettiQueryLength, \"SpaghettiQueryLength, SQL最大长度警告，超过该长度会给警告\")\n\tallowDropIdx := flag.Bool(\"allow-drop-index\", Config.AllowDropIndex, \"AllowDropIndex, 允许输出删除重复索引的建议\")\n\tmaxInCount := flag.Int(\"max-in-count\", Config.MaxInCount, \"MaxInCount, IN()最大数量\")\n\tmaxIdxBytesPerColumn := flag.Int(\"max-index-bytes-percolumn\", Config.MaxIdxBytesPerColumn, \"MaxIdxBytesPerColumn, 索引中单列最大字节数\")\n\tmaxIdxBytes := flag.Int(\"max-index-bytes\", Config.MaxIdxBytes, \"MaxIdxBytes, 索引总长度限制\")\n\tallowCharsets := flag.String(\"allow-charsets\", strings.ToLower(strings.Join(Config.AllowCharsets, \",\")), \"AllowCharsets\")\n\tallowCollates := flag.String(\"allow-collates\", strings.ToLower(strings.Join(Config.AllowCollates, \",\")), \"AllowCollates\")\n\tallowEngines := flag.String(\"allow-engines\", strings.ToLower(strings.Join(Config.AllowEngines, \",\")), \"AllowEngines\")\n\tmaxIdxCount := flag.Int(\"max-index-count\", Config.MaxIdxCount, \"MaxIdxCount, 单表最大索引个数\")\n\tmaxColCount := flag.Int(\"max-column-count\", Config.MaxColCount, \"MaxColCount, 单表允许的最大列数\")\n\tmaxValueCount := flag.Int(\"max-value-count\", Config.MaxValueCount, \"MaxValueCount, INSERT/REPLACE 单次批量写入允许的行数\")\n\tidxPrefix := flag.String(\"index-prefix\", Config.IdxPrefix, \"IdxPrefix\")\n\tukPrefix := flag.String(\"unique-key-prefix\", Config.UkPrefix, \"UkPrefix\")\n\tmaxSubqueryDepth := flag.Int(\"max-subquery-depth\", Config.MaxSubqueryDepth, \"MaxSubqueryDepth\")\n\tmaxVarcharLength := flag.Int(\"max-varchar-length\", Config.MaxVarcharLength, \"MaxVarcharLength\")\n\tcolumnNotAllowType := flag.String(\"column-not-allow-type\", strings.Join(Config.ColumnNotAllowType, \",\"), \"ColumnNotAllowType\")\n\t// ++++++++++++++EXPLAIN检查项+++++++++++++\n\texplainSQLReportType := flag.String(\"explain-sql-report-type\", strings.ToLower(Config.ExplainSQLReportType), \"ExplainSQLReportType [pretty, sample, fingerprint]\")\n\texplainType := flag.String(\"explain-type\", strings.ToLower(Config.ExplainType), \"ExplainType [extended, partitions, traditional]\")\n\texplainFormat := flag.String(\"explain-format\", strings.ToLower(Config.ExplainFormat), \"ExplainFormat [json, traditional]\")\n\texplainWarnSelectType := flag.String(\"explain-warn-select-type\", strings.Join(Config.ExplainWarnSelectType, \",\"), \"ExplainWarnSelectType, 哪些select_type不建议使用\")\n\texplainWarnAccessType := flag.String(\"explain-warn-access-type\", strings.Join(Config.ExplainWarnAccessType, \",\"), \"ExplainWarnAccessType, 哪些access type不建议使用\")\n\texplainMaxKeyLength := flag.Int(\"explain-max-keys\", Config.ExplainMaxKeyLength, \"ExplainMaxKeyLength, 最大key_len\")\n\texplainMinPossibleKeys := flag.Int(\"explain-min-keys\", Config.ExplainMinPossibleKeys, \"ExplainMinPossibleKeys, 最小possible_keys警告\")\n\texplainMaxRows := flag.Int64(\"explain-max-rows\", Config.ExplainMaxRows, \"ExplainMaxRows, 最大扫描行数警告\")\n\texplainWarnExtra := flag.String(\"explain-warn-extra\", strings.Join(Config.ExplainWarnExtra, \",\"), \"ExplainWarnExtra, 哪些extra信息会给警告\")\n\texplainMaxFiltered := flag.Float64(\"explain-max-filtered\", Config.ExplainMaxFiltered, \"ExplainMaxFiltered, filtered大于该配置给出警告\")\n\texplainWarnScalability := flag.String(\"explain-warn-scalability\", strings.Join(Config.ExplainWarnScalability, \",\"), \"ExplainWarnScalability, 复杂度警告名单, 支持O(n),O(log n),O(1),O(?)\")\n\tshowWarnings := flag.Bool(\"show-warnings\", Config.ShowWarnings, \"ShowWarnings\")\n\tshowLastQueryCost := flag.Bool(\"show-last-query-cost\", Config.ShowLastQueryCost, \"ShowLastQueryCost\")\n\t// +++++++++++++++++其他+++++++++++++++++++\n\tprintConfig := flag.Bool(\"print-config\", false, \"Print configs\")\n\tcheckConfig := flag.Bool(\"check-config\", false, \"Check configs\")\n\tprintVersion := flag.Bool(\"version\", false, \"Print version info\")\n\tquery := flag.String(\"query\", Config.Query, \"待评审的 SQL 或 SQL 文件，如 SQL 中包含特殊字符建议使用文件名。\")\n\tlistHeuristicRules := flag.Bool(\"list-heuristic-rules\", Config.ListHeuristicRules, \"ListHeuristicRules, 打印支持的评审规则列表\")\n\tlistRewriteRules := flag.Bool(\"list-rewrite-rules\", Config.ListRewriteRules, \"ListRewriteRules, 打印支持的重写规则列表\")\n\tlistTestSQLs := flag.Bool(\"list-test-sqls\", Config.ListTestSqls, \"ListTestSqls, 打印测试case用于测试\")\n\tlistReportTypes := flag.Bool(\"list-report-types\", Config.ListReportTypes, \"ListReportTypes, 打印支持的报告输出类型\")\n\tverbose := flag.Bool(\"verbose\", Config.Verbose, \"Verbose\")\n\tdryrun := flag.Bool(\"dry-run\", Config.DryRun, \"是否在预演环境执行\")\n\tmaxPrettySQLLength := flag.Int(\"max-pretty-sql-length\", Config.MaxPrettySQLLength, \"MaxPrettySQLLength, 超出该长度的SQL会转换成指纹输出\")\n\t// 一个不存在 log-level，用于更新 usage。\n\t// 因为 vitess 里面也用了 flag，这些 vitess 的参数我们不需要关注\n\tif !Config.Verbose && runtime.GOOS != \"windows\" {\n\t\tflag.Usage = usage\n\t}\n\tflag.Parse()\n\n\tConfig.OnlineDSN = ParseDSN(*onlineDSN, Config.OnlineDSN)\n\tConfig.TestDSN = ParseDSN(*testDSN, Config.TestDSN)\n\tConfig.AllowOnlineAsTest = *allowOnlineAsTest\n\tConfig.DropTestTemporary = *dropTestTemporary\n\tConfig.CleanupTestDatabase = *cleanupTestDatabase\n\tConfig.OnlySyntaxCheck = *onlySyntaxCheck\n\tConfig.Profiling = *profiling\n\tConfig.Trace = *trace\n\tConfig.Explain = *explain\n\tConfig.Sampling = *sampling\n\tConfig.SamplingStatisticTarget = *samplingStatisticTarget\n\tConfig.SamplingCondition = *samplingCondition\n\n\tConfig.LogLevel = *logLevel\n\n\tif filepath.IsAbs(*logOutput) || *logOutput == \"\" {\n\t\tConfig.LogOutput = *logOutput\n\t} else {\n\t\tConfig.LogOutput = filepath.Join(BaseDir, *logOutput)\n\t}\n\n\tConfig.ReportType = strings.ToLower(*reportType)\n\tif Config.ReportType == \"tables\" && Config.TestDSN.Schema == \"information_schema\" {\n\t\tConfig.TestDSN.Schema = \"unknown\"\n\t}\n\tConfig.ReportCSS = *reportCSS\n\tConfig.ReportJavascript = *reportJavascript\n\tConfig.ReportTitle = *reportTitle\n\tConfig.MarkdownExtensions = *markdownExtensions\n\tConfig.MarkdownHTMLFlags = *markdownHTMLFlags\n\tConfig.IgnoreRules = strings.Split(*ignoreRules, \",\")\n\tConfig.RewriteRules = strings.Split(*rewriteRules, \",\")\n\t*blackList = strings.TrimSpace(*blackList)\n\tConfig.MinCardinality = *minCardinality\n\n\tif filepath.IsAbs(*blackList) || *blackList == \"\" {\n\t\tConfig.BlackList = *blackList\n\t} else {\n\t\tConfig.BlackList = filepath.Join(BaseDir, *blackList)\n\t}\n\n\tConfig.MaxJoinTableCount = *maxJoinTableCount\n\tConfig.MaxGroupByColsCount = *maxGroupByColsCount\n\tConfig.MaxDistinctCount = *maxDistinctCount\n\n\tif *maxIdxColsCount < 16 {\n\t\tConfig.MaxIdxColsCount = *maxIdxColsCount\n\t} else {\n\t\tConfig.MaxIdxColsCount = 16\n\t}\n\n\tConfig.MaxTextColsCount = *maxTextColsCount\n\tConfig.MaxIdxBytesPerColumn = *maxIdxBytesPerColumn\n\tConfig.MaxIdxBytes = *maxIdxBytes\n\tif *allowCharsets != \"\" {\n\t\tConfig.AllowCharsets = strings.Split(strings.ToLower(*allowCharsets), \",\")\n\t}\n\tif *allowCollates != \"\" {\n\t\tConfig.AllowCollates = strings.Split(strings.ToLower(*allowCollates), \",\")\n\t}\n\tif *allowEngines != \"\" {\n\t\tConfig.AllowEngines = strings.Split(strings.ToLower(*allowEngines), \",\")\n\t}\n\tConfig.MaxIdxCount = *maxIdxCount\n\tConfig.MaxColCount = *maxColCount\n\tConfig.MaxValueCount = *maxValueCount\n\tConfig.IdxPrefix = *idxPrefix\n\tConfig.UkPrefix = *ukPrefix\n\tConfig.MaxSubqueryDepth = *maxSubqueryDepth\n\tConfig.MaxTotalRows = *maxTotalRows\n\tConfig.MaxQueryCost = *maxQueryCost\n\tConfig.AllowDropIndex = *allowDropIdx\n\tConfig.MaxInCount = *maxInCount\n\tConfig.SpaghettiQueryLength = *spaghettiQueryLength\n\tConfig.Query = *query\n\tConfig.Delimiter = *delimiter\n\n\tConfig.ExplainSQLReportType = strings.ToLower(*explainSQLReportType)\n\tConfig.ExplainType = strings.ToLower(*explainType)\n\tConfig.ExplainFormat = strings.ToLower(*explainFormat)\n\tConfig.ExplainWarnSelectType = strings.Split(*explainWarnSelectType, \",\")\n\tConfig.ExplainWarnAccessType = strings.Split(*explainWarnAccessType, \",\")\n\tConfig.ExplainMaxKeyLength = *explainMaxKeyLength\n\tConfig.ExplainMinPossibleKeys = *explainMinPossibleKeys\n\tConfig.ExplainMaxRows = *explainMaxRows\n\tConfig.ExplainWarnExtra = strings.Split(*explainWarnExtra, \",\")\n\tConfig.ExplainMaxFiltered = *explainMaxFiltered\n\tConfig.ExplainWarnScalability = strings.Split(*explainWarnScalability, \",\")\n\tConfig.ShowWarnings = *showWarnings\n\tConfig.ShowLastQueryCost = *showLastQueryCost\n\tConfig.ListHeuristicRules = *listHeuristicRules\n\tConfig.ListRewriteRules = *listRewriteRules\n\tConfig.ListTestSqls = *listTestSQLs\n\tConfig.ListReportTypes = *listReportTypes\n\tConfig.Verbose = *verbose\n\tConfig.DryRun = *dryrun\n\tConfig.MaxPrettySQLLength = *maxPrettySQLLength\n\tConfig.MaxVarcharLength = *maxVarcharLength\n\tif *columnNotAllowType != \"\" {\n\t\tConfig.ColumnNotAllowType = strings.Split(strings.ToLower(*columnNotAllowType), \",\")\n\t}\n\n\tPrintVersion = *printVersion\n\tPrintConfig = *printConfig\n\tCheckConfig = *checkConfig\n\n\thasParsed = true\n\treturn nil\n}\n\n// ParseConfig 加载配置文件和命令行参数\nfunc ParseConfig(configFile string) error {\n\tvar err error\n\tvar configs []string\n\t// 指定了配置文件优先读配置文件，未指定配置文件按如下顺序加载，先找到哪个加载哪个\n\tif configFile == \"\" {\n\t\tconfigs = []string{\n\t\t\t\"/etc/soar.yaml\",\n\t\t\tfilepath.Join(BaseDir, \"etc\", \"soar.yaml\"),\n\t\t\tfilepath.Join(BaseDir, \"soar.yaml\"),\n\t\t}\n\t} else {\n\t\tconfigs = []string{\n\t\t\tconfigFile,\n\t\t}\n\t}\n\n\tfor _, config := range configs {\n\t\tif _, err = os.Stat(config); err == nil {\n\t\t\terr = Config.readConfigFile(config)\n\t\t\tif err != nil {\n\t\t\t\tLog.Error(\"ParseConfig Config.readConfigFile Error: %v\", err)\n\t\t\t}\n\t\t\t// LogOutput now is \"console\", if add Log.Debug here will print into stdout anyway.\n\t\t\t// Log.Debug(\"ParseConfig use config file: %s\", config)\n\t\t\tbreak\n\t\t}\n\t}\n\n\terr = readCmdFlags()\n\tif err != nil {\n\t\tLog.Error(\"ParseConfig readCmdFlags Error: %v\", err)\n\t\treturn err\n\t}\n\n\t// parse blacklist & ignore blacklist file parse error\n\tif _, e := os.Stat(Config.BlackList); e == nil {\n\t\tvar blFd *os.File\n\t\tblFd, err = os.Open(Config.BlackList)\n\t\tif err == nil {\n\t\t\tbl := bufio.NewReader(blFd)\n\t\t\tfor {\n\t\t\t\trule, e := bl.ReadString('\\n')\n\t\t\t\tif e != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\trule = strings.TrimSpace(rule)\n\t\t\t\tif strings.HasPrefix(rule, \"#\") || rule == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tBlackList = append(BlackList, rule)\n\t\t\t}\n\t\t}\n\t\tdefer blFd.Close()\n\t}\n\tLoggerInit()\n\treturn err\n}\n\n// ReportType 元数据结构定义\ntype ReportType struct {\n\tName        string `json:\"Name\"`\n\tDescription string `json:\"Description\"`\n\tExample     string `json:\"Example\"`\n}\n\n// ReportTypes 命令行-report-type支持的形式\nvar ReportTypes = []ReportType{\n\t{\n\t\tName:        \"lint\",\n\t\tDescription: \"参考sqlint格式，以插件形式集成到代码编辑器，显示输出更加友好\",\n\t\tExample:     `soar -report-type lint -query test.sql`,\n\t},\n\t{\n\t\tName:        \"markdown\",\n\t\tDescription: \"该格式为默认输出格式，以markdown格式展现，可以用网页浏览器插件直接打开，也可以用markdown编辑器打开\",\n\t\tExample:     `echo \"select * from film\" | soar`,\n\t},\n\t{\n\t\tName:        \"rewrite\",\n\t\tDescription: \"SQL重写功能，配合-rewrite-rules参数一起使用，可以通过-list-rewrite-rules 查看所有支持的 SQL 重写规则\",\n\t\tExample:     `echo \"select * from film\" | soar -rewrite-rules star2columns,delimiter -report-type rewrite`,\n\t},\n\t{\n\t\tName:        \"ast\",\n\t\tDescription: \"输出 SQL 的抽象语法树，主要用于测试\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type ast`,\n\t},\n\t{\n\t\tName:        \"ast-json\",\n\t\tDescription: \"以 JSON 格式输出 SQL 的抽象语法树，主要用于测试\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type ast-json`,\n\t},\n\t{\n\t\tName:        \"tiast\",\n\t\tDescription: \"输出 SQL 的 TiDB抽象语法树，主要用于测试\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type tiast`,\n\t},\n\t{\n\t\tName:        \"tiast-json\",\n\t\tDescription: \"以 JSON 格式输出 SQL 的 TiDB抽象语法树，主要用于测试\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type tiast-json`,\n\t},\n\t{\n\t\tName:        \"tables\",\n\t\tDescription: \"以 JSON 格式输出 SQL 使用的库表名\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type tables`,\n\t},\n\t{\n\t\tName:        \"query-type\",\n\t\tDescription: \"SQL 语句的请求类型\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type query-type`,\n\t},\n\t{\n\t\tName:        \"fingerprint\",\n\t\tDescription: \"输出SQL的指纹\",\n\t\tExample:     `echo \"select * from film where language_id=1\" | soar -report-type fingerprint`,\n\t},\n\t{\n\t\tName:        \"md2html\",\n\t\tDescription: \"markdown 格式转 html 格式小工具\",\n\t\tExample:     `soar -list-heuristic-rules | soar -report-type md2html > heuristic_rules.html`,\n\t},\n\t{\n\t\tName:        \"explain-digest\",\n\t\tDescription: \"输入为EXPLAIN的表格，JSON 或 Vertical格式，对其进行分析，给出分析结果\",\n\t\tExample: `soar -report-type explain-digest << EOF\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\n| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\n|  1 | SIMPLE      | film  | ALL  | NULL          | NULL | NULL    | NULL | 1131 |       |\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\nEOF`,\n\t},\n\t{\n\t\tName:        \"duplicate-key-checker\",\n\t\tDescription: \"对 OnlineDsn 中指定的 database 进行索引重复检查\",\n\t\tExample:     `soar -report-type duplicate-key-checker -online-dsn user:password@127.0.0.1:3306/db`,\n\t},\n\t{\n\t\tName:        \"html\",\n\t\tDescription: \"以HTML格式输出报表\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type html`,\n\t},\n\t{\n\t\tName:        \"json\",\n\t\tDescription: \"输出JSON格式报表，方便应用程序处理\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type json`,\n\t},\n\t{\n\t\tName:        \"tokenize\",\n\t\tDescription: \"对SQL进行切词，主要用于测试\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type tokenize`,\n\t},\n\t{\n\t\tName:        \"compress\",\n\t\tDescription: \"SQL压缩小工具，使用内置SQL压缩逻辑，测试中的功能\",\n\t\tExample: `echo \"select\n*\nfrom\n  film\" | soar -report-type compress`,\n\t},\n\t{\n\t\tName:        \"pretty\",\n\t\tDescription: \"使用kr/pretty打印报告，主要用于测试\",\n\t\tExample:     `echo \"select * from film\" | soar -report-type pretty`,\n\t},\n\t{\n\t\tName:        \"remove-comment\",\n\t\tDescription: \"去除SQL语句中的注释，支持单行多行注释的去除\",\n\t\tExample:     `echo \"select/*comment*/ * from film\" | soar -report-type remove-comment`,\n\t},\n\t{\n\t\tName:        \"chardet\",\n\t\tDescription: \"猜测输入的 SQL 使用的字符集\",\n\t\tExample:     \"echo '中文' | soar -report-type chardet\",\n\t},\n}\n\n// ListReportTypes 查看所有支持的report-type\nfunc ListReportTypes() {\n\tswitch Config.ReportType {\n\tcase \"json\":\n\t\tjs, err := json.MarshalIndent(ReportTypes, \"\", \"  \")\n\t\tif err == nil {\n\t\t\tfmt.Println(string(js))\n\t\t}\n\tdefault:\n\t\tfmt.Print(\"# 支持的报告类型\\n\\n[toc]\\n\\n\")\n\t\tfor _, r := range ReportTypes {\n\t\t\tfmt.Print(\"## \", MarkdownEscape(r.Name),\n\t\t\t\t\"\\n* **Description**:\", r.Description+\"\\n\",\n\t\t\t\t\"\\n* **Example**:\\n\\n```bash\\n\", r.Example, \"\\n```\\n\")\n\t\t}\n\t}\n}\n\n// ArgConfig get -config arg value from cli\nfunc ArgConfig() string {\n\tvar configFile string\n\tif len(os.Args) > 1 && strings.HasPrefix(os.Args[1], \"-config\") {\n\t\tif os.Args[1] == \"-config\" && len(os.Args) > 2 {\n\t\t\tif os.Args[2] == \"=\" && len(os.Args) > 3 {\n\t\t\t\t// -config = soar.yaml not support\n\t\t\t\tfmt.Println(\"wrong format, no space between '=', eg: -config=soar.yaml\")\n\t\t\t} else {\n\t\t\t\t// -config soar.yaml\n\t\t\t\tconfigFile = os.Args[2]\n\t\t\t}\n\t\t\tif strings.HasPrefix(configFile, \"=\") {\n\t\t\t\t// -config =soar.yaml\n\t\t\t\tconfigFile = strings.Split(configFile, \"=\")[1]\n\t\t\t}\n\t\t}\n\t\tif strings.Contains(os.Args[1], \"=\") {\n\t\t\t// -config=soar.yaml\n\t\t\tconfigFile = strings.Split(os.Args[1], \"=\")[1]\n\t\t}\n\t} else {\n\t\tfor i, c := range os.Args {\n\t\t\tif strings.HasPrefix(c, \"-config\") && i != 1 {\n\t\t\t\tfmt.Println(\"-config must be the first arg\")\n\t\t\t}\n\t\t}\n\t}\n\treturn configFile\n}\n"
  },
  {
    "path": "common/config_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/kr/pretty\"\n)\n\nvar update = flag.Bool(\"update\", false, \"update .golden files\")\n\nfunc TestMain(m *testing.M) {\n\t// 初始化 init\n\tif DevPath == \"\" {\n\t\t_, file, _, _ := runtime.Caller(0)\n\t\tDevPath, _ = filepath.Abs(filepath.Dir(filepath.Join(file, \"..\"+string(filepath.Separator))))\n\t}\n\tBaseDir = DevPath\n\terr := ParseConfig(\"\")\n\tLogIfError(err, \"init ParseConfig\")\n\tLog.Debug(\"mysql_test init\")\n\n\t// 分割线\n\tflag.Parse()\n\tm.Run()\n\n\t// 环境清理\n\t//\n}\n\nfunc TestParseConfig(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\terr := ParseConfig(\"\")\n\tif err != nil {\n\t\tt.Error(\"sqlparser.Parse Error:\", err)\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestReadConfigFile(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tif Config == nil {\n\t\tConfig = new(Configuration)\n\t}\n\tConfig.readConfigFile(filepath.Join(DevPath, \"etc/soar.yaml\"))\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestParseDSN(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tvar dsns = []string{\n\t\t// version < 0.11.0\n\t\t\"\",\n\t\t\"user:password@hostname:3307/database\",\n\t\t\"user:password@hostname:3307/database?charset=utf8\",\n\t\t\"user:password@hostname:3307\",\n\t\t\"user:password@hostname:/database\",\n\t\t\"user:password@:3307/database\",\n\t\t\"user@hostname/database\",\n\t\t\"user:pwd:pwd@pwd/pwd@hostname/database\",\n\t\t\"user:password@\",\n\t\t\"hostname:3307/database\",\n\t\t\"@hostname:3307/database\",\n\t\t\"@hostname\",\n\t\t\"hostname\",\n\t\t\"@/database\",\n\t\t\"@hostname:3307\",\n\t\t\"@:3307/database\",\n\t\t\":3307/database\",\n\t\t\"/database\",\n\t\t// go-sql-driver dsn\n\t\t\"user@unix(/path/to/socket)/dbname\",\n\t\t\"root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local\",\n\t\t\"user:password@tcp(localhost:5555)/dbname?tls=skip-verify\",\n\t\t\"user:password@tcp(localhost:5555)/dbname?autocommit=true\",\n\t\t\"user:password@/dbname?sql_mode=TRADITIONAL\",\n\t\t\"user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s\",\n\t\t\"user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?collation=utf8mb4_unicode_ci\",\n\t\t\"id:password@tcp(your-amazonaws-uri.com:3306)/dbname\",\n\t\t\"user@cloudsql(project-id:instance-name)/dbname\",\n\t\t\"user@cloudsql(project-id:regionname:instance-name)/dbname\",\n\t\t//\"user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped\", multi key in map, pretty print in random order, by pass\n\t\t\"user:password@tcp/dbname?sys_var=esc%40ped\",\n\t\t\"user:password@/dbname\",\n\t\t\"user:password@/\",\n\t\t\"user:password@tcp(localhost:3307)/database?timeout=5s\",\n\t}\n\terr := GoldenDiff(func() {\n\t\tfor _, dsn := range dsns {\n\t\t\tpretty.Println(dsn)\n\t\t\tpretty.Println(ParseDSN(dsn, nil))\n\t\t}\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestListReportTypes(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\terr := GoldenDiff(func() { ListReportTypes() }, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestArgConfig(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\ttestArgs1 := [][]string{\n\t\t{\"soar\", \"-config\", \"=\", \"soar.yaml\"},\n\t\t{\"soar\", \"-print-config\", \"-config\", \"soar.yaml\"},\n\t}\n\ttestArgs2 := [][]string{\n\t\t{\"soar\", \"-config\", \"soar.yaml\"},\n\t\t{\"soar\", \"-config\", \"=soar.yaml\"},\n\t\t{\"soar\", \"-config=soar.yaml\"},\n\t}\n\tfor _, args := range testArgs1 {\n\t\tos.Args = args\n\t\tconfigFile := ArgConfig()\n\t\tif configFile != \"\" {\n\t\t\tt.Errorf(\"should return '', but got %s\", configFile)\n\t\t}\n\t}\n\tfor _, args := range testArgs2 {\n\t\tos.Args = args\n\t\tconfigFile := ArgConfig()\n\t\tif configFile != \"soar.yaml\" {\n\t\t\tt.Errorf(\"should return soar.yaml, but got %s\", configFile)\n\t\t}\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestPrintConfiguration(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tConfig.readConfigFile(filepath.Join(DevPath, \"etc/soar.yaml\"))\n\toldLogOutput := Config.LogOutput\n\tConfig.LogOutput = \"soar.log\"\n\terr := GoldenDiff(func() {\n\t\tPrintConfiguration()\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tConfig.LogOutput = oldLogOutput\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n"
  },
  {
    "path": "common/doc.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package common contain many useful functions for logging, formatting and so on.\npackage common\n"
  },
  {
    "path": "common/example_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport \"fmt\"\n\nfunc ExampleFormatDSN() {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tdsxExp := newDSN(nil)\n\tdsxExp.Addr = \"127.0.0.1:3306\"\n\tdsxExp.Schema = \"mysql\"\n\tdsxExp.User = \"root\"\n\tdsxExp.Password = \"1t'sB1g3rt\"\n\tdsxExp.Charset = \"utf8mb4\"\n\tdsxExp.Disable = false\n\n\t// 根据 &dsn 生成 dsnStr\n\tfmt.Println(FormatDSN(dsxExp))\n\n\t// Output: root:1t'sB1g3rt@tcp(127.0.0.1:3306)/mysql?timeout=3s&charset=utf8mb4\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc ExampleIsColsPart() {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\t// IsColsPart() 会 按照顺序 检查两个Column队列是否是包含（或相等）关系。\n\ta := []*Column{{Name: \"1\"}, {Name: \"2\"}, {Name: \"3\"}}\n\tb := []*Column{{Name: \"1\"}, {Name: \"2\"}}\n\tc := []*Column{{Name: \"1\"}, {Name: \"3\"}}\n\td := []*Column{{Name: \"1\"}, {Name: \"2\"}, {Name: \"3\"}, {Name: \"4\"}}\n\tid := []*Column{{Name: \"id\"}}\n\tiD := []*Column{{Name: \"iD\"}}\n\n\tab := IsColsPart(a, b)\n\tac := IsColsPart(a, c)\n\tad := IsColsPart(a, d)\n\tidiD := IsColsPart(id, iD) // 大小写对比\n\n\tfmt.Println(ab, ac, ad, idiD)\n\t// Output: true false true true\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc ExampleSortedKey() {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tages := map[string]int{\n\t\t\"a\": 1,\n\t\t\"c\": 3,\n\t\t\"d\": 4,\n\t\t\"b\": 2,\n\t}\n\tfor _, name := range SortedKey(ages) {\n\t\tfmt.Print(ages[name])\n\t}\n\t// Output: 1234\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n"
  },
  {
    "path": "common/logger.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/astaxie/beego/logs\"\n)\n\n// Log 使用 beego 的 log 库\nvar Log *logs.BeeLogger\n\n// BaseDir 日志打印在binary的根路径\nvar BaseDir string\n\nfunc init() {\n\tLog = logs.NewLogger(0)\n\tLog.EnableFuncCallDepth(true)\n}\n\n// LoggerInit Log配置初始化\nfunc LoggerInit() {\n\tLog.SetLevel(Config.LogLevel)\n\tfunc() { _ = Log.DelLogger(logs.AdapterFile) }()\n\tlogConfig := map[string]interface{}{\n\t\t\"filename\": Config.LogOutput,\n\t\t\"level\":    7,\n\t\t\"maxlines\": 0,\n\t\t\"maxsize\":  0,\n\t\t\"daily\":    false,\n\t\t\"maxdays\":  0,\n\t}\n\tlogConfigJSON, _ := json.Marshal(logConfig)\n\terr := Log.SetLogger(logs.AdapterFile, string(logConfigJSON))\n\tif err != nil {\n\t\tfmt.Println(err.Error())\n\t}\n}\n\n// Caller returns the caller of the function that called it :)\n// https://stackoverflow.com/questions/35212985/is-it-possible-get-information-about-caller-function-in-golang\nfunc Caller() string {\n\t// we get the callers as uintptrs - but we just need 1\n\tfpcs := make([]uintptr, 1)\n\n\t// skip 3 levels to get to the caller of whoever called Caller()\n\tn := runtime.Callers(3, fpcs)\n\tif n == 0 {\n\t\treturn \"n/a\" // proper error her would be better\n\t}\n\n\t// get the info of the actual function that's in the pointer\n\tfun := runtime.FuncForPC(fpcs[0] - 1)\n\tif fun == nil {\n\t\treturn \"n/a\"\n\t}\n\n\t// return its name\n\treturn fun.Name()\n}\n\n// GetFunctionName 获取调当前函数名\nfunc GetFunctionName() string {\n\t// Skip this function, and fetch the PC and file for its parent\n\tpc, _, _, _ := runtime.Caller(1)\n\t// Retrieve a Function object this functions parent\n\tfunctionObject := runtime.FuncForPC(pc)\n\t// Regex to extract just the function name (and not the module path)\n\textractFnName := regexp.MustCompile(`^.*\\.(.*)$`)\n\tfnName := extractFnName.ReplaceAllString(functionObject.Name(), \"$1\")\n\treturn fnName\n}\n\n// fileName get filename from path\nfunc fileName(original string) string {\n\ti := strings.LastIndex(original, \"/\")\n\tif i == -1 {\n\t\treturn original\n\t}\n\treturn original[i+1:]\n}\n\n// LogIfError 简化if err != nil 打 Error 日志代码长度\nfunc LogIfError(err error, format string, v ...interface{}) {\n\tif err != nil {\n\t\t_, fn, line, _ := runtime.Caller(1)\n\t\tif format == \"\" {\n\t\t\tformat = \"[%s:%d] %s\"\n\t\t\tLog.Error(format, fileName(fn), line, err.Error())\n\t\t} else {\n\t\t\tformat = \"[%s:%d] \" + format + \" Error: %s\"\n\t\t\tLog.Error(format, fileName(fn), line, v, err.Error())\n\t\t}\n\t}\n}\n\n// LogIfWarn 简化if err != nil 打 Warn 日志代码长度\nfunc LogIfWarn(err error, format string, v ...interface{}) {\n\tif err != nil {\n\t\t_, fn, line, _ := runtime.Caller(1)\n\t\tif format == \"\" {\n\t\t\tformat = \"[%s:%d] %s\"\n\t\t\tLog.Warn(format, fileName(fn), line, err.Error())\n\t\t} else {\n\t\t\tformat = \"[%s:%d] \" + format + \" Error: %s\"\n\t\t\tLog.Warn(format, fileName(fn), line, v, err.Error())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/logger_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestLogger(t *testing.T) {\n\tLog.Info(\"TestLogger_Info\")\n\tLog.Debug(\"TestLogger_Debug\")\n\tLog.Warning(\"TestLogger_Warning\")\n\tLog.Error(\"Warning_Error\")\n}\n\nfunc TestCaller(t *testing.T) {\n\tcaller := Caller()\n\tif caller != \"testing.tRunner\" {\n\t\tt.Error(\"get caller failed\")\n\t}\n}\n\nfunc TestGetFunctionName(t *testing.T) {\n\tf := GetFunctionName()\n\tif f != \"TestGetFunctionName\" {\n\t\tt.Error(\"get functionname failed\")\n\t}\n}\n\nfunc TestIfError(t *testing.T) {\n\terr := errors.New(\"TestIfError\")\n\tLogIfError(err, \"\")\n\tLogIfError(err, \"func %s\", \"func_test\")\n}\n\nfunc TestIfWarn(t *testing.T) {\n\terr := errors.New(\"test\")\n\tLogIfWarn(err, \"\")\n\tLogIfWarn(err, \"func %s\", \"func_test\")\n}\n"
  },
  {
    "path": "common/markdown.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/russross/blackfriday\"\n)\n\n// BuiltinCSS 内置HTML风格\nvar BuiltinCSS = `\na:link,a:visited{text-decoration:none}h3,h4{margin-top:2em}h5,h6{margin-top:20px}h3,h4,h5,h6{margin-bottom:.5em;color:#000}body,h1,h2,h3,h4,h5,h6{color:#000}ol,ul{margin:0 0 0 30px;padding:0 0 12px 6px}ol,ol ol{list-style-position:outside}table td p,table th p{margin-bottom:0}input,select{vertical-align:middle;padding:0}h5,h6,input,select{padding:0}hr,table,textarea{width:100%}body{margin:20px auto;width:800px;background-color:#fff;font:13px \"Myriad Pro\",\"Lucida Grande\",Lucida,Verdana,sans-serif}h1,table th p{font-weight:700}a:link{color:#00f}a:visited{color:#00a}a:active,a:hover{color:#f60;text-decoration:underline}* html code,* html pre{font-size:101%}code,pre{font-size:11px;font-family:monaco,courier,consolas,monospace}pre{border:1px solid #c7cfd5;background:#f1f5f9;margin:20px 0;padding:8px;text-align:left}hr{color:#919699;size:1;noshade:\"noshade\"}h1,h2,h3,h4,h5,h6{font-family:\"Myriad Pro\",\"Lucida Grande\",Lucida,Verdana,sans-serif;font-weight:700}h1{margin-top:1em;margin-bottom:25px;font-size:30px}h2{margin-top:2.5em;font-size:24px;padding-bottom:2px;border-bottom:1px solid #919699}h3{font-size:17px}h4{font-size:15px}h5{font-size:13px}h6{font-size:11px}table td,table th{font-size:12px;border-bottom:1px solid #919699;border-right:1px solid #919699}p{margin-top:0;margin-bottom:10px}ul{list-style:square}li{margin-top:7px}ol{list-style-type:decimal}ol ol{list-style-type:lower-alpha;margin:7px 0 0 30px;padding:0 0 0 10px}ul ul{margin-left:40px;padding:0 0 0 6px}li>p{display:inline}li>a+p,li>p+p{display:block}table{border-top:1px solid #919699;border-left:1px solid #919699;border-spacing:0}table th{padding:4px 8px;background:#E2E2E2}table td{padding:8px;vertical-align:top}table td p+p,table td p+p+p{margin-top:5px}form{margin:0}button{margin:3px 0 10px}input{margin:0 0 5px}select{margin:0 0 3px}textarea{margin:0 0 10px}\n`\n\n// BuiltinJavascript 内置 SQL 美化 Javascript 脚本\nvar BuiltinJavascript = `IWZ1bmN0aW9uKGUsRSl7Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzJiYib2JqZWN0Ij09dHlwZW9mIG1vZHVsZT9tb2R1bGUuZXhwb3J0cz1FKCk6ImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoW10sRSk6Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzP2V4cG9ydHMuc3FsRm9ybWF0dGVyPUUoKTplLnNxbEZvcm1hdHRlcj1FKCl9KHRoaXMsZnVuY3Rpb24oKXtyZXR1cm4gZnVuY3Rpb24oZSl7ZnVuY3Rpb24gRShuKXtpZih0W25dKXJldHVybiB0W25dLmV4cG9ydHM7dmFyIHI9dFtuXT17ZXhwb3J0czp7fSxpZDpuLGxvYWRlZDohMX07cmV0dXJuIGVbbl0uY2FsbChyLmV4cG9ydHMscixyLmV4cG9ydHMsRSksci5sb2FkZWQ9ITAsci5leHBvcnRzfXZhciB0PXt9O3JldHVybiBFLm09ZSxFLmM9dCxFLnA9IiIsRSgwKX0oW2Z1bmN0aW9uKGUsRSx0KXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gbihlKXtyZXR1cm4gZSYmZS5fX2VzTW9kdWxlP2U6eyJkZWZhdWx0IjplfX1FLl9fZXNNb2R1bGU9ITA7dmFyIHI9dCgxOCksVD1uKHIpLFI9dCgxOSksbz1uKFIpLE49dCgyMCksQT1uKE4pLEk9dCgyMSksTz1uKEkpO0VbImRlZmF1bHQiXT17Zm9ybWF0OmZ1bmN0aW9uKGUsRSl7c3dpdGNoKEU9RXx8e30sRS5sYW5ndWFnZSl7Y2FzZSJkYjIiOnJldHVybiBuZXcgVFsiZGVmYXVsdCJdKEUpLmZvcm1hdChlKTtjYXNlIm4xcWwiOnJldHVybiBuZXcgb1siZGVmYXVsdCJdKEUpLmZvcm1hdChlKTtjYXNlInBsL3NxbCI6cmV0dXJuIG5ldyBBWyJkZWZhdWx0Il0oRSkuZm9ybWF0KGUpO2Nhc2Uic3FsIjpjYXNlIHZvaWQgMDpyZXR1cm4gbmV3IE9bImRlZmF1bHQiXShFKS5mb3JtYXQoZSk7ZGVmYXVsdDp0aHJvdyBFcnJvcigiVW5zdXBwb3J0ZWQgU1FMIGRpYWxlY3Q6ICIrRS5sYW5ndWFnZSl9fX0sZS5leHBvcnRzPUVbImRlZmF1bHQiXX0sZnVuY3Rpb24oZSxFKXsidXNlIHN0cmljdCI7RS5fX2VzTW9kdWxlPSEwLEVbImRlZmF1bHQiXT1mdW5jdGlvbihlLEUpe2lmKCEoZSBpbnN0YW5jZW9mIEUpKXRocm93IG5ldyBUeXBlRXJyb3IoIkNhbm5vdCBjYWxsIGEgY2xhc3MgYXMgYSBmdW5jdGlvbiIpfX0sZnVuY3Rpb24oZSxFLHQpe3ZhciBuPXQoMzkpLHI9Im9iamVjdCI9PXR5cGVvZiBzZWxmJiZzZWxmJiZzZWxmLk9iamVjdD09PU9iamVjdCYmc2VsZixUPW58fHJ8fEZ1bmN0aW9uKCJyZXR1cm4gdGhpcyIpKCk7ZS5leHBvcnRzPVR9LGZ1bmN0aW9uKGUsRSx0KXtmdW5jdGlvbiBuKGUsRSl7dmFyIHQ9VChlLEUpO3JldHVybiByKHQpP3Q6dm9pZCAwfXZhciByPXQoMzMpLFQ9dCg0MSk7ZS5leHBvcnRzPW59LGZ1bmN0aW9uKGUsRSx0KXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gbihlKXtyZXR1cm4gZSYmZS5fX2VzTW9kdWxlP2U6eyJkZWZhdWx0IjplfX1FLl9fZXNNb2R1bGU9ITA7dmFyIHI9dCgxKSxUPW4ociksUj10KDY2KSxvPW4oUiksTj10KDcpLEE9bihOKSxJPXQoMTUpLE89bihJKSxpPXQoMTYpLFM9bihpKSx1PXQoMTcpLEw9bih1KSxDPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZShFLHQpeygwLFRbImRlZmF1bHQiXSkodGhpcyxlKSx0aGlzLmNmZz1FfHx7fSx0aGlzLmluZGVudGF0aW9uPW5ldyBPWyJkZWZhdWx0Il0odGhpcy5jZmcuaW5kZW50KSx0aGlzLmlubGluZUJsb2NrPW5ldyBTWyJkZWZhdWx0Il0sdGhpcy5wYXJhbXM9bmV3IExbImRlZmF1bHQiXSh0aGlzLmNmZy5wYXJhbXMpLHRoaXMudG9rZW5pemVyPXQsdGhpcy5wcmV2aW91c1Jlc2VydmVkV29yZD17fX1yZXR1cm4gZS5wcm90b3R5cGUuZm9ybWF0PWZ1bmN0aW9uKGUpe3ZhciBFPXRoaXMudG9rZW5pemVyLnRva2VuaXplKGUpLHQ9dGhpcy5nZXRGb3JtYXR0ZWRRdWVyeUZyb21Ub2tlbnMoRSk7cmV0dXJuIHQudHJpbSgpfSxlLnByb3RvdHlwZS5nZXRGb3JtYXR0ZWRRdWVyeUZyb21Ub2tlbnM9ZnVuY3Rpb24oZSl7dmFyIEU9dGhpcyx0PSIiO3JldHVybiBlLmZvckVhY2goZnVuY3Rpb24obixyKXtuLnR5cGUhPT1BWyJkZWZhdWx0Il0uV0hJVEVTUEFDRSYmKG4udHlwZT09PUFbImRlZmF1bHQiXS5MSU5FX0NPTU1FTlQ/dD1FLmZvcm1hdExpbmVDb21tZW50KG4sdCk6bi50eXBlPT09QVsiZGVmYXVsdCJdLkJMT0NLX0NPTU1FTlQ/dD1FLmZvcm1hdEJsb2NrQ29tbWVudChuLHQpOm4udHlwZT09PUFbImRlZmF1bHQiXS5SRVNFUlZFRF9UT1BMRVZFTD8odD1FLmZvcm1hdFRvcGxldmVsUmVzZXJ2ZWRXb3JkKG4sdCksRS5wcmV2aW91c1Jlc2VydmVkV29yZD1uKTpuLnR5cGU9PT1BWyJkZWZhdWx0Il0uUkVTRVJWRURfTkVXTElORT8odD1FLmZvcm1hdE5ld2xpbmVSZXNlcnZlZFdvcmQobix0KSxFLnByZXZpb3VzUmVzZXJ2ZWRXb3JkPW4pOm4udHlwZT09PUFbImRlZmF1bHQiXS5SRVNFUlZFRD8odD1FLmZvcm1hdFdpdGhTcGFjZXMobix0KSxFLnByZXZpb3VzUmVzZXJ2ZWRXb3JkPW4pOnQ9bi50eXBlPT09QVsiZGVmYXVsdCJdLk9QRU5fUEFSRU4/RS5mb3JtYXRPcGVuaW5nUGFyZW50aGVzZXMoZSxyLHQpOm4udHlwZT09PUFbImRlZmF1bHQiXS5DTE9TRV9QQVJFTj9FLmZvcm1hdENsb3NpbmdQYXJlbnRoZXNlcyhuLHQpOm4udHlwZT09PUFbImRlZmF1bHQiXS5QTEFDRUhPTERFUj9FLmZvcm1hdFBsYWNlaG9sZGVyKG4sdCk6IiwiPT09bi52YWx1ZT9FLmZvcm1hdENvbW1hKG4sdCk6IjoiPT09bi52YWx1ZT9FLmZvcm1hdFdpdGhTcGFjZUFmdGVyKG4sdCk6Ii4iPT09bi52YWx1ZXx8IjsiPT09bi52YWx1ZT9FLmZvcm1hdFdpdGhvdXRTcGFjZXMobix0KTpFLmZvcm1hdFdpdGhTcGFjZXMobix0KSl9KSx0fSxlLnByb3RvdHlwZS5mb3JtYXRMaW5lQ29tbWVudD1mdW5jdGlvbihlLEUpe3JldHVybiB0aGlzLmFkZE5ld2xpbmUoRStlLnZhbHVlKX0sZS5wcm90b3R5cGUuZm9ybWF0QmxvY2tDb21tZW50PWZ1bmN0aW9uKGUsRSl7cmV0dXJuIHRoaXMuYWRkTmV3bGluZSh0aGlzLmFkZE5ld2xpbmUoRSkrdGhpcy5pbmRlbnRDb21tZW50KGUudmFsdWUpKX0sZS5wcm90b3R5cGUuaW5kZW50Q29tbWVudD1mdW5jdGlvbihlKXtyZXR1cm4gZS5yZXBsYWNlKC9cbi9nLCJcbiIrdGhpcy5pbmRlbnRhdGlvbi5nZXRJbmRlbnQoKSl9LGUucHJvdG90eXBlLmZvcm1hdFRvcGxldmVsUmVzZXJ2ZWRXb3JkPWZ1bmN0aW9uKGUsRSl7cmV0dXJuIHRoaXMuaW5kZW50YXRpb24uZGVjcmVhc2VUb3BMZXZlbCgpLEU9dGhpcy5hZGROZXdsaW5lKEUpLHRoaXMuaW5kZW50YXRpb24uaW5jcmVhc2VUb3BsZXZlbCgpLEUrPXRoaXMuZXF1YWxpemVXaGl0ZXNwYWNlKGUudmFsdWUpLHRoaXMuYWRkTmV3bGluZShFKX0sZS5wcm90b3R5cGUuZm9ybWF0TmV3bGluZVJlc2VydmVkV29yZD1mdW5jdGlvbihlLEUpe3JldHVybiB0aGlzLmFkZE5ld2xpbmUoRSkrdGhpcy5lcXVhbGl6ZVdoaXRlc3BhY2UoZS52YWx1ZSkrIiAifSxlLnByb3RvdHlwZS5lcXVhbGl6ZVdoaXRlc3BhY2U9ZnVuY3Rpb24oZSl7cmV0dXJuIGUucmVwbGFjZSgvXHMrL2csIiAiKX0sZS5wcm90b3R5cGUuZm9ybWF0T3BlbmluZ1BhcmVudGhlc2VzPWZ1bmN0aW9uKGUsRSx0KXt2YXIgbj1lW0UtMV07cmV0dXJuIG4mJm4udHlwZSE9PUFbImRlZmF1bHQiXS5XSElURVNQQUNFJiZuLnR5cGUhPT1BWyJkZWZhdWx0Il0uT1BFTl9QQVJFTiYmKHQ9KDAsb1siZGVmYXVsdCJdKSh0KSksdCs9ZVtFXS52YWx1ZSx0aGlzLmlubGluZUJsb2NrLmJlZ2luSWZQb3NzaWJsZShlLEUpLHRoaXMuaW5saW5lQmxvY2suaXNBY3RpdmUoKXx8KHRoaXMuaW5kZW50YXRpb24uaW5jcmVhc2VCbG9ja0xldmVsKCksdD10aGlzLmFkZE5ld2xpbmUodCkpLHR9LGUucHJvdG90eXBlLmZvcm1hdENsb3NpbmdQYXJlbnRoZXNlcz1mdW5jdGlvbihlLEUpe3JldHVybiB0aGlzLmlubGluZUJsb2NrLmlzQWN0aXZlKCk/KHRoaXMuaW5saW5lQmxvY2suZW5kKCksdGhpcy5mb3JtYXRXaXRoU3BhY2VBZnRlcihlLEUpKToodGhpcy5pbmRlbnRhdGlvbi5kZWNyZWFzZUJsb2NrTGV2ZWwoKSx0aGlzLmZvcm1hdFdpdGhTcGFjZXMoZSx0aGlzLmFkZE5ld2xpbmUoRSkpKX0sZS5wcm90b3R5cGUuZm9ybWF0UGxhY2Vob2xkZXI9ZnVuY3Rpb24oZSxFKXtyZXR1cm4gRSt0aGlzLnBhcmFtcy5nZXQoZSkrIiAifSxlLnByb3RvdHlwZS5mb3JtYXRDb21tYT1mdW5jdGlvbihlLEUpe3JldHVybiBFPSgwLG9bImRlZmF1bHQiXSkoRSkrZS52YWx1ZSsiICIsdGhpcy5pbmxpbmVCbG9jay5pc0FjdGl2ZSgpP0U6L15MSU1JVCQvaS50ZXN0KHRoaXMucHJldmlvdXNSZXNlcnZlZFdvcmQudmFsdWUpP0U6dGhpcy5hZGROZXdsaW5lKEUpfSxlLnByb3RvdHlwZS5mb3JtYXRXaXRoU3BhY2VBZnRlcj1mdW5jdGlvbihlLEUpe3JldHVybigwLG9bImRlZmF1bHQiXSkoRSkrZS52YWx1ZSsiICJ9LGUucHJvdG90eXBlLmZvcm1hdFdpdGhvdXRTcGFjZXM9ZnVuY3Rpb24oZSxFKXtyZXR1cm4oMCxvWyJkZWZhdWx0Il0pKEUpK2UudmFsdWV9LGUucHJvdG90eXBlLmZvcm1hdFdpdGhTcGFjZXM9ZnVuY3Rpb24oZSxFKXtyZXR1cm4gRStlLnZhbHVlKyIgIn0sZS5wcm90b3R5cGUuYWRkTmV3bGluZT1mdW5jdGlvbihlKXtyZXR1cm4oMCxvWyJkZWZhdWx0Il0pKGUpKyJcbiIrdGhpcy5pbmRlbnRhdGlvbi5nZXRJbmRlbnQoKX0sZX0oKTtFWyJkZWZhdWx0Il09QyxlLmV4cG9ydHM9RVsiZGVmYXVsdCJdfSxmdW5jdGlvbihlLEUsdCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIG4oZSl7cmV0dXJuIGUmJmUuX19lc01vZHVsZT9lOnsiZGVmYXVsdCI6ZX19RS5fX2VzTW9kdWxlPSEwO3ZhciByPXQoMSksVD1uKHIpLFI9dCg1OCksbz1uKFIpLE49dCg1MyksQT1uKE4pLEk9dCg3KSxPPW4oSSksaT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoRSl7KDAsVFsiZGVmYXVsdCJdKSh0aGlzLGUpLHRoaXMuV0hJVEVTUEFDRV9SRUdFWD0vXihccyspLyx0aGlzLk5VTUJFUl9SRUdFWD0vXigoLVxzKik/WzAtOV0rKFwuWzAtOV0rKT98MHhbMC05YS1mQS1GXSt8MGJbMDFdKylcYi8sdGhpcy5PUEVSQVRPUl9SRUdFWD0vXighPXw8Pnw9PXw8PXw+PXwhPHwhPnxcfFx8fDo6fC0+PnwtPnx+flwqfH5+fCF+flwqfCF+fnx+XCp8IX5cKnwhfnwuKS8sdGhpcy5CTE9DS19DT01NRU5UX1JFR0VYPS9eKFwvXCpbXl0qPyg/OlwqXC98JCkpLyx0aGlzLkxJTkVfQ09NTUVOVF9SRUdFWD10aGlzLmNyZWF0ZUxpbmVDb21tZW50UmVnZXgoRS5saW5lQ29tbWVudFR5cGVzKSx0aGlzLlJFU0VSVkVEX1RPUExFVkVMX1JFR0VYPXRoaXMuY3JlYXRlUmVzZXJ2ZWRXb3JkUmVnZXgoRS5yZXNlcnZlZFRvcGxldmVsV29yZHMpLHRoaXMuUkVTRVJWRURfTkVXTElORV9SRUdFWD10aGlzLmNyZWF0ZVJlc2VydmVkV29yZFJlZ2V4KEUucmVzZXJ2ZWROZXdsaW5lV29yZHMpLHRoaXMuUkVTRVJWRURfUExBSU5fUkVHRVg9dGhpcy5jcmVhdGVSZXNlcnZlZFdvcmRSZWdleChFLnJlc2VydmVkV29yZHMpLHRoaXMuV09SRF9SRUdFWD10aGlzLmNyZWF0ZVdvcmRSZWdleChFLnNwZWNpYWxXb3JkQ2hhcnMpLHRoaXMuU1RSSU5HX1JFR0VYPXRoaXMuY3JlYXRlU3RyaW5nUmVnZXgoRS5zdHJpbmdUeXBlcyksdGhpcy5PUEVOX1BBUkVOX1JFR0VYPXRoaXMuY3JlYXRlUGFyZW5SZWdleChFLm9wZW5QYXJlbnMpLHRoaXMuQ0xPU0VfUEFSRU5fUkVHRVg9dGhpcy5jcmVhdGVQYXJlblJlZ2V4KEUuY2xvc2VQYXJlbnMpLHRoaXMuSU5ERVhFRF9QTEFDRUhPTERFUl9SRUdFWD10aGlzLmNyZWF0ZVBsYWNlaG9sZGVyUmVnZXgoRS5pbmRleGVkUGxhY2Vob2xkZXJUeXBlcywiWzAtOV0qIiksdGhpcy5JREVOVF9OQU1FRF9QTEFDRUhPTERFUl9SRUdFWD10aGlzLmNyZWF0ZVBsYWNlaG9sZGVyUmVnZXgoRS5uYW1lZFBsYWNlaG9sZGVyVHlwZXMsIlthLXpBLVowLTkuXyRdKyIpLHRoaXMuU1RSSU5HX05BTUVEX1BMQUNFSE9MREVSX1JFR0VYPXRoaXMuY3JlYXRlUGxhY2Vob2xkZXJSZWdleChFLm5hbWVkUGxhY2Vob2xkZXJUeXBlcyx0aGlzLmNyZWF0ZVN0cmluZ1BhdHRlcm4oRS5zdHJpbmdUeXBlcykpfXJldHVybiBlLnByb3RvdHlwZS5jcmVhdGVMaW5lQ29tbWVudFJlZ2V4PWZ1bmN0aW9uKGUpe3JldHVybiBSZWdFeHAoIl4oKD86IitlLm1hcChmdW5jdGlvbihlKXtyZXR1cm4oMCxBWyJkZWZhdWx0Il0pKGUpfSkuam9pbigifCIpKyIpLio/KD86XG58JCkpIil9LGUucHJvdG90eXBlLmNyZWF0ZVJlc2VydmVkV29yZFJlZ2V4PWZ1bmN0aW9uKGUpe3ZhciBFPWUuam9pbigifCIpLnJlcGxhY2UoLyAvZywiXFxzKyIpO3JldHVybiBSZWdFeHAoIl4oIitFKyIpXFxiIiwiaSIpfSxlLnByb3RvdHlwZS5jcmVhdGVXb3JkUmVnZXg9ZnVuY3Rpb24oKXt2YXIgZT1hcmd1bWVudHMubGVuZ3RoPjAmJnZvaWQgMCE9PWFyZ3VtZW50c1swXT9hcmd1bWVudHNbMF06W107cmV0dXJuIFJlZ0V4cCgiXihbXFx3IitlLmpvaW4oIiIpKyJdKykiKX0sZS5wcm90b3R5cGUuY3JlYXRlU3RyaW5nUmVnZXg9ZnVuY3Rpb24oZSl7cmV0dXJuIFJlZ0V4cCgiXigiK3RoaXMuY3JlYXRlU3RyaW5nUGF0dGVybihlKSsiKSIpfSxlLnByb3RvdHlwZS5jcmVhdGVTdHJpbmdQYXR0ZXJuPWZ1bmN0aW9uKGUpe3ZhciBFPXsiYGAiOiIoKGBbXmBdKigkfGApKSspIiwiW10iOiIoKFxcW1teXFxdXSooJHxcXF0pKShcXF1bXlxcXV0qKCR8XFxdKSkqKSIsJyIiJzonKCgiW14iXFxcXF0qKD86XFxcXC5bXiJcXFxcXSopKigifCQpKSspJywiJyciOiIoKCdbXidcXFxcXSooPzpcXFxcLlteJ1xcXFxdKikqKCd8JCkpKykiLCJOJyciOiIoKE4nW15OJ1xcXFxdKig/OlxcXFwuW15OJ1xcXFxdKikqKCd8JCkpKykifTtyZXR1cm4gZS5tYXAoZnVuY3Rpb24oZSl7cmV0dXJuIEVbZV19KS5qb2luKCJ8Iil9LGUucHJvdG90eXBlLmNyZWF0ZVBhcmVuUmVnZXg9ZnVuY3Rpb24oZSl7dmFyIEU9dGhpcztyZXR1cm4gUmVnRXhwKCJeKCIrZS5tYXAoZnVuY3Rpb24oZSl7cmV0dXJuIEUuZXNjYXBlUGFyZW4oZSl9KS5qb2luKCJ8IikrIikiLCJpIil9LGUucHJvdG90eXBlLmVzY2FwZVBhcmVuPWZ1bmN0aW9uKGUpe3JldHVybiAxPT09ZS5sZW5ndGg/KDAsQVsiZGVmYXVsdCJdKShlKToiXFxiIitlKyJcXGIifSxlLnByb3RvdHlwZS5jcmVhdGVQbGFjZWhvbGRlclJlZ2V4PWZ1bmN0aW9uKGUsRSl7aWYoKDAsb1siZGVmYXVsdCJdKShlKSlyZXR1cm4hMTt2YXIgdD1lLm1hcChBWyJkZWZhdWx0Il0pLmpvaW4oInwiKTtyZXR1cm4gUmVnRXhwKCJeKCg/OiIrdCsiKSg/OiIrRSsiKSkiKX0sZS5wcm90b3R5cGUudG9rZW5pemU9ZnVuY3Rpb24oZSl7Zm9yKHZhciBFPVtdLHQ9dm9pZCAwO2UubGVuZ3RoOyl0PXRoaXMuZ2V0TmV4dFRva2VuKGUsdCksZT1lLnN1YnN0cmluZyh0LnZhbHVlLmxlbmd0aCksRS5wdXNoKHQpO3JldHVybiBFfSxlLnByb3RvdHlwZS5nZXROZXh0VG9rZW49ZnVuY3Rpb24oZSxFKXtyZXR1cm4gdGhpcy5nZXRXaGl0ZXNwYWNlVG9rZW4oZSl8fHRoaXMuZ2V0Q29tbWVudFRva2VuKGUpfHx0aGlzLmdldFN0cmluZ1Rva2VuKGUpfHx0aGlzLmdldE9wZW5QYXJlblRva2VuKGUpfHx0aGlzLmdldENsb3NlUGFyZW5Ub2tlbihlKXx8dGhpcy5nZXRQbGFjZWhvbGRlclRva2VuKGUpfHx0aGlzLmdldE51bWJlclRva2VuKGUpfHx0aGlzLmdldFJlc2VydmVkV29yZFRva2VuKGUsRSl8fHRoaXMuZ2V0V29yZFRva2VuKGUpfHx0aGlzLmdldE9wZXJhdG9yVG9rZW4oZSl9LGUucHJvdG90eXBlLmdldFdoaXRlc3BhY2VUb2tlbj1mdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5nZXRUb2tlbk9uRmlyc3RNYXRjaCh7aW5wdXQ6ZSx0eXBlOk9bImRlZmF1bHQiXS5XSElURVNQQUNFLHJlZ2V4OnRoaXMuV0hJVEVTUEFDRV9SRUdFWH0pfSxlLnByb3RvdHlwZS5nZXRDb21tZW50VG9rZW49ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuZ2V0TGluZUNvbW1lbnRUb2tlbihlKXx8dGhpcy5nZXRCbG9ja0NvbW1lbnRUb2tlbihlKX0sZS5wcm90b3R5cGUuZ2V0TGluZUNvbW1lbnRUb2tlbj1mdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5nZXRUb2tlbk9uRmlyc3RNYXRjaCh7aW5wdXQ6ZSx0eXBlOk9bImRlZmF1bHQiXS5MSU5FX0NPTU1FTlQscmVnZXg6dGhpcy5MSU5FX0NPTU1FTlRfUkVHRVh9KX0sZS5wcm90b3R5cGUuZ2V0QmxvY2tDb21tZW50VG9rZW49ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuZ2V0VG9rZW5PbkZpcnN0TWF0Y2goe2lucHV0OmUsdHlwZTpPWyJkZWZhdWx0Il0uQkxPQ0tfQ09NTUVOVCxyZWdleDp0aGlzLkJMT0NLX0NPTU1FTlRfUkVHRVh9KX0sZS5wcm90b3R5cGUuZ2V0U3RyaW5nVG9rZW49ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuZ2V0VG9rZW5PbkZpcnN0TWF0Y2goe2lucHV0OmUsdHlwZTpPWyJkZWZhdWx0Il0uU1RSSU5HLHJlZ2V4OnRoaXMuU1RSSU5HX1JFR0VYfSl9LGUucHJvdG90eXBlLmdldE9wZW5QYXJlblRva2VuPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLmdldFRva2VuT25GaXJzdE1hdGNoKHtpbnB1dDplLHR5cGU6T1siZGVmYXVsdCJdLk9QRU5fUEFSRU4scmVnZXg6dGhpcy5PUEVOX1BBUkVOX1JFR0VYfSl9LGUucHJvdG90eXBlLmdldENsb3NlUGFyZW5Ub2tlbj1mdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5nZXRUb2tlbk9uRmlyc3RNYXRjaCh7aW5wdXQ6ZSx0eXBlOk9bImRlZmF1bHQiXS5DTE9TRV9QQVJFTixyZWdleDp0aGlzLkNMT1NFX1BBUkVOX1JFR0VYfSl9LGUucHJvdG90eXBlLmdldFBsYWNlaG9sZGVyVG9rZW49ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuZ2V0SWRlbnROYW1lZFBsYWNlaG9sZGVyVG9rZW4oZSl8fHRoaXMuZ2V0U3RyaW5nTmFtZWRQbGFjZWhvbGRlclRva2VuKGUpfHx0aGlzLmdldEluZGV4ZWRQbGFjZWhvbGRlclRva2VuKGUpfSxlLnByb3RvdHlwZS5nZXRJZGVudE5hbWVkUGxhY2Vob2xkZXJUb2tlbj1mdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5nZXRQbGFjZWhvbGRlclRva2VuV2l0aEtleSh7aW5wdXQ6ZSxyZWdleDp0aGlzLklERU5UX05BTUVEX1BMQUNFSE9MREVSX1JFR0VYLHBhcnNlS2V5OmZ1bmN0aW9uKGUpe3JldHVybiBlLnNsaWNlKDEpfX0pfSxlLnByb3RvdHlwZS5nZXRTdHJpbmdOYW1lZFBsYWNlaG9sZGVyVG9rZW49ZnVuY3Rpb24oZSl7dmFyIEU9dGhpcztyZXR1cm4gdGhpcy5nZXRQbGFjZWhvbGRlclRva2VuV2l0aEtleSh7aW5wdXQ6ZSxyZWdleDp0aGlzLlNUUklOR19OQU1FRF9QTEFDRUhPTERFUl9SRUdFWCxwYXJzZUtleTpmdW5jdGlvbihlKXtyZXR1cm4gRS5nZXRFc2NhcGVkUGxhY2Vob2xkZXJLZXkoe2tleTplLnNsaWNlKDIsLTEpLHF1b3RlQ2hhcjplLnNsaWNlKC0xKX0pfX0pfSxlLnByb3RvdHlwZS5nZXRJbmRleGVkUGxhY2Vob2xkZXJUb2tlbj1mdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5nZXRQbGFjZWhvbGRlclRva2VuV2l0aEtleSh7aW5wdXQ6ZSxyZWdleDp0aGlzLklOREVYRURfUExBQ0VIT0xERVJfUkVHRVgscGFyc2VLZXk6ZnVuY3Rpb24oZSl7cmV0dXJuIGUuc2xpY2UoMSl9fSl9LGUucHJvdG90eXBlLmdldFBsYWNlaG9sZGVyVG9rZW5XaXRoS2V5PWZ1bmN0aW9uKGUpe3ZhciBFPWUuaW5wdXQsdD1lLnJlZ2V4LG49ZS5wYXJzZUtleSxyPXRoaXMuZ2V0VG9rZW5PbkZpcnN0TWF0Y2goe2lucHV0OkUscmVnZXg6dCx0eXBlOk9bImRlZmF1bHQiXS5QTEFDRUhPTERFUn0pO3JldHVybiByJiYoci5rZXk9bihyLnZhbHVlKSkscn0sZS5wcm90b3R5cGUuZ2V0RXNjYXBlZFBsYWNlaG9sZGVyS2V5PWZ1bmN0aW9uKGUpe3ZhciBFPWUua2V5LHQ9ZS5xdW90ZUNoYXI7cmV0dXJuIEUucmVwbGFjZShSZWdFeHAoKDAsQVsiZGVmYXVsdCJdKSgiXFwiKSt0LCJnIiksdCl9LGUucHJvdG90eXBlLmdldE51bWJlclRva2VuPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLmdldFRva2VuT25GaXJzdE1hdGNoKHtpbnB1dDplLHR5cGU6T1siZGVmYXVsdCJdLk5VTUJFUixyZWdleDp0aGlzLk5VTUJFUl9SRUdFWH0pfSxlLnByb3RvdHlwZS5nZXRPcGVyYXRvclRva2VuPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLmdldFRva2VuT25GaXJzdE1hdGNoKHtpbnB1dDplLHR5cGU6T1siZGVmYXVsdCJdLk9QRVJBVE9SLHJlZ2V4OnRoaXMuT1BFUkFUT1JfUkVHRVh9KX0sZS5wcm90b3R5cGUuZ2V0UmVzZXJ2ZWRXb3JkVG9rZW49ZnVuY3Rpb24oZSxFKXtpZighRXx8IUUudmFsdWV8fCIuIiE9PUUudmFsdWUpcmV0dXJuIHRoaXMuZ2V0VG9wbGV2ZWxSZXNlcnZlZFRva2VuKGUpfHx0aGlzLmdldE5ld2xpbmVSZXNlcnZlZFRva2VuKGUpfHx0aGlzLmdldFBsYWluUmVzZXJ2ZWRUb2tlbihlKX0sZS5wcm90b3R5cGUuZ2V0VG9wbGV2ZWxSZXNlcnZlZFRva2VuPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLmdldFRva2VuT25GaXJzdE1hdGNoKHtpbnB1dDplLHR5cGU6T1siZGVmYXVsdCJdLlJFU0VSVkVEX1RPUExFVkVMLHJlZ2V4OnRoaXMuUkVTRVJWRURfVE9QTEVWRUxfUkVHRVh9KX0sZS5wcm90b3R5cGUuZ2V0TmV3bGluZVJlc2VydmVkVG9rZW49ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuZ2V0VG9rZW5PbkZpcnN0TWF0Y2goe2lucHV0OmUsdHlwZTpPWyJkZWZhdWx0Il0uUkVTRVJWRURfTkVXTElORSxyZWdleDp0aGlzLlJFU0VSVkVEX05FV0xJTkVfUkVHRVh9KX0sZS5wcm90b3R5cGUuZ2V0UGxhaW5SZXNlcnZlZFRva2VuPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLmdldFRva2VuT25GaXJzdE1hdGNoKHtpbnB1dDplLHR5cGU6T1siZGVmYXVsdCJdLlJFU0VSVkVELHJlZ2V4OnRoaXMuUkVTRVJWRURfUExBSU5fUkVHRVh9KX0sZS5wcm90b3R5cGUuZ2V0V29yZFRva2VuPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLmdldFRva2VuT25GaXJzdE1hdGNoKHtpbnB1dDplLHR5cGU6T1siZGVmYXVsdCJdLldPUkQscmVnZXg6dGhpcy5XT1JEX1JFR0VYfSl9LGUucHJvdG90eXBlLmdldFRva2VuT25GaXJzdE1hdGNoPWZ1bmN0aW9uKGUpe3ZhciBFPWUuaW5wdXQsdD1lLnR5cGUsbj1lLnJlZ2V4LHI9RS5tYXRjaChuKTtpZihyKXJldHVybnt0eXBlOnQsdmFsdWU6clsxXX19LGV9KCk7RVsiZGVmYXVsdCJdPWksZS5leHBvcnRzPUVbImRlZmF1bHQiXX0sZnVuY3Rpb24oZSxFKXtmdW5jdGlvbiB0KGUpe3ZhciBFPXR5cGVvZiBlO3JldHVybiBudWxsIT1lJiYoIm9iamVjdCI9PUV8fCJmdW5jdGlvbiI9PUUpfWUuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUpeyJ1c2Ugc3RyaWN0IjtFLl9fZXNNb2R1bGU9ITAsRVsiZGVmYXVsdCJdPXtXSElURVNQQUNFOiJ3aGl0ZXNwYWNlIixXT1JEOiJ3b3JkIixTVFJJTkc6InN0cmluZyIsUkVTRVJWRUQ6InJlc2VydmVkIixSRVNFUlZFRF9UT1BMRVZFTDoicmVzZXJ2ZWQtdG9wbGV2ZWwiLFJFU0VSVkVEX05FV0xJTkU6InJlc2VydmVkLW5ld2xpbmUiLE9QRVJBVE9SOiJvcGVyYXRvciIsT1BFTl9QQVJFTjoib3Blbi1wYXJlbiIsQ0xPU0VfUEFSRU46ImNsb3NlLXBhcmVuIixMSU5FX0NPTU1FTlQ6ImxpbmUtY29tbWVudCIsQkxPQ0tfQ09NTUVOVDoiYmxvY2stY29tbWVudCIsTlVNQkVSOiJudW1iZXIiLFBMQUNFSE9MREVSOiJwbGFjZWhvbGRlciJ9LGUuZXhwb3J0cz1FWyJkZWZhdWx0Il19LGZ1bmN0aW9uKGUsRSx0KXtmdW5jdGlvbiBuKGUpe3JldHVybiBudWxsIT1lJiZUKGUubGVuZ3RoKSYmIXIoZSl9dmFyIHI9dCgxMiksVD10KDU5KTtlLmV4cG9ydHM9bn0sZnVuY3Rpb24oZSxFLHQpe2Z1bmN0aW9uIG4oZSl7cmV0dXJuIG51bGw9PWU/IiI6cihlKX12YXIgcj10KDEwKTtlLmV4cG9ydHM9bn0sZnVuY3Rpb24oZSxFLHQpe2Z1bmN0aW9uIG4oZSl7aWYoInN0cmluZyI9PXR5cGVvZiBlKXJldHVybiBlO2lmKFQoZSkpcmV0dXJuIE4/Ti5jYWxsKGUpOiIiO3ZhciBFPWUrIiI7cmV0dXJuIjAiPT1FJiYxL2U9PS1SPyItMCI6RX12YXIgcj10KDI2KSxUPXQoMTQpLFI9MS8wLG89cj9yLnByb3RvdHlwZTp2b2lkIDAsTj1vP28udG9TdHJpbmc6dm9pZCAwO2UuZXhwb3J0cz1ufSxmdW5jdGlvbihlLEUpe2Z1bmN0aW9uIHQoZSl7aWYobnVsbCE9ZSl7dHJ5e3JldHVybiByLmNhbGwoZSl9Y2F0Y2goRSl7fXRyeXtyZXR1cm4gZSsiIn1jYXRjaChFKXt9fXJldHVybiIifXZhciBuPUZ1bmN0aW9uLnByb3RvdHlwZSxyPW4udG9TdHJpbmc7ZS5leHBvcnRzPXR9LGZ1bmN0aW9uKGUsRSx0KXtmdW5jdGlvbiBuKGUpe3ZhciBFPXIoZSk/Ti5jYWxsKGUpOiIiO3JldHVybiBFPT1UfHxFPT1SfXZhciByPXQoNiksVD0iW29iamVjdCBGdW5jdGlvbl0iLFI9IltvYmplY3QgR2VuZXJhdG9yRnVuY3Rpb25dIixvPU9iamVjdC5wcm90b3R5cGUsTj1vLnRvU3RyaW5nO2UuZXhwb3J0cz1ufSxmdW5jdGlvbihlLEUpe2Z1bmN0aW9uIHQoZSl7cmV0dXJuIG51bGwhPWUmJiJvYmplY3QiPT10eXBlb2YgZX1lLmV4cG9ydHM9dH0sZnVuY3Rpb24oZSxFLHQpe2Z1bmN0aW9uIG4oZSl7cmV0dXJuInN5bWJvbCI9PXR5cGVvZiBlfHxyKGUpJiZvLmNhbGwoZSk9PVR9dmFyIHI9dCgxMyksVD0iW29iamVjdCBTeW1ib2xdIixSPU9iamVjdC5wcm90b3R5cGUsbz1SLnRvU3RyaW5nO2UuZXhwb3J0cz1ufSxmdW5jdGlvbihlLEUsdCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIG4oZSl7cmV0dXJuIGUmJmUuX19lc01vZHVsZT9lOnsiZGVmYXVsdCI6ZX19RS5fX2VzTW9kdWxlPSEwO3ZhciByPXQoMSksVD1uKHIpLFI9dCg2MSksbz1uKFIpLE49dCg2MCksQT1uKE4pLEk9InRvcC1sZXZlbCIsTz0iYmxvY2stbGV2ZWwiLGk9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKEUpeygwLFRbImRlZmF1bHQiXSkodGhpcyxlKSx0aGlzLmluZGVudD1FfHwiICAiLHRoaXMuaW5kZW50VHlwZXM9W119cmV0dXJuIGUucHJvdG90eXBlLmdldEluZGVudD1mdW5jdGlvbigpe3JldHVybigwLG9bImRlZmF1bHQiXSkodGhpcy5pbmRlbnQsdGhpcy5pbmRlbnRUeXBlcy5sZW5ndGgpfSxlLnByb3RvdHlwZS5pbmNyZWFzZVRvcGxldmVsPWZ1bmN0aW9uKCl7dGhpcy5pbmRlbnRUeXBlcy5wdXNoKEkpfSxlLnByb3RvdHlwZS5pbmNyZWFzZUJsb2NrTGV2ZWw9ZnVuY3Rpb24oKXt0aGlzLmluZGVudFR5cGVzLnB1c2goTyl9LGUucHJvdG90eXBlLmRlY3JlYXNlVG9wTGV2ZWw9ZnVuY3Rpb24oKXsoMCxBWyJkZWZhdWx0Il0pKHRoaXMuaW5kZW50VHlwZXMpPT09SSYmdGhpcy5pbmRlbnRUeXBlcy5wb3AoKX0sZS5wcm90b3R5cGUuZGVjcmVhc2VCbG9ja0xldmVsPWZ1bmN0aW9uKCl7Zm9yKDt0aGlzLmluZGVudFR5cGVzLmxlbmd0aD4wOyl7dmFyIGU9dGhpcy5pbmRlbnRUeXBlcy5wb3AoKTtpZihlIT09SSlicmVha319LGV9KCk7RVsiZGVmYXVsdCJdPWksZS5leHBvcnRzPUVbImRlZmF1bHQiXX0sZnVuY3Rpb24oZSxFLHQpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBuKGUpe3JldHVybiBlJiZlLl9fZXNNb2R1bGU/ZTp7ImRlZmF1bHQiOmV9fUUuX19lc01vZHVsZT0hMDt2YXIgcj10KDEpLFQ9bihyKSxSPXQoNyksbz1uKFIpLE49NTAsQT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoKXsoMCxUWyJkZWZhdWx0Il0pKHRoaXMsZSksdGhpcy5sZXZlbD0wfXJldHVybiBlLnByb3RvdHlwZS5iZWdpbklmUG9zc2libGU9ZnVuY3Rpb24oZSxFKXswPT09dGhpcy5sZXZlbCYmdGhpcy5pc0lubGluZUJsb2NrKGUsRSk/dGhpcy5sZXZlbD0xOnRoaXMubGV2ZWw+MD90aGlzLmxldmVsKys6dGhpcy5sZXZlbD0wfSxlLnByb3RvdHlwZS5lbmQ9ZnVuY3Rpb24oKXt0aGlzLmxldmVsLS19LGUucHJvdG90eXBlLmlzQWN0aXZlPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMubGV2ZWw+MH0sZS5wcm90b3R5cGUuaXNJbmxpbmVCbG9jaz1mdW5jdGlvbihlLEUpe2Zvcih2YXIgdD0wLG49MCxyPUU7ZS5sZW5ndGg+cjtyKyspe3ZhciBUPWVbcl07aWYodCs9VC52YWx1ZS5sZW5ndGgsdD5OKXJldHVybiExO2lmKFQudHlwZT09PW9bImRlZmF1bHQiXS5PUEVOX1BBUkVOKW4rKztlbHNlIGlmKFQudHlwZT09PW9bImRlZmF1bHQiXS5DTE9TRV9QQVJFTiYmKG4tLSwwPT09bikpcmV0dXJuITA7aWYodGhpcy5pc0ZvcmJpZGRlblRva2VuKFQpKXJldHVybiExfXJldHVybiExfSxlLnByb3RvdHlwZS5pc0ZvcmJpZGRlblRva2VuPWZ1bmN0aW9uKGUpe3ZhciBFPWUudHlwZSx0PWUudmFsdWU7cmV0dXJuIEU9PT1vWyJkZWZhdWx0Il0uUkVTRVJWRURfVE9QTEVWRUx8fEU9PT1vWyJkZWZhdWx0Il0uUkVTRVJWRURfTkVXTElORXx8RT09PW9bImRlZmF1bHQiXS5DT01NRU5UfHxFPT09b1siZGVmYXVsdCJdLkJMT0NLX0NPTU1FTlR8fCI7Ij09PXR9LGV9KCk7RVsiZGVmYXVsdCJdPUEsZS5leHBvcnRzPUVbImRlZmF1bHQiXX0sZnVuY3Rpb24oZSxFLHQpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBuKGUpe3JldHVybiBlJiZlLl9fZXNNb2R1bGU/ZTp7ImRlZmF1bHQiOmV9fUUuX19lc01vZHVsZT0hMDt2YXIgcj10KDEpLFQ9bihyKSxSPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZShFKXsoMCxUWyJkZWZhdWx0Il0pKHRoaXMsZSksdGhpcy5wYXJhbXM9RSx0aGlzLmluZGV4PTB9cmV0dXJuIGUucHJvdG90eXBlLmdldD1mdW5jdGlvbihlKXt2YXIgRT1lLmtleSx0PWUudmFsdWU7cmV0dXJuIHRoaXMucGFyYW1zP0U/dGhpcy5wYXJhbXNbRV06dGhpcy5wYXJhbXNbdGhpcy5pbmRleCsrXTp0fSxlfSgpO0VbImRlZmF1bHQiXT1SLGUuZXhwb3J0cz1FWyJkZWZhdWx0Il19LGZ1bmN0aW9uKGUsRSx0KXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gbihlKXtyZXR1cm4gZSYmZS5fX2VzTW9kdWxlP2U6eyJkZWZhdWx0IjplfX1FLl9fZXNNb2R1bGU9ITA7dmFyIHI9dCgxKSxUPW4ociksUj10KDQpLG89bihSKSxOPXQoNSksQT1uKE4pLEk9WyJBQlMiLCJBQ1RJVkFURSIsIkFMSUFTIiwiQUxMIiwiQUxMT0NBVEUiLCJBTExPVyIsIkFMVEVSIiwiQU5ZIiwiQVJFIiwiQVJSQVkiLCJBUyIsIkFTQyIsIkFTRU5TSVRJVkUiLCJBU1NPQ0lBVEUiLCJBU1VUSU1FIiwiQVNZTU1FVFJJQyIsIkFUIiwiQVRPTUlDIiwiQVRUUklCVVRFUyIsIkFVRElUIiwiQVVUSE9SSVpBVElPTiIsIkFVWCIsIkFVWElMSUFSWSIsIkFWRyIsIkJFRk9SRSIsIkJFR0lOIiwiQkVUV0VFTiIsIkJJR0lOVCIsIkJJTkFSWSIsIkJMT0IiLCJCT09MRUFOIiwiQk9USCIsIkJVRkZFUlBPT0wiLCJCWSIsIkNBQ0hFIiwiQ0FMTCIsIkNBTExFRCIsIkNBUFRVUkUiLCJDQVJESU5BTElUWSIsIkNBU0NBREVEIiwiQ0FTRSIsIkNBU1QiLCJDQ1NJRCIsIkNFSUwiLCJDRUlMSU5HIiwiQ0hBUiIsIkNIQVJBQ1RFUiIsIkNIQVJBQ1RFUl9MRU5HVEgiLCJDSEFSX0xFTkdUSCIsIkNIRUNLIiwiQ0xPQiIsIkNMT05FIiwiQ0xPU0UiLCJDTFVTVEVSIiwiQ09BTEVTQ0UiLCJDT0xMQVRFIiwiQ09MTEVDVCIsIkNPTExFQ1RJT04iLCJDT0xMSUQiLCJDT0xVTU4iLCJDT01NRU5UIiwiQ09NTUlUIiwiQ09OQ0FUIiwiQ09ORElUSU9OIiwiQ09OTkVDVCIsIkNPTk5FQ1RJT04iLCJDT05TVFJBSU5UIiwiQ09OVEFJTlMiLCJDT05USU5VRSIsIkNPTlZFUlQiLCJDT1JSIiwiQ09SUkVTUE9ORElORyIsIkNPVU5UIiwiQ09VTlRfQklHIiwiQ09WQVJfUE9QIiwiQ09WQVJfU0FNUCIsIkNSRUFURSIsIkNST1NTIiwiQ1VCRSIsIkNVTUVfRElTVCIsIkNVUlJFTlQiLCJDVVJSRU5UX0RBVEUiLCJDVVJSRU5UX0RFRkFVTFRfVFJBTlNGT1JNX0dST1VQIiwiQ1VSUkVOVF9MQ19DVFlQRSIsIkNVUlJFTlRfUEFUSCIsIkNVUlJFTlRfUk9MRSIsIkNVUlJFTlRfU0NIRU1BIiwiQ1VSUkVOVF9TRVJWRVIiLCJDVVJSRU5UX1RJTUUiLCJDVVJSRU5UX1RJTUVTVEFNUCIsIkNVUlJFTlRfVElNRVpPTkUiLCJDVVJSRU5UX1RSQU5TRk9STV9HUk9VUF9GT1JfVFlQRSIsIkNVUlJFTlRfVVNFUiIsIkNVUlNPUiIsIkNZQ0xFIiwiREFUQSIsIkRBVEFCQVNFIiwiREFUQVBBUlRJVElPTk5BTUUiLCJEQVRBUEFSVElUSU9OTlVNIiwiREFURSIsIkRBWSIsIkRBWVMiLCJEQjJHRU5FUkFMIiwiREIyR0VOUkwiLCJEQjJTUUwiLCJEQklORk8iLCJEQlBBUlRJVElPTk5BTUUiLCJEQlBBUlRJVElPTk5VTSIsIkRFQUxMT0NBVEUiLCJERUMiLCJERUNJTUFMIiwiREVDTEFSRSIsIkRFRkFVTFQiLCJERUZBVUxUUyIsIkRFRklOSVRJT04iLCJERUxFVEUiLCJERU5TRVJBTksiLCJERU5TRV9SQU5LIiwiREVSRUYiLCJERVNDUklCRSIsIkRFU0NSSVBUT1IiLCJERVRFUk1JTklTVElDIiwiRElBR05PU1RJQ1MiLCJESVNBQkxFIiwiRElTQUxMT1ciLCJESVNDT05ORUNUIiwiRElTVElOQ1QiLCJETyIsIkRPQ1VNRU5UIiwiRE9VQkxFIiwiRFJPUCIsIkRTU0laRSIsIkRZTkFNSUMiLCJFQUNIIiwiRURJVFBST0MiLCJFTEVNRU5UIiwiRUxTRSIsIkVMU0VJRiIsIkVOQUJMRSIsIkVOQ09ESU5HIiwiRU5DUllQVElPTiIsIkVORCIsIkVORC1FWEVDIiwiRU5ESU5HIiwiRVJBU0UiLCJFU0NBUEUiLCJFVkVSWSIsIkVYQ0VQVElPTiIsIkVYQ0xVRElORyIsIkVYQ0xVU0lWRSIsIkVYRUMiLCJFWEVDVVRFIiwiRVhJU1RTIiwiRVhJVCIsIkVYUCIsIkVYUExBSU4iLCJFWFRFTkRFRCIsIkVYVEVSTkFMIiwiRVhUUkFDVCIsIkZBTFNFIiwiRkVOQ0VEIiwiRkVUQ0giLCJGSUVMRFBST0MiLCJGSUxFIiwiRklMVEVSIiwiRklOQUwiLCJGSVJTVCIsIkZMT0FUIiwiRkxPT1IiLCJGT1IiLCJGT1JFSUdOIiwiRlJFRSIsIkZVTEwiLCJGVU5DVElPTiIsIkZVU0lPTiIsIkdFTkVSQUwiLCJHRU5FUkFURUQiLCJHRVQiLCJHTE9CQUwiLCJHT1RPIiwiR1JBTlQiLCJHUkFQSElDIiwiR1JPVVAiLCJHUk9VUElORyIsIkhBTkRMRVIiLCJIQVNIIiwiSEFTSEVEX1ZBTFVFIiwiSElOVCIsIkhPTEQiLCJIT1VSIiwiSE9VUlMiLCJJREVOVElUWSIsIklGIiwiSU1NRURJQVRFIiwiSU4iLCJJTkNMVURJTkciLCJJTkNMVVNJVkUiLCJJTkNSRU1FTlQiLCJJTkRFWCIsIklORElDQVRPUiIsIklORElDQVRPUlMiLCJJTkYiLCJJTkZJTklUWSIsIklOSEVSSVQiLCJJTk5FUiIsIklOT1VUIiwiSU5TRU5TSVRJVkUiLCJJTlNFUlQiLCJJTlQiLCJJTlRFR0VSIiwiSU5URUdSSVRZIiwiSU5URVJTRUNUSU9OIiwiSU5URVJWQUwiLCJJTlRPIiwiSVMiLCJJU09CSUQiLCJJU09MQVRJT04iLCJJVEVSQVRFIiwiSkFSIiwiSkFWQSIsIktFRVAiLCJLRVkiLCJMQUJFTCIsIkxBTkdVQUdFIiwiTEFSR0UiLCJMQVRFUkFMIiwiTENfQ1RZUEUiLCJMRUFESU5HIiwiTEVBVkUiLCJMRUZUIiwiTElLRSIsIkxJTktUWVBFIiwiTE4iLCJMT0NBTCIsIkxPQ0FMREFURSIsIkxPQ0FMRSIsIkxPQ0FMVElNRSIsIkxPQ0FMVElNRVNUQU1QIiwiTE9DQVRPUiIsIkxPQ0FUT1JTIiwiTE9DSyIsIkxPQ0tNQVgiLCJMT0NLU0laRSIsIkxPTkciLCJMT09QIiwiTE9XRVIiLCJNQUlOVEFJTkVEIiwiTUFUQ0giLCJNQVRFUklBTElaRUQiLCJNQVgiLCJNQVhWQUxVRSIsIk1FTUJFUiIsIk1FUkdFIiwiTUVUSE9EIiwiTUlDUk9TRUNPTkQiLCJNSUNST1NFQ09ORFMiLCJNSU4iLCJNSU5VVEUiLCJNSU5VVEVTIiwiTUlOVkFMVUUiLCJNT0QiLCJNT0RFIiwiTU9ESUZJRVMiLCJNT0RVTEUiLCJNT05USCIsIk1PTlRIUyIsIk1VTFRJU0VUIiwiTkFOIiwiTkFUSU9OQUwiLCJOQVRVUkFMIiwiTkNIQVIiLCJOQ0xPQiIsIk5FVyIsIk5FV19UQUJMRSIsIk5FWFRWQUwiLCJOTyIsIk5PQ0FDSEUiLCJOT0NZQ0xFIiwiTk9ERU5BTUUiLCJOT0RFTlVNQkVSIiwiTk9NQVhWQUxVRSIsIk5PTUlOVkFMVUUiLCJOT05FIiwiTk9PUkRFUiIsIk5PUk1BTElaRSIsIk5PUk1BTElaRUQiLCJOT1QiLCJOVUxMIiwiTlVMTElGIiwiTlVMTFMiLCJOVU1FUklDIiwiTlVNUEFSVFMiLCJPQklEIiwiT0NURVRfTEVOR1RIIiwiT0YiLCJPRkZTRVQiLCJPTEQiLCJPTERfVEFCTEUiLCJPTiIsIk9OTFkiLCJPUEVOIiwiT1BUSU1JWkFUSU9OIiwiT1BUSU1JWkUiLCJPUFRJT04iLCJPUkRFUiIsIk9VVCIsIk9VVEVSIiwiT1ZFUiIsIk9WRVJMQVBTIiwiT1ZFUkxBWSIsIk9WRVJSSURJTkciLCJQQUNLQUdFIiwiUEFEREVEIiwiUEFHRVNJWkUiLCJQQVJBTUVURVIiLCJQQVJUIiwiUEFSVElUSU9OIiwiUEFSVElUSU9ORUQiLCJQQVJUSVRJT05JTkciLCJQQVJUSVRJT05TIiwiUEFTU1dPUkQiLCJQQVRIIiwiUEVSQ0VOVElMRV9DT05UIiwiUEVSQ0VOVElMRV9ESVNDIiwiUEVSQ0VOVF9SQU5LIiwiUElFQ0VTSVpFIiwiUExBTiIsIlBPU0lUSU9OIiwiUE9XRVIiLCJQUkVDSVNJT04iLCJQUkVQQVJFIiwiUFJFVlZBTCIsIlBSSU1BUlkiLCJQUklRVFkiLCJQUklWSUxFR0VTIiwiUFJPQ0VEVVJFIiwiUFJPR1JBTSIsIlBTSUQiLCJQVUJMSUMiLCJRVUVSWSIsIlFVRVJZTk8iLCJSQU5HRSIsIlJBTksiLCJSRUFEIiwiUkVBRFMiLCJSRUFMIiwiUkVDT1ZFUlkiLCJSRUNVUlNJVkUiLCJSRUYiLCJSRUZFUkVOQ0VTIiwiUkVGRVJFTkNJTkciLCJSRUZSRVNIIiwiUkVHUl9BVkdYIiwiUkVHUl9BVkdZIiwiUkVHUl9DT1VOVCIsIlJFR1JfSU5URVJDRVBUIiwiUkVHUl9SMiIsIlJFR1JfU0xPUEUiLCJSRUdSX1NYWCIsIlJFR1JfU1hZIiwiUkVHUl9TWVkiLCJSRUxFQVNFIiwiUkVOQU1FIiwiUkVQRUFUIiwiUkVTRVQiLCJSRVNJR05BTCIsIlJFU1RBUlQiLCJSRVNUUklDVCIsIlJFU1VMVCIsIlJFU1VMVF9TRVRfTE9DQVRPUiIsIlJFVFVSTiIsIlJFVFVSTlMiLCJSRVZPS0UiLCJSSUdIVCIsIlJPTEUiLCJST0xMQkFDSyIsIlJPTExVUCIsIlJPVU5EX0NFSUxJTkciLCJST1VORF9ET1dOIiwiUk9VTkRfRkxPT1IiLCJST1VORF9IQUxGX0RPV04iLCJST1VORF9IQUxGX0VWRU4iLCJST1VORF9IQUxGX1VQIiwiUk9VTkRfVVAiLCJST1VUSU5FIiwiUk9XIiwiUk9XTlVNQkVSIiwiUk9XUyIsIlJPV1NFVCIsIlJPV19OVU1CRVIiLCJSUk4iLCJSVU4iLCJTQVZFUE9JTlQiLCJTQ0hFTUEiLCJTQ09QRSIsIlNDUkFUQ0hQQUQiLCJTQ1JPTEwiLCJTRUFSQ0giLCJTRUNPTkQiLCJTRUNPTkRTIiwiU0VDUVRZIiwiU0VDVVJJVFkiLCJTRU5TSVRJVkUiLCJTRVFVRU5DRSIsIlNFU1NJT04iLCJTRVNTSU9OX1VTRVIiLCJTSUdOQUwiLCJTSU1JTEFSIiwiU0lNUExFIiwiU01BTExJTlQiLCJTTkFOIiwiU09NRSIsIlNPVVJDRSIsIlNQRUNJRklDIiwiU1BFQ0lGSUNUWVBFIiwiU1FMIiwiU1FMRVhDRVBUSU9OIiwiU1FMSUQiLCJTUUxTVEFURSIsIlNRTFdBUk5JTkciLCJTUVJUIiwiU1RBQ0tFRCIsIlNUQU5EQVJEIiwiU1RBUlQiLCJTVEFSVElORyIsIlNUQVRFTUVOVCIsIlNUQVRJQyIsIlNUQVRNRU5UIiwiU1RBWSIsIlNURERFVl9QT1AiLCJTVERERVZfU0FNUCIsIlNUT0dST1VQIiwiU1RPUkVTIiwiU1RZTEUiLCJTVUJNVUxUSVNFVCIsIlNVQlNUUklORyIsIlNVTSIsIlNVTU1BUlkiLCJTWU1NRVRSSUMiLCJTWU5PTllNIiwiU1lTRlVOIiwiU1lTSUJNIiwiU1lTUFJPQyIsIlNZU1RFTSIsIlNZU1RFTV9VU0VSIiwiVEFCTEUiLCJUQUJMRVNBTVBMRSIsIlRBQkxFU1BBQ0UiLCJUSEVOIiwiVElNRSIsIlRJTUVTVEFNUCIsIlRJTUVaT05FX0hPVVIiLCJUSU1FWk9ORV9NSU5VVEUiLCJUTyIsIlRSQUlMSU5HIiwiVFJBTlNBQ1RJT04iLCJUUkFOU0xBVEUiLCJUUkFOU0xBVElPTiIsIlRSRUFUIiwiVFJJR0dFUiIsIlRSSU0iLCJUUlVFIiwiVFJVTkNBVEUiLCJUWVBFIiwiVUVTQ0FQRSIsIlVORE8iLCJVTklRVUUiLCJVTktOT1dOIiwiVU5ORVNUIiwiVU5USUwiLCJVUFBFUiIsIlVTQUdFIiwiVVNFUiIsIlVTSU5HIiwiVkFMSURQUk9DIiwiVkFMVUUiLCJWQVJDSEFSIiwiVkFSSUFCTEUiLCJWQVJJQU5UIiwiVkFSWUlORyIsIlZBUl9QT1AiLCJWQVJfU0FNUCIsIlZDQVQiLCJWRVJTSU9OIiwiVklFVyIsIlZPTEFUSUxFIiwiVk9MVU1FUyIsIldIRU4iLCJXSEVORVZFUiIsIldISUxFIiwiV0lEVEhfQlVDS0VUIiwiV0lORE9XIiwiV0lUSCIsIldJVEhJTiIsIldJVEhPVVQiLCJXTE0iLCJXUklURSIsIlhNTEVMRU1FTlQiLCJYTUxFWElTVFMiLCJYTUxOQU1FU1BBQ0VTIiwiWUVBUiIsIllFQVJTIl0sTz1bIkFERCIsIkFGVEVSIiwiQUxURVIgQ09MVU1OIiwiQUxURVIgVEFCTEUiLCJERUxFVEUgRlJPTSIsIkVYQ0VQVCIsIkZFVENIIEZJUlNUIiwiRlJPTSIsIkdST1VQIEJZIiwiR08iLCJIQVZJTkciLCJJTlNFUlQgSU5UTyIsIklOVEVSU0VDVCIsIkxJTUlUIiwiT1JERVIgQlkiLCJTRUxFQ1QiLCJTRVQgQ1VSUkVOVCBTQ0hFTUEiLCJTRVQgU0NIRU1BIiwiU0VUIiwiVU5JT04gQUxMIiwiVVBEQVRFIiwiVkFMVUVTIiwiV0hFUkUiXSxpPVsiQU5EIiwiQ1JPU1MgSk9JTiIsIklOTkVSIEpPSU4iLCJKT0lOIiwiTEVGVCBKT0lOIiwiTEVGVCBPVVRFUiBKT0lOIiwiT1IiLCJPVVRFUiBKT0lOIiwiUklHSFQgSk9JTiIsIlJJR0hUIE9VVEVSIEpPSU4iXSxTPXZvaWQgMCx1PWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZShFKXsoMCxUWyJkZWZhdWx0Il0pKHRoaXMsZSksdGhpcy5jZmc9RX1yZXR1cm4gZS5wcm90b3R5cGUuZm9ybWF0PWZ1bmN0aW9uKGUpe3JldHVybiBTfHwoUz1uZXcgQVsiZGVmYXVsdCJdKHtyZXNlcnZlZFdvcmRzOkkscmVzZXJ2ZWRUb3BsZXZlbFdvcmRzOk8scmVzZXJ2ZWROZXdsaW5lV29yZHM6aSxzdHJpbmdUeXBlczpbJyIiJywiJyciLCJgYCIsIltdIl0sb3BlblBhcmVuczpbIigiXSxjbG9zZVBhcmVuczpbIikiXSxpbmRleGVkUGxhY2Vob2xkZXJUeXBlczpbIj8iXSxuYW1lZFBsYWNlaG9sZGVyVHlwZXM6WyI6Il0sbGluZUNvbW1lbnRUeXBlczpbIi0tIl0sc3BlY2lhbFdvcmRDaGFyczpbIiMiLCJAIl19KSksbmV3IG9bImRlZmF1bHQiXSh0aGlzLmNmZyxTKS5mb3JtYXQoZSl9LGV9KCk7RVsiZGVmYXVsdCJdPXUsZS5leHBvcnRzPUVbImRlZmF1bHQiXX0sZnVuY3Rpb24oZSxFLHQpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBuKGUpe3JldHVybiBlJiZlLl9fZXNNb2R1bGU/ZTp7ImRlZmF1bHQiOmV9fUUuX19lc01vZHVsZT0hMDt2YXIgcj10KDEpLFQ9bihyKSxSPXQoNCksbz1uKFIpLE49dCg1KSxBPW4oTiksST1bIkFMTCIsIkFMVEVSIiwiQU5BTFlaRSIsIkFORCIsIkFOWSIsIkFSUkFZIiwiQVMiLCJBU0MiLCJCRUdJTiIsIkJFVFdFRU4iLCJCSU5BUlkiLCJCT09MRUFOIiwiQlJFQUsiLCJCVUNLRVQiLCJCVUlMRCIsIkJZIiwiQ0FMTCIsIkNBU0UiLCJDQVNUIiwiQ0xVU1RFUiIsIkNPTExBVEUiLCJDT0xMRUNUSU9OIiwiQ09NTUlUIiwiQ09OTkVDVCIsIkNPTlRJTlVFIiwiQ09SUkVMQVRFIiwiQ09WRVIiLCJDUkVBVEUiLCJEQVRBQkFTRSIsIkRBVEFTRVQiLCJEQVRBU1RPUkUiLCJERUNMQVJFIiwiREVDUkVNRU5UIiwiREVMRVRFIiwiREVSSVZFRCIsIkRFU0MiLCJERVNDUklCRSIsIkRJU1RJTkNUIiwiRE8iLCJEUk9QIiwiRUFDSCIsIkVMRU1FTlQiLCJFTFNFIiwiRU5EIiwiRVZFUlkiLCJFWENFUFQiLCJFWENMVURFIiwiRVhFQ1VURSIsIkVYSVNUUyIsIkVYUExBSU4iLCJGQUxTRSIsIkZFVENIIiwiRklSU1QiLCJGTEFUVEVOIiwiRk9SIiwiRk9SQ0UiLCJGUk9NIiwiRlVOQ1RJT04iLCJHUkFOVCIsIkdST1VQIiwiR1NJIiwiSEFWSU5HIiwiSUYiLCJJR05PUkUiLCJJTElLRSIsIklOIiwiSU5DTFVERSIsIklOQ1JFTUVOVCIsIklOREVYIiwiSU5GRVIiLCJJTkxJTkUiLCJJTk5FUiIsIklOU0VSVCIsIklOVEVSU0VDVCIsIklOVE8iLCJJUyIsIkpPSU4iLCJLRVkiLCJLRVlTIiwiS0VZU1BBQ0UiLCJLTk9XTiIsIkxBU1QiLCJMRUZUIiwiTEVUIiwiTEVUVElORyIsIkxJS0UiLCJMSU1JVCIsIkxTTSIsIk1BUCIsIk1BUFBJTkciLCJNQVRDSEVEIiwiTUFURVJJQUxJWkVEIiwiTUVSR0UiLCJNSU5VUyIsIk1JU1NJTkciLCJOQU1FU1BBQ0UiLCJORVNUIiwiTk9UIiwiTlVMTCIsIk5VTUJFUiIsIk9CSkVDVCIsIk9GRlNFVCIsIk9OIiwiT1BUSU9OIiwiT1IiLCJPUkRFUiIsIk9VVEVSIiwiT1ZFUiIsIlBBUlNFIiwiUEFSVElUSU9OIiwiUEFTU1dPUkQiLCJQQVRIIiwiUE9PTCIsIlBSRVBBUkUiLCJQUklNQVJZIiwiUFJJVkFURSIsIlBSSVZJTEVHRSIsIlBST0NFRFVSRSIsIlBVQkxJQyIsIlJBVyIsIlJFQUxNIiwiUkVEVUNFIiwiUkVOQU1FIiwiUkVUVVJOIiwiUkVUVVJOSU5HIiwiUkVWT0tFIiwiUklHSFQiLCJST0xFIiwiUk9MTEJBQ0siLCJTQVRJU0ZJRVMiLCJTQ0hFTUEiLCJTRUxFQ1QiLCJTRUxGIiwiU0VNSSIsIlNFVCIsIlNIT1ciLCJTT01FIiwiU1RBUlQiLCJTVEFUSVNUSUNTIiwiU1RSSU5HIiwiU1lTVEVNIiwiVEhFTiIsIlRPIiwiVFJBTlNBQ1RJT04iLCJUUklHR0VSIiwiVFJVRSIsIlRSVU5DQVRFIiwiVU5ERVIiLCJVTklPTiIsIlVOSVFVRSIsIlVOS05PV04iLCJVTk5FU1QiLCJVTlNFVCIsIlVQREFURSIsIlVQU0VSVCIsIlVTRSIsIlVTRVIiLCJVU0lORyIsIlZBTElEQVRFIiwiVkFMVUUiLCJWQUxVRUQiLCJWQUxVRVMiLCJWSUEiLCJWSUVXIiwiV0hFTiIsIldIRVJFIiwiV0hJTEUiLCJXSVRIIiwiV0lUSElOIiwiV09SSyIsIlhPUiJdLE89WyJERUxFVEUgRlJPTSIsIkVYQ0VQVCBBTEwiLCJFWENFUFQiLCJFWFBMQUlOIERFTEVURSBGUk9NIiwiRVhQTEFJTiBVUERBVEUiLCJFWFBMQUlOIFVQU0VSVCIsIkZST00iLCJHUk9VUCBCWSIsIkhBVklORyIsIklORkVSIiwiSU5TRVJUIElOVE8iLCJJTlRFUlNFQ1QgQUxMIiwiSU5URVJTRUNUIiwiTEVUIiwiTElNSVQiLCJNRVJHRSIsIk5FU1QiLCJPUkRFUiBCWSIsIlBSRVBBUkUiLCJTRUxFQ1QiLCJTRVQgQ1VSUkVOVCBTQ0hFTUEiLCJTRVQgU0NIRU1BIiwiU0VUIiwiVU5JT04gQUxMIiwiVU5JT04iLCJVTk5FU1QiLCJVUERBVEUiLCJVUFNFUlQiLCJVU0UgS0VZUyIsIlZBTFVFUyIsIldIRVJFIl0saT1bIkFORCIsIklOTkVSIEpPSU4iLCJKT0lOIiwiTEVGVCBKT0lOIiwiTEVGVCBPVVRFUiBKT0lOIiwiT1IiLCJPVVRFUiBKT0lOIiwiUklHSFQgSk9JTiIsIlJJR0hUIE9VVEVSIEpPSU4iLCJYT1IiXSxTPXZvaWQgMCx1PWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZShFKXsoMCxUWyJkZWZhdWx0Il0pKHRoaXMsZSksdGhpcy5jZmc9RX1yZXR1cm4gZS5wcm90b3R5cGUuZm9ybWF0PWZ1bmN0aW9uKGUpe3JldHVybiBTfHwoUz1uZXcgQVsiZGVmYXVsdCJdKHtyZXNlcnZlZFdvcmRzOkkscmVzZXJ2ZWRUb3BsZXZlbFdvcmRzOk8scmVzZXJ2ZWROZXdsaW5lV29yZHM6aSxzdHJpbmdUeXBlczpbJyIiJywiJyciLCJgYCJdLG9wZW5QYXJlbnM6WyIoIiwiWyIsInsiXSxjbG9zZVBhcmVuczpbIikiLCJdIiwifSJdLG5hbWVkUGxhY2Vob2xkZXJUeXBlczpbIiQiXSxsaW5lQ29tbWVudFR5cGVzOlsiIyIsIi0tIl19KSksbmV3IG9bImRlZmF1bHQiXSh0aGlzLmNmZyxTKS5mb3JtYXQoZSl9LGV9KCk7RVsiZGVmYXVsdCJdPXUsZS5leHBvcnRzPUVbImRlZmF1bHQiXX0sZnVuY3Rpb24oZSxFLHQpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBuKGUpe3JldHVybiBlJiZlLl9fZXNNb2R1bGU/ZTp7ImRlZmF1bHQiOmV9fUUuX19lc01vZHVsZT0hMDt2YXIgcj10KDEpLFQ9bihyKSxSPXQoNCksbz1uKFIpLE49dCg1KSxBPW4oTiksST1bIkEiLCJBQ0NFU1NJQkxFIiwiQUdFTlQiLCJBR0dSRUdBVEUiLCJBTEwiLCJBTFRFUiIsIkFOWSIsIkFSUkFZIiwiQVMiLCJBU0MiLCJBVCIsIkFUVFJJQlVURSIsIkFVVEhJRCIsIkFWRyIsIkJFVFdFRU4iLCJCRklMRV9CQVNFIiwiQklOQVJZX0lOVEVHRVIiLCJCSU5BUlkiLCJCTE9CX0JBU0UiLCJCTE9DSyIsIkJPRFkiLCJCT09MRUFOIiwiQk9USCIsIkJPVU5EIiwiQlVMSyIsIkJZIiwiQllURSIsIkMiLCJDQUxMIiwiQ0FMTElORyIsIkNBU0NBREUiLCJDQVNFIiwiQ0hBUl9CQVNFIiwiQ0hBUiIsIkNIQVJBQ1RFUiIsIkNIQVJTRVQiLCJDSEFSU0VURk9STSIsIkNIQVJTRVRJRCIsIkNIRUNLIiwiQ0xPQl9CQVNFIiwiQ0xPTkUiLCJDTE9TRSIsIkNMVVNURVIiLCJDTFVTVEVSUyIsIkNPQUxFU0NFIiwiQ09MQVVUSCIsIkNPTExFQ1QiLCJDT0xVTU5TIiwiQ09NTUVOVCIsIkNPTU1JVCIsIkNPTU1JVFRFRCIsIkNPTVBJTEVEIiwiQ09NUFJFU1MiLCJDT05ORUNUIiwiQ09OU1RBTlQiLCJDT05TVFJVQ1RPUiIsIkNPTlRFWFQiLCJDT05USU5VRSIsIkNPTlZFUlQiLCJDT1VOVCIsIkNSQVNIIiwiQ1JFQVRFIiwiQ1JFREVOVElBTCIsIkNVUlJFTlQiLCJDVVJSVkFMIiwiQ1VSU09SIiwiQ1VTVE9NREFUVU0iLCJEQU5HTElORyIsIkRBVEEiLCJEQVRFX0JBU0UiLCJEQVRFIiwiREFZIiwiREVDSU1BTCIsIkRFRkFVTFQiLCJERUZJTkUiLCJERUxFVEUiLCJERVNDIiwiREVURVJNSU5JU1RJQyIsIkRJUkVDVE9SWSIsIkRJU1RJTkNUIiwiRE8iLCJET1VCTEUiLCJEUk9QIiwiRFVSQVRJT04iLCJFTEVNRU5UIiwiRUxTSUYiLCJFTVBUWSIsIkVTQ0FQRSIsIkVYQ0VQVElPTlMiLCJFWENMVVNJVkUiLCJFWEVDVVRFIiwiRVhJU1RTIiwiRVhJVCIsIkVYVEVORFMiLCJFWFRFUk5BTCIsIkVYVFJBQ1QiLCJGQUxTRSIsIkZFVENIIiwiRklOQUwiLCJGSVJTVCIsIkZJWEVEIiwiRkxPQVQiLCJGT1IiLCJGT1JBTEwiLCJGT1JDRSIsIkZST00iLCJGVU5DVElPTiIsIkdFTkVSQUwiLCJHT1RPIiwiR1JBTlQiLCJHUk9VUCIsIkhBU0giLCJIRUFQIiwiSElEREVOIiwiSE9VUiIsIklERU5USUZJRUQiLCJJRiIsIklNTUVESUFURSIsIklOIiwiSU5DTFVESU5HIiwiSU5ERVgiLCJJTkRFWEVTIiwiSU5ESUNBVE9SIiwiSU5ESUNFUyIsIklORklOSVRFIiwiSU5TVEFOVElBQkxFIiwiSU5UIiwiSU5URUdFUiIsIklOVEVSRkFDRSIsIklOVEVSVkFMIiwiSU5UTyIsIklOVkFMSURBVEUiLCJJUyIsIklTT0xBVElPTiIsIkpBVkEiLCJMQU5HVUFHRSIsIkxBUkdFIiwiTEVBRElORyIsIkxFTkdUSCIsIkxFVkVMIiwiTElCUkFSWSIsIkxJS0UiLCJMSUtFMiIsIkxJS0U0IiwiTElLRUMiLCJMSU1JVEVEIiwiTE9DQUwiLCJMT0NLIiwiTE9ORyIsIk1BUCIsIk1BWCIsIk1BWExFTiIsIk1FTUJFUiIsIk1FUkdFIiwiTUlOIiwiTUlOVVMiLCJNSU5VVEUiLCJNTFNMQUJFTCIsIk1PRCIsIk1PREUiLCJNT05USCIsIk1VTFRJU0VUIiwiTkFNRSIsIk5BTiIsIk5BVElPTkFMIiwiTkFUSVZFIiwiTkFUVVJBTCIsIk5BVFVSQUxOIiwiTkNIQVIiLCJORVciLCJORVhUVkFMIiwiTk9DT01QUkVTUyIsIk5PQ09QWSIsIk5PVCIsIk5PV0FJVCIsIk5VTEwiLCJOVUxMSUYiLCJOVU1CRVJfQkFTRSIsIk5VTUJFUiIsIk9CSkVDVCIsIk9DSUNPTEwiLCJPQ0lEQVRFIiwiT0NJREFURVRJTUUiLCJPQ0lEVVJBVElPTiIsIk9DSUlOVEVSVkFMIiwiT0NJTE9CTE9DQVRPUiIsIk9DSU5VTUJFUiIsIk9DSVJBVyIsIk9DSVJFRiIsIk9DSVJFRkNVUlNPUiIsIk9DSVJPV0lEIiwiT0NJU1RSSU5HIiwiT0NJVFlQRSIsIk9GIiwiT0xEIiwiT04iLCJPTkxZIiwiT1BBUVVFIiwiT1BFTiIsIk9QRVJBVE9SIiwiT1BUSU9OIiwiT1JBQ0xFIiwiT1JBREFUQSIsIk9SREVSIiwiT1JHQU5JWkFUSU9OIiwiT1JMQU5ZIiwiT1JMVkFSWSIsIk9USEVSUyIsIk9VVCIsIk9WRVJMQVBTIiwiT1ZFUlJJRElORyIsIlBBQ0tBR0UiLCJQQVJBTExFTF9FTkFCTEUiLCJQQVJBTUVURVIiLCJQQVJBTUVURVJTIiwiUEFSRU5UIiwiUEFSVElUSU9OIiwiUEFTQ0FMIiwiUENURlJFRSIsIlBJUEUiLCJQSVBFTElORUQiLCJQTFNfSU5URUdFUiIsIlBMVUdHQUJMRSIsIlBPU0lUSVZFIiwiUE9TSVRJVkVOIiwiUFJBR01BIiwiUFJFQ0lTSU9OIiwiUFJJT1IiLCJQUklWQVRFIiwiUFJPQ0VEVVJFIiwiUFVCTElDIiwiUkFJU0UiLCJSQU5HRSIsIlJBVyIsIlJFQUQiLCJSRUFMIiwiUkVDT1JEIiwiUkVGIiwiUkVGRVJFTkNFIiwiUkVMRUFTRSIsIlJFTElFU19PTiIsIlJFTSIsIlJFTUFJTkRFUiIsIlJFTkFNRSIsIlJFU09VUkNFIiwiUkVTVUxUX0NBQ0hFIiwiUkVTVUxUIiwiUkVUVVJOIiwiUkVUVVJOSU5HIiwiUkVWRVJTRSIsIlJFVk9LRSIsIlJPTExCQUNLIiwiUk9XIiwiUk9XSUQiLCJST1dOVU0iLCJST1dUWVBFIiwiU0FNUExFIiwiU0FWRSIsIlNBVkVQT0lOVCIsIlNCMSIsIlNCMiIsIlNCNCIsIlNFQ09ORCIsIlNFR01FTlQiLCJTRUxGIiwiU0VQQVJBVEUiLCJTRVFVRU5DRSIsIlNFUklBTElaQUJMRSIsIlNIQVJFIiwiU0hPUlQiLCJTSVpFX1QiLCJTSVpFIiwiU01BTExJTlQiLCJTT01FIiwiU1BBQ0UiLCJTUEFSU0UiLCJTUUwiLCJTUUxDT0RFIiwiU1FMREFUQSIsIlNRTEVSUk0iLCJTUUxOQU1FIiwiU1FMU1RBVEUiLCJTVEFOREFSRCIsIlNUQVJUIiwiU1RBVElDIiwiU1REREVWIiwiU1RPUkVEIiwiU1RSSU5HIiwiU1RSVUNUIiwiU1RZTEUiLCJTVUJNVUxUSVNFVCIsIlNVQlBBUlRJVElPTiIsIlNVQlNUSVRVVEFCTEUiLCJTVUJUWVBFIiwiU1VDQ0VTU0ZVTCIsIlNVTSIsIlNZTk9OWU0iLCJTWVNEQVRFIiwiVEFCQVVUSCIsIlRBQkxFIiwiVERPIiwiVEhFIiwiVEhFTiIsIlRJTUUiLCJUSU1FU1RBTVAiLCJUSU1FWk9ORV9BQkJSIiwiVElNRVpPTkVfSE9VUiIsIlRJTUVaT05FX01JTlVURSIsIlRJTUVaT05FX1JFR0lPTiIsIlRPIiwiVFJBSUxJTkciLCJUUkFOU0FDVElPTiIsIlRSQU5TQUNUSU9OQUwiLCJUUklHR0VSIiwiVFJVRSIsIlRSVVNURUQiLCJUWVBFIiwiVUIxIiwiVUIyIiwiVUI0IiwiVUlEIiwiVU5ERVIiLCJVTklRVUUiLCJVTlBMVUciLCJVTlNJR05FRCIsIlVOVFJVU1RFRCIsIlVTRSIsIlVTRVIiLCJVU0lORyIsIlZBTElEQVRFIiwiVkFMSVNUIiwiVkFMVUUiLCJWQVJDSEFSIiwiVkFSQ0hBUjIiLCJWQVJJQUJMRSIsIlZBUklBTkNFIiwiVkFSUkFZIiwiVkFSWUlORyIsIlZJRVciLCJWSUVXUyIsIlZPSUQiLCJXSEVORVZFUiIsIldISUxFIiwiV0lUSCIsIldPUksiLCJXUkFQUEVEIiwiV1JJVEUiLCJZRUFSIiwiWk9ORSJdLE89WyJBREQiLCJBTFRFUiBDT0xVTU4iLCJBTFRFUiBUQUJMRSIsIkJFR0lOIiwiQ09OTkVDVCBCWSIsIkRFQ0xBUkUiLCJERUxFVEUgRlJPTSIsIkRFTEVURSIsIkVORCIsIkVYQ0VQVCIsIkVYQ0VQVElPTiIsIkZFVENIIEZJUlNUIiwiRlJPTSIsIkdST1VQIEJZIiwiSEFWSU5HIiwiSU5TRVJUIElOVE8iLCJJTlNFUlQiLCJJTlRFUlNFQ1QiLCJMSU1JVCIsIkxPT1AiLCJNT0RJRlkiLCJPUkRFUiBCWSIsIlNFTEVDVCIsIlNFVCBDVVJSRU5UIFNDSEVNQSIsIlNFVCBTQ0hFTUEiLCJTRVQiLCJTVEFSVCBXSVRIIiwiVU5JT04gQUxMIiwiVU5JT04iLCJVUERBVEUiLCJWQUxVRVMiLCJXSEVSRSJdLGk9WyJBTkQiLCJDUk9TUyBBUFBMWSIsIkNST1NTIEpPSU4iLCJFTFNFIiwiRU5EIiwiSU5ORVIgSk9JTiIsIkpPSU4iLCJMRUZUIEpPSU4iLCJMRUZUIE9VVEVSIEpPSU4iLCJPUiIsIk9VVEVSIEFQUExZIiwiT1VURVIgSk9JTiIsIlJJR0hUIEpPSU4iLCJSSUdIVCBPVVRFUiBKT0lOIiwiV0hFTiIsIlhPUiJdLFM9dm9pZCAwLHU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKEUpeygwLFRbImRlZmF1bHQiXSkodGhpcyxlKSx0aGlzLmNmZz1FfXJldHVybiBlLnByb3RvdHlwZS5mb3JtYXQ9ZnVuY3Rpb24oZSl7cmV0dXJuIFN8fChTPW5ldyBBWyJkZWZhdWx0Il0oe3Jlc2VydmVkV29yZHM6SSxyZXNlcnZlZFRvcGxldmVsV29yZHM6TyxyZXNlcnZlZE5ld2xpbmVXb3JkczppLHN0cmluZ1R5cGVzOlsnIiInLCJOJyciLCInJyIsImBgIl0sb3BlblBhcmVuczpbIigiLCJDQVNFIl0sY2xvc2VQYXJlbnM6WyIpIiwiRU5EIl0saW5kZXhlZFBsYWNlaG9sZGVyVHlwZXM6WyI/Il0sbmFtZWRQbGFjZWhvbGRlclR5cGVzOlsiOiJdLGxpbmVDb21tZW50VHlwZXM6WyItLSJdLHNwZWNpYWxXb3JkQ2hhcnM6WyJfIiwiJCIsIiMiLCIuIiwiQCJdfSkpLG5ldyBvWyJkZWZhdWx0Il0odGhpcy5jZmcsUykuZm9ybWF0KGUpfSxlfSgpO0VbImRlZmF1bHQiXT11LGUuZXhwb3J0cz1FWyJkZWZhdWx0Il19LGZ1bmN0aW9uKGUsRSx0KXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gbihlKXtyZXR1cm4gZSYmZS5fX2VzTW9kdWxlP2U6eyJkZWZhdWx0IjplfX1FLl9fZXNNb2R1bGU9ITA7dmFyIHI9dCgxKSxUPW4ociksUj10KDQpLG89bihSKSxOPXQoNSksQT1uKE4pLEk9WyJBQ0NFU1NJQkxFIiwiQUNUSU9OIiwiQUdBSU5TVCIsIkFHR1JFR0FURSIsIkFMR09SSVRITSIsIkFMTCIsIkFMVEVSIiwiQU5BTFlTRSIsIkFOQUxZWkUiLCJBUyIsIkFTQyIsIkFVVE9DT01NSVQiLCJBVVRPX0lOQ1JFTUVOVCIsIkJBQ0tVUCIsIkJFR0lOIiwiQkVUV0VFTiIsIkJJTkxPRyIsIkJPVEgiLCJDQVNDQURFIiwiQ0FTRSIsIkNIQU5HRSIsIkNIQU5HRUQiLCJDSEFSQUNURVIgU0VUIiwiQ0hBUlNFVCIsIkNIRUNLIiwiQ0hFQ0tTVU0iLCJDT0xMQVRFIiwiQ09MTEFUSU9OIiwiQ09MVU1OIiwiQ09MVU1OUyIsIkNPTU1FTlQiLCJDT01NSVQiLCJDT01NSVRURUQiLCJDT01QUkVTU0VEIiwiQ09OQ1VSUkVOVCIsIkNPTlNUUkFJTlQiLCJDT05UQUlOUyIsIkNPTlZFUlQiLCJDUkVBVEUiLCJDUk9TUyIsIkNVUlJFTlRfVElNRVNUQU1QIiwiREFUQUJBU0UiLCJEQVRBQkFTRVMiLCJEQVkiLCJEQVlfSE9VUiIsIkRBWV9NSU5VVEUiLCJEQVlfU0VDT05EIiwiREVGQVVMVCIsIkRFRklORVIiLCJERUxBWUVEIiwiREVMRVRFIiwiREVTQyIsIkRFU0NSSUJFIiwiREVURVJNSU5JU1RJQyIsIkRJU1RJTkNUIiwiRElTVElOQ1RST1ciLCJESVYiLCJETyIsIkRST1AiLCJEVU1QRklMRSIsIkRVUExJQ0FURSIsIkRZTkFNSUMiLCJFTFNFIiwiRU5DTE9TRUQiLCJFTkQiLCJFTkdJTkUiLCJFTkdJTkVTIiwiRU5HSU5FX1RZUEUiLCJFU0NBUEUiLCJFU0NBUEVEIiwiRVZFTlRTIiwiRVhFQyIsIkVYRUNVVEUiLCJFWElTVFMiLCJFWFBMQUlOIiwiRVhURU5ERUQiLCJGQVNUIiwiRkVUQ0giLCJGSUVMRFMiLCJGSUxFIiwiRklSU1QiLCJGSVhFRCIsIkZMVVNIIiwiRk9SIiwiRk9SQ0UiLCJGT1JFSUdOIiwiRlVMTCIsIkZVTExURVhUIiwiRlVOQ1RJT04iLCJHTE9CQUwiLCJHUkFOVCIsIkdSQU5UUyIsIkdST1VQX0NPTkNBVCIsIkhFQVAiLCJISUdIX1BSSU9SSVRZIiwiSE9TVFMiLCJIT1VSIiwiSE9VUl9NSU5VVEUiLCJIT1VSX1NFQ09ORCIsIklERU5USUZJRUQiLCJJRiIsIklGTlVMTCIsIklHTk9SRSIsIklOIiwiSU5ERVgiLCJJTkRFWEVTIiwiSU5GSUxFIiwiSU5TRVJUIiwiSU5TRVJUX0lEIiwiSU5TRVJUX01FVEhPRCIsIklOVEVSVkFMIiwiSU5UTyIsIklOVk9LRVIiLCJJUyIsIklTT0xBVElPTiIsIktFWSIsIktFWVMiLCJLSUxMIiwiTEFTVF9JTlNFUlRfSUQiLCJMRUFESU5HIiwiTEVWRUwiLCJMSUtFIiwiTElORUFSIiwiTElORVMiLCJMT0FEIiwiTE9DQUwiLCJMT0NLIiwiTE9DS1MiLCJMT0dTIiwiTE9XX1BSSU9SSVRZIiwiTUFSSUEiLCJNQVNURVIiLCJNQVNURVJfQ09OTkVDVF9SRVRSWSIsIk1BU1RFUl9IT1NUIiwiTUFTVEVSX0xPR19GSUxFIiwiTUFUQ0giLCJNQVhfQ09OTkVDVElPTlNfUEVSX0hPVVIiLCJNQVhfUVVFUklFU19QRVJfSE9VUiIsIk1BWF9ST1dTIiwiTUFYX1VQREFURVNfUEVSX0hPVVIiLCJNQVhfVVNFUl9DT05ORUNUSU9OUyIsIk1FRElVTSIsIk1FUkdFIiwiTUlOVVRFIiwiTUlOVVRFX1NFQ09ORCIsIk1JTl9ST1dTIiwiTU9ERSIsIk1PRElGWSIsIk1PTlRIIiwiTVJHX01ZSVNBTSIsIk1ZSVNBTSIsIk5BTUVTIiwiTkFUVVJBTCIsIk5PVCIsIk5PVygpIiwiTlVMTCIsIk9GRlNFVCIsIk9OIERFTEVURSIsIk9OIFVQREFURSIsIk9OIiwiT05MWSIsIk9QRU4iLCJPUFRJTUlaRSIsIk9QVElPTiIsIk9QVElPTkFMTFkiLCJPVVRGSUxFIiwiUEFDS19LRVlTIiwiUEFHRSIsIlBBUlRJQUwiLCJQQVJUSVRJT04iLCJQQVJUSVRJT05TIiwiUEFTU1dPUkQiLCJQUklNQVJZIiwiUFJJVklMRUdFUyIsIlBST0NFRFVSRSIsIlBST0NFU1MiLCJQUk9DRVNTTElTVCIsIlBVUkdFIiwiUVVJQ0siLCJSQUlEMCIsIlJBSURfQ0hVTktTIiwiUkFJRF9DSFVOS1NJWkUiLCJSQUlEX1RZUEUiLCJSQU5HRSIsIlJFQUQiLCJSRUFEX09OTFkiLCJSRUFEX1dSSVRFIiwiUkVGRVJFTkNFUyIsIlJFR0VYUCIsIlJFTE9BRCIsIlJFTkFNRSIsIlJFUEFJUiIsIlJFUEVBVEFCTEUiLCJSRVBMQUNFIiwiUkVQTElDQVRJT04iLCJSRVNFVCIsIlJFU1RPUkUiLCJSRVNUUklDVCIsIlJFVFVSTiIsIlJFVFVSTlMiLCJSRVZPS0UiLCJSTElLRSIsIlJPTExCQUNLIiwiUk9XIiwiUk9XUyIsIlJPV19GT1JNQVQiLCJTRUNPTkQiLCJTRUNVUklUWSIsIlNFUEFSQVRPUiIsIlNFUklBTElaQUJMRSIsIlNFU1NJT04iLCJTSEFSRSIsIlNIT1ciLCJTSFVURE9XTiIsIlNMQVZFIiwiU09OQU1FIiwiU09VTkRTIiwiU1FMIiwiU1FMX0FVVE9fSVNfTlVMTCIsIlNRTF9CSUdfUkVTVUxUIiwiU1FMX0JJR19TRUxFQ1RTIiwiU1FMX0JJR19UQUJMRVMiLCJTUUxfQlVGRkVSX1JFU1VMVCIsIlNRTF9DQUNIRSIsIlNRTF9DQUxDX0ZPVU5EX1JPV1MiLCJTUUxfTE9HX0JJTiIsIlNRTF9MT0dfT0ZGIiwiU1FMX0xPR19VUERBVEUiLCJTUUxfTE9XX1BSSU9SSVRZX1VQREFURVMiLCJTUUxfTUFYX0pPSU5fU0laRSIsIlNRTF9OT19DQUNIRSIsIlNRTF9RVU9URV9TSE9XX0NSRUFURSIsIlNRTF9TQUZFX1VQREFURVMiLCJTUUxfU0VMRUNUX0xJTUlUIiwiU1FMX1NMQVZFX1NLSVBfQ09VTlRFUiIsIlNRTF9TTUFMTF9SRVNVTFQiLCJTUUxfV0FSTklOR1MiLCJTVEFSVCIsIlNUQVJUSU5HIiwiU1RBVFVTIiwiU1RPUCIsIlNUT1JBR0UiLCJTVFJBSUdIVF9KT0lOIiwiU1RSSU5HIiwiU1RSSVBFRCIsIlNVUEVSIiwiVEFCTEUiLCJUQUJMRVMiLCJURU1QT1JBUlkiLCJURVJNSU5BVEVEIiwiVEhFTiIsIlRPIiwiVFJBSUxJTkciLCJUUkFOU0FDVElPTkFMIiwiVFJVRSIsIlRSVU5DQVRFIiwiVFlQRSIsIlRZUEVTIiwiVU5DT01NSVRURUQiLCJVTklRVUUiLCJVTkxPQ0siLCJVTlNJR05FRCIsIlVTQUdFIiwiVVNFIiwiVVNJTkciLCJWQVJJQUJMRVMiLCJWSUVXIiwiV0hFTiIsIldJVEgiLCJXT1JLIiwiV1JJVEUiLCJZRUFSX01PTlRIIl0sTz1bIkFERCIsIkFGVEVSIiwiQUxURVIgQ09MVU1OIiwiQUxURVIgVEFCTEUiLCJERUxFVEUgRlJPTSIsIkVYQ0VQVCIsIkZFVENIIEZJUlNUIiwiRlJPTSIsIkdST1VQIEJZIiwiR08iLCJIQVZJTkciLCJJTlNFUlQgSU5UTyIsIklOU0VSVCIsIklOVEVSU0VDVCIsIkxJTUlUIiwiTU9ESUZZIiwiT1JERVIgQlkiLCJTRUxFQ1QiLCJTRVQgQ1VSUkVOVCBTQ0hFTUEiLCJTRVQgU0NIRU1BIiwiU0VUIiwiVU5JT04gQUxMIiwiVU5JT04iLCJVUERBVEUiLCJWQUxVRVMiLCJXSEVSRSJdLGk9WyJBTkQiLCJDUk9TUyBBUFBMWSIsIkNST1NTIEpPSU4iLCJFTFNFIiwiSU5ORVIgSk9JTiIsIkpPSU4iLCJMRUZUIEpPSU4iLCJMRUZUIE9VVEVSIEpPSU4iLCJPUiIsIk9VVEVSIEFQUExZIiwiT1VURVIgSk9JTiIsIlJJR0hUIEpPSU4iLCJSSUdIVCBPVVRFUiBKT0lOIiwiV0hFTiIsIlhPUiJdLFM9dm9pZCAwLHU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKEUpeygwLFRbImRlZmF1bHQiXSkodGhpcyxlKSx0aGlzLmNmZz1FfXJldHVybiBlLnByb3RvdHlwZS5mb3JtYXQ9ZnVuY3Rpb24oZSl7cmV0dXJuIFN8fChTPW5ldyBBWyJkZWZhdWx0Il0oe3Jlc2VydmVkV29yZHM6SSxyZXNlcnZlZFRvcGxldmVsV29yZHM6TyxyZXNlcnZlZE5ld2xpbmVXb3JkczppLHN0cmluZ1R5cGVzOlsnIiInLCJOJyciLCInJyIsImBgIiwiW10iXSxvcGVuUGFyZW5zOlsiKCIsIkNBU0UiXSxjbG9zZVBhcmVuczpbIikiLCJFTkQiXSxpbmRleGVkUGxhY2Vob2xkZXJUeXBlczpbIj8iXSxuYW1lZFBsYWNlaG9sZGVyVHlwZXM6WyJAIiwiOiJdLGxpbmVDb21tZW50VHlwZXM6WyIjIiwiLS0iXX0pKSxuZXcgb1siZGVmYXVsdCJdKHRoaXMuY2ZnLFMpLmZvcm1hdChlKX0sZX0oKTtFWyJkZWZhdWx0Il09dSxlLmV4cG9ydHM9RVsiZGVmYXVsdCJdfSxmdW5jdGlvbihlLEUsdCl7dmFyIG49dCgzKSxyPXQoMiksVD1uKHIsIkRhdGFWaWV3Iik7ZS5leHBvcnRzPVR9LGZ1bmN0aW9uKGUsRSx0KXt2YXIgbj10KDMpLHI9dCgyKSxUPW4ociwiTWFwIik7ZS5leHBvcnRzPVR9LGZ1bmN0aW9uKGUsRSx0KXt2YXIgbj10KDMpLHI9dCgyKSxUPW4ociwiUHJvbWlzZSIpO2UuZXhwb3J0cz1UfSxmdW5jdGlvbihlLEUsdCl7dmFyIG49dCgzKSxyPXQoMiksVD1uKHIsIlNldCIpO2UuZXhwb3J0cz1UfSxmdW5jdGlvbihlLEUsdCl7dmFyIG49dCgyKSxyPW4uU3ltYm9sO2UuZXhwb3J0cz1yfSxmdW5jdGlvbihlLEUsdCl7dmFyIG49dCgzKSxyPXQoMiksVD1uKHIsIldlYWtNYXAiKTtlLmV4cG9ydHM9VH0sZnVuY3Rpb24oZSxFKXtmdW5jdGlvbiB0KGUpe3JldHVybiBlLnNwbGl0KCIiKX1lLmV4cG9ydHM9dH0sZnVuY3Rpb24oZSxFKXtmdW5jdGlvbiB0KGUsRSx0LG4pe2Zvcih2YXIgcj1lLmxlbmd0aCxUPXQrKG4/MTotMSk7bj9ULS06KytUPHI7KWlmKEUoZVtUXSxULGUpKXJldHVybiBUOwpyZXR1cm4tMX1lLmV4cG9ydHM9dH0sZnVuY3Rpb24oZSxFKXtmdW5jdGlvbiB0KGUpe3JldHVybiByLmNhbGwoZSl9dmFyIG49T2JqZWN0LnByb3RvdHlwZSxyPW4udG9TdHJpbmc7ZS5leHBvcnRzPXR9LGZ1bmN0aW9uKGUsRSx0KXtmdW5jdGlvbiBuKGUsRSx0KXtyZXR1cm4gRT09PUU/UihlLEUsdCk6cihlLFQsdCl9dmFyIHI9dCgyOSksVD10KDMyKSxSPXQoNDkpO2UuZXhwb3J0cz1ufSxmdW5jdGlvbihlLEUpe2Z1bmN0aW9uIHQoZSl7cmV0dXJuIGUhPT1lfWUuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUsdCl7ZnVuY3Rpb24gbihlKXtpZighUihlKXx8VChlKSlyZXR1cm4hMTt2YXIgRT1yKGUpP3U6QTtyZXR1cm4gRS50ZXN0KG8oZSkpfXZhciByPXQoMTIpLFQ9dCg0NSksUj10KDYpLG89dCgxMSksTj0vW1xcXiQuKis/KClbXF17fXxdL2csQT0vXlxbb2JqZWN0IC4rP0NvbnN0cnVjdG9yXF0kLyxJPUZ1bmN0aW9uLnByb3RvdHlwZSxPPU9iamVjdC5wcm90b3R5cGUsaT1JLnRvU3RyaW5nLFM9Ty5oYXNPd25Qcm9wZXJ0eSx1PVJlZ0V4cCgiXiIraS5jYWxsKFMpLnJlcGxhY2UoTiwiXFwkJiIpLnJlcGxhY2UoL2hhc093blByb3BlcnR5fChmdW5jdGlvbikuKj8oPz1cXFwoKXwgZm9yIC4rPyg/PVxcXF0pL2csIiQxLio/IikrIiQiKTtlLmV4cG9ydHM9bn0sZnVuY3Rpb24oZSxFKXtmdW5jdGlvbiB0KGUsRSl7dmFyIHQ9IiI7aWYoIWV8fDE+RXx8RT5uKXJldHVybiB0O2RvIEUlMiYmKHQrPWUpLEU9cihFLzIpLEUmJihlKz1lKTt3aGlsZShFKTtyZXR1cm4gdH12YXIgbj05MDA3MTk5MjU0NzQwOTkxLHI9TWF0aC5mbG9vcjtlLmV4cG9ydHM9dH0sZnVuY3Rpb24oZSxFKXtmdW5jdGlvbiB0KGUsRSx0KXt2YXIgbj0tMSxyPWUubGVuZ3RoOzA+RSYmKEU9LUU+cj8wOnIrRSksdD10PnI/cjp0LDA+dCYmKHQrPXIpLHI9RT50PzA6dC1FPj4+MCxFPj4+PTA7Zm9yKHZhciBUPUFycmF5KHIpOysrbjxyOylUW25dPWVbbitFXTtyZXR1cm4gVH1lLmV4cG9ydHM9dH0sZnVuY3Rpb24oZSxFLHQpe2Z1bmN0aW9uIG4oZSxFLHQpe3ZhciBuPWUubGVuZ3RoO3JldHVybiB0PXZvaWQgMD09PXQ/bjp0LEV8fG4+dD9yKGUsRSx0KTplfXZhciByPXQoMzUpO2UuZXhwb3J0cz1ufSxmdW5jdGlvbihlLEUsdCl7ZnVuY3Rpb24gbihlLEUpe2Zvcih2YXIgdD1lLmxlbmd0aDt0LS0mJnIoRSxlW3RdLDApPi0xOyk7cmV0dXJuIHR9dmFyIHI9dCgzMSk7ZS5leHBvcnRzPW59LGZ1bmN0aW9uKGUsRSx0KXt2YXIgbj10KDIpLHI9blsiX19jb3JlLWpzX3NoYXJlZF9fIl07ZS5leHBvcnRzPXJ9LGZ1bmN0aW9uKGUsRSl7KGZ1bmN0aW9uKEUpe3ZhciB0PSJvYmplY3QiPT10eXBlb2YgRSYmRSYmRS5PYmplY3Q9PT1PYmplY3QmJkU7ZS5leHBvcnRzPXR9KS5jYWxsKEUsZnVuY3Rpb24oKXtyZXR1cm4gdGhpc30oKSl9LGZ1bmN0aW9uKGUsRSx0KXt2YXIgbj10KDIyKSxyPXQoMjMpLFQ9dCgyNCksUj10KDI1KSxvPXQoMjcpLE49dCgzMCksQT10KDExKSxJPSJbb2JqZWN0IE1hcF0iLE89IltvYmplY3QgT2JqZWN0XSIsaT0iW29iamVjdCBQcm9taXNlXSIsUz0iW29iamVjdCBTZXRdIix1PSJbb2JqZWN0IFdlYWtNYXBdIixMPSJbb2JqZWN0IERhdGFWaWV3XSIsQz1PYmplY3QucHJvdG90eXBlLHM9Qy50b1N0cmluZyxhPUEobiksZj1BKHIpLGM9QShUKSxwPUEoUiksbD1BKG8pLEQ9TjsobiYmRChuZXcgbihuZXcgQXJyYXlCdWZmZXIoMSkpKSE9THx8ciYmRChuZXcgcikhPUl8fFQmJkQoVC5yZXNvbHZlKCkpIT1pfHxSJiZEKG5ldyBSKSE9U3x8byYmRChuZXcgbykhPXUpJiYoRD1mdW5jdGlvbihlKXt2YXIgRT1zLmNhbGwoZSksdD1FPT1PP2UuY29uc3RydWN0b3I6dm9pZCAwLG49dD9BKHQpOnZvaWQgMDtpZihuKXN3aXRjaChuKXtjYXNlIGE6cmV0dXJuIEw7Y2FzZSBmOnJldHVybiBJO2Nhc2UgYzpyZXR1cm4gaTtjYXNlIHA6cmV0dXJuIFM7Y2FzZSBsOnJldHVybiB1fXJldHVybiBFfSksZS5leHBvcnRzPUR9LGZ1bmN0aW9uKGUsRSl7ZnVuY3Rpb24gdChlLEUpe3JldHVybiBudWxsPT1lP3ZvaWQgMDplW0VdfWUuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUpe2Z1bmN0aW9uIHQoZSl7cmV0dXJuIE4udGVzdChlKX12YXIgbj0iXFx1ZDgwMC1cXHVkZmZmIixyPSJcXHUwMzAwLVxcdTAzNmZcXHVmZTIwLVxcdWZlMjMiLFQ9IlxcdTIwZDAtXFx1MjBmMCIsUj0iXFx1ZmUwZVxcdWZlMGYiLG89IlxcdTIwMGQiLE49UmVnRXhwKCJbIitvK24rcitUK1IrIl0iKTtlLmV4cG9ydHM9dH0sZnVuY3Rpb24oZSxFKXtmdW5jdGlvbiB0KGUsRSl7cmV0dXJuIEU9bnVsbD09RT9uOkUsISFFJiYoIm51bWJlciI9PXR5cGVvZiBlfHxyLnRlc3QoZSkpJiZlPi0xJiZlJTE9PTAmJkU+ZX12YXIgbj05MDA3MTk5MjU0NzQwOTkxLHI9L14oPzowfFsxLTldXGQqKSQvO2UuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUsdCl7ZnVuY3Rpb24gbihlLEUsdCl7aWYoIW8odCkpcmV0dXJuITE7dmFyIG49dHlwZW9mIEU7cmV0dXJuISEoIm51bWJlciI9PW4/VCh0KSYmUihFLHQubGVuZ3RoKToic3RyaW5nIj09biYmRSBpbiB0KSYmcih0W0VdLGUpfXZhciByPXQoNTIpLFQ9dCg4KSxSPXQoNDMpLG89dCg2KTtlLmV4cG9ydHM9bn0sZnVuY3Rpb24oZSxFLHQpe2Z1bmN0aW9uIG4oZSl7cmV0dXJuISFUJiZUIGluIGV9dmFyIHI9dCgzOCksVD1mdW5jdGlvbigpe3ZhciBlPS9bXi5dKyQvLmV4ZWMociYmci5rZXlzJiZyLmtleXMuSUVfUFJPVE98fCIiKTtyZXR1cm4gZT8iU3ltYm9sKHNyYylfMS4iK2U6IiJ9KCk7ZS5leHBvcnRzPW59LGZ1bmN0aW9uKGUsRSl7ZnVuY3Rpb24gdChlKXt2YXIgRT1lJiZlLmNvbnN0cnVjdG9yLHQ9ImZ1bmN0aW9uIj09dHlwZW9mIEUmJkUucHJvdG90eXBlfHxuO3JldHVybiBlPT09dH12YXIgbj1PYmplY3QucHJvdG90eXBlO2UuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUsdCl7dmFyIG49dCg0OCkscj1uKE9iamVjdC5rZXlzLE9iamVjdCk7ZS5leHBvcnRzPXJ9LGZ1bmN0aW9uKGUsRSl7ZnVuY3Rpb24gdChlLEUpe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4gZShFKHQpKX19ZS5leHBvcnRzPXR9LGZ1bmN0aW9uKGUsRSl7ZnVuY3Rpb24gdChlLEUsdCl7Zm9yKHZhciBuPXQtMSxyPWUubGVuZ3RoOysrbjxyOylpZihlW25dPT09RSlyZXR1cm4gbjtyZXR1cm4tMX1lLmV4cG9ydHM9dH0sZnVuY3Rpb24oZSxFLHQpe2Z1bmN0aW9uIG4oZSl7cmV0dXJuIFQoZSk/UihlKTpyKGUpfXZhciByPXQoMjgpLFQ9dCg0MiksUj10KDUxKTtlLmV4cG9ydHM9bn0sZnVuY3Rpb24oZSxFKXtmdW5jdGlvbiB0KGUpe3JldHVybiBlLm1hdGNoKGMpfHxbXX12YXIgbj0iXFx1ZDgwMC1cXHVkZmZmIixyPSJcXHUwMzAwLVxcdTAzNmZcXHVmZTIwLVxcdWZlMjMiLFQ9IlxcdTIwZDAtXFx1MjBmMCIsUj0iXFx1ZmUwZVxcdWZlMGYiLG89IlsiK24rIl0iLE49IlsiK3IrVCsiXSIsQT0iXFx1ZDgzY1tcXHVkZmZiLVxcdWRmZmZdIixJPSIoPzoiK04rInwiK0ErIikiLE89IlteIituKyJdIixpPSIoPzpcXHVkODNjW1xcdWRkZTYtXFx1ZGRmZl0pezJ9IixTPSJbXFx1ZDgwMC1cXHVkYmZmXVtcXHVkYzAwLVxcdWRmZmZdIix1PSJcXHUyMDBkIixMPUkrIj8iLEM9IlsiK1IrIl0/IixzPSIoPzoiK3UrIig/OiIrW08saSxTXS5qb2luKCJ8IikrIikiK0MrTCsiKSoiLGE9QytMK3MsZj0iKD86IitbTytOKyI/IixOLGksUyxvXS5qb2luKCJ8IikrIikiLGM9UmVnRXhwKEErIig/PSIrQSsiKXwiK2YrYSwiZyIpO2UuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUpe2Z1bmN0aW9uIHQoZSxFKXtyZXR1cm4gZT09PUV8fGUhPT1lJiZFIT09RX1lLmV4cG9ydHM9dH0sZnVuY3Rpb24oZSxFLHQpe2Z1bmN0aW9uIG4oZSl7cmV0dXJuIGU9cihlKSxlJiZSLnRlc3QoZSk/ZS5yZXBsYWNlKFQsIlxcJCYiKTplfXZhciByPXQoOSksVD0vW1xcXiQuKis/KClbXF17fXxdL2csUj1SZWdFeHAoVC5zb3VyY2UpO2UuZXhwb3J0cz1ufSxmdW5jdGlvbihlLEUsdCl7ZnVuY3Rpb24gbihlKXtyZXR1cm4gcihlKSYmby5jYWxsKGUsImNhbGxlZSIpJiYoIUEuY2FsbChlLCJjYWxsZWUiKXx8Ti5jYWxsKGUpPT1UKX12YXIgcj10KDU2KSxUPSJbb2JqZWN0IEFyZ3VtZW50c10iLFI9T2JqZWN0LnByb3RvdHlwZSxvPVIuaGFzT3duUHJvcGVydHksTj1SLnRvU3RyaW5nLEE9Ui5wcm9wZXJ0eUlzRW51bWVyYWJsZTtlLmV4cG9ydHM9bn0sZnVuY3Rpb24oZSxFKXt2YXIgdD1BcnJheS5pc0FycmF5O2UuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUsdCl7ZnVuY3Rpb24gbihlKXtyZXR1cm4gVChlKSYmcihlKX12YXIgcj10KDgpLFQ9dCgxMyk7ZS5leHBvcnRzPW59LGZ1bmN0aW9uKGUsRSx0KXsoZnVuY3Rpb24oZSl7dmFyIG49dCgyKSxyPXQoNjIpLFQ9Im9iamVjdCI9PXR5cGVvZiBFJiZFJiYhRS5ub2RlVHlwZSYmRSxSPVQmJiJvYmplY3QiPT10eXBlb2YgZSYmZSYmIWUubm9kZVR5cGUmJmUsbz1SJiZSLmV4cG9ydHM9PT1ULE49bz9uLkJ1ZmZlcjp2b2lkIDAsQT1OP04uaXNCdWZmZXI6dm9pZCAwLEk9QXx8cjtlLmV4cG9ydHM9SX0pLmNhbGwoRSx0KDY3KShlKSl9LGZ1bmN0aW9uKGUsRSx0KXtmdW5jdGlvbiBuKGUpe2lmKG8oZSkmJihSKGUpfHwic3RyaW5nIj09dHlwZW9mIGV8fCJmdW5jdGlvbiI9PXR5cGVvZiBlLnNwbGljZXx8TihlKXx8VChlKSkpcmV0dXJuIWUubGVuZ3RoO3ZhciBFPXIoZSk7aWYoRT09T3x8RT09aSlyZXR1cm4hZS5zaXplO2lmKEEoZSkpcmV0dXJuIUkoZSkubGVuZ3RoO2Zvcih2YXIgdCBpbiBlKWlmKHUuY2FsbChlLHQpKXJldHVybiExO3JldHVybiEwfXZhciByPXQoNDApLFQ9dCg1NCksUj10KDU1KSxvPXQoOCksTj10KDU3KSxBPXQoNDYpLEk9dCg0NyksTz0iW29iamVjdCBNYXBdIixpPSJbb2JqZWN0IFNldF0iLFM9T2JqZWN0LnByb3RvdHlwZSx1PVMuaGFzT3duUHJvcGVydHk7ZS5leHBvcnRzPW59LGZ1bmN0aW9uKGUsRSl7ZnVuY3Rpb24gdChlKXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIGUmJmU+LTEmJmUlMT09MCYmbj49ZX12YXIgbj05MDA3MTk5MjU0NzQwOTkxO2UuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUpe2Z1bmN0aW9uIHQoZSl7dmFyIEU9ZT9lLmxlbmd0aDowO3JldHVybiBFP2VbRS0xXTp2b2lkIDB9ZS5leHBvcnRzPXR9LGZ1bmN0aW9uKGUsRSx0KXtmdW5jdGlvbiBuKGUsRSx0KXtyZXR1cm4gRT0odD9UKGUsRSx0KTp2b2lkIDA9PT1FKT8xOlIoRSkscihvKGUpLEUpfXZhciByPXQoMzQpLFQ9dCg0NCksUj10KDY0KSxvPXQoOSk7ZS5leHBvcnRzPW59LGZ1bmN0aW9uKGUsRSl7ZnVuY3Rpb24gdCgpe3JldHVybiExfWUuZXhwb3J0cz10fSxmdW5jdGlvbihlLEUsdCl7ZnVuY3Rpb24gbihlKXtpZighZSlyZXR1cm4gMD09PWU/ZTowO2lmKGU9cihlKSxlPT09VHx8ZT09PS1UKXt2YXIgRT0wPmU/LTE6MTtyZXR1cm4gRSpSfXJldHVybiBlPT09ZT9lOjB9dmFyIHI9dCg2NSksVD0xLzAsUj0xLjc5NzY5MzEzNDg2MjMxNTdlMzA4O2UuZXhwb3J0cz1ufSxmdW5jdGlvbihlLEUsdCl7ZnVuY3Rpb24gbihlKXt2YXIgRT1yKGUpLHQ9RSUxO3JldHVybiBFPT09RT90P0UtdDpFOjB9dmFyIHI9dCg2Myk7ZS5leHBvcnRzPW59LGZ1bmN0aW9uKGUsRSx0KXtmdW5jdGlvbiBuKGUpe2lmKCJudW1iZXIiPT10eXBlb2YgZSlyZXR1cm4gZTtpZihUKGUpKXJldHVybiBSO2lmKHIoZSkpe3ZhciBFPSJmdW5jdGlvbiI9PXR5cGVvZiBlLnZhbHVlT2Y/ZS52YWx1ZU9mKCk6ZTtlPXIoRSk/RSsiIjpFfWlmKCJzdHJpbmciIT10eXBlb2YgZSlyZXR1cm4gMD09PWU/ZTorZTtlPWUucmVwbGFjZShvLCIiKTt2YXIgdD1BLnRlc3QoZSk7cmV0dXJuIHR8fEkudGVzdChlKT9PKGUuc2xpY2UoMiksdD8yOjgpOk4udGVzdChlKT9SOitlfXZhciByPXQoNiksVD10KDE0KSxSPU5hTixvPS9eXHMrfFxzKyQvZyxOPS9eWy0rXTB4WzAtOWEtZl0rJC9pLEE9L14wYlswMV0rJC9pLEk9L14wb1swLTddKyQvaSxPPXBhcnNlSW50O2UuZXhwb3J0cz1ufSxmdW5jdGlvbihlLEUsdCl7ZnVuY3Rpb24gbihlLEUsdCl7aWYoZT1OKGUpLGUmJih0fHx2b2lkIDA9PT1FKSlyZXR1cm4gZS5yZXBsYWNlKEEsIiIpO2lmKCFlfHwhKEU9cihFKSkpcmV0dXJuIGU7dmFyIG49byhlKSxJPVIobixvKEUpKSsxO3JldHVybiBUKG4sMCxJKS5qb2luKCIiKX12YXIgcj10KDEwKSxUPXQoMzYpLFI9dCgzNyksbz10KDUwKSxOPXQoOSksQT0vXHMrJC87ZS5leHBvcnRzPW59LGZ1bmN0aW9uKGUsRSl7ZS5leHBvcnRzPWZ1bmN0aW9uKGUpe3JldHVybiBlLndlYnBhY2tQb2x5ZmlsbHx8KGUuZGVwcmVjYXRlPWZ1bmN0aW9uKCl7fSxlLnBhdGhzPVtdLGUuY2hpbGRyZW49W10sZS53ZWJwYWNrUG9seWZpbGw9MSksZX19XSl9KTsKCgkJZnVuY3Rpb24gZXNjYXBlMkh0bWwoc3RyKSB7CiAgICAJICAgIHZhciBhcnJFbnRpdGllcyA9IHsnbHQnOiAnPCcsICdndCc6ICc+JywgJ25ic3AnOiAnJywgJ2FtcCc6ICcmJywgJ3F1b3QnOiAnIid9OwogICAgCSAgICByZXR1cm4gc3RyLnJlcGxhY2UoLyYobHR8Z3R8bmJzcHxhbXB8cXVvdCk7L2lnLCBmdW5jdGlvbiAoYWxsLCB0KSB7CiAgICAJICAgICAgICByZXR1cm4gYXJyRW50aXRpZXNbdF07CiAgICAJICAgIH0pOwogICAgCX0KCQogICAgCWZ1bmN0aW9uIGxvYWQoKSB7CiAgICAJICAgIGxldCBjb2RlTGlzdCA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2xhbmd1YWdlLXNxbCcpOwoJCiAgICAJICAgIGZvciAobGV0IGkgPSAwIDtpPGNvZGVMaXN0Lmxlbmd0aDtpKyspIHsKICAgIAkgICAgICAgIGNvZGVMaXN0W2ldLmlubmVySFRNTCA9IHdpbmRvdy5zcWxGb3JtYXR0ZXIuZm9ybWF0KGVzY2FwZTJIdG1sKGNvZGVMaXN0W2ldLmlubmVySFRNTCkpCiAgICAJICAgIH0KICAgIAl9Owo=\n`\n\n// MarkdownEscape markdown格式转义，原样输出\nfunc MarkdownEscape(str string) string {\n\tfor _, b := range \"_`*\" {\n\t\tstr = strings.Replace(str, string(b), \"\\\\\"+string(b), -1)\n\t}\n\treturn str\n}\n\n// loadExternalResource load js/css resource from http[s] url\nfunc loadExternalResource(resource string) string {\n\tvar content string\n\tvar body []byte\n\tif strings.HasPrefix(resource, \"http\") {\n\t\tresp, err := http.Get(resource)\n\t\tif err != nil {\n\t\t\tLog.Error(\"http.Get %s Error: %v\", resource, err)\n\t\t\treturn content\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tbody, err = ioutil.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tLog.Error(\"ioutil.ReadAll %s Error: %v\", resource, err)\n\t\t} else {\n\t\t\tcontent = string(body)\n\t\t}\n\t} else {\n\t\tfd, err := os.Open(resource)\n\t\tif err != nil {\n\t\t\tLog.Error(\"os.Open %s Error: %v\", resource, err)\n\t\t\treturn content\n\t\t}\n\t\tdefer fd.Close()\n\n\t\tbody, err = ioutil.ReadAll(fd)\n\t\tif err != nil {\n\t\t\tLog.Error(\"ioutil.ReadAll %s Error: %v\", resource, err)\n\t\t} else {\n\t\t\tcontent = string(body)\n\t\t}\n\t}\n\treturn content\n}\n\n// MarkdownHTMLHeader markdown 转 HTML 输出时添加 HTML 头\nfunc MarkdownHTMLHeader() string {\n\t// load css\n\tvar css string\n\tif Config.ReportCSS == \"\" {\n\t\tcss = BuiltinCSS\n\t} else {\n\t\tcss = loadExternalResource(Config.ReportCSS)\n\t}\n\n\t// load javascript\n\tvar js string\n\tif Config.ReportJavascript == \"\" {\n\t\tdecode, _ := base64.StdEncoding.DecodeString(BuiltinJavascript)\n\t\tjs = string(decode)\n\t} else {\n\t\tjs = loadExternalResource(Config.ReportJavascript)\n\t}\n\n\theader := `<head>\n<meta http-equiv=Content-Type content=\"text/html;charset=utf-8\">\n<title>` + Config.ReportTitle + `</title>\n<script>` + js + `</script>\n<style id=\"soar_md\">\n` + css + `\n</style>\n</head>\n<body onload=load()>\n`\n\treturn header\n}\n\n// Markdown2HTML markdown 转 HTML 输出\nfunc Markdown2HTML(buf string) string {\n\t// extensions default: 94\n\t// extensions |= blackfriday.EXTENSION_TABLES\n\t// extensions |= blackfriday.EXTENSION_FENCED_CODE\n\t// extensions |= blackfriday.EXTENSION_AUTOLINK\n\t// extensions |= blackfriday.EXTENSION_STRIKETHROUGH\n\t// extensions |= blackfriday.EXTENSION_SPACE_HEADERS\n\textensions := Config.MarkdownExtensions\n\n\t// htmlFlags\n\thtmlFlags := Config.MarkdownHTMLFlags\n\n\trenderer := blackfriday.HtmlRenderer(htmlFlags, \"\", \"\")\n\tbuf = string(blackfriday.Markdown([]byte(buf), renderer, extensions))\n\treturn buf\n}\n\n// Score SQL评审打分\nfunc Score(score int) string {\n\t// 不需要打分的功能\n\tswitch Config.ReportType {\n\tcase \"duplicate-key-checker\", \"explain-digest\":\n\t\treturn \"\"\n\t}\n\ts1, s2 := \"★ \", \"☆ \"\n\tif score > 100 {\n\t\tscore = 100\n\t\tLog.Debug(\"Score Error: score larger than 100, %d\", score)\n\t}\n\tif score < 0 {\n\t\tscore = 0\n\t\tLog.Debug(\"Score Warn: score less than 0, %d\", score)\n\t}\n\ts1Count := score / 20\n\ts2Count := 5 - s1Count\n\tstr := fmt.Sprintf(\"%s %d分\", strings.TrimSpace(strings.Repeat(s1, s1Count)+strings.Repeat(s2, s2Count)), score)\n\treturn str\n}\n"
  },
  {
    "path": "common/markdown_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestMarkdownEscape(_ *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tvar strs = []string{\n\t\t\"a`bc\",\n\t\t\"abc\",\n\t\t\"a'bc\",\n\t\t\"a\\\"bc\",\n\t}\n\tfor _, str := range strs {\n\t\tfmt.Println(MarkdownEscape(str))\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestMarkdown2Html(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tmd := filepath.Join(\"testdata\", t.Name()+\".md\")\n\tbuf, err := ioutil.ReadFile(md)\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\terr = GoldenDiff(func() {\n\t\tfmt.Println(Markdown2HTML(string(buf)))\n\t}, t.Name(), update)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\n\t// golden 文件拷贝成 html 文件，这步是给人看的\n\tgd, err := os.OpenFile(\"testdata/\"+t.Name()+\".golden\", os.O_RDONLY, 0666)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\thtml, err := os.OpenFile(\"testdata/\"+t.Name()+\".html\", os.O_CREATE|os.O_RDWR, 0666)\n\tif nil != err {\n\t\tt.Fatal(err)\n\t}\n\tio.Copy(html, gd)\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestScore(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tscores := map[int]string{\n\t\t50:  \"★ ★ ☆ ☆ ☆ 50分\",\n\t\t100: \"★ ★ ★ ★ ★ 100分\",\n\t\t-1:  \"☆ ☆ ☆ ☆ ☆ 0分\",\n\t\t101: \"★ ★ ★ ★ ★ 100分\",\n\t}\n\tfor score, want := range scores {\n\t\tget := Score(score)\n\t\tif want != get {\n\t\t\tt.Error(score, want, get)\n\t\t}\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestLoadExternalResource(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tbuf := loadExternalResource(\"../doc/themes/github.css\")\n\tif buf == \"\" {\n\t\tt.Error(\"loadExternalResource local error\")\n\t}\n\tbuf = loadExternalResource(\"http://www.baidu.com\")\n\tif buf == \"\" {\n\t\tt.Error(\"loadExternalResource http error\")\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestMarkdownHTMLHeader(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\terr := GoldenDiff(func() {\n\t\tMarkdownHTMLHeader()\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n"
  },
  {
    "path": "common/meta.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Meta 以 'database' 为 key, DB 的 map, 按 db->table->column 组织的元数据\ntype Meta map[string]*DB\n\n// DB 数据库相关的结构体\ntype DB struct {\n\tName  string\n\tTable map[string]*Table // ['table_name']*TableName\n}\n\n// NewDB 用于初始化*DB\nfunc NewDB(db string) *DB {\n\treturn &DB{\n\t\tName:  db,\n\t\tTable: make(map[string]*Table),\n\t}\n}\n\n// Table 含有表的属性\ntype Table struct {\n\tTableName    string\n\tTableAliases []string\n\tColumn       map[string]*Column\n}\n\n// NewTable 初始化*TableName\nfunc NewTable(tb string) *Table {\n\treturn &Table{\n\t\tTableName:    tb,\n\t\tTableAliases: make([]string, 0),\n\t\tColumn:       make(map[string]*Column),\n\t}\n}\n\n// KeyType 用于标志每个Key的类别\ntype KeyType int\n\n// Column 含有列的定义属性\ntype Column struct {\n\tName        string   `json:\"col_name\"`    // 列名\n\tAlias       []string `json:\"alias\"`       // 别名\n\tTable       string   `json:\"tb_name\"`     // 表名\n\tDB          string   `json:\"db_name\"`     // 数据库名称\n\tDataType    string   `json:\"data_type\"`   // 数据类型\n\tCharacter   string   `json:\"character\"`   // 字符集\n\tCollation   string   `json:\"collation\"`   // collation\n\tCardinality float64  `json:\"cardinality\"` // 散粒度\n\tNull        string   `json:\"null\"`        // 是否为空: YES/NO\n\tKey         string   `json:\"key\"`         // 键类型\n\tDefault     string   `json:\"default\"`     // 默认值\n\tExtra       string   `json:\"extra\"`       // 其他\n\tComment     string   `json:\"comment\"`     // 备注\n\tPrivileges  string   `json:\"privileges\"`  // 权限\n}\n\n// TableColumns 这个结构体中的元素是有序的  map[db]map[table][]columns\ntype TableColumns map[string]map[string][]*Column\n\n// Equal 判断两个column是否相等\nfunc (col *Column) Equal(column *Column) bool {\n\treturn col.Name == column.Name &&\n\t\tcol.Table == column.Table &&\n\t\tcol.DB == column.DB\n}\n\n// IsColsPart 判断两个column队列是否是包含关系（包括相等）\nfunc IsColsPart(a, b []*Column) bool {\n\ttimes := len(a)\n\tif len(b) < times {\n\t\ttimes = len(b)\n\t}\n\n\tfor i := 0; i < times; i++ {\n\t\tif strings.ToLower(a[i].DB) != strings.ToLower(b[i].DB) ||\n\t\t\tstrings.ToLower(a[i].Table) != strings.ToLower(b[i].Table) ||\n\t\t\tstrings.ToLower(a[i].Name) != strings.ToLower(b[i].Name) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// JoinColumnsName 将所有的列合并\nfunc JoinColumnsName(cols []*Column, sep string) string {\n\tname := \"\"\n\tfor _, col := range cols {\n\t\tname += col.Name + sep\n\t}\n\treturn strings.Trim(name, sep)\n}\n\n// Tables 获取 Meta 中指定 db 的所有表名\n// Input:数据库名\n// Output:表名组成的list\nfunc (b Meta) Tables(db string) []string {\n\tvar result []string\n\tif b[db] != nil {\n\t\tfor tb := range b[db].Table {\n\t\t\tresult = append(result, tb)\n\t\t}\n\n\t}\n\treturn result\n}\n\n// SetDefault 设置默认值\nfunc (b Meta) SetDefault(defaultDB string) Meta {\n\tif defaultDB == \"\" {\n\t\treturn b\n\t}\n\n\tfor db := range b {\n\t\tif db == \"\" {\n\t\t\t// 当获取到的 join 中的 DB 为空的时候，说明 SQL 未显示的指定 DB，即使用的是 rEnv 默认 DB, 需要将表合并到原 DB 中\n\t\t\tif _, ok := b[defaultDB]; ok {\n\t\t\t\tfor tbName, table := range b[\"\"].Table {\n\t\t\t\t\tif _, ok := b[defaultDB].Table[tbName]; ok {\n\t\t\t\t\t\tb[defaultDB].Table[tbName].TableAliases = append(\n\t\t\t\t\t\t\tb[defaultDB].Table[tbName].TableAliases,\n\t\t\t\t\t\t\ttable.TableAliases...,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tb[defaultDB].Table[tbName] = table\n\t\t\t\t}\n\t\t\t\tdelete(b, \"\")\n\t\t\t}\n\n\t\t\t// 如果没有出现DB指定不一致的情况，直接进行合并\n\t\t\tb[defaultDB] = b[\"\"]\n\t\t\tdelete(b, \"\")\n\t\t}\n\t}\n\n\treturn b\n}\n\n// MergeColumn 将使用到的列按 db->table 组织去重\n// 注意：Column 中的 db, table 信息可能为空，需要提前通过env环境补齐再调用该函数。\n// @input: 目标列list, 源列list（可以将多个源合并到一个目标列list）\n// @output: 合并后的列list\nfunc MergeColumn(dst []*Column, src ...*Column) []*Column {\n\tvar tmp []*Column\n\tfor _, newCol := range src {\n\t\tif len(dst) == 0 {\n\t\t\ttmp = append(tmp, newCol)\n\t\t\tcontinue\n\t\t}\n\n\t\thas := false\n\t\tfor _, oldCol := range dst {\n\t\t\tif (newCol.Name == oldCol.Name) && (newCol.Table == oldCol.Table) && (newCol.DB == oldCol.DB) {\n\t\t\t\thas = true\n\t\t\t}\n\t\t}\n\n\t\tif !has {\n\t\t\ttmp = append(tmp, newCol)\n\t\t}\n\n\t}\n\treturn append(dst, tmp...)\n}\n\n// ColumnSort 通过散粒度对 colList 进行排序， 散粒度排序由大到小\nfunc ColumnSort(colList []*Column) []*Column {\n\t// 使用冒泡排序保持相等情况下左右两边顺序不变\n\tif len(colList) < 2 {\n\t\treturn colList\n\t}\n\n\tfor i := 0; i < len(colList)-1; i++ {\n\t\tfor j := i + 1; j < len(colList); j++ {\n\t\t\tif colList[i].Cardinality < colList[j].Cardinality {\n\t\t\t\tcolList[i], colList[j] = colList[j], colList[i]\n\t\t\t}\n\t\t}\n\t}\n\n\treturn colList\n}\n\n// GetDataTypeBase 获取dataType中的数据类型，忽略长度\nfunc GetDataTypeBase(dataType string) string {\n\tdataType = strings.TrimSpace(dataType)\n\tif dataType == \"\" {\n\t\treturn dataType\n\t}\n\t// smallint unsigned, remove unsigned\n\tdataType = strings.Fields(dataType)[0]\n\t// int(10), remote (10)\n\tif i := strings.Index(dataType, \"(\"); i > 0 {\n\t\treturn dataType[0:i]\n\t}\n\n\treturn dataType\n}\n\n// GetDataTypeLength 获取dataType中的数据类型长度\nfunc GetDataTypeLength(dataType string) []int {\n\tvar length []int\n\tif si := strings.Index(dataType, \"(\"); si > 0 {\n\t\tdataLength := dataType[si+1:]\n\t\tif ei := strings.Index(dataLength, \")\"); ei > 0 {\n\t\t\tdataLength = dataLength[:ei]\n\t\t\tif strings.HasPrefix(dataType, \"enum\") ||\n\t\t\t\tstrings.HasPrefix(dataType, \"set\") {\n\t\t\t\t// set('one', 'two'), enum('G','PG','PG-13','R','NC-17')\n\t\t\t\tlength = []int{len(strings.Split(dataLength, \",\"))}\n\t\t\t} else {\n\t\t\t\t// char(10), varchar(10)\n\t\t\t\tfor _, l := range strings.Split(dataLength, \",\") {\n\t\t\t\t\tv, err := strconv.Atoi(l)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tLog.Debug(\"GetDataTypeLength() Error: %v\", err)\n\t\t\t\t\t\treturn []int{-1}\n\t\t\t\t\t}\n\t\t\t\t\tlength = append(length, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(length) == 0 {\n\t\tlength = []int{-1}\n\t}\n\n\treturn length\n}\n\n// GetDataBytes 计算数据类型字节数\n// https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html\n// return -1 表示该列无法计算数据大小\nfunc (col *Column) GetDataBytes(dbVersion int) int {\n\tif col.DataType == \"\" {\n\t\tLog.Warning(\"Can't get %s.%s data type\", col.Table, col.Name)\n\t\treturn -1\n\t}\n\tswitch strings.ToLower(GetDataTypeBase(col.DataType)) {\n\tcase \"tinyint\", \"smallint\", \"mediumint\",\n\t\t\"int\", \"integer\", \"bigint\",\n\t\t\"double\", \"real\", \"float\", \"decimal\",\n\t\t\"numeric\", \"bit\", \"smallint unsigned\":\n\t\t// numeric\n\t\treturn numericStorageReq(col.DataType)\n\n\tcase \"year\", \"date\", \"time\", \"datetime\", \"timestamp\":\n\t\t// date & time\n\t\treturn timeStorageReq(col.DataType, dbVersion)\n\n\tcase \"char\", \"binary\", \"varchar\", \"varbinary\", \"enum\", \"set\":\n\t\t// string\n\t\treturn StringStorageReq(col.DataType, col.Character)\n\tcase \"tinyblob\", \"tinytext\", \"blob\", \"text\", \"mediumblob\", \"mediumtext\",\n\t\t\"longblob\", \"longtext\":\n\t\t// strings length depend on it's values\n\t\t// 这些字段为不定长字段，添加索引时必须指定前缀，索引前缀与字符集相关\n\t\treturn Config.MaxIdxBytesPerColumn + 1\n\tdefault:\n\t\tLog.Warning(\"Type %s not support:\", col.DataType)\n\t\treturn -1\n\t}\n}\n\n// Numeric Type Storage Requirements\n// return bytes count\nfunc numericStorageReq(dataType string) int {\n\ttypeLength := GetDataTypeLength(dataType)\n\tbaseType := strings.ToLower(GetDataTypeBase(dataType))\n\n\tswitch baseType {\n\tcase \"tinyint\":\n\t\treturn 1\n\tcase \"smallint\":\n\t\treturn 2\n\tcase \"mediumint\":\n\t\treturn 3\n\tcase \"int\", \"integer\":\n\t\treturn 4\n\tcase \"bigint\", \"double\", \"real\":\n\t\treturn 8\n\tcase \"float\":\n\t\tif typeLength[0] == -1 || typeLength[0] >= 0 && typeLength[0] <= 24 {\n\t\t\t// 4 bytes if 0 <= p <= 24\n\t\t\treturn 4\n\t\t}\n\t\t// 8 bytes if no p || 25 <= p <= 53\n\t\treturn 8\n\tcase \"decimal\", \"numeric\":\n\t\t// Values for DECIMAL (and NUMERIC) columns are represented using a binary format\n\t\t// that packs nine decimal (base 10) digits into four bytes. Storage for the integer\n\t\t// and fractional parts of each value are determined separately. Each multiple of nine\n\t\t// digits requires four bytes, and the “leftover” digits require some fraction of four bytes.\n\n\t\tif typeLength[0] == -1 {\n\t\t\treturn 4\n\t\t}\n\n\t\tleftover := func(leftover int) int {\n\t\t\tif leftover > 0 && leftover <= 2 {\n\t\t\t\treturn 1\n\t\t\t} else if leftover > 2 && leftover <= 4 {\n\t\t\t\treturn 2\n\t\t\t} else if leftover > 4 && leftover <= 6 {\n\t\t\t\treturn 3\n\t\t\t} else if leftover > 6 && leftover <= 8 {\n\t\t\t\treturn 4\n\t\t\t} else {\n\t\t\t\treturn 4\n\t\t\t}\n\t\t}\n\n\t\tinteger := typeLength[0]/9*4 + leftover(typeLength[0]%9)\n\t\tfractional := typeLength[1]/9*4 + leftover(typeLength[1]%9)\n\n\t\treturn integer + fractional\n\n\tcase \"bit\":\n\t\t// approximately (M+7)/8 bytes\n\t\tif typeLength[0] == -1 {\n\t\t\treturn 1\n\t\t}\n\t\treturn (typeLength[0] + 7) / 8\n\n\tdefault:\n\t\tLog.Error(\"No such numeric type: %s\", baseType)\n\t\treturn 8\n\t}\n}\n\n// Date and Time Type Storage Requirements\n// return bytes count\nfunc timeStorageReq(dataType string, version int) int {\n\t/*\n\t\t\thttps://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html\n\t\t\t*   ============================================================================================\n\t\t\t*   |\tData Type |\tStorage Required Before MySQL 5.6.4\t| Storage Required as of MySQL 5.6.4   |\n\t\t\t*   | ---------------------------------------------------------------------------------------- |\n\t\t\t*   |\tYEAR\t  |\t1 byte\t                            | 1 byte                               |\n\t\t\t*   |\tDATE\t  | 3 bytes\t                            | 3 bytes                              |\n\t\t\t*   |\tTIME\t  | 3 bytes\t                            | 3 bytes + fractional seconds storage |\n\t\t\t*   |\tDATETIME  | 8 bytes\t                            | 5 bytes + fractional seconds storage |\n\t\t\t*   |\tTIMESTAMP |\t4 bytes\t                            | 4 bytes + fractional seconds storage |\n\t\t\t*   ============================================================================================\n\t\t\t*\t|  Fractional Seconds Precision |Storage Required  |\n\t\t\t*   | ------------------------------------------------ |\n\t\t\t*\t|  0\t    \t\t\t\t\t|0 bytes\t\t   |\n\t\t\t*\t|  1, 2\t\t\t\t\t\t    |1 byte            |\n\t\t\t*\t|  3, 4\t\t\t\t\t\t    |2 bytes           |\n\t\t\t*\t|  5, 6\t\t\t\t\t\t    |3 bytes           |\n\t\t    *   ====================================================\n\t*/\n\n\ttypeLength := GetDataTypeLength(dataType)\n\n\textr := func(length int) int {\n\t\tif length > 0 && length <= 2 {\n\t\t\treturn 1\n\t\t} else if length > 2 && length <= 4 {\n\t\t\treturn 2\n\t\t} else if length > 4 && length <= 6 || length > 6 {\n\t\t\treturn 3\n\t\t}\n\t\treturn 0\n\t}\n\n\tswitch strings.ToLower(GetDataTypeBase(dataType)) {\n\tcase \"year\":\n\t\treturn 1\n\tcase \"date\":\n\t\treturn 3\n\tcase \"time\":\n\t\tif version < 50604 {\n\t\t\treturn 3\n\t\t}\n\t\t// 3 bytes + fractional seconds storage\n\t\treturn 3 + extr(typeLength[0])\n\tcase \"datetime\":\n\t\tif version < 50604 {\n\t\t\treturn 8\n\t\t}\n\t\t// 5 bytes + fractional seconds storage\n\t\treturn 5 + extr(typeLength[0])\n\tcase \"timestamp\":\n\t\tif version < 50604 {\n\t\t\treturn 4\n\t\t}\n\t\t// 4 bytes + fractional seconds storage\n\t\treturn 4 + extr(typeLength[0])\n\tdefault:\n\t\treturn 8\n\t}\n}\n\n// SHOW CHARACTER SET\n\n// CharSets character bytes per charcharacter bytes per char\nvar CharSets = map[string]int{\n\t\"armscii8\": 1,\n\t\"ascii\":    1,\n\t\"big5\":     2,\n\t\"binary\":   1,\n\t\"cp1250\":   1,\n\t\"cp1251\":   1,\n\t\"cp1256\":   1,\n\t\"cp1257\":   1,\n\t\"cp850\":    1,\n\t\"cp852\":    1,\n\t\"cp866\":    1,\n\t\"cp932\":    2,\n\t\"dec8\":     1,\n\t\"eucjpms\":  3,\n\t\"euckr\":    2,\n\t\"gb18030\":  4,\n\t\"gb2312\":   2,\n\t\"gbk\":      2,\n\t\"geostd8\":  1,\n\t\"greek\":    1,\n\t\"hebrew\":   1,\n\t\"hp8\":      1,\n\t\"keybcs2\":  1,\n\t\"koi8r\":    1,\n\t\"koi8u\":    1,\n\t\"latin1\":   1,\n\t\"latin2\":   1,\n\t\"latin5\":   1,\n\t\"latin7\":   1,\n\t\"macce\":    1,\n\t\"macroman\": 1,\n\t\"sjis\":     2,\n\t\"swe7\":     1,\n\t\"tis620\":   1,\n\t\"ucs2\":     2,\n\t\"ujis\":     3,\n\t\"utf16\":    4,\n\t\"utf16le\":  4,\n\t\"utf32\":    4,\n\t\"utf8\":     3,\n\t\"utf8mb4\":  4,\n}\n\n// StringStorageReq String Type Storage Requirements return bytes count\nfunc StringStorageReq(dataType string, charset string) int {\n\t// get bytes per character, default 1\n\tbysPerChar := 1\n\tif _, ok := CharSets[strings.ToLower(charset)]; ok {\n\t\tbysPerChar = CharSets[strings.ToLower(charset)]\n\t}\n\n\t// get length\n\ttypeLength := GetDataTypeLength(dataType)\n\tif typeLength[0] == -1 {\n\t\treturn 0\n\t}\n\n\t// get type\n\tbaseType := strings.ToLower(GetDataTypeBase(dataType))\n\n\tswitch baseType {\n\tcase \"char\":\n\t\t// Otherwise, M × w bytes, <= M <= 255,\n\t\t// where w is the number of bytes required for the maximum-length character in the character set.\n\t\tif typeLength[0] > 255 {\n\t\t\ttypeLength[0] = 255\n\t\t}\n\t\treturn typeLength[0] * bysPerChar\n\tcase \"binary\":\n\t\t// M bytes, 0 <= M <= 255\n\t\tif typeLength[0] > 255 {\n\t\t\ttypeLength[0] = 255\n\t\t}\n\t\treturn typeLength[0]\n\tcase \"varchar\", \"varbinary\":\n\t\tif typeLength[0] < 255 {\n\t\t\treturn typeLength[0]*bysPerChar + 1\n\t\t}\n\t\treturn typeLength[0]*bysPerChar + 2\n\tcase \"enum\":\n\t\t// 1 or 2 bytes, depending on the number of enumeration values (65,535 values maximum)\n\t\treturn typeLength[0]/(2^15) + 1\n\tcase \"set\":\n\t\t// 1, 2, 3, 4, or 8 bytes, depending on the number of set members (64 members maximum)\n\t\treturn typeLength[0]/8 + 1\n\tdefault:\n\t\treturn 0\n\t}\n}\n"
  },
  {
    "path": "common/meta_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestGetDataTypeLength(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\ttypeList := map[string][]int{\n\t\t\"varchar(20)\":  {20},\n\t\t\"int(2)\":       {2},\n\t\t\"int(2000000)\": {2000000},\n\t\t\"DECIMAL(1,2)\": {1, 2},\n\t\t\"int\":          {-1},\n\t}\n\n\tfor typ, want := range typeList {\n\t\tgot := GetDataTypeLength(typ)\n\t\tfor i := 0; i < len(want); i++ {\n\t\t\tif want[i] != got[i] {\n\t\t\t\tt.Errorf(\"Not match, want %v, got %v\", want, got)\n\t\t\t}\n\t\t}\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestGetDataTypeBase(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\ttypeList := map[string]string{\n\t\t\"varchar(20)\":  \"varchar\",\n\t\t\"int(2)\":       \"int\",\n\t\t\"int(2000000)\": \"int\",\n\t}\n\n\tfor typ := range typeList {\n\t\tif got := GetDataTypeBase(typ); got != typeList[typ] {\n\t\t\tt.Errorf(\"Not match, want %s, got %s\", typeList[typ], got)\n\t\t}\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestGetDataBytes(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tcols50604 := map[*Column]int{\n\t\t// numeric type\n\t\t{Name: \"col000\", DataType: \"tinyint\", Character: \"utf8\"}:        1,\n\t\t{Name: \"col001\", DataType: \"SMALLINT\", Character: \"utf8\"}:       2,\n\t\t{Name: \"col002\", DataType: \"MEDIUMINT\", Character: \"utf8\"}:      3,\n\t\t{Name: \"col003\", DataType: \"int(32)\", Character: \"utf8\"}:        4,\n\t\t{Name: \"col004\", DataType: \"integer(32)\", Character: \"utf8\"}:    4,\n\t\t{Name: \"col005\", DataType: \"bigint(10)\", Character: \"utf8\"}:     8,\n\t\t{Name: \"col006\", DataType: \"float(12)\", Character: \"utf8\"}:      4,\n\t\t{Name: \"col007\", DataType: \"float(50)\", Character: \"utf8\"}:      8,\n\t\t{Name: \"col008\", DataType: \"float(100)\", Character: \"utf8\"}:     8,\n\t\t{Name: \"col009\", DataType: \"float\", Character: \"utf8\"}:          4,\n\t\t{Name: \"col010\", DataType: \"double\", Character: \"utf8\"}:         8,\n\t\t{Name: \"col011\", DataType: \"real\", Character: \"utf8\"}:           8,\n\t\t{Name: \"col012\", DataType: \"BIT(32)\", Character: \"utf8\"}:        4,\n\t\t{Name: \"col013\", DataType: \"numeric(32,32)\", Character: \"utf8\"}: 30,\n\t\t{Name: \"col013\", DataType: \"decimal(2,32)\", Character: \"utf8\"}:  16,\n\t\t{Name: \"col014\", DataType: \"BIT(32)\", Character: \"utf8\"}:        4,\n\n\t\t// date & time\n\t\t{Name: \"col015\", DataType: \"year(32)\", Character: \"utf8mb4\"}:      1,\n\t\t{Name: \"col016\", DataType: \"date\", Character: \"utf8mb4\"}:          3,\n\t\t{Name: \"col017\", DataType: \"time\", Character: \"utf8mb4\"}:          3,\n\t\t{Name: \"col018\", DataType: \"time(0)\", Character: \"utf8mb4\"}:       3,\n\t\t{Name: \"col019\", DataType: \"time(2)\", Character: \"utf8mb4\"}:       4,\n\t\t{Name: \"col020\", DataType: \"time(4)\", Character: \"utf8mb4\"}:       5,\n\t\t{Name: \"col021\", DataType: \"time(6)\", Character: \"utf8mb4\"}:       6,\n\t\t{Name: \"col022\", DataType: \"datetime\", Character: \"utf8mb4\"}:      5,\n\t\t{Name: \"col023\", DataType: \"timestamp(32)\", Character: \"utf8mb4\"}: 7,\n\n\t\t// string\n\t\t{Name: \"col024\", DataType: \"varchar(255)\", Character: \"utf8\"}:    767,\n\t\t{Name: \"col025\", DataType: \"varchar(191)\", Character: \"utf8mb4\"}: 765,\n\t}\n\n\tfor col, bytes := range cols50604 {\n\t\tif got := col.GetDataBytes(50604); got != bytes {\n\t\t\tt.Errorf(\"Version 5.6.4, %s Not match, want %d, got %d\", col.Name, bytes, got)\n\t\t}\n\t}\n\n\tcols50500 := map[*Column]int{\n\t\t// numeric type\n\t\t{Name: \"col000\", DataType: \"tinyint\", Character: \"utf8\"}:        1,\n\t\t{Name: \"col001\", DataType: \"SMALLINT\", Character: \"utf8\"}:       2,\n\t\t{Name: \"col002\", DataType: \"MEDIUMINT\", Character: \"utf8\"}:      3,\n\t\t{Name: \"col003\", DataType: \"int(32)\", Character: \"utf8\"}:        4,\n\t\t{Name: \"col004\", DataType: \"integer(32)\", Character: \"utf8\"}:    4,\n\t\t{Name: \"col005\", DataType: \"bigint(10)\", Character: \"utf8\"}:     8,\n\t\t{Name: \"col006\", DataType: \"float(12)\", Character: \"utf8\"}:      4,\n\t\t{Name: \"col007\", DataType: \"float(50)\", Character: \"utf8\"}:      8,\n\t\t{Name: \"col008\", DataType: \"float(100)\", Character: \"utf8\"}:     8,\n\t\t{Name: \"col009\", DataType: \"float\", Character: \"utf8\"}:          4,\n\t\t{Name: \"col010\", DataType: \"double\", Character: \"utf8\"}:         8,\n\t\t{Name: \"col011\", DataType: \"real\", Character: \"utf8\"}:           8,\n\t\t{Name: \"col012\", DataType: \"BIT(32)\", Character: \"utf8\"}:        4,\n\t\t{Name: \"col013\", DataType: \"numeric(32,32)\", Character: \"utf8\"}: 30,\n\t\t{Name: \"col013\", DataType: \"decimal(2,32)\", Character: \"utf8\"}:  16,\n\t\t{Name: \"col014\", DataType: \"BIT(32)\", Character: \"utf8\"}:        4,\n\n\t\t// date & time\n\t\t{Name: \"col015\", DataType: \"year(32)\", Character: \"utf8mb4\"}:      1,\n\t\t{Name: \"col016\", DataType: \"date\", Character: \"utf8mb4\"}:          3,\n\t\t{Name: \"col017\", DataType: \"time\", Character: \"utf8mb4\"}:          3,\n\t\t{Name: \"col018\", DataType: \"time(0)\", Character: \"utf8mb4\"}:       3,\n\t\t{Name: \"col019\", DataType: \"time(2)\", Character: \"utf8mb4\"}:       3,\n\t\t{Name: \"col020\", DataType: \"time(4)\", Character: \"utf8mb4\"}:       3,\n\t\t{Name: \"col021\", DataType: \"time(6)\", Character: \"utf8mb4\"}:       3,\n\t\t{Name: \"col022\", DataType: \"datetime\", Character: \"utf8mb4\"}:      8,\n\t\t{Name: \"col023\", DataType: \"timestamp(32)\", Character: \"utf8mb4\"}: 4,\n\n\t\t// string\n\t\t{Name: \"col024\", DataType: \"varchar(255)\", Character: \"utf8\"}:    767,\n\t\t{Name: \"col025\", DataType: \"varchar(191)\", Character: \"utf8mb4\"}: 765,\n\t}\n\n\tfor col, bytes := range cols50500 {\n\t\tif got := col.GetDataBytes(50500); got != bytes {\n\t\t\tt.Errorf(\"Version: 5.5.0, %s Not match, want %d, got %d\", col.Name, bytes, got)\n\t\t}\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestStringStorageReq(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tdataTypes := []string{\n\t\t\"char(10)\",\n\t\t\"char(256)\",\n\t\t\"binary(10)\",\n\t\t\"binary(256)\",\n\t\t\"varchar(10)\",\n\t\t\"varbinary(10)\",\n\t\t\"enum('G','PG','PG-13','R','NC-17')\",\n\t\t\"set('one', 'two')\",\n\t\t// wrong case\n\t\t\"not_exist\",\n\t\t\"char(-1)\",\n\t}\n\n\terr := GoldenDiff(func() {\n\t\tfor _, name := range SortedKey(CharSets) {\n\t\t\tfor _, tp := range dataTypes {\n\t\t\t\tfmt.Println(tp, name, StringStorageReq(tp, name))\n\t\t\t}\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n"
  },
  {
    "path": "common/signal.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\n// HandleSignal 当程序卡死的时候，或者由于某些原因程序没有退出，可以通过捕获信号量的形式让程序优雅退出并且清理测试环境\nfunc HandleSignal(f func()) {\n\tsc := make(chan os.Signal, 1)\n\tsignal.Notify(sc,\n\t\tsyscall.SIGHUP,\n\t\tsyscall.SIGINT,\n\t\tsyscall.SIGTERM,\n\t\tsyscall.SIGQUIT)\n\n\tgo func() {\n\t\tn := <-sc\n\t\tLog.Info(\"receive signal %v, closing\", n)\n\t\tf()\n\t}()\n}\n"
  },
  {
    "path": "common/signal_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestHandleSignal(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tHandleSignal(func() {\n\t\tfmt.Println(\"done\")\n\t})\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n"
  },
  {
    "path": "common/testdata/TestJSONFind.golden",
    "content": "[{\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\t\t\t\"key\":\"value\"\n\t\t\t\t}]\n[McLaughlin Hunter Harold]\n[{\n      \"title\": \"Sample Konfabulator Widget\",\n      \"name\": \"main_window\",\n      \"width\": 500,\n      \"height\": 500\n    }]\n[    binary    binary  utf8mb4_bin  ]\n"
  },
  {
    "path": "common/testdata/TestListReportTypes.golden",
    "content": "# 支持的报告类型\n\n[toc]\n\n## lint\n* **Description**:参考sqlint格式，以插件形式集成到代码编辑器，显示输出更加友好\n\n* **Example**:\n\n```bash\nsoar -report-type lint -query test.sql\n```\n## markdown\n* **Description**:该格式为默认输出格式，以markdown格式展现，可以用网页浏览器插件直接打开，也可以用markdown编辑器打开\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar\n```\n## rewrite\n* **Description**:SQL重写功能，配合-rewrite-rules参数一起使用，可以通过-list-rewrite-rules 查看所有支持的 SQL 重写规则\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -rewrite-rules star2columns,delimiter -report-type rewrite\n```\n## ast\n* **Description**:输出 SQL 的抽象语法树，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type ast\n```\n## ast-json\n* **Description**:以 JSON 格式输出 SQL 的抽象语法树，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type ast-json\n```\n## tiast\n* **Description**:输出 SQL 的 TiDB抽象语法树，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type tiast\n```\n## tiast-json\n* **Description**:以 JSON 格式输出 SQL 的 TiDB抽象语法树，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type tiast-json\n```\n## tables\n* **Description**:以 JSON 格式输出 SQL 使用的库表名\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type tables\n```\n## query-type\n* **Description**:SQL 语句的请求类型\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type query-type\n```\n## fingerprint\n* **Description**:输出SQL的指纹\n\n* **Example**:\n\n```bash\necho \"select * from film where language_id=1\" | soar -report-type fingerprint\n```\n## md2html\n* **Description**:markdown 格式转 html 格式小工具\n\n* **Example**:\n\n```bash\nsoar -list-heuristic-rules | soar -report-type md2html > heuristic_rules.html\n```\n## explain-digest\n* **Description**:输入为EXPLAIN的表格，JSON 或 Vertical格式，对其进行分析，给出分析结果\n\n* **Example**:\n\n```bash\nsoar -report-type explain-digest << EOF\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\n| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\n|  1 | SIMPLE      | film  | ALL  | NULL          | NULL | NULL    | NULL | 1131 |       |\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\nEOF\n```\n## duplicate-key-checker\n* **Description**:对 OnlineDsn 中指定的 database 进行索引重复检查\n\n* **Example**:\n\n```bash\nsoar -report-type duplicate-key-checker -online-dsn user:password@127.0.0.1:3306/db\n```\n## html\n* **Description**:以HTML格式输出报表\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type html\n```\n## json\n* **Description**:输出JSON格式报表，方便应用程序处理\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type json\n```\n## tokenize\n* **Description**:对SQL进行切词，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type tokenize\n```\n## compress\n* **Description**:SQL压缩小工具，使用内置SQL压缩逻辑，测试中的功能\n\n* **Example**:\n\n```bash\necho \"select\n*\nfrom\n  film\" | soar -report-type compress\n```\n## pretty\n* **Description**:使用kr/pretty打印报告，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type pretty\n```\n## remove-comment\n* **Description**:去除SQL语句中的注释，支持单行多行注释的去除\n\n* **Example**:\n\n```bash\necho \"select/*comment*/ * from film\" | soar -report-type remove-comment\n```\n## chardet\n* **Description**:猜测输入的 SQL 使用的字符集\n\n* **Example**:\n\n```bash\necho '中文' | soar -report-type chardet\n```\n"
  },
  {
    "path": "common/testdata/TestMarkdown2Html.golden",
    "content": "<h1>Markdown For Typora</h1>\n\n<h2>Overview</h2>\n\n<p><strong>Markdown</strong> is created by <a href=\"http://daringfireball.net/\">Daring Fireball</a>, the original guideline is <a href=\"http://daringfireball.net/projects/markdown/syntax\">here</a>. Its syntax, however, varies between different parsers or editors. <strong>Typora</strong> is using <a href=\"https://help.github.com/articles/github-flavored-markdown/\">GitHub Flavored Markdown</a>.</p>\n\n<p>Please note that HTML fragments in markdown source will be recognized but not parsed or rendered. Also, there may be small reformatting on the original markdown source code after saving.</p>\n\n<p><em>Outline</em></p>\n\n<p>[TOC]</p>\n\n<h2>Block Elements</h2>\n\n<h3>Paragraph and line breaks</h3>\n\n<p>A paragraph is simply one or more consecutive lines of text. In markdown source code, paragraphs are separated by more than one blank lines. In Typora, you only need to press <code>Return</code> to create a new paragraph.</p>\n\n<p>Press <code>Shift</code> + <code>Return</code> to create a single line break. However, most markdown parser will ignore single line break, to make other markdown parsers recognize your line break, you can leave two whitespace at the end of the line, or insert <code>&lt;br/&gt;</code>.</p>\n\n<h3>Headers</h3>\n\n<p>Headers use 1-6 hash characters at the start of the line, corresponding to header levels 1-6. For example:</p>\n\n<pre><code class=\"language-markdown\"># This is an H1\n\n## This is an H2\n\n###### This is an H6\n</code></pre>\n\n<p>In typora, input ‘#’s followed by title content, and press <code>Return</code> key will create a header.</p>\n\n<h3>Blockquotes</h3>\n\n<p>Markdown uses email-style &gt; characters for block quoting. They are presented as:</p>\n\n<pre><code class=\"language-markdown\">&gt; This is a blockquote with two paragraphs. This is first paragraph.\n&gt;\n&gt; This is second pragraph.Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.\n\n\n\n&gt; This is another blockquote with one paragraph. There is three empty line to seperate two blockquote.\n</code></pre>\n\n<p>In typora, just input ‘&gt;’ followed by quote contents a block quote is  generated. Typora will insert proper ‘&gt;’ or line break for you. Block quote inside anther block quote is allowed by adding additional levels of ‘&gt;’.</p>\n\n<h3>Lists</h3>\n\n<p>Input <code>* list item 1</code> will create an un-ordered list, the <code>*</code> symbol can be replace with <code>+</code> or <code>-</code>.</p>\n\n<p>Input <code>1. list item 1</code> will create an ordered list, their markdown source code is like:</p>\n\n<pre><code class=\"language-markdown\">## un-ordered list\n*   Red\n*   Green\n*   Blue\n\n## ordered list\n1.  Red\n2. \tGreen\n3.\tBlue\n</code></pre>\n\n<h3>Task List</h3>\n\n<p>Task lists are lists with items marked as either [ ] or <a href=\"incomplete or complete\">x</a>. For example:</p>\n\n<pre><code class=\"language-markdown\">- [ ] a task list item\n- [ ] list syntax required\n- [ ] normal **formatting**, @mentions, #1234 refs\n- [ ] incomplete\n- [x] completed\n</code></pre>\n\n<p>You can change the complete/incomplete state by click the checkbox before the item.</p>\n\n<h3>(Fenced) Code Blocks</h3>\n\n<p>Typora only support fences in Github Flavored Markdown. Original code blocks in markdown is not supported.</p>\n\n<p>Using fences is easy: Input ``` and press <code>return</code>. Add an optional language identifier after ``` and we'll run it through syntax highlighting:</p>\n\n<pre><code class=\"language-gfm\">Here's an example:\n\n​```\nfunction test() {\n  console.log(&quot;notice the blank line before this function?&quot;);\n}\n​```\n\nsyntax highlighting:\n​```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new(&quot;Hello World!&quot;)\nputs markdown.to_html\n​```\n</code></pre>\n\n<h3>Math Blocks</h3>\n\n<p>You can render <em>LaTeX</em> mathematical expressions using <strong>MathJax</strong>.</p>\n\n<p>Input <code>$$</code>, then press 'Return' key will trigger an input field which accept <em>Tex/LaTex</em> source. Following is an example:\n$$\n\\mathbf{V}<em>1 \\times \\mathbf{V}</em>2 =  \\begin{vmatrix}\n\\mathbf{i} &amp; \\mathbf{j} &amp; \\mathbf{k} \\\n\\frac{\\partial X}{\\partial u} &amp;  \\frac{\\partial Y}{\\partial u} &amp; 0 \\\n\\frac{\\partial X}{\\partial v} &amp;  \\frac{\\partial Y}{\\partial v} &amp; 0 \\\n\\end{vmatrix}\n$$</p>\n\n<p>In markdown source file, math block is <em>LaTeX</em> expression wrapped by ‘$$’ mark:</p>\n\n<pre><code class=\"language-markdown\">$$\n\\mathbf{V}_1 \\times \\mathbf{V}_2 =  \\begin{vmatrix} \n\\mathbf{i} &amp; \\mathbf{j} &amp; \\mathbf{k} \\\\\n\\frac{\\partial X}{\\partial u} &amp;  \\frac{\\partial Y}{\\partial u} &amp; 0 \\\\\n\\frac{\\partial X}{\\partial v} &amp;  \\frac{\\partial Y}{\\partial v} &amp; 0 \\\\\n\\end{vmatrix}\n$$\n</code></pre>\n\n<h3>Tables</h3>\n\n<p>Input <code>| First Header  | Second Header |</code> and press <code>return</code> key will create a table with two column.</p>\n\n<p>After table is created, focus on that table will pop up a toolbar for table, where you can resize, align, or delete table. You can also use context menu to copy and add/delete column/row.</p>\n\n<p>Following descriptions can be skipped, as markdown source code for tables are generated by typora automatically.</p>\n\n<p>In markdown source code, they look like:</p>\n\n<pre><code class=\"language-markdown\">| First Header  | Second Header |\n| ------------- | ------------- |\n| Content Cell  | Content Cell  |\n| Content Cell  | Content Cell  |\n</code></pre>\n\n<p>You can also include inline Markdown such as links, bold, italics, or strikethrough.</p>\n\n<p>Finally, by including colons : within the header row, you can define text to be left-aligned, right-aligned, or center-aligned:</p>\n\n<pre><code class=\"language-markdown\">| Left-Aligned  | Center Aligned  | Right Aligned |\n| :------------ |:---------------:| -----:|\n| col 3 is      | some wordy text | $1600 |\n| col 2 is      | centered        |   $12 |\n| zebra stripes | are neat        |    $1 |\n</code></pre>\n\n<p>A colon on the left-most side indicates a left-aligned column; a colon on the right-most side indicates a right-aligned column; a colon on both sides indicates a center-aligned column.</p>\n\n<h3>Footnotes</h3>\n\n<pre><code class=\"language-markdown\">You can create footnotes like this[^footnote].\n\n[^footnote]: Here is the *text* of the **footnote**.\n</code></pre>\n\n<p>will produce:</p>\n\n<p>You can create footnotes like this[^footnote].</p>\n\n<p>[^footnote]: Here is the <em>text</em> of the <strong>footnote</strong>.</p>\n\n<p>Mouse on the ‘footnote’ superscript to see content of the footnote.</p>\n\n<h3>Horizontal Rules</h3>\n\n<p>Input <code>***</code> or <code>---</code> on a blank line and press <code>return</code> will draw a horizontal line.</p>\n\n<hr>\n\n<h3>YAML Front Matter</h3>\n\n<p>Typora support <a href=\"http://jekyllrb.com/docs/frontmatter/\">YAML Front Matter</a> now. Input <code>---</code> at the top of the article and then press <code>Enter</code> will introduce one. Or insert one metadata block from the menu.</p>\n\n<h3>TableName of Contents (TOC)</h3>\n\n<p>Input <code>[toc]</code> then press <code>Return</code> key will create a section for “TableName of Contents” extracting all headers from one’s writing, its contents will be updated automatically.</p>\n\n<h3>Diagrams (Sequence, Flowchart and Mermaid)</h3>\n\n<p>Typora supports, <a href=\"https://bramp.github.io/js-sequence-diagrams/\">sequence</a>, <a href=\"http://flowchart.js.org/\">flowchart</a> and <a href=\"https://knsv.github.io/mermaid/#mermaid\">mermaid</a>, after this feature is enabled from preference panel.</p>\n\n<p>See this <a href=\"http://support.typora.io/Draw-Diagrams-With-Markdown/\">document</a> for detail.</p>\n\n<h2>Span Elements</h2>\n\n<p>Span elements will be parsed and rendered right after your typing. Moving cursor in middle of those span elements will expand those elements into markdown source. Following will explain the syntax of those span element.</p>\n\n<h3>Links</h3>\n\n<p>Markdown supports two style of links: inline and reference.</p>\n\n<p>In both styles, the link text is delimited by [square brackets].</p>\n\n<p>To create an inline link, use a set of regular parentheses immediately after the link text’s closing square bracket. Inside the parentheses, put the URL where you want the link to point, along with an optional title for the link, surrounded in quotes. For example:</p>\n\n<pre><code class=\"language-markdown\">This is [an example](http://example.com/ &quot;Title&quot;) inline link.\n\n[This link](http://example.net/) has no title attribute.\n</code></pre>\n\n<p>will produce:</p>\n\n<p>This is <a href=\"http://example.com/\" title=\"Title\">an example</a> inline link. (<code>&lt;p&gt;This is &lt;a href=&quot;http://example.com/&quot; title=&quot;Title&quot;&gt;</code>)</p>\n\n<p><a href=\"http://example.net/\">This link</a> has no title attribute. (<code>&lt;p&gt;&lt;a href=&quot;http://example.net/&quot;&gt;This link&lt;/a&gt; has no</code>)</p>\n\n<h4>Internal Links</h4>\n\n<p><strong>You can set the href to headers</strong>, which will create a bookmark that allow you to jump to that section after clicking. For example:</p>\n\n<p>Command(on Windows: Ctrl) + Click <a href=\"#block-elements\">This link</a> will jump to header <code>Block Elements</code>. To see how to write that, please move cursor or click that link with <code>⌘</code> key pressed to expand the element into markdown source.</p>\n\n<h4>Reference Links</h4>\n\n<p>Reference-style links use a second set of square brackets, inside which you place a label of your choosing to identify the link:</p>\n\n<pre><code class=\"language-markdown\">This is [an example][id] reference-style link.\n\nThen, anywhere in the document, you define your link label like this, on a line by itself:\n\n[id]: http://example.com/  &quot;Optional Title Here&quot;\n</code></pre>\n\n<p>In typora, they will be rendered like:</p>\n\n<p>This is <a href=\"http://example.com/\" title=\"Optional Title Here\">an example</a> reference-style link.</p>\n\n<p>The implicit link name shortcut allows you to omit the name of the link, in which case the link text itself is used as the name. Just use an empty set of square brackets — e.g., to link the word “Google” to the google.com web site, you could simply write:</p>\n\n<pre><code class=\"language-markdown\">[Google][]\nAnd then define the link:\n\n[Google]: http://google.com/\n</code></pre>\n\n<p>In typora click link will expand it for editing, command+click will open the hyperlink in web browser.</p>\n\n<h3>URLs</h3>\n\n<p>Typora allows you to insert urls as links, wrapped by <code>&lt;</code>brackets<code>&gt;</code>.</p>\n\n<p><code>&lt;i@typora.io&gt;</code> becomes <a href=\"mailto:i@typora.io\">i@typora.io</a>.</p>\n\n<p>Typora will aslo auto link standard URLs. e.g: www.google.com.</p>\n\n<h3>Images</h3>\n\n<p>Image looks similar with links, but it requires an additional <code>!</code> char before the start of link. Image syntax looks like this:</p>\n\n<pre><code class=\"language-markdown\">![Alt text](/path/to/img.jpg)\n\n![Alt text](/path/to/img.jpg &quot;Optional title&quot;)\n</code></pre>\n\n<p>You are able to use drag &amp; drop to insert image from image file or we browser. And modify the markdown source code by clicking on the image. Relative path will be used if image is in same directory or sub-directory with current editing document when drag &amp; drop.</p>\n\n<p>For more tips on images, please read <a href=\"http://support.typora.io//Images/\">http://support.typora.io//Images/</a></p>\n\n<h3>Emphasis</h3>\n\n<p>Markdown treats asterisks (<code>*</code>) and underscores (<code>_</code>) as indicators of emphasis. Text wrapped with one <code>*</code> or <code>_</code> will be wrapped with an HTML <code>&lt;em&gt;</code> tag. E.g:</p>\n\n<pre><code class=\"language-markdown\">*single asterisks*\n\n_single underscores_\n</code></pre>\n\n<p>output:</p>\n\n<p><em>single asterisks</em></p>\n\n<p><em>single underscores</em></p>\n\n<p>GFM will ignores underscores in words, which is commonly used in code and names, like this:</p>\n\n<blockquote>\n<p>wow<em>great</em>stuff</p>\n\n<p>do<em>this</em>and<em>do</em>that<em>and</em>another_thing.</p>\n</blockquote>\n\n<p>To produce a literal asterisk or underscore at a position where it would otherwise be used as an emphasis delimiter, you can backslash escape it:</p>\n\n<pre><code class=\"language-markdown\">\\*this text is surrounded by literal asterisks\\*\n</code></pre>\n\n<p>Typora recommends to use <code>*</code> symbol.</p>\n\n<h3>Strong</h3>\n\n<p>double *’s or _’s will be wrapped with an HTML <code>&lt;strong&gt;</code> tag, e.g:</p>\n\n<pre><code class=\"language-markdown\">**double asterisks**\n\n__double underscores__\n</code></pre>\n\n<p>output:</p>\n\n<p><strong>double asterisks</strong></p>\n\n<p><strong>double underscores</strong></p>\n\n<p>Typora recommends to use <code>**</code> symbol.</p>\n\n<h3>Code</h3>\n\n<p>To indicate a span of code, wrap it with backtick quotes (`). Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example:</p>\n\n<pre><code class=\"language-markdown\">Use the `printf()` function.\n</code></pre>\n\n<p>will produce:</p>\n\n<p>Use the <code>printf()</code> function.</p>\n\n<h3>Strikethrough</h3>\n\n<p>GFM adds syntax to create strikethrough text, which is missing from standard Markdown.</p>\n\n<p><code>~~Mistaken text.~~</code> becomes <del>Mistaken text.</del></p>\n\n<h3>Underline</h3>\n\n<p>Underline is powered by raw HTML.</p>\n\n<p><code>&lt;u&gt;Underline&lt;/u&gt;</code> becomes <u>Underline</u>.</p>\n\n<h3>Emoji :happy:</h3>\n\n<p>Input emoji with syntax <code>:smile:</code>.</p>\n\n<p>User can trigger auto-complete suggestions for emoji by pressing <code>ESC</code> key, or trigger it automatically after enable it on preference panel. Also, input UTF8 emoji char directly from <code>Edit</code> -&gt; <code>Emoji &amp; Symbols</code> from menu bar is also supported.</p>\n\n<h3>HTML</h3>\n\n<p>Typora cannot render html fragments. But typora can parse and render very limited HTML fragments, as an extension of Markdown, including:</p>\n\n<ul>\n<li>Underline: <code>&lt;u&gt;underline&lt;/u&gt;</code></li>\n<li>Image: <code>&lt;img src=&quot;http://www.w3.org/html/logo/img/mark-word-icon.png&quot; width=&quot;200px&quot; /&gt;</code> (And <code>width</code>, <code>height</code> attribute in HTML tag, and <code>width</code>, <code>height</code>, <code>zoom</code> style in <code>style</code> attribute will be applied.)</li>\n<li>Comments: <code>&lt;!-- This is some comments --&gt;</code></li>\n<li>Hyperlink: <code>&lt;a href=&quot;http://typora.io&quot; target=&quot;_blank&quot;&gt;link&lt;/a&gt;</code>.</li>\n</ul>\n\n<p>Most of their attributes, styles, or classes will be ignored. For other tags, typora will render them as raw HTML snippets.</p>\n\n<p>But those HTML will be exported on print or export.</p>\n\n<h3>Inline Math</h3>\n\n<p>To use this feature, first, please enable it in <code>Preference</code> Panel -&gt; <code>Markdown</code> Tab. Then use <code>$</code> to wrap TeX command, for example: <code>$\\lim_{x \\to \\infty} \\exp(-x) = 0$</code> will be rendered as LaTeX command.</p>\n\n<p>To trigger inline preview for inline math: input “$”, then press <code>ESC</code> key, then input TeX command, a preview tooltip will be visible like below:</p>\n\n<p><img src=\"http://typora.io/img/inline-math.gif\" style=\"zoom:50%;\" /></p>\n\n<h3>Subscript</h3>\n\n<p>To use this feature, first, please enable it in <code>Preference</code> Panel -&gt; <code>Markdown</code> Tab. Then use <code>~</code> to wrap subscript content, for example: <code>H~2~O</code>, <code>X~long\\ text~</code>/</p>\n\n<h3>Superscript</h3>\n\n<p>To use this feature, first, please enable it in <code>Preference</code> Panel -&gt; <code>Markdown</code> Tab. Then use <code>^</code> to wrap superscript content, for example: <code>X^2^</code>.</p>\n\n<h3>Highlight</h3>\n\n<p>To use this feature, first, please enable it in <code>Preference</code> Panel -&gt; <code>Markdown</code> Tab. Then use <code>==</code> to wrap superscript content, for example: <code>==highlight==</code>.</p>\n\n"
  },
  {
    "path": "common/testdata/TestMarkdown2Html.md",
    "content": "# Markdown For Typora\n\n## Overview\n\n**Markdown** is created by [Daring Fireball](http://daringfireball.net/), the original guideline is [here](http://daringfireball.net/projects/markdown/syntax). Its syntax, however, varies between different parsers or editors. **Typora** is using [GitHub Flavored Markdown][GFM]. \n\nPlease note that HTML fragments in markdown source will be recognized but not parsed or rendered. Also, there may be small reformatting on the original markdown source code after saving.\n\n*Outline*\n\n[TOC]\n\n## Block Elements\n\n### Paragraph and line breaks\n\nA paragraph is simply one or more consecutive lines of text. In markdown source code, paragraphs are separated by more than one blank lines. In Typora, you only need to press `Return` to create a new paragraph.\n\nPress `Shift` + `Return` to create a single line break. However, most markdown parser will ignore single line break, to make other markdown parsers recognize your line break, you can leave two whitespace at the end of the line, or insert `<br/>`.\n\n### Headers\n\nHeaders use 1-6 hash characters at the start of the line, corresponding to header levels 1-6. For example:\n\n``` markdown\n# This is an H1\n\n## This is an H2\n\n###### This is an H6\n```\n\nIn typora, input ‘#’s followed by title content, and press `Return` key will create a header.\n\n### Blockquotes\n\nMarkdown uses email-style > characters for block quoting. They are presented as:\n\n``` markdown\n> This is a blockquote with two paragraphs. This is first paragraph.\n>\n> This is second pragraph.Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.\n\n\n\n> This is another blockquote with one paragraph. There is three empty line to seperate two blockquote.\n```\n\nIn typora, just input ‘>’ followed by quote contents a block quote is  generated. Typora will insert proper ‘>’ or line break for you. Block quote inside anther block quote is allowed by adding additional levels of ‘>’. \n\n### Lists\n\nInput `* list item 1` will create an un-ordered list, the `*` symbol can be replace with `+` or `-`. \n\nInput `1. list item 1` will create an ordered list, their markdown source code is like:\n\n``` markdown\n## un-ordered list\n*   Red\n*   Green\n*   Blue\n\n## ordered list\n1.  Red\n2. \tGreen\n3.\tBlue\n```\n\n### Task List\n\nTask lists are lists with items marked as either [ ] or [x] (incomplete or complete). For example:\n\n``` markdown\n- [ ] a task list item\n- [ ] list syntax required\n- [ ] normal **formatting**, @mentions, #1234 refs\n- [ ] incomplete\n- [x] completed\n```\n\nYou can change the complete/incomplete state by click the checkbox before the item.\n\n### (Fenced) Code Blocks\n\nTypora only support fences in Github Flavored Markdown. Original code blocks in markdown is not supported.\n\nUsing fences is easy: Input \\`\\`\\` and press `return`. Add an optional language identifier after \\`\\`\\` and we'll run it through syntax highlighting:\n\n``` gfm\nHere's an example:\n\n​```\nfunction test() {\n  console.log(\"notice the blank line before this function?\");\n}\n​```\n\nsyntax highlighting:\n​```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new(\"Hello World!\")\nputs markdown.to_html\n​```\n```\n\n### Math Blocks\n\nYou can render *LaTeX* mathematical expressions using **MathJax**.\n\nInput `$$`, then press 'Return' key will trigger an input field which accept *Tex/LaTex* source. Following is an example:\n$$\n\\mathbf{V}_1 \\times \\mathbf{V}_2 =  \\begin{vmatrix} \n\\mathbf{i} & \\mathbf{j} & \\mathbf{k} \\\\\n\\frac{\\partial X}{\\partial u} &  \\frac{\\partial Y}{\\partial u} & 0 \\\\\n\\frac{\\partial X}{\\partial v} &  \\frac{\\partial Y}{\\partial v} & 0 \\\\\n\\end{vmatrix}\n$$\n\n\nIn markdown source file, math block is *LaTeX* expression wrapped by ‘$$’ mark:\n\n``` markdown\n$$\n\\mathbf{V}_1 \\times \\mathbf{V}_2 =  \\begin{vmatrix} \n\\mathbf{i} & \\mathbf{j} & \\mathbf{k} \\\\\n\\frac{\\partial X}{\\partial u} &  \\frac{\\partial Y}{\\partial u} & 0 \\\\\n\\frac{\\partial X}{\\partial v} &  \\frac{\\partial Y}{\\partial v} & 0 \\\\\n\\end{vmatrix}\n$$\n```\n\n### Tables\n\nInput `| First Header  | Second Header |` and press `return` key will create a table with two column.\n\nAfter table is created, focus on that table will pop up a toolbar for table, where you can resize, align, or delete table. You can also use context menu to copy and add/delete column/row.\n\nFollowing descriptions can be skipped, as markdown source code for tables are generated by typora automatically.\n\nIn markdown source code, they look like:\n\n``` markdown\n| First Header  | Second Header |\n| ------------- | ------------- |\n| Content Cell  | Content Cell  |\n| Content Cell  | Content Cell  |\n```\n\nYou can also include inline Markdown such as links, bold, italics, or strikethrough.\n\nFinally, by including colons : within the header row, you can define text to be left-aligned, right-aligned, or center-aligned:\n\n``` markdown\n| Left-Aligned  | Center Aligned  | Right Aligned |\n| :------------ |:---------------:| -----:|\n| col 3 is      | some wordy text | $1600 |\n| col 2 is      | centered        |   $12 |\n| zebra stripes | are neat        |    $1 |\n```\n\nA colon on the left-most side indicates a left-aligned column; a colon on the right-most side indicates a right-aligned column; a colon on both sides indicates a center-aligned column.\n\n### Footnotes\n\n``` markdown\nYou can create footnotes like this[^footnote].\n\n[^footnote]: Here is the *text* of the **footnote**.\n```\n\nwill produce:\n\nYou can create footnotes like this[^footnote].\n\n[^footnote]: Here is the *text* of the **footnote**.\n\nMouse on the ‘footnote’ superscript to see content of the footnote.\n\n### Horizontal Rules\n\nInput `***` or `---` on a blank line and press `return` will draw a horizontal line.\n\n------\n\n### YAML Front Matter\n\nTypora support [YAML Front Matter](http://jekyllrb.com/docs/frontmatter/) now. Input `---` at the top of the article and then press `Enter` will introduce one. Or insert one metadata block from the menu.\n\n### TableName of Contents (TOC)\n\nInput `[toc]` then press `Return` key will create a section for “TableName of Contents” extracting all headers from one’s writing, its contents will be updated automatically. \n\n### Diagrams (Sequence, Flowchart and Mermaid)\n\nTypora supports, [sequence](https://bramp.github.io/js-sequence-diagrams/), [flowchart](http://flowchart.js.org/) and [mermaid](https://knsv.github.io/mermaid/#mermaid), after this feature is enabled from preference panel.\n\nSee this [document](http://support.typora.io/Draw-Diagrams-With-Markdown/) for detail.\n\n## Span Elements\n\nSpan elements will be parsed and rendered right after your typing. Moving cursor in middle of those span elements will expand those elements into markdown source. Following will explain the syntax of those span element.\n\n### Links\n\nMarkdown supports two style of links: inline and reference.\n\nIn both styles, the link text is delimited by [square brackets].\n\nTo create an inline link, use a set of regular parentheses immediately after the link text’s closing square bracket. Inside the parentheses, put the URL where you want the link to point, along with an optional title for the link, surrounded in quotes. For example:\n\n``` markdown\nThis is [an example](http://example.com/ \"Title\") inline link.\n\n[This link](http://example.net/) has no title attribute.\n```\n\nwill produce:\n\nThis is [an example](http://example.com/\"Title\") inline link. (`<p>This is <a href=\"http://example.com/\" title=\"Title\">`)\n\n[This link](http://example.net/) has no title attribute. (`<p><a href=\"http://example.net/\">This link</a> has no`)\n\n#### Internal Links\n\n**You can set the href to headers**, which will create a bookmark that allow you to jump to that section after clicking. For example:\n\nCommand(on Windows: Ctrl) + Click [This link](#block-elements) will jump to header `Block Elements`. To see how to write that, please move cursor or click that link with `⌘` key pressed to expand the element into markdown source.\n\n#### Reference Links \n\nReference-style links use a second set of square brackets, inside which you place a label of your choosing to identify the link:\n\n``` markdown\nThis is [an example][id] reference-style link.\n\nThen, anywhere in the document, you define your link label like this, on a line by itself:\n\n[id]: http://example.com/  \"Optional Title Here\"\n```\n\nIn typora, they will be rendered like:\n\nThis is [an example][id] reference-style link.\n\n[id]: http://example.com/\t\"Optional Title Here\"\n\nThe implicit link name shortcut allows you to omit the name of the link, in which case the link text itself is used as the name. Just use an empty set of square brackets — e.g., to link the word “Google” to the google.com web site, you could simply write:\n\n``` markdown\n[Google][]\nAnd then define the link:\n\n[Google]: http://google.com/\n```\n\nIn typora click link will expand it for editing, command+click will open the hyperlink in web browser.\n\n### URLs\n\nTypora allows you to insert urls as links, wrapped by `<`brackets`>`.\n\n`<i@typora.io>` becomes <i@typora.io>.\n\nTypora will aslo auto link standard URLs. e.g: www.google.com.\n\n### Images\n\nImage looks similar with links, but it requires an additional `!` char before the start of link. Image syntax looks like this:\n\n``` markdown\n![Alt text](/path/to/img.jpg)\n\n![Alt text](/path/to/img.jpg \"Optional title\")\n```\n\nYou are able to use drag & drop to insert image from image file or we browser. And modify the markdown source code by clicking on the image. Relative path will be used if image is in same directory or sub-directory with current editing document when drag & drop.\n\nFor more tips on images, please read <http://support.typora.io//Images/>\n\n### Emphasis\n\nMarkdown treats asterisks (`*`) and underscores (`_`) as indicators of emphasis. Text wrapped with one `*` or `_` will be wrapped with an HTML `<em>` tag. E.g:\n\n``` markdown\n*single asterisks*\n\n_single underscores_\n```\n\noutput: \n\n*single asterisks*\n\n_single underscores_\n\nGFM will ignores underscores in words, which is commonly used in code and names, like this:\n\n> wow_great_stuff\n>\n> do_this_and_do_that_and_another_thing.\n\nTo produce a literal asterisk or underscore at a position where it would otherwise be used as an emphasis delimiter, you can backslash escape it:\n\n``` markdown\n\\*this text is surrounded by literal asterisks\\*\n```\n\nTypora recommends to use `*` symbol.\n\n### Strong\n\ndouble *’s or _’s will be wrapped with an HTML `<strong>` tag, e.g:\n\n``` markdown\n**double asterisks**\n\n__double underscores__\n```\n\noutput:\n\n**double asterisks**\n\n__double underscores__\n\nTypora recommends to use `**` symbol.\n\n### Code\n\nTo indicate a span of code, wrap it with backtick quotes (`). Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example:\n\n``` markdown\nUse the `printf()` function.\n```\n\nwill produce:\n\nUse the `printf()` function.\n\n### Strikethrough\n\nGFM adds syntax to create strikethrough text, which is missing from standard Markdown.\n\n`~~Mistaken text.~~` becomes ~~Mistaken text.~~\n\n### Underline\n\nUnderline is powered by raw HTML.\n\n`<u>Underline</u>` becomes <u>Underline</u>.\n\n### Emoji :happy:\n\nInput emoji with syntax `:smile:`. \n\nUser can trigger auto-complete suggestions for emoji by pressing `ESC` key, or trigger it automatically after enable it on preference panel. Also, input UTF8 emoji char directly from `Edit` -> `Emoji & Symbols` from menu bar is also supported. \n\n### HTML\n\nTypora cannot render html fragments. But typora can parse and render very limited HTML fragments, as an extension of Markdown, including:\n\n- Underline: `<u>underline</u>`\n- Image: `<img src=\"http://www.w3.org/html/logo/img/mark-word-icon.png\" width=\"200px\" />` (And `width`, `height` attribute in HTML tag, and `width`, `height`, `zoom` style in `style` attribute will be applied.)\n- Comments: `<!-- This is some comments -->`\n- Hyperlink: `<a href=\"http://typora.io\" target=\"_blank\">link</a>`.\n\nMost of their attributes, styles, or classes will be ignored. For other tags, typora will render them as raw HTML snippets. \n\nBut those HTML will be exported on print or export.\n\n### Inline Math\n\nTo use this feature, first, please enable it in `Preference` Panel -> `Markdown` Tab. Then use `$` to wrap TeX command, for example: `$\\lim_{x \\to \\infty} \\exp(-x) = 0$` will be rendered as LaTeX command. \n\nTo trigger inline preview for inline math: input “$”, then press `ESC` key, then input TeX command, a preview tooltip will be visible like below:\n\n<img src=\"http://typora.io/img/inline-math.gif\" style=\"zoom:50%;\" />\n\n### Subscript\n\nTo use this feature, first, please enable it in `Preference` Panel -> `Markdown` Tab. Then use `~` to wrap subscript content, for example: `H~2~O`, `X~long\\ text~`/\n\n### Superscript\n\nTo use this feature, first, please enable it in `Preference` Panel -> `Markdown` Tab. Then use `^` to wrap superscript content, for example: `X^2^`.\n\n### Highlight\n\nTo use this feature, first, please enable it in `Preference` Panel -> `Markdown` Tab. Then use `==` to wrap superscript content, for example: `==highlight==`. \n\n[GFM]: https://help.github.com/articles/github-flavored-markdown/\n"
  },
  {
    "path": "common/testdata/TestMarkdownHTMLHeader.golden",
    "content": ""
  },
  {
    "path": "common/testdata/TestParseDSN.golden",
    "content": "\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3306\",\n    Schema:               \"\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@hostname:3307/database\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3307\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@hostname:3307/database?charset=utf8\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3307\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@hostname:3307\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3307\",\n    Schema:               \"information_schema\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@hostname:/database\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3306\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@:3307/database\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3307\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser@hostname/database\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3306\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:pwd:pwd@pwd/pwd@hostname/database\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"pwd:pwd@pwd/pwd\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3306\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3306\",\n    Schema:               \"information_schema\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nhostname:3307/database\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3307\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\n@hostname:3307/database\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3307\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\n@hostname\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3306\",\n    Schema:               \"information_schema\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nhostname\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3306\",\n    Schema:               \"information_schema\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\n@/database\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3306\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\n@hostname:3307\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"hostname:3307\",\n    Schema:               \"information_schema\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\n@:3307/database\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3307\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\n:3307/database\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3307\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"\",\n    Loc:                  \"\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"3s\",\n    ReadTimeout:          \"\",\n    WriteTimeout:         \"\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\n/database\n&common.Dsn{\n    User:                 \"\",\n    Password:             \"\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3306\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser@unix(/path/to/socket)/dbname\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"\",\n    Net:                  \"unix\",\n    Addr:                 \"/path/to/socket\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nroot:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local\n&common.Dsn{\n    User:                 \"root\",\n    Password:             \"pw\",\n    Net:                  \"unix\",\n    Addr:                 \"/tmp/mysql.sock\",\n    Schema:               \"myDatabase\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"Local\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@tcp(localhost:5555)/dbname?tls=skip-verify\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"localhost:5555\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"skip-verify\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@tcp(localhost:5555)/dbname?autocommit=true\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"localhost:5555\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {\"autocommit\":\"true\"},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@/dbname?sql_mode=TRADITIONAL\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3306\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {\"sql_mode\":\"TRADITIONAL\"},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"[de:ad:be:ef::ca:fe]:80\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"1m30s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?collation=utf8mb4_unicode_ci\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"[de:ad:be:ef::ca:fe]:80\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_unicode_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nid:password@tcp(your-amazonaws-uri.com:3306)/dbname\n&common.Dsn{\n    User:                 \"id\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"your-amazonaws-uri.com:3306\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser@cloudsql(project-id:instance-name)/dbname\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"\",\n    Net:                  \"cloudsql\",\n    Addr:                 \"project-id:instance-name\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser@cloudsql(project-id:regionname:instance-name)/dbname\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"\",\n    Net:                  \"cloudsql\",\n    Addr:                 \"project-id:regionname:instance-name\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@tcp/dbname?sys_var=esc%40ped\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3306\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {\"sys_var\":\"esc@ped\"},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@/dbname\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3306\",\n    Schema:               \"dbname\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@/\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"127.0.0.1:3306\",\n    Schema:               \"\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"0s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\nuser:password@tcp(localhost:3307)/database?timeout=5s\n&common.Dsn{\n    User:                 \"user\",\n    Password:             \"password\",\n    Net:                  \"tcp\",\n    Addr:                 \"localhost:3307\",\n    Schema:               \"database\",\n    Charset:              \"utf8\",\n    Collation:            \"utf8mb4_general_ci\",\n    Loc:                  \"UTC\",\n    TLS:                  \"\",\n    ServerPubKey:         \"\",\n    MaxAllowedPacket:     4194304,\n    Params:               {},\n    Timeout:              \"5s\",\n    ReadTimeout:          \"0s\",\n    WriteTimeout:         \"0s\",\n    AllowNativePasswords: true,\n    AllowOldPasswords:    false,\n    Disable:              false,\n    Version:              99999,\n}\n"
  },
  {
    "path": "common/testdata/TestPrintConfiguration.golden",
    "content": "online-dsn:\n  user: root\n  password: '********'\n  net: tcp\n  addr: 127.0.0.1:3306\n  schema: sakila\n  charset: utf8\n  collation: utf8mb4_general_ci\n  loc: UTC\n  tls: \"\"\n  server-public-key: \"\"\n  max-allowed-packet: 4194304\n  params:\n    charset: utf8\n  timeout: 3s\n  read-timeout: 0s\n  write-timeout: 0s\n  allow-native-passwords: true\n  allow-old-passwords: false\n  disable: false\ntest-dsn:\n  user: root\n  password: '********'\n  net: tcp\n  addr: 127.0.0.1:3306\n  schema: sakila\n  charset: utf8\n  collation: utf8mb4_general_ci\n  loc: UTC\n  tls: \"\"\n  server-public-key: \"\"\n  max-allowed-packet: 4194304\n  params:\n    charset: utf8\n  timeout: 3s\n  read-timeout: 0s\n  write-timeout: 0s\n  allow-native-passwords: true\n  allow-old-passwords: false\n  disable: false\nallow-online-as-test: true\ndisable-version-check: false\ndrop-test-temporary: true\ncleanup-test-database: false\nonly-syntax-check: false\nsampling-statistic-target: 100\nsampling: true\nsampling-condition: \"\"\nprofiling: false\ntrace: false\nexplain: true\ndelimiter: ;\nlog-level: 7\nlog-output: soar.log\nreport-type: markdown\nreport-css: \"\"\nreport-javascript: \"\"\nreport-title: SQL优化分析报告\nmarkdown-extensions: 94\nmarkdown-html-flags: 0\nignore-rules:\n- COL.011\nrewrite-rules:\n- delimiter\n- orderbynull\n- groupbyconst\n- dmlorderby\n- having\n- star2columns\n- insertcolumns\n- distinctstar\nblacklist: \"\"\nmax-join-table-count: 5\nmax-group-by-cols-count: 5\nmax-distinct-count: 5\nmax-index-cols-count: 5\nmax-text-cols-count: 2\nmax-total-rows: 9999999\nmax-query-cost: 9999\nspaghetti-query-length: 2048\nallow-drop-index: false\nmax-in-count: 10\nmax-index-bytes-percolumn: 767\nmax-index-bytes: 3072\nallow-charsets:\n- utf8\n- utf8mb4\nallow-collates: []\nallow-engines:\n- innodb\nmax-index-count: 10\nmax-column-count: 40\nmax-value-count: 100\nindex-prefix: idx_\nunique-key-prefix: uk_\nmax-subquery-depth: 5\nmax-varchar-length: 1024\ncolumn-not-allow-type:\n- json\n- text\n- boolean\nmin-cardinality: 0\nexplain-sql-report-type: pretty\nexplain-type: extended\nexplain-format: traditional\nexplain-warn-select-type:\n- \"\"\nexplain-warn-access-type:\n- ALL\nexplain-max-keys: 3\nexplain-min-keys: 0\nexplain-max-rows: 10000\nexplain-warn-extra:\n- Using temporary\n- Using filesort\nexplain-max-filtered: 100\nexplain-warn-scalability:\n- O(n)\nshow-warnings: false\nshow-last-query-cost: false\nquery: \"\"\nlist-heuristic-rules: false\nlist-rewrite-rules: false\nlist-test-sqls: false\nlist-report-types: false\nverbose: false\ndry-run: true\nmax-pretty-sql-length: 1024\n"
  },
  {
    "path": "common/testdata/TestRemoveBOM.golden",
    "content": "select col from tb c = 1;\n [239 187 191]\n"
  },
  {
    "path": "common/testdata/TestStringStorageReq.golden",
    "content": "char(10) armscii8 10\nchar(256) armscii8 255\nbinary(10) armscii8 10\nbinary(256) armscii8 255\nvarchar(10) armscii8 11\nvarbinary(10) armscii8 11\nenum('G','PG','PG-13','R','NC-17') armscii8 1\nset('one', 'two') armscii8 1\nnot_exist armscii8 0\nchar(-1) armscii8 0\nchar(10) ascii 10\nchar(256) ascii 255\nbinary(10) ascii 10\nbinary(256) ascii 255\nvarchar(10) ascii 11\nvarbinary(10) ascii 11\nenum('G','PG','PG-13','R','NC-17') ascii 1\nset('one', 'two') ascii 1\nnot_exist ascii 0\nchar(-1) ascii 0\nchar(10) big5 20\nchar(256) big5 510\nbinary(10) big5 10\nbinary(256) big5 255\nvarchar(10) big5 21\nvarbinary(10) big5 21\nenum('G','PG','PG-13','R','NC-17') big5 1\nset('one', 'two') big5 1\nnot_exist big5 0\nchar(-1) big5 0\nchar(10) binary 10\nchar(256) binary 255\nbinary(10) binary 10\nbinary(256) binary 255\nvarchar(10) binary 11\nvarbinary(10) binary 11\nenum('G','PG','PG-13','R','NC-17') binary 1\nset('one', 'two') binary 1\nnot_exist binary 0\nchar(-1) binary 0\nchar(10) cp1250 10\nchar(256) cp1250 255\nbinary(10) cp1250 10\nbinary(256) cp1250 255\nvarchar(10) cp1250 11\nvarbinary(10) cp1250 11\nenum('G','PG','PG-13','R','NC-17') cp1250 1\nset('one', 'two') cp1250 1\nnot_exist cp1250 0\nchar(-1) cp1250 0\nchar(10) cp1251 10\nchar(256) cp1251 255\nbinary(10) cp1251 10\nbinary(256) cp1251 255\nvarchar(10) cp1251 11\nvarbinary(10) cp1251 11\nenum('G','PG','PG-13','R','NC-17') cp1251 1\nset('one', 'two') cp1251 1\nnot_exist cp1251 0\nchar(-1) cp1251 0\nchar(10) cp1256 10\nchar(256) cp1256 255\nbinary(10) cp1256 10\nbinary(256) cp1256 255\nvarchar(10) cp1256 11\nvarbinary(10) cp1256 11\nenum('G','PG','PG-13','R','NC-17') cp1256 1\nset('one', 'two') cp1256 1\nnot_exist cp1256 0\nchar(-1) cp1256 0\nchar(10) cp1257 10\nchar(256) cp1257 255\nbinary(10) cp1257 10\nbinary(256) cp1257 255\nvarchar(10) cp1257 11\nvarbinary(10) cp1257 11\nenum('G','PG','PG-13','R','NC-17') cp1257 1\nset('one', 'two') cp1257 1\nnot_exist cp1257 0\nchar(-1) cp1257 0\nchar(10) cp850 10\nchar(256) cp850 255\nbinary(10) cp850 10\nbinary(256) cp850 255\nvarchar(10) cp850 11\nvarbinary(10) cp850 11\nenum('G','PG','PG-13','R','NC-17') cp850 1\nset('one', 'two') cp850 1\nnot_exist cp850 0\nchar(-1) cp850 0\nchar(10) cp852 10\nchar(256) cp852 255\nbinary(10) cp852 10\nbinary(256) cp852 255\nvarchar(10) cp852 11\nvarbinary(10) cp852 11\nenum('G','PG','PG-13','R','NC-17') cp852 1\nset('one', 'two') cp852 1\nnot_exist cp852 0\nchar(-1) cp852 0\nchar(10) cp866 10\nchar(256) cp866 255\nbinary(10) cp866 10\nbinary(256) cp866 255\nvarchar(10) cp866 11\nvarbinary(10) cp866 11\nenum('G','PG','PG-13','R','NC-17') cp866 1\nset('one', 'two') cp866 1\nnot_exist cp866 0\nchar(-1) cp866 0\nchar(10) cp932 20\nchar(256) cp932 510\nbinary(10) cp932 10\nbinary(256) cp932 255\nvarchar(10) cp932 21\nvarbinary(10) cp932 21\nenum('G','PG','PG-13','R','NC-17') cp932 1\nset('one', 'two') cp932 1\nnot_exist cp932 0\nchar(-1) cp932 0\nchar(10) dec8 10\nchar(256) dec8 255\nbinary(10) dec8 10\nbinary(256) dec8 255\nvarchar(10) dec8 11\nvarbinary(10) dec8 11\nenum('G','PG','PG-13','R','NC-17') dec8 1\nset('one', 'two') dec8 1\nnot_exist dec8 0\nchar(-1) dec8 0\nchar(10) eucjpms 30\nchar(256) eucjpms 765\nbinary(10) eucjpms 10\nbinary(256) eucjpms 255\nvarchar(10) eucjpms 31\nvarbinary(10) eucjpms 31\nenum('G','PG','PG-13','R','NC-17') eucjpms 1\nset('one', 'two') eucjpms 1\nnot_exist eucjpms 0\nchar(-1) eucjpms 0\nchar(10) euckr 20\nchar(256) euckr 510\nbinary(10) euckr 10\nbinary(256) euckr 255\nvarchar(10) euckr 21\nvarbinary(10) euckr 21\nenum('G','PG','PG-13','R','NC-17') euckr 1\nset('one', 'two') euckr 1\nnot_exist euckr 0\nchar(-1) euckr 0\nchar(10) gb18030 40\nchar(256) gb18030 1020\nbinary(10) gb18030 10\nbinary(256) gb18030 255\nvarchar(10) gb18030 41\nvarbinary(10) gb18030 41\nenum('G','PG','PG-13','R','NC-17') gb18030 1\nset('one', 'two') gb18030 1\nnot_exist gb18030 0\nchar(-1) gb18030 0\nchar(10) gb2312 20\nchar(256) gb2312 510\nbinary(10) gb2312 10\nbinary(256) gb2312 255\nvarchar(10) gb2312 21\nvarbinary(10) gb2312 21\nenum('G','PG','PG-13','R','NC-17') gb2312 1\nset('one', 'two') gb2312 1\nnot_exist gb2312 0\nchar(-1) gb2312 0\nchar(10) gbk 20\nchar(256) gbk 510\nbinary(10) gbk 10\nbinary(256) gbk 255\nvarchar(10) gbk 21\nvarbinary(10) gbk 21\nenum('G','PG','PG-13','R','NC-17') gbk 1\nset('one', 'two') gbk 1\nnot_exist gbk 0\nchar(-1) gbk 0\nchar(10) geostd8 10\nchar(256) geostd8 255\nbinary(10) geostd8 10\nbinary(256) geostd8 255\nvarchar(10) geostd8 11\nvarbinary(10) geostd8 11\nenum('G','PG','PG-13','R','NC-17') geostd8 1\nset('one', 'two') geostd8 1\nnot_exist geostd8 0\nchar(-1) geostd8 0\nchar(10) greek 10\nchar(256) greek 255\nbinary(10) greek 10\nbinary(256) greek 255\nvarchar(10) greek 11\nvarbinary(10) greek 11\nenum('G','PG','PG-13','R','NC-17') greek 1\nset('one', 'two') greek 1\nnot_exist greek 0\nchar(-1) greek 0\nchar(10) hebrew 10\nchar(256) hebrew 255\nbinary(10) hebrew 10\nbinary(256) hebrew 255\nvarchar(10) hebrew 11\nvarbinary(10) hebrew 11\nenum('G','PG','PG-13','R','NC-17') hebrew 1\nset('one', 'two') hebrew 1\nnot_exist hebrew 0\nchar(-1) hebrew 0\nchar(10) hp8 10\nchar(256) hp8 255\nbinary(10) hp8 10\nbinary(256) hp8 255\nvarchar(10) hp8 11\nvarbinary(10) hp8 11\nenum('G','PG','PG-13','R','NC-17') hp8 1\nset('one', 'two') hp8 1\nnot_exist hp8 0\nchar(-1) hp8 0\nchar(10) keybcs2 10\nchar(256) keybcs2 255\nbinary(10) keybcs2 10\nbinary(256) keybcs2 255\nvarchar(10) keybcs2 11\nvarbinary(10) keybcs2 11\nenum('G','PG','PG-13','R','NC-17') keybcs2 1\nset('one', 'two') keybcs2 1\nnot_exist keybcs2 0\nchar(-1) keybcs2 0\nchar(10) koi8r 10\nchar(256) koi8r 255\nbinary(10) koi8r 10\nbinary(256) koi8r 255\nvarchar(10) koi8r 11\nvarbinary(10) koi8r 11\nenum('G','PG','PG-13','R','NC-17') koi8r 1\nset('one', 'two') koi8r 1\nnot_exist koi8r 0\nchar(-1) koi8r 0\nchar(10) koi8u 10\nchar(256) koi8u 255\nbinary(10) koi8u 10\nbinary(256) koi8u 255\nvarchar(10) koi8u 11\nvarbinary(10) koi8u 11\nenum('G','PG','PG-13','R','NC-17') koi8u 1\nset('one', 'two') koi8u 1\nnot_exist koi8u 0\nchar(-1) koi8u 0\nchar(10) latin1 10\nchar(256) latin1 255\nbinary(10) latin1 10\nbinary(256) latin1 255\nvarchar(10) latin1 11\nvarbinary(10) latin1 11\nenum('G','PG','PG-13','R','NC-17') latin1 1\nset('one', 'two') latin1 1\nnot_exist latin1 0\nchar(-1) latin1 0\nchar(10) latin2 10\nchar(256) latin2 255\nbinary(10) latin2 10\nbinary(256) latin2 255\nvarchar(10) latin2 11\nvarbinary(10) latin2 11\nenum('G','PG','PG-13','R','NC-17') latin2 1\nset('one', 'two') latin2 1\nnot_exist latin2 0\nchar(-1) latin2 0\nchar(10) latin5 10\nchar(256) latin5 255\nbinary(10) latin5 10\nbinary(256) latin5 255\nvarchar(10) latin5 11\nvarbinary(10) latin5 11\nenum('G','PG','PG-13','R','NC-17') latin5 1\nset('one', 'two') latin5 1\nnot_exist latin5 0\nchar(-1) latin5 0\nchar(10) latin7 10\nchar(256) latin7 255\nbinary(10) latin7 10\nbinary(256) latin7 255\nvarchar(10) latin7 11\nvarbinary(10) latin7 11\nenum('G','PG','PG-13','R','NC-17') latin7 1\nset('one', 'two') latin7 1\nnot_exist latin7 0\nchar(-1) latin7 0\nchar(10) macce 10\nchar(256) macce 255\nbinary(10) macce 10\nbinary(256) macce 255\nvarchar(10) macce 11\nvarbinary(10) macce 11\nenum('G','PG','PG-13','R','NC-17') macce 1\nset('one', 'two') macce 1\nnot_exist macce 0\nchar(-1) macce 0\nchar(10) macroman 10\nchar(256) macroman 255\nbinary(10) macroman 10\nbinary(256) macroman 255\nvarchar(10) macroman 11\nvarbinary(10) macroman 11\nenum('G','PG','PG-13','R','NC-17') macroman 1\nset('one', 'two') macroman 1\nnot_exist macroman 0\nchar(-1) macroman 0\nchar(10) sjis 20\nchar(256) sjis 510\nbinary(10) sjis 10\nbinary(256) sjis 255\nvarchar(10) sjis 21\nvarbinary(10) sjis 21\nenum('G','PG','PG-13','R','NC-17') sjis 1\nset('one', 'two') sjis 1\nnot_exist sjis 0\nchar(-1) sjis 0\nchar(10) swe7 10\nchar(256) swe7 255\nbinary(10) swe7 10\nbinary(256) swe7 255\nvarchar(10) swe7 11\nvarbinary(10) swe7 11\nenum('G','PG','PG-13','R','NC-17') swe7 1\nset('one', 'two') swe7 1\nnot_exist swe7 0\nchar(-1) swe7 0\nchar(10) tis620 10\nchar(256) tis620 255\nbinary(10) tis620 10\nbinary(256) tis620 255\nvarchar(10) tis620 11\nvarbinary(10) tis620 11\nenum('G','PG','PG-13','R','NC-17') tis620 1\nset('one', 'two') tis620 1\nnot_exist tis620 0\nchar(-1) tis620 0\nchar(10) ucs2 20\nchar(256) ucs2 510\nbinary(10) ucs2 10\nbinary(256) ucs2 255\nvarchar(10) ucs2 21\nvarbinary(10) ucs2 21\nenum('G','PG','PG-13','R','NC-17') ucs2 1\nset('one', 'two') ucs2 1\nnot_exist ucs2 0\nchar(-1) ucs2 0\nchar(10) ujis 30\nchar(256) ujis 765\nbinary(10) ujis 10\nbinary(256) ujis 255\nvarchar(10) ujis 31\nvarbinary(10) ujis 31\nenum('G','PG','PG-13','R','NC-17') ujis 1\nset('one', 'two') ujis 1\nnot_exist ujis 0\nchar(-1) ujis 0\nchar(10) utf16 40\nchar(256) utf16 1020\nbinary(10) utf16 10\nbinary(256) utf16 255\nvarchar(10) utf16 41\nvarbinary(10) utf16 41\nenum('G','PG','PG-13','R','NC-17') utf16 1\nset('one', 'two') utf16 1\nnot_exist utf16 0\nchar(-1) utf16 0\nchar(10) utf16le 40\nchar(256) utf16le 1020\nbinary(10) utf16le 10\nbinary(256) utf16le 255\nvarchar(10) utf16le 41\nvarbinary(10) utf16le 41\nenum('G','PG','PG-13','R','NC-17') utf16le 1\nset('one', 'two') utf16le 1\nnot_exist utf16le 0\nchar(-1) utf16le 0\nchar(10) utf32 40\nchar(256) utf32 1020\nbinary(10) utf32 10\nbinary(256) utf32 255\nvarchar(10) utf32 41\nvarbinary(10) utf32 41\nenum('G','PG','PG-13','R','NC-17') utf32 1\nset('one', 'two') utf32 1\nnot_exist utf32 0\nchar(-1) utf32 0\nchar(10) utf8 30\nchar(256) utf8 765\nbinary(10) utf8 10\nbinary(256) utf8 255\nvarchar(10) utf8 31\nvarbinary(10) utf8 31\nenum('G','PG','PG-13','R','NC-17') utf8 1\nset('one', 'two') utf8 1\nnot_exist utf8 0\nchar(-1) utf8 0\nchar(10) utf8mb4 40\nchar(256) utf8mb4 1020\nbinary(10) utf8mb4 10\nbinary(256) utf8mb4 255\nvarchar(10) utf8mb4 41\nvarbinary(10) utf8mb4 41\nenum('G','PG','PG-13','R','NC-17') utf8mb4 1\nset('one', 'two') utf8mb4 1\nnot_exist utf8mb4 0\nchar(-1) utf8mb4 0\n"
  },
  {
    "path": "common/testdata/UTF-8.bom.sql",
    "content": "﻿select col from tb c = 1;\n"
  },
  {
    "path": "common/testdata/chardet_BIG5.txt",
    "content": "\n"
  },
  {
    "path": "common/testdata/chardet_GB-18030.txt",
    "content": "\n"
  },
  {
    "path": "common/testdata/chardet_UTF-8.txt",
    "content": "中文\n"
  },
  {
    "path": "common/tricks.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"sort\"\n\n\t\"github.com/tidwall/gjson\"\n)\n\n// GoldenDiff 从 gofmt 学来的测试方法\n// https://medium.com/soon-london/testing-with-golden-files-in-go-7fccc71c43d3\nfunc GoldenDiff(f func(), name string, update *bool) error {\n\tvar b bytes.Buffer\n\tw := bufio.NewWriter(&b)\n\tstr := captureOutput(f)\n\t_, err := w.WriteString(str)\n\tif err != nil {\n\t\tLog.Warning(err.Error())\n\t}\n\terr = w.Flush()\n\tif err != nil {\n\t\tLog.Warning(err.Error())\n\t}\n\n\tgp := filepath.Join(\"testdata\", name+\".golden\")\n\tif *update {\n\t\tif err = ioutil.WriteFile(gp, b.Bytes(), 0644); err != nil {\n\t\t\terr = fmt.Errorf(\"%s failed to update golden file: %s\", name, err)\n\t\t\treturn err\n\t\t}\n\t}\n\tg, err := ioutil.ReadFile(gp)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"%s failed reading .golden: %s\", name, err)\n\t}\n\tif !bytes.Equal(b.Bytes(), g) {\n\t\terr = fmt.Errorf(\"%s does not match .golden file\", name)\n\t}\n\treturn err\n}\n\n// captureOutput 获取函数标准输出\nfunc captureOutput(f func()) string {\n\t// keep backup of the real stdout\n\toldStdout := os.Stdout\n\tr, w, _ := os.Pipe()\n\tos.Stdout = w\n\n\t// copy the output in a separate goroutine so printing can't block indefinitely\n\toutC := make(chan string)\n\tgo func() {\n\t\tbuf, err := ioutil.ReadAll(r)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\toutC <- string(buf)\n\t}()\n\n\t// execute function\n\tf()\n\n\t// back to normal state\n\tif err := w.Close(); err != nil {\n\t\tpanic(err)\n\t}\n\tos.Stdout = oldStdout // restoring the real stdout\n\tout := <-outC\n\tos.Stdout = oldStdout\n\treturn out\n}\n\n// SortedKey sort map[string]interface{}, use in range clause\nfunc SortedKey(m interface{}) []string {\n\tvar keys []string\n\tswitch reflect.TypeOf(m).Kind() {\n\tcase reflect.Map:\n\t\tswitch reflect.TypeOf(m).Key().Kind() {\n\t\tcase reflect.String:\n\t\t\tfor _, k := range reflect.ValueOf(m).MapKeys() {\n\t\t\t\tkeys = append(keys, k.String())\n\t\t\t}\n\t\t}\n\t}\n\tsort.Strings(keys)\n\treturn keys\n}\n\n// jsonFind internal function\nfunc jsonFind(json string, name string, find *[]string) (next []string) {\n\tres := gjson.Parse(json)\n\tres.ForEach(func(key, value gjson.Result) bool {\n\t\tif key.String() == name {\n\t\t\t*find = append(*find, value.String())\n\t\t}\n\t\tswitch value.Type {\n\t\tcase gjson.Number, gjson.True, gjson.False, gjson.Null:\n\t\tdefault:\n\t\t\t// String, JSON\n\t\t\tnext = append(next, value.String())\n\t\t}\n\t\treturn true // keep iterating\n\t})\n\treturn next\n}\n\n// JSONFind iterate find name in json\n// TODO: for complicate SQL JSONFind will run a long time for json interactions.\nfunc JSONFind(json string, name string) []string {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tvar find []string\n\tnext := []string{json}\n\tfor len(next) > 0 {\n\t\tvar tmpNext []string\n\t\tfor _, subJSON := range next {\n\t\t\ttmpNext = append(tmpNext, jsonFind(subJSON, name, &find)...)\n\t\t}\n\t\tnext = tmpNext\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n\treturn find\n}\n\n// RemoveDuplicatesItem remove duplicate item from list\nfunc RemoveDuplicatesItem(duplicate []string) []string {\n\tm := make(map[string]bool)\n\tfor _, item := range duplicate {\n\t\tif _, ok := m[item]; !ok {\n\t\t\tm[item] = true\n\t\t}\n\t}\n\n\tvar unique []string\n\tfor item := range m {\n\t\tunique = append(unique, item)\n\t}\n\tsort.Strings(unique)\n\treturn unique\n}\n"
  },
  {
    "path": "common/tricks_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage common\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestCaptureOutput(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tc1 := make(chan string, 1)\n\t// test output buf large than 65535\n\tlength := 1<<16 + 1\n\tgo func() {\n\t\tstr := captureOutput(\n\t\t\tfunc() {\n\t\t\t\tvar str []string\n\t\t\t\tfor i := 0; i < length; i++ {\n\t\t\t\t\tstr = append(str, \"a\")\n\t\t\t\t}\n\t\t\t\tfmt.Println(strings.Join(str, \"\"))\n\t\t\t},\n\t\t)\n\t\tc1 <- str\n\t}()\n\n\tselect {\n\tcase res := <-c1:\n\t\tif len(res) <= length {\n\t\t\tt.Errorf(\"want %d, got %d\", length, len(res))\n\t\t}\n\tcase <-time.After(1 * time.Second):\n\t\tt.Error(\"capture timeout, pipe read hangup\")\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestJSONFind(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tjsons := []string{\n\t\t`\n\t{\n\t\t\"Collate\":{\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}\n`,\n\t\t`{\n  \"programmers\": [\n    {\n      \"firstName\": \"Janet\",\n      \"Collate\": \"McLaughlin\",\n    }, {\n      \"firstName\": \"Elliotte\",\n      \"Collate\": \"Hunter\",\n    }, {\n      \"firstName\": \"Jason\",\n      \"Collate\": \"Harold\",\n    }\n  ]\n}`,\n\t\t`\n{\n  \"widget\": {\n    \"debug\": \"on\",\n    \"Collate\": {\n      \"title\": \"Sample Konfabulator Widget\",\n      \"name\": \"main_window\",\n      \"width\": 500,\n      \"height\": 500\n    },\n    \"image\": {\n      \"src\": \"Images/Sun.png\",\n      \"hOffset\": 250,\n      \"vOffset\": 250,\n      \"alignment\": \"center\"\n    },\n    \"text\": {\n      \"data\": \"Click Here\",\n      \"size\": 36,\n      \"style\": \"bold\",\n      \"vOffset\": 100,\n      \"alignment\": \"center\",\n      \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n    }\n  }\n}\n`,\n\t\t`\n[\n  {\n    \"SQLCache\": true,\n    \"CalcFoundRows\": false,\n    \"StraightJoin\": false,\n    \"Priority\": 0,\n    \"Distinct\": false,\n    \"From\": {\n      \"TableRefs\": {\n        \"Left\": {\n          \"Source\": {\n            \"Schema\": {\n              \"O\": \"\",\n              \"L\": \"\"\n            },\n            \"Name\": {\n              \"O\": \"tb\",\n              \"L\": \"tb\"\n            },\n            \"DBInfo\": null,\n            \"TableInfo\": null,\n            \"IndexHints\": null\n          },\n          \"AsName\": {\n            \"O\": \"\",\n            \"L\": \"\"\n          }\n        },\n        \"Right\": null,\n        \"Tp\": 0,\n        \"On\": null,\n        \"Using\": null,\n        \"NaturalJoin\": false,\n        \"StraightJoin\": false\n      }\n    },\n    \"Where\": {\n      \"Type\": {\n        \"Tp\": 0,\n        \"Flag\": 0,\n        \"Flen\": 0,\n        \"Decimal\": 0,\n        \"Charset\": \"\",\n        \"Collate\": \"\",\n        \"Elems\": null\n      },\n      \"Op\": 4,\n      \"L\": {\n        \"Type\": {\n          \"Tp\": 0,\n          \"Flag\": 0,\n          \"Flen\": 0,\n          \"Decimal\": 0,\n          \"Charset\": \"\",\n          \"Collate\": \"\",\n          \"Elems\": null\n        },\n        \"Op\": 7,\n        \"L\": {\n          \"Type\": {\n            \"Tp\": 0,\n            \"Flag\": 0,\n            \"Flen\": 0,\n            \"Decimal\": 0,\n            \"Charset\": \"\",\n            \"Collate\": \"\",\n            \"Elems\": null\n          },\n          \"Name\": {\n            \"Schema\": {\n              \"O\": \"\",\n              \"L\": \"\"\n            },\n            \"Table\": {\n              \"O\": \"\",\n              \"L\": \"\"\n            },\n            \"Name\": {\n              \"O\": \"col3\",\n              \"L\": \"col3\"\n            }\n          },\n          \"Refer\": null\n        },\n        \"R\": {\n          \"Type\": {\n            \"Tp\": 8,\n            \"Flag\": 128,\n            \"Flen\": 1,\n            \"Decimal\": 0,\n            \"Charset\": \"binary\",\n            \"Collate\": \"binary\",\n            \"Elems\": null\n          }\n        }\n      },\n      \"R\": {\n        \"Type\": {\n          \"Tp\": 0,\n          \"Flag\": 0,\n          \"Flen\": 0,\n          \"Decimal\": 0,\n          \"Charset\": \"\",\n          \"Collate\": \"\",\n          \"Elems\": null\n        },\n        \"Op\": 1,\n        \"L\": {\n          \"Type\": {\n            \"Tp\": 0,\n            \"Flag\": 0,\n            \"Flen\": 0,\n            \"Decimal\": 0,\n            \"Charset\": \"\",\n            \"Collate\": \"\",\n            \"Elems\": null\n          },\n          \"Op\": 7,\n          \"L\": {\n            \"Type\": {\n              \"Tp\": 0,\n              \"Flag\": 0,\n              \"Flen\": 0,\n              \"Decimal\": 0,\n              \"Charset\": \"\",\n              \"Collate\": \"\",\n              \"Elems\": null\n            },\n            \"Name\": {\n              \"Schema\": {\n                \"O\": \"\",\n                \"L\": \"\"\n              },\n              \"Table\": {\n                \"O\": \"\",\n                \"L\": \"\"\n              },\n              \"Name\": {\n                \"O\": \"col3\",\n                \"L\": \"col3\"\n              }\n            },\n            \"Refer\": null\n          },\n          \"R\": {\n            \"Type\": {\n              \"Tp\": 8,\n              \"Flag\": 128,\n              \"Flen\": 1,\n              \"Decimal\": 0,\n              \"Charset\": \"binary\",\n              \"Collate\": \"binary\",\n              \"Elems\": null\n            }\n          }\n        },\n        \"R\": {\n          \"Type\": {\n            \"Tp\": 0,\n            \"Flag\": 0,\n            \"Flen\": 0,\n            \"Decimal\": 0,\n            \"Charset\": \"\",\n            \"Collate\": \"\",\n            \"Elems\": null\n          },\n          \"Op\": 7,\n          \"L\": {\n            \"Type\": {\n              \"Tp\": 0,\n              \"Flag\": 0,\n              \"Flen\": 0,\n              \"Decimal\": 0,\n              \"Charset\": \"\",\n              \"Collate\": \"\",\n              \"Elems\": null\n            },\n            \"Op\": 7,\n            \"L\": {\n              \"Type\": {\n                \"Tp\": 0,\n                \"Flag\": 0,\n                \"Flen\": 0,\n                \"Decimal\": 0,\n                \"Charset\": \"\",\n                \"Collate\": \"\",\n                \"Elems\": null\n              },\n              \"Name\": {\n                \"Schema\": {\n                  \"O\": \"\",\n                  \"L\": \"\"\n                },\n                \"Table\": {\n                  \"O\": \"\",\n                  \"L\": \"\"\n                },\n                \"Name\": {\n                  \"O\": \"col1\",\n                  \"L\": \"col1\"\n                }\n              },\n              \"Refer\": null\n            },\n            \"R\": {\n              \"Type\": {\n                \"Tp\": 0,\n                \"Flag\": 0,\n                \"Flen\": 0,\n                \"Decimal\": 0,\n                \"Charset\": \"\",\n                \"Collate\": \"\",\n                \"Elems\": null\n              },\n              \"Name\": {\n                \"Schema\": {\n                  \"O\": \"\",\n                  \"L\": \"\"\n                },\n                \"Table\": {\n                  \"O\": \"\",\n                  \"L\": \"\"\n                },\n                \"Name\": {\n                  \"O\": \"col2\",\n                  \"L\": \"col2\"\n                }\n              },\n              \"Refer\": null\n            }\n          },\n          \"R\": {\n            \"Type\": {\n              \"Tp\": 253,\n              \"Flag\": 0,\n              \"Flen\": 3,\n              \"Decimal\": -1,\n              \"Charset\": \"utf8mb4\",\n              \"Collate\": \"utf8mb4_bin\",\n              \"Elems\": null\n            }\n          }\n        }\n      }\n    },\n    \"Fields\": {\n      \"Fields\": [\n        {\n          \"Offset\": 7,\n          \"WildCard\": {\n            \"Table\": {\n              \"O\": \"\",\n              \"L\": \"\"\n            },\n            \"Schema\": {\n              \"O\": \"\",\n              \"L\": \"\"\n            }\n          },\n          \"Expr\": null,\n          \"AsName\": {\n            \"O\": \"\",\n            \"L\": \"\"\n          },\n          \"Auxiliary\": false\n        }\n      ]\n    },\n    \"GroupBy\": null,\n    \"Having\": null,\n    \"WindowSpecs\": null,\n    \"OrderBy\": null,\n    \"Limit\": null,\n    \"LockTp\": 0,\n    \"TableHints\": null,\n    \"IsAfterUnionDistinct\": false,\n    \"IsInBraces\": false\n  }\n]\n`,\n\t}\n\terr := GoldenDiff(func() {\n\t\tfor _, json := range jsons {\n\t\t\tresult := JSONFind(json, \"Collate\")\n\t\t\tfmt.Println(result)\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n\nfunc TestRemoveDuplicatesItem(t *testing.T) {\n\tLog.Debug(\"Entering function: %s\", GetFunctionName())\n\tunique := RemoveDuplicatesItem([]string{\"a\", \"a\", \"b\", \"c\"})\n\tif len(unique) != 3 {\n\t\tt.Error(\"string list length should 3\", unique)\n\t}\n\tLog.Debug(\"Exiting function: %s\", GetFunctionName())\n}\n"
  },
  {
    "path": "common/version.go",
    "content": "package common\n\n// -version输出信息\nvar (\n\tVersion  = \"No Version Provided\"\n\tCompile  = \"\"\n\tBranch   = \"\"\n\tGitDirty = \"\"\n\tDevPath  = \"\"\n)\n"
  },
  {
    "path": "database/doc.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package database will take cover of communicate with mysql database.\npackage database\n"
  },
  {
    "path": "database/explain.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/ast\"\n\t\"github.com/XiaoMi/soar/common\"\n\n\ttidb \"github.com/pingcap/parser/ast\"\n\t\"github.com/tidwall/gjson\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// format_type 支持的输出格式\n// https://dev.mysql.com/doc/refman/5.7/en/explain-output.html\nconst (\n\tTraditionalFormatExplain = iota // 默认输出\n\tJSONFormatExplain               // JSON格式输出\n)\n\n// ExplainFormatType EXPLAIN 支持的 FORMAT_TYPE\nvar ExplainFormatType = map[string]int{\n\t\"traditional\": 0,\n\t\"json\":        1,\n}\n\n// explain_type\nconst (\n\tTraditionalExplainType = iota // 默认转出\n\tExtendedExplainType           // EXTENDED输出\n\tPartitionsExplainType         // PARTITIONS输出\n)\n\n// ExplainType EXPLAIN命令支持的参数\nvar ExplainType = map[string]int{\n\t\"traditional\": 0,\n\t\"extended\":    1,\n\t\"partitions\":  2,\n}\n\n// 为TraditionalFormatExplain准备的结构体 { start\n\n// ExplainInfo 用于存放Explain信息\ntype ExplainInfo struct {\n\tSQL           string\n\tExplainFormat int\n\tExplainRows   []ExplainRow\n\tExplainJSON   *ExplainJSON\n\tWarnings      []ExplainWarning\n\tQueryCost     float64\n}\n\n// ExplainRow 单行Explain\ntype ExplainRow struct {\n\tID           int\n\tSelectType   string\n\tTableName    string\n\tPartitions   string // explain partitions\n\tAccessType   string\n\tPossibleKeys []string\n\tKey          string\n\tKeyLen       string // 索引长度，如果发生了index_merge， KeyLen 格式为 N,N，所以不能定义为整型\n\tRef          []string\n\tRows         int64\n\tFiltered     float64 // 5.6 JSON, 5.7+, 5.5 EXTENDED\n\tScalability  string  // O(1), O(n), O(log n), O(log n)+\n\tExtra        string\n}\n\n// ExplainWarning explain extended 后 SHOW WARNINGS 输出的结果\ntype ExplainWarning struct {\n\tLevel   string\n\tCode    int\n\tMessage string\n}\n\n// 为TraditionalFormatExplain准备的结构体 end }\n\n// 为JSONFormatExplain准备的结构体 { start\n\n// ExplainJSONCostInfo JSON\ntype ExplainJSONCostInfo struct {\n\tReadCost        string `json:\"read_cost\"`\n\tEvalCost        string `json:\"eval_cost\"`\n\tPrefixCost      string `json:\"prefix_cost\"`\n\tDataReadPerJoin string `json:\"data_read_per_join\"`\n\tQueryCost       string `json:\"query_cost\"`\n\tSortCost        string `json:\"sort_cost\"`\n}\n\n// ExplainJSONMaterializedFromSubquery JSON\ntype ExplainJSONMaterializedFromSubquery struct {\n\tUsingTemporaryTable bool                   `json:\"using_temporary_table\"`\n\tDependent           bool                   `json:\"dependent\"`\n\tCacheable           bool                   `json:\"cacheable\"`\n\tQueryBlock          *ExplainJSONQueryBlock `json:\"query_block\"`\n}\n\n// 该变量用于存放 JSON 到 Traditional 模式的所有 ExplainJSONTable\nvar explainJSONTables []*ExplainJSONTable\n\n// ExplainJSONTable JSON\ntype ExplainJSONTable struct {\n\tTableName                string                              `json:\"table_name\"`\n\tAccessType               string                              `json:\"access_type\"`\n\tPossibleKeys             []string                            `json:\"possible_keys\"`\n\tKey                      string                              `json:\"key\"`\n\tUsedKeyParts             []string                            `json:\"used_key_parts\"`\n\tKeyLength                string                              `json:\"key_length\"`\n\tRef                      []string                            `json:\"ref\"`\n\tRowsExaminedPerScan      int64                               `json:\"rows_examined_per_scan\"`\n\tRowsProducedPerJoin      int                                 `json:\"rows_produced_per_join\"`\n\tFiltered                 string                              `json:\"filtered\"`\n\tUsingIndex               bool                                `json:\"using_index\"`\n\tUsingIndexForGroupBy     bool                                `json:\"using_index_for_group_by\"`\n\tCostInfo                 ExplainJSONCostInfo                 `json:\"cost_info\"`\n\tUsedColumns              []string                            `json:\"used_columns\"`\n\tAttachedCondition        string                              `json:\"attached_condition\"`\n\tAttachedSubqueries       []ExplainJSONSubqueries             `json:\"attached_subqueries\"`\n\tMaterializedFromSubquery ExplainJSONMaterializedFromSubquery `json:\"materialized_from_subquery\"`\n}\n\n// ExplainJSONNestedLoop JSON\ntype ExplainJSONNestedLoop struct {\n\tTable ExplainJSONTable `json:\"table\"`\n}\n\n// ExplainJSONBufferResult JSON\ntype ExplainJSONBufferResult struct {\n\tUsingTemporaryTable bool                    `json:\"using_temporary_table\"`\n\tNestedLoop          []ExplainJSONNestedLoop `json:\"nested_loop\"`\n\tTable               ExplainJSONTable        `json:\"table\"`\n}\n\n// ExplainJSONSubqueries JSON\ntype ExplainJSONSubqueries struct {\n\tDependent  bool                  `json:\"dependent\"`\n\tCacheable  bool                  `json:\"cacheable\"`\n\tQueryBlock ExplainJSONQueryBlock `json:\"query_block\"`\n}\n\n// ExplainJSONGroupingOperation JSON\ntype ExplainJSONGroupingOperation struct {\n\tUsingTemporaryTable bool                    `json:\"using_temporary_table\"`\n\tUsingFilesort       bool                    `json:\"using_filesort\"`\n\tTable               ExplainJSONTable        `json:\"table\"`\n\tCostInfo            ExplainJSONCostInfo     `json:\"cost_info\"`\n\tNestedLoop          []ExplainJSONNestedLoop `json:\"nested_loop\"`\n\tGroupBySubqueries   []ExplainJSONSubqueries `json:\"group_by_subqueries\"`\n}\n\n// ExplainJSONDuplicatesRemoval JSON\ntype ExplainJSONDuplicatesRemoval struct {\n\tUsingTemporaryTable bool                         `json:\"using_temporary_table\"`\n\tUsingFilesort       bool                         `json:\"using_filesort\"`\n\tBufferResult        ExplainJSONBufferResult      `json:\"buffer_result\"`\n\tGroupingOperation   ExplainJSONGroupingOperation `json:\"grouping_operation\"`\n\tTable               ExplainJSONTable             `json:\"table\"`\n}\n\n// ExplainJSONOrderingOperation JSON\ntype ExplainJSONOrderingOperation struct {\n\tUsingFilesort           bool                         `json:\"using_filesort\"`\n\tTable                   ExplainJSONTable             `json:\"table\"`\n\tDuplicatesRemoval       ExplainJSONDuplicatesRemoval `json:\"duplicates_removal\"`\n\tGroupingOperation       ExplainJSONGroupingOperation `json:\"grouping_operation\"`\n\tOrderbySubqueries       []ExplainJSONSubqueries      `json:\"order_by_subqueries\"`\n\tOptimizedAwaySubqueries []ExplainJSONSubqueries      `json:\"optimized_away_subqueries\"`\n}\n\n// ExplainJSONQueryBlock JSON\ntype ExplainJSONQueryBlock struct {\n\tSelectID                int                          `json:\"select_id\"`\n\tCostInfo                ExplainJSONCostInfo          `json:\"cost_info\"`\n\tTable                   ExplainJSONTable             `json:\"table\"`\n\tNestedLoop              []ExplainJSONNestedLoop      `json:\"nested_loop\"`\n\tOrderingOperation       ExplainJSONOrderingOperation `json:\"ordering_operation\"`\n\tGroupingOperation       ExplainJSONGroupingOperation `json:\"grouping_operation\"`\n\tOptimizedAwaySubqueries []ExplainJSONSubqueries      `json:\"optimized_away_subqueries\"`\n\tHavingSubqueries        []ExplainJSONSubqueries      `json:\"having_subqueries\"`\n\tSelectListSubqueries    []ExplainJSONSubqueries      `json:\"select_list_subqueries\"`\n\tUpdateValueSubqueries   []ExplainJSONSubqueries      `json:\"update_value_subqueries\"`\n\tQuerySpecifications     []ExplainJSONSubqueries      `json:\"query_specifications\"`\n\tUnionResult             ExplainJSONUnionResult       `json:\"union_result\"`\n\tMessage                 string                       `json:\"message\"`\n}\n\n// ExplainJSONUnionResult JSON\ntype ExplainJSONUnionResult struct {\n\tUsingTemporaryTable bool                    `json:\"using_temporary_table\"`\n\tTableName           string                  `json:\"table_name\"`\n\tAccessType          string                  `json:\"access_type\"`\n\tQuerySpecifications []ExplainJSONSubqueries `json:\"query_specifications\"`\n}\n\n// ExplainJSON 根结点\ntype ExplainJSON struct {\n\tQueryBlock ExplainJSONQueryBlock `json:\"query_block\"`\n}\n\n// 为JSONFormatExplain准备的结构体 end }\n\n// ExplainKeyWords 需要解释的关键字\nvar ExplainKeyWords = []string{\n\t\"access_type\",\n\t\"attached_condition\",\n\t\"attached_subqueries\",\n\t\"buffer_result\",\n\t\"cacheable\",\n\t\"cost_info\",\n\t\"data_read_per_join\",\n\t\"dependent\",\n\t\"duplicates_removal\",\n\t\"eval_cost\",\n\t\"filtered\",\n\t\"group_by_subqueries\",\n\t\"grouping_operation\",\n\t\"having_subqueries\",\n\t\"key\",\n\t\"key_length\",\n\t\"materialized_from_subquery\",\n\t\"message\",\n\t\"nested_loop\",\n\t\"optimized_away_subqueries\",\n\t\"order_by_subqueries\",\n\t\"ordering_operation\",\n\t\"possible_keys\",\n\t\"prefix_cost\",\n\t\"query_block\",\n\t\"query_cost\",\n\t\"query_specifications\",\n\t\"read_cost\",\n\t\"ref\",\n\t\"rows_examined_per_scan\",\n\t\"rows_produced_per_join\",\n\t\"select_id\",\n\t\"select_list_subqueries\",\n\t\"sort_cost\",\n\t\"table\",\n\t\"table_name\",\n\t\"union_result\",\n\t\"update_value_subqueries\",\n\t\"used_columns\",\n\t\"used_key_parts\",\n\t\"using_filesort\",\n\t\"using_index\",\n\t\"using_index_for_group_by\",\n\t\"using_temporary_table\",\n}\n\n/*\n// ExplainColumnIndent EXPLAIN表头\nvar ExplainColumnIndent = map[string]string{\n\t\"id\":            \"id为SELECT的标识符. 它是在SELECT查询中的顺序编号. 如果这一行表示其他行的union结果, 这个值可以为空. 在这种情况下, table列会显示为形如<union M,N>, 表示它是id为M和N的查询行的联合结果.\",\n\t\"select_type\":   \"表示查询的类型. \",\n\t\"table\":         \"输出行所引用的表.\",\n\t\"type\":          \"type显示连接使用的类型, 有关不同类型的描述, 请参见解释连接类型.\",\n\t\"possible_keys\": \"指出MySQL能在该表中使用哪些索引有助于查询. 如果为空, 说明没有可用的索引.\",\n\t\"key\":           \"MySQL实际从possible_keys选择使用的索引. 如果为NULL, 则没有使用索引. 很少情况下, MySQL会选择优化不足的索引. 这种情况下, 可以在select语句中使用USE INDEX (indexname)来强制使用一个索引或者用IGNORE INDEX (indexname)来强制MySQL忽略索引.\",\n\t\"key_len\":       \"显示MySQL使用索引键的长度. 如果key是NULL, 则key_len为NULL. 使用的索引的长度. 在不损失精确性的情况下, 长度越短越好.\",\n\t\"ref\":           \"显示索引的哪一列被使用了.\",\n\t\"rows\":          \"表示MySQL认为必须检查的用来返回请求数据的行数.\",\n\t\"filtered\":      \"表示返回结果的行占需要读到的行(rows列的值)的百分比.\",\n\t\"Extra\":         \"该列显示MySQL在查询过程中的一些详细信息, MySQL查询优化器执行查询的过程中对查询计划的重要补充信息.\",\n}\n*/\n\n// ExplainSelectType EXPLAIN中SELECT TYPE会出现的类型\nvar ExplainSelectType = map[string]string{\n\t\"SIMPLE\":               \"简单SELECT(不使用UNION或子查询等).\",\n\t\"PRIMARY\":              \"最外层的select.\",\n\t\"UNION\":                \"UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集.\",\n\t\"DEPENDENT\":            \"UNION中的第二个或后面的SELECT查询, 依赖于外部查询的结果集.\",\n\t\"UNION RESULT\":         \"UNION查询的结果集.\",\n\t\"SUBQUERY\":             \"子查询中的第一个SELECT查询, 不依赖于外部查询的结果集.\",\n\t\"DEPENDENT SUBQUERY\":   \"子查询中的第一个SELECT查询, 依赖于外部查询的结果集.\",\n\t\"DERIVED\":              \"用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里.\",\n\t\"MATERIALIZED\":         \"Materialized subquery.\",\n\t\"UNCACHEABLE SUBQUERY\": \"结果集不能被缓存的子查询, 必须重新为外层查询的每一行进行评估.\",\n\t\"UNCACHEABLE UNION\":    \"UNION中的第二个或后面的select查询, 属于不可缓存的子查询（类似于UNCACHEABLE SUBQUERY）.\",\n}\n\n// ExplainAccessType EXPLAIN中ACCESS TYPE会出现的类型\nvar ExplainAccessType = map[string]string{\n\t\"system\":          \"这是const连接类型的一种特例, 该表仅有一行数据(=系统表).\",\n\t\"const\":           `const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.`,\n\t\"eq_ref\":          `除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.`,\n\t\"ref\":             `连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.`,\n\t\"fulltext\":        \"查询时使用 FULLTEXT 索引.\",\n\t\"ref_or_null\":     \"如同ref, 但是MySQL必须在初次查找的结果里找出null条目, 然后进行二次查找.\",\n\t\"index_merge\":     `表示使用了索引合并优化方法. 在这种情况下. key列包含了使用的索引的清单, key_len包含了使用的索引的最长的关键元素. 详情请见 8.2.1.4, “Index Merge Optimization”.`,\n\t\"unique_subquery\": `在某些IN查询中使用此种类型，而不是常规的ref:'value IN (SELECT PrimaryKey FROM SingleTable WHERE SomeExpr)'.`,\n\t\"index_subquery\":  \"在某些IN查询中使用此种类型, 与 unique_subquery 类似, 但是查询的是非唯一索引性索引.\",\n\t\"range\":           `只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.`,\n\t\"index\":           \"全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\",\n\t\"ALL\":             `最坏的情况, 从头到尾全表扫描.`,\n}\n\n// ExplainScalability ACCESS TYPE对应的运算复杂度 [AccessType]scalability map\nvar ExplainScalability = map[string]string{\n\t\"NULL\":            \"NULL\",\n\t\"ALL\":             \"O(n)\",\n\t\"index\":           \"O(n)\",\n\t\"range\":           \"O(log n)+\",\n\t\"index_subquery\":  \"O(log n)+\",\n\t\"unique_subquery\": \"O(log n)+\",\n\t\"index_merge\":     \"O(log n)+\",\n\t\"ref_or_null\":     \"O(log n)+\",\n\t\"fulltext\":        \"O(log n)+\",\n\t\"ref\":             \"O(log n)\",\n\t\"eq_ref\":          \"O(log n)\",\n\t\"const\":           \"O(1)\",\n\t\"system\":          \"O(1)\",\n}\n\n// ExplainExtra Extra信息解读\n// https://dev.mysql.com/doc/refman/8.0/en/explain-output.html\n// sql/opt_explain_traditional.cc:traditional_extra_tags\nvar ExplainExtra = map[string]string{\n\t\"Using temporary\":                   \"表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\",\n\t\"Using filesort\":                    \"MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\",\n\t\"Using index condition\":             \"在5.6版本后加入的新特性（Index Condition Pushdown）。Using index condition 会先条件过滤索引，过滤完索引后找到所有符合索引条件的数据行，随后用 WHERE 子句中的其他条件去过滤这些数据行。\",\n\t\"Range checked for each record\":     \"MySQL没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用。\",\n\t\"Using where with pushed condition\": \"这是一个仅仅在NDBCluster存储引擎中才会出现的信息，打开condition pushdown优化功能才可能被使用。\",\n\t\"Using MRR\":                         \"使用了 MRR Optimization IO 层面进行了优化，减少 IO 方面的开销。\",\n\t\"Skip_open_table\":                   \"Tables are read using the Multi-Range Read optimization strategy.\",\n\t\"Open_frm_only\":                     \"Table files do not need to be opened. The information is already available from the data dictionary.\",\n\t\"Open_full_table\":                   \"Unoptimized information lookup. Table information must be read from the data dictionary and by reading table files.\",\n\t\"Scanned\":                           \"This indicates how many directory scans the server performs when processing a query for INFORMATION_SCHEMA tables.\",\n\t\"Using index for group-by\":          \"Similar to the Using index table access method, Using index for group-by indicates that MySQL found an index that can be used to retrieve all columns of a GROUP BY or DISTINCT query without any extra disk access to the actual table. Additionally, the index is used in the most efficient way so that for each group, only a few index entries are read.\",\n\t\"Start temporary\":                   \"This indicates temporary table use for the semi-join Duplicate Weedout strategy.Start\",\n\t\"End temporary\":                     \"This indicates temporary table use for the semi-join Duplicate Weedout strategy.End\",\n\t\"FirstMatch\":                        \"The semi-join FirstMatch join shortcutting strategy is used for tbl_name.\",\n\t\"Materialize\":                       \"Materialized subquery\",\n\t\"Start materialize\":                 \"Materialized subquery Start\",\n\t\"End materialize\":                   \"Materialized subquery End\",\n\t\"unique row not found\":              \"For a query such as SELECT ... FROM tbl_name, no rows satisfy the condition for a UNIQUE index or PRIMARY KEY on the table.\",\n\t// \"Scan\":                                                \"\",\n\t// \"Impossible ON condition\":                             \"\",\n\t// \"Ft_hints:\":                                           \"\",\n\t// \"Backward index scan\":                                 \"\",\n\t// \"Recursive\":                                           \"\",\n\t// \"Table function:\":                                     \"\",\n\t\"Index dive skipped due to FORCE\":                     \"This item applies to NDB tables only. It means that MySQL Cluster is using the Condition Pushdown optimization to improve the efficiency of a direct comparison between a nonindexed column and a constant. In such cases, the condition is “pushed down” to the cluster's data nodes and is evaluated on all data nodes simultaneously. This eliminates the need to send nonmatching rows over the network, and can speed up such queries by a factor of 5 to 10 times over cases where Condition Pushdown could be but is not used.\",\n\t\"Impossible WHERE noticed after reading const tables\": \"查询了所有const(和system)表, 但发现WHERE查询条件不起作用.\",\n\t\"Using where\":                              \"WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\",\n\t\"Using join buffer\":                        \"从已有连接中找被读入缓存的数据, 并且通过缓存来完成与当前表的连接.\",\n\t\"Using index\":                              \"只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\",\n\t\"const row not found\":                      \"空表做类似 SELECT ... FROM tbl_name 的查询操作.\",\n\t\"Distinct\":                                 \"MySQL is looking for distinct values, so it stops searching for more rows for the current row combination after it has found the first matching row.\",\n\t\"Full scan on NULL key\":                    \"子查询中的一种优化方式, 常见于无法通过索引访问null值.\",\n\t\"Impossible HAVING\":                        \"HAVING条件过滤没有效果, 返回已有查询的结果集.\",\n\t\"Impossible WHERE\":                         \"WHERE条件过滤没有效果, 最终是全表扫描.\",\n\t\"LooseScan\":                                \"使用半连接LooseScan策略.\",\n\t\"No matching min/max row\":                  \"没有行满足查询的条件, 如 SELECT MIN(...) FROM ... WHERE condition.\",\n\t\"no matching row in const table\":           \"对于连接查询, 列未满足唯一索引的条件或表为空.\",\n\t\"No matching rows after partition pruning\": \"对于DELETE 或 UPDATE, 优化器在分区之后, 未发现任何要删除或更新的内容. 类似查询 Impossible WHERE.\",\n\t\"No tables used\":                           \"查询没有FROM子句, 或者有一个 FROM DUAL子句.\",\n\t\"Not exists\":                               \"MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行.\",\n\t\"Plan isn't ready yet\":                     \"This value occurs with EXPLAIN FOR CONNECTION when the optimizer has not finished creating the execution plan for the statement executing in the named connection. If execution plan output comprises multiple lines, any or all of them could have this Extra value, depending on the progress of the optimizer in determining the full execution plan.\",\n\t\"Select tables optimized away\":             \"仅通过使用索引，优化器可能仅从聚合函数结果中返回一行。如：在没有 GROUP BY 子句的情况下，基于索引优化 MIN/MAX 操作，或者对于 MyISAM 存储引擎优化 COUNT(*) 操作，不必等到执行阶段再进行计算，查询执行计划生成的阶段即完成优化。\",\n\t\"Using intersect\":                          \"开启了index merge，即：对多个索引分别进行条件扫描，然后将它们各自的结果进行合并，使用的算法为：index_merge_intersection\",\n\t\"Using union\":                              \"开启了index merge，即：对多个索引分别进行条件扫描，然后将它们各自的结果进行合并，使用的算法为：index_merge_union\",\n\t\"Using sort_union\":                         \"开启了index merge，即：对多个索引分别进行条件扫描，然后将它们各自的结果进行合并，使用的算法为：index_merge_sort_union\",\n}\n\n// 提取ExplainJSON中所有的ExplainJSONTable, 将其写入全局变量explainJSONTables\n// depth只是用于debug，逻辑上并未使用\nfunc findTablesInJSON(explainJSON string, depth int) {\n\tcommon.Log.Debug(\"findTablesInJSON Enter: depth(%d), json(%s)\", depth, explainJSON)\n\t// 去除注释，语法检查\n\texplainJSON = RemoveSQLComments(explainJSON)\n\tif !gjson.Valid(explainJSON) {\n\t\treturn\n\t}\n\t// 提取所有ExplainJSONTable struct\n\tfor _, key := range ExplainKeyWords {\n\t\tresult := gjson.Get(explainJSON, key)\n\t\tif result.String() == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif key == \"table\" {\n\t\t\ttable := new(ExplainJSONTable)\n\t\t\tcommon.Log.Debug(\"findTablesInJSON FindTable: depth(%d), table(%s)\", depth, result.String)\n\t\t\terr := json.Unmarshal([]byte(result.Raw), table)\n\t\t\tcommon.LogIfError(err, \"\")\n\t\t\tif table.TableName != \"\" {\n\t\t\t\texplainJSONTables = append(explainJSONTables, table)\n\t\t\t}\n\t\t\tfindTablesInJSON(result.String(), depth+1)\n\t\t} else {\n\t\t\tcommon.Log.Debug(\"findTablesInJSON ScanOther: depth(%d), key(%s), array_len(%d), json(%s)\", depth, key, len(result.Array()), result.String)\n\t\t\tfor _, val := range result.Array() {\n\t\t\t\tif val.String() != \"\" {\n\t\t\t\t\tfindTablesInJSON(val.String(), depth+1)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfindTablesInJSON(result.String(), depth+1)\n\t\t}\n\t}\n}\n\n// FormatJSONIntoTraditional 将JSON形式转换为TRADITIONAL形式，方便前端展现\nfunc FormatJSONIntoTraditional(explainJSON string) []ExplainRow {\n\t// 查找JSON中的所有ExplainJSONTable\n\texplainJSONTables = []*ExplainJSONTable{}\n\tfindTablesInJSON(explainJSON, 0)\n\n\tvar explainRows []ExplainRow\n\tid := -1\n\tfor _, table := range explainJSONTables {\n\t\tkeyLen := table.KeyLength\n\t\tfiltered, err := strconv.ParseFloat(table.Filtered, 64)\n\t\tif err != nil {\n\t\t\tfiltered = 0.00\n\t\t}\n\t\tif filtered > 100.00 {\n\t\t\tfiltered = 100.00\n\t\t}\n\t\texplainRows = append(explainRows, ExplainRow{\n\t\t\tID:           id + 1,\n\t\t\tSelectType:   \"\",\n\t\t\tTableName:    table.TableName,\n\t\t\tPartitions:   \"NULL\",\n\t\t\tAccessType:   table.AccessType,\n\t\t\tPossibleKeys: table.PossibleKeys,\n\t\t\tKey:          table.Key,\n\t\t\tKeyLen:       keyLen,\n\t\t\tRef:          table.Ref,\n\t\t\tRows:         table.RowsExaminedPerScan,\n\t\t\tFiltered:     filtered,\n\t\t\tScalability:  ExplainScalability[table.AccessType],\n\t\t\tExtra:        \"\",\n\t\t})\n\t}\n\treturn explainRows\n}\n\n// ConvertExplainJSON2Row 将 JSON 格式转成 ROW 格式，为方便统一做优化建议\n// 但是会损失一些 JSON 特有的分析结果\nfunc ConvertExplainJSON2Row(explainJSON *ExplainJSON) []ExplainRow {\n\tbuf, err := json.Marshal(explainJSON)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn FormatJSONIntoTraditional(string(buf))\n}\n\n// 用于检测 MySQL 版本是否低于 MySQL5.6\n// 低于5.6 返回 true， 表示需要改写非 SELECT 的 SQL --> SELECT\nfunc (db *Connector) supportExplainWrite() (bool, error) {\n\tdefer func() {\n\t\terr := recover()\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"Recover supportExplainWrite() Error:\", err)\n\t\t}\n\t}()\n\n\t// 5.6以上版本支持 EXPLAIN UPDATE/DELETE 等语句，但需要开启写入\n\t// 如开启了 read_only, EXPLAIN UPDATE/DELETE 也会受限制\n\tif common.Config.TestDSN.Version >= 50600 {\n\t\treadOnly, err := db.SingleIntValue(\"read_only\")\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tsuperReadOnly, err := db.SingleIntValue(\"super_read_only\")\n\t\t// Percona, MariaDB 5.6就已经有super_read_only了，但社区版5.6还没有这个参数\n\t\tif err != nil {\n\t\t\tif !strings.Contains(err.Error(), \"Unknown system variable\") {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tsuperReadOnly = readOnly\n\t\t}\n\n\t\tif readOnly == 1 || superReadOnly == 1 {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\t}\n\n\treturn true, nil\n}\n\n// 将SQL语句转换为可以被Explain的语句，如：写转读\n// 当输出为空时，表示语法错误或不支持EXPLAIN\nfunc (db *Connector) explainAbleSQL(sql string) (string, error) {\n\tstmt, err := sqlparser.Parse(sql)\n\tif err != nil {\n\t\t// TODO: charset, collation\n\t\ttiStmt, tiErr := ast.TiParse(sql, \"\", \"\")\n\t\tif tiErr != nil {\n\t\t\tcommon.Log.Error(\"explainAbleSQL ast.TiParse Error: %v\", tiErr)\n\t\t\treturn \"\", tiErr\n\t\t}\n\n\t\tvar isSelect bool\n\t\tfor _, st := range tiStmt {\n\t\t\tswitch st.(type) {\n\t\t\t// SetOprStmt represents \"union/except/intersect statement\"\n\t\t\tcase *tidb.SelectStmt, *tidb.SetOprStmt:\n\t\t\t\tisSelect = true\n\t\t\tdefault:\n\t\t\t\tisSelect = false\n\t\t\t}\n\t\t\tif !isSelect {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif isSelect {\n\t\t\treturn sql, nil\n\t\t}\n\n\t\tcommon.Log.Error(\"explainAbleSQL sqlparser.Parse Error: %v\", err)\n\t\treturn \"\", err\n\t}\n\n\tswitch stmt.(type) {\n\tcase *sqlparser.Insert, *sqlparser.Update, *sqlparser.Delete: // REPLACE 和 INSERT 的 AST 基本相同，只是 Action 不同\n\t\t// 判断 Explain 的 SQL 是否需要被改写\n\t\tneed, err := db.supportExplainWrite()\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"explainAbleSQL db.supportExplainWrite Error: %v\", err)\n\t\t\treturn \"\", err\n\t\t}\n\t\tif need {\n\t\t\trw := ast.NewRewrite(sql)\n\t\t\tif rw != nil {\n\t\t\t\treturn rw.RewriteDML2Select().NewSQL, nil\n\t\t\t}\n\t\t}\n\t\treturn sql, nil\n\n\tcase *sqlparser.Union, *sqlparser.ParenSelect, *sqlparser.Select, sqlparser.SelectStatement:\n\t\treturn sql, nil\n\tdefault:\n\t}\n\treturn \"\", nil\n}\n\n// explainQuery 生成可执行的 explain 查询请求\nfunc (db *Connector) explainQuery(sql string, explainType int, formatType int) string {\n\tvar err error\n\tsql, err = db.explainAbleSQL(sql)\n\tif sql == \"\" || err != nil {\n\t\treturn sql\n\t} else {\n\t\t// MySQL 5.7 support MAX_EXECUTION_TIME hint\n\t\t// ref: https://dev.mysql.com/doc/refman/5.7/en/optimizer-hints.html\n\t\tre := regexp.MustCompile(`(?i)(^select)(.*)`)\n\t\tsql = re.ReplaceAllString(sql, \"SELECT /*+ MAX_EXECUTION_TIME(1000) */${2}\")\n\t}\n\n\t// 5.6以上支持 FORMAT=JSON\n\texplainFormat := \"\"\n\tswitch formatType {\n\tcase JSONFormatExplain:\n\t\tif common.Config.TestDSN.Version >= 50600 {\n\t\t\texplainFormat = \"FORMAT=JSON\"\n\t\t}\n\t}\n\n\t// 执行 explain\n\tswitch explainType {\n\tcase ExtendedExplainType:\n\t\t// 5.6以上extended关键字已经不推荐使用，8.0废弃了这个关键字\n\t\tif common.Config.TestDSN.Version >= 50600 {\n\t\t\tsql = fmt.Sprintf(\"explain %s\", sql)\n\t\t} else {\n\t\t\tsql = fmt.Sprintf(\"explain extended %s\", sql)\n\t\t}\n\tcase PartitionsExplainType:\n\t\tsql = fmt.Sprintf(\"explain partitions %s\", sql)\n\n\tdefault:\n\t\tsql = fmt.Sprintf(\"explain %s %s\", explainFormat, sql)\n\t}\n\treturn sql\n}\n\n// MySQLExplainWarnings WARNINGS信息中包含的优化器信息\nfunc MySQLExplainWarnings(exp *ExplainInfo) string {\n\tcontent := \"## MySQL优化器调优结果\\n\\n```sql\\n\"\n\tfor _, row := range exp.Warnings {\n\t\tcontent += \"\\n\" + row.Message + \"\\n\"\n\t}\n\tcontent += \"\\n```\"\n\treturn content\n}\n\n// MySQLExplainQueryCost 将last_query_cost信息补充到评审结果中\nfunc MySQLExplainQueryCost(exp *ExplainInfo) string {\n\tvar content string\n\tif exp == nil {\n\t\treturn content\n\t}\n\tif exp.QueryCost > 0 {\n\n\t\ttmp := fmt.Sprintf(\"%.3f\\n\", exp.QueryCost)\n\n\t\tcontent = \"Query cost: \"\n\t\tif exp.QueryCost > float64(common.Config.MaxQueryCost) {\n\t\t\tcontent += fmt.Sprintf(\"☠️ **%s**\", tmp)\n\t\t} else {\n\t\t\tcontent += tmp\n\t\t}\n\n\t}\n\treturn content\n}\n\n// ExplainInfoTranslator 将explain信息翻译成人能读懂的\nfunc ExplainInfoTranslator(exp *ExplainInfo) string {\n\tvar buf []string\n\tvar selectTypeBuf []string\n\tvar accessTypeBuf []string\n\tvar extraTypeBuf []string\n\tbuf = append(buf, fmt.Sprint(\"### Explain信息解读\\n\"))\n\trows := exp.ExplainRows\n\tif exp.ExplainFormat == JSONFormatExplain {\n\t\t// JSON形式遍历分析不方便，转成Row格式统一处理\n\t\trows = ConvertExplainJSON2Row(exp.ExplainJSON)\n\t}\n\tif len(rows) == 0 {\n\t\treturn \"\"\n\t}\n\n\t// SelectType信息解读\n\texplainSelectType := make(map[string]string)\n\tfor k, v := range ExplainSelectType {\n\t\texplainSelectType[k] = v\n\t}\n\tfor _, row := range rows {\n\t\tif _, ok := explainSelectType[row.SelectType]; ok {\n\t\t\tdesc := fmt.Sprintf(\"* **%s**: %s\\n\", row.SelectType, explainSelectType[row.SelectType])\n\t\t\tselectTypeBuf = append(selectTypeBuf, desc)\n\t\t\tdelete(explainSelectType, row.SelectType)\n\t\t}\n\t}\n\tif len(selectTypeBuf) > 0 {\n\t\tbuf = append(buf, fmt.Sprint(\"#### SelectType信息解读\\n\"))\n\t\tsort.Strings(selectTypeBuf)\n\t\tbuf = append(buf, strings.Join(selectTypeBuf, \"\\n\"))\n\t}\n\n\t// #### Type信息解读\n\texplainAccessType := make(map[string]string)\n\tfor k, v := range ExplainAccessType {\n\t\texplainAccessType[k] = v\n\t}\n\tfor _, row := range rows {\n\t\tif _, ok := explainAccessType[row.AccessType]; ok {\n\t\t\tvar warn bool\n\t\t\tvar desc string\n\t\t\tfor _, t := range common.Config.ExplainWarnAccessType {\n\t\t\t\tif row.AccessType == t {\n\t\t\t\t\twarn = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif warn {\n\t\t\t\tdesc = fmt.Sprintf(\"* ☠️ **%s**: %s\\n\", row.AccessType, explainAccessType[row.AccessType])\n\t\t\t} else {\n\t\t\t\tdesc = fmt.Sprintf(\"* **%s**: %s\\n\", row.AccessType, explainAccessType[row.AccessType])\n\t\t\t}\n\n\t\t\taccessTypeBuf = append(accessTypeBuf, desc)\n\t\t\tdelete(explainAccessType, row.AccessType)\n\t\t}\n\t}\n\tif len(accessTypeBuf) > 0 {\n\t\tbuf = append(buf, fmt.Sprint(\"#### Type信息解读\\n\"))\n\t\tsort.Strings(accessTypeBuf)\n\t\tbuf = append(buf, strings.Join(accessTypeBuf, \"\\n\"))\n\t}\n\n\t// #### Extra信息解读\n\tif exp.ExplainFormat != JSONFormatExplain {\n\t\texplainExtra := make(map[string]string)\n\t\tfor k, v := range ExplainExtra {\n\t\t\texplainExtra[k] = v\n\t\t}\n\t\tfor _, row := range rows {\n\t\t\tfor k, c := range explainExtra {\n\t\t\t\tif strings.Contains(row.Extra, k) {\n\t\t\t\t\tif k == \"Impossible WHERE\" && strings.Contains(row.Extra, \"Impossible WHERE noticed after reading const tables\") {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif k == \"Using index\" && strings.Contains(row.Extra, \"Using index condition\") {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\twarn := false\n\t\t\t\t\tfor _, w := range common.Config.ExplainWarnExtra {\n\t\t\t\t\t\tif k == w {\n\t\t\t\t\t\t\twarn = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif warn {\n\t\t\t\t\t\textraTypeBuf = append(extraTypeBuf, fmt.Sprintf(\"* ☠️ **%s**: %s\\n\", k, c))\n\t\t\t\t\t} else {\n\t\t\t\t\t\textraTypeBuf = append(extraTypeBuf, fmt.Sprintf(\"* **%s**: %s\\n\", k, c))\n\t\t\t\t\t}\n\t\t\t\t\tdelete(explainExtra, k)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(extraTypeBuf) > 0 {\n\t\tbuf = append(buf, fmt.Sprint(\"#### Extra信息解读\\n\"))\n\t\tsort.Strings(extraTypeBuf)\n\t\tbuf = append(buf, strings.Join(extraTypeBuf, \"\\n\"))\n\t}\n\n\treturn strings.Join(buf, \"\\n\")\n}\n\n// ParseExplainText 解析explain文本信息（很可能是用户复制粘贴得到），返回格式化数据\nfunc ParseExplainText(content string) (exp *ExplainInfo, err error) {\n\texp = &ExplainInfo{ExplainFormat: TraditionalFormatExplain}\n\n\tcontent = strings.TrimSpace(content)\n\tverticalFormat := strings.HasPrefix(content, \"*\")\n\tjsonFormat := strings.HasPrefix(content, \"{\")\n\ttraditionalFormat := strings.HasPrefix(content, \"+\")\n\n\tif verticalFormat && traditionalFormat && jsonFormat {\n\t\treturn nil, errors.New(\"not supported explain type\")\n\t}\n\n\tif verticalFormat {\n\t\texp.ExplainRows, err = parseVerticalExplainText(content)\n\t}\n\n\tif jsonFormat {\n\t\texp.ExplainFormat = JSONFormatExplain\n\t\texp.ExplainJSON, err = parseJSONExplainText(content)\n\t}\n\n\tif traditionalFormat {\n\t\texp.ExplainRows, err = parseTraditionalExplainText(content)\n\t}\n\treturn exp, err\n}\n\n// 解析文本形式传统形式Explain信息\nfunc parseTraditionalExplainText(content string) (explainRows []ExplainRow, err error) {\n\tLS := regexp.MustCompile(`^\\+`) // 华丽的分隔线:)\n\n\t// 格式正确性检查\n\tlines := strings.Split(content, \"\\n\")\n\tif len(lines) < 3 {\n\t\treturn nil, errors.New(\"explain Rows less than 3\")\n\t}\n\n\t// 提取头部，用于后续list到map的转换\n\tvar header []string\n\tfor _, h := range strings.Split(strings.Trim(lines[1], \"|\"), \"|\") {\n\t\theader = append(header, strings.TrimSpace(h))\n\t}\n\tcolIdx := make(map[string]int)\n\tfor i, item := range header {\n\t\tcolIdx[strings.ToLower(item)] = i\n\t}\n\n\t// explain format=json未把外面的框去了\n\tif strings.ToLower(header[0]) == \"explain\" {\n\t\treturn nil, errors.New(\"json format explain need remove\")\n\t}\n\n\t// 将每一列填充至ExplainRow结构体\n\tcolsMap := make(map[string]string)\n\tfor _, l := range lines[3:] {\n\t\tvar keylen string\n\t\tvar rows int64\n\t\tvar filtered float64\n\t\tvar partitions string\n\t\t// 跳过分割线\n\t\tif LS.MatchString(l) || strings.TrimSpace(l) == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\t// list到map的转换\n\t\tvar cols []string\n\t\tfor _, c := range strings.Split(strings.Trim(l, \"|\"), \"|\") {\n\t\t\tcols = append(cols, strings.TrimSpace(c))\n\t\t}\n\t\tfor item, i := range colIdx {\n\t\t\tcolsMap[item] = cols[i]\n\t\t}\n\n\t\t// 值类型转换\n\t\tid, err := strconv.Atoi(colsMap[\"id\"])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// 不存在字段给默认值\n\t\tif colsMap[\"partitions\"] == \"\" {\n\t\t\tpartitions = \"NULL\"\n\t\t} else {\n\t\t\tpartitions = colsMap[\"partitions\"]\n\t\t}\n\n\t\tkeylen = colsMap[\"key_len\"]\n\n\t\trows, err = strconv.ParseInt(colsMap[\"Rows\"], 10, 64)\n\t\tif err != nil {\n\t\t\trows = 0\n\t\t}\n\n\t\tfiltered, err = strconv.ParseFloat(colsMap[\"filtered\"], 64)\n\t\tif err != nil {\n\t\t\tfiltered = 0.00\n\t\t}\n\t\t// filtered may larger than 100.00\n\t\t// https://bugs.mysql.com/bug.php?id=34124\n\t\tif filtered >= 100.00 {\n\t\t\tfiltered = 100.00\n\t\t}\n\n\t\t// 拼接结构体\n\t\texplainRows = append(explainRows, ExplainRow{\n\t\t\tID:           id,\n\t\t\tSelectType:   colsMap[\"select_type\"],\n\t\t\tTableName:    colsMap[\"table\"],\n\t\t\tPartitions:   partitions,\n\t\t\tAccessType:   colsMap[\"type\"],\n\t\t\tPossibleKeys: strings.Split(colsMap[\"possible_keys\"], \",\"),\n\t\t\tKey:          colsMap[\"key\"],\n\t\t\tKeyLen:       keylen,\n\t\t\tRef:          strings.Split(colsMap[\"ref\"], \",\"),\n\t\t\tRows:         rows,\n\t\t\tFiltered:     filtered,\n\t\t\tScalability:  ExplainScalability[colsMap[\"type\"]],\n\t\t\tExtra:        colsMap[\"extra\"],\n\t\t})\n\t}\n\treturn explainRows, nil\n}\n\n// 解析文本形式竖排版 Explain信息\nfunc parseVerticalExplainText(content string) (explainRows []ExplainRow, err error) {\n\tvar lines []string\n\texplainRow := ExplainRow{\n\t\tPartitions: \"NULL\",\n\t\tFiltered:   0.00,\n\t}\n\tLS := regexp.MustCompile(`^\\*.*\\*$`) // 华丽的分隔线:)\n\n\t// 格式正确性检查\n\tfor _, l := range strings.Split(content, \"\\n\") {\n\t\tlines = append(lines, strings.TrimSpace(l))\n\t}\n\tif len(lines) < 11 {\n\t\treturn nil, errors.New(\"explain rows less than 11\")\n\t}\n\n\t// 将每一行填充至ExplainRow结构体\n\tfor _, l := range lines {\n\t\tif LS.MatchString(l) || strings.TrimSpace(l) == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(l, \"id:\") {\n\t\t\tid := strings.TrimPrefix(l, \"id: \")\n\t\t\texplainRow.ID, err = strconv.Atoi(id)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif strings.HasPrefix(l, \"select_type:\") {\n\t\t\texplainRow.SelectType = strings.TrimPrefix(l, \"select_type: \")\n\t\t}\n\t\tif strings.HasPrefix(l, \"table:\") {\n\t\t\texplainRow.TableName = strings.TrimPrefix(l, \"table: \")\n\t\t}\n\t\tif strings.HasPrefix(l, \"partitions:\") {\n\t\t\texplainRow.AccessType = strings.TrimPrefix(l, \"partitions: \")\n\t\t}\n\t\tif strings.HasPrefix(l, \"type:\") {\n\t\t\texplainRow.AccessType = strings.TrimPrefix(l, \"type: \")\n\t\t\texplainRow.Scalability = ExplainScalability[explainRow.AccessType]\n\t\t}\n\t\tif strings.HasPrefix(l, \"possible_keys:\") {\n\t\t\texplainRow.PossibleKeys = strings.Split(strings.TrimPrefix(l, \"possible_keys: \"), \",\")\n\t\t}\n\t\tif strings.HasPrefix(l, \"key:\") {\n\t\t\texplainRow.Key = strings.TrimPrefix(l, \"key: \")\n\t\t}\n\t\tif strings.HasPrefix(l, \"key_len:\") {\n\t\t\tkeyLen := strings.TrimPrefix(l, \"key_len: \")\n\t\t\texplainRow.KeyLen = keyLen\n\t\t}\n\t\tif strings.HasPrefix(l, \"ref:\") {\n\t\t\texplainRow.Ref = strings.Split(strings.TrimPrefix(l, \"ref: \"), \",\")\n\t\t}\n\t\tif strings.HasPrefix(l, \"Rows:\") {\n\t\t\trows := strings.TrimPrefix(l, \"Rows: \")\n\t\t\texplainRow.Rows, err = strconv.ParseInt(rows, 10, 64)\n\t\t\tif err != nil {\n\t\t\t\texplainRow.Rows = 0\n\t\t\t}\n\t\t}\n\t\tif strings.HasPrefix(l, \"filtered:\") {\n\t\t\tfiltered := strings.TrimPrefix(l, \"filtered: \")\n\t\t\texplainRow.Filtered, err = strconv.ParseFloat(filtered, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t} else if explainRow.Filtered > 100.00 {\n\t\t\t\texplainRow.Filtered = 100.00\n\t\t\t}\n\t\t}\n\t\tif strings.HasPrefix(l, \"Extra:\") {\n\t\t\texplainRow.Extra = strings.TrimPrefix(l, \"Extra: \")\n\t\t\texplainRows = append(explainRows, explainRow)\n\t\t}\n\t}\n\treturn explainRows, err\n}\n\n// 解析文本形式JSON Explain信息\nfunc parseJSONExplainText(content string) (*ExplainJSON, error) {\n\texplainJSON := new(ExplainJSON)\n\terr := json.Unmarshal([]byte(RemoveSQLComments(content)), explainJSON)\n\treturn explainJSON, err\n}\n\n// ParseExplainResult 分析 mysql 执行 explain 的结果，返回 ExplainInfo 结构化数据\nfunc ParseExplainResult(res QueryResult, formatType int) (exp *ExplainInfo, err error) {\n\texp = &ExplainInfo{\n\t\tExplainFormat: formatType,\n\t}\n\t// JSON 格式直接调用文本方式解析\n\tif formatType == JSONFormatExplain {\n\t\tif res.Rows.Next() {\n\t\t\tvar explainString string\n\t\t\terr = res.Rows.Scan(&explainString)\n\t\t\tif err != nil {\n\t\t\t\tcommon.Log.Debug(err.Error())\n\t\t\t}\n\t\t\texp.ExplainJSON, err = parseJSONExplainText(explainString)\n\t\t}\n\t\tres.Rows.Close()\n\t\treturn exp, err\n\t}\n\n\t/*\n\t\t\t\t+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+\n\t\t\t\t| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |\n\t\t\t\t+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+\n\t\t\t\t|  1 | SIMPLE      | film  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | NULL  |\n\t\t\t\t+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+\n\n\t\t        +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+\n\t\t        | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                          |\n\t\t        +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+\n\t\t        |  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | no matching row in const table |\n\t\t        +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+\n\t*/\n\n\t// Different MySQL version has different columns define\n\tvar selectType, table, partitions, accessType, possibleKeys, key, keyLen, ref, extra, rows, filtered []byte\n\texpRow := ExplainRow{}\n\texplainFields := make([]interface{}, 0)\n\tfields := map[string]interface{}{\n\t\t\"id\":            &expRow.ID,\n\t\t\"select_type\":   &selectType,\n\t\t\"table\":         &table,\n\t\t\"partitions\":    &partitions,\n\t\t\"type\":          &accessType,\n\t\t\"possible_keys\": &possibleKeys,\n\t\t\"key\":           &key,\n\t\t\"key_len\":       &keyLen,\n\t\t\"ref\":           &ref,\n\t\t\"rows\":          &rows,\n\t\t\"filtered\":      &filtered,\n\t\t\"Extra\":         &extra,\n\t}\n\tcols, err := res.Rows.Columns()\n\tcommon.LogIfError(err, \"\")\n\tvar colByPass []byte\n\tfor _, col := range cols {\n\t\tif _, ok := fields[col]; ok {\n\t\t\texplainFields = append(explainFields, fields[col])\n\t\t} else {\n\t\t\tcommon.Log.Debug(\"ParseExplainResult by pass column %s\", col)\n\t\t\texplainFields = append(explainFields, &colByPass)\n\t\t}\n\t}\n\n\t// 补全 ExplainRows\n\tvar explainRows []ExplainRow\n\tfor res.Rows.Next() {\n\t\terr := res.Rows.Scan(explainFields...)\n\t\tif err != nil {\n\t\t\tcommon.Log.Warn(err.Error())\n\t\t}\n\t\texpRow.SelectType = NullString(selectType)\n\t\texpRow.TableName = NullString(table)\n\t\texpRow.Partitions = NullString(partitions)\n\t\texpRow.AccessType = NullString(accessType)\n\t\texpRow.PossibleKeys = strings.Split(NullString(possibleKeys), \",\")\n\t\texpRow.Key = NullString(key)\n\t\texpRow.KeyLen = NullString(keyLen)\n\t\texpRow.Ref = strings.Split(NullString(ref), \",\")\n\t\texpRow.Rows = NullInt(rows)\n\t\texpRow.Filtered = NullFloat(filtered)\n\t\texpRow.Extra = NullString(extra)\n\n\t\t// MySQL bug: https://bugs.mysql.com/bug.php?id=34124\n\t\tif expRow.Filtered > 100.00 {\n\t\t\texpRow.Filtered = 100.00\n\t\t}\n\n\t\texpRow.Scalability = ExplainScalability[expRow.AccessType]\n\t\texplainRows = append(explainRows, expRow)\n\t}\n\tres.Rows.Close()\n\texp.ExplainRows = explainRows\n\n\t// check explain warning info\n\tif common.Config.ShowWarnings {\n\t\tfor res.Warning.Next() {\n\t\t\tvar expWarning ExplainWarning\n\t\t\terr = res.Warning.Scan(&expWarning.Level, &expWarning.Code, &expWarning.Message)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// 'EXTENDED' is deprecated and will be removed in a future release.\n\t\t\tif expWarning.Code != 1681 {\n\t\t\t\texp.Warnings = append(exp.Warnings, expWarning)\n\t\t\t}\n\t\t}\n\t\tres.Warning.Close()\n\t}\n\n\t// 添加 last_query_cost\n\texp.QueryCost = res.QueryCost\n\n\treturn exp, err\n}\n\n// Explain 获取 SQL 的 explain 信息\nfunc (db *Connector) Explain(sql string, explainType int, formatType int) (exp *ExplainInfo, err error) {\n\texp = &ExplainInfo{SQL: sql}\n\tif explainType != TraditionalExplainType {\n\t\tformatType = TraditionalFormatExplain\n\t}\n\n\tdefer func() {\n\t\tif e := recover(); e != nil {\n\t\t\tconst size = 4096\n\t\t\tbuf := make([]byte, size)\n\t\t\tbuf = buf[:runtime.Stack(buf, false)]\n\t\t\tcommon.Log.Error(\"Recover Explain() Error: %v\\n%v\", e, string(buf))\n\t\t\terr = errors.New(fmt.Sprint(e))\n\t\t}\n\t}()\n\n\t// 执行EXPLAIN请求\n\texp.SQL = db.explainQuery(sql, explainType, formatType)\n\tif exp.SQL == \"\" {\n\t\treturn exp, nil\n\t}\n\tres, err := db.Query(exp.SQL)\n\tif err != nil {\n\t\treturn exp, err\n\t}\n\n\t// 解析mysql结果，输出ExplainInfo\n\texp, err = ParseExplainResult(res, formatType)\n\treturn exp, err\n}\n\n// PrintMarkdownExplainTable 打印 markdown 格式的 explain table\nfunc PrintMarkdownExplainTable(exp *ExplainInfo) string {\n\tvar buf []string\n\trows := exp.ExplainRows\n\t// JSON 转换为 TRADITIONAL 格式\n\tif exp.ExplainFormat == JSONFormatExplain {\n\t\tbuf = append(buf, fmt.Sprint(\"以下为 JSON 格式转为传统格式 EXPLAIN 表格\", \"\\n\\n\"))\n\t\trows = ConvertExplainJSON2Row(exp.ExplainJSON)\n\t}\n\n\t// explain出错\n\tif len(rows) == 0 {\n\t\treturn \"\"\n\t}\n\tif exp.ExplainFormat == JSONFormatExplain {\n\t\tbuf = append(buf, fmt.Sprintln(\"| table | partitions | type | possible\\\\_keys | key | key\\\\_len | ref | rows | filtered | scalability | Extra |\"))\n\t\tbuf = append(buf, fmt.Sprintln(\"|---|---|---|---|---|---|---|---|---|---|---|\"))\n\t\tfor _, row := range rows {\n\t\t\tbuf = append(buf, fmt.Sprintln(\"|\", row.TableName, \"|\", row.Partitions, \"|\", row.AccessType,\n\t\t\t\t\"|\", strings.Join(row.PossibleKeys, \",\"), \"|\", row.Key, \"|\", row.KeyLen, \"|\",\n\t\t\t\tstrings.Join(row.Ref, \",\"), \"|\", row.Rows, \"|\", fmt.Sprintf(\"%.2f%s\", row.Filtered, \"%\"),\n\t\t\t\t\"|\", row.Scalability, \"|\", row.Extra, \"|\"))\n\t\t}\n\t} else {\n\t\tbuf = append(buf, fmt.Sprintln(\"| id | select\\\\_type | table | partitions | type | possible_keys | key | key\\\\_len | ref | rows | filtered | scalability | Extra |\"))\n\t\tbuf = append(buf, fmt.Sprintln(\"|---|---|---|---|---|---|---|---|---|---|---|---|---|\"))\n\t\tfor _, row := range rows {\n\t\t\t// 加粗\n\t\t\trows := fmt.Sprint(row.Rows)\n\t\t\tif row.Rows >= common.Config.ExplainMaxRows {\n\t\t\t\trows = \"☠️ **\" + rows + \"**\"\n\t\t\t}\n\t\t\tfiltered := fmt.Sprintf(\"%.2f%s\", row.Filtered, \"%\")\n\t\t\tif row.Filtered >= common.Config.ExplainMaxFiltered {\n\t\t\t\tfiltered = \"☠️ **\" + filtered + \"**\"\n\t\t\t}\n\t\t\tscalability := row.Scalability\n\t\t\tfor _, s := range common.Config.ExplainWarnScalability {\n\t\t\t\tif s == scalability {\n\t\t\t\t\tscalability = \"☠️ **\" + s + \"**\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tbuf = append(buf, fmt.Sprintln(\"|\", row.ID, \" |\",\n\t\t\t\tcommon.MarkdownEscape(row.SelectType),\n\t\t\t\t\"| *\"+common.MarkdownEscape(row.TableName)+\"* |\",\n\t\t\t\tcommon.MarkdownEscape(row.Partitions), \"|\",\n\t\t\t\tcommon.MarkdownEscape(row.AccessType), \"|\",\n\t\t\t\tcommon.MarkdownEscape(strings.Join(row.PossibleKeys, \",<br>\")), \"|\",\n\t\t\t\tcommon.MarkdownEscape(row.Key), \"|\",\n\t\t\t\trow.KeyLen, \"|\",\n\t\t\t\tcommon.MarkdownEscape(strings.Join(row.Ref, \",<br>\")),\n\t\t\t\t\"|\", rows, \"|\",\n\t\t\t\tfiltered, \"|\", scalability, \"|\",\n\t\t\t\tstrings.Replace(common.MarkdownEscape(row.Extra), \",\", \",<br>\", -1),\n\t\t\t\t\"|\"))\n\t\t}\n\t}\n\tbuf = append(buf, \"\\n\")\n\treturn strings.Join(buf, \"\")\n}\n"
  },
  {
    "path": "database/explain_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n)\n\nvar sqls = []string{\n\t`use sakila`, // not explain able sql, will convert to empty!\n\t`select * from city where country_id = 44;`,\n\t`select * from address where address2 is not null;`,\n\t`select * from address where address2 is null;`,\n\t`select * from address where address2 >= 44;`,\n\t`select * from city where country_id between 44 and 107;`,\n\t`select * from city where city like 'Ad%';`,\n\t`select * from city where city = 'Aden' and country_id = 107;`,\n\t`select * from city where country_id > 31 and city = 'Aden';`,\n\t`select * from address where address_id > 8 and city_id < 400 and district = 'Nantou';`,\n\t`select * from address where address_id > 8 and city_id < 400;`,\n\t`select first_name from actor where last_update='2006-02-15 04:34:33' and last_name='CHASE' group by first_name;`,\n\t`select district from address where last_update >='2014-09-25 22:33:47' group by district;`,\n\t`select address from address group by address,district;`,\n\t`select district from address where last_update='2014-09-25 22:30:27' group by district,(address_id+city_id);`,\n\t`select * from customer where active=1 order by last_name limit 10;`,\n\t`select * from customer order by last_name limit 10;`,\n\t`select * from customer where address_id > 224 order by address_id limit 10;`,\n\t`select * from customer where address_id < 224 order by address_id limit 10;`,\n\t`select * from customer where active=1 order by last_name;`,\n\t`select * from customer where address_id > 224 order by address_id;`,\n\t`select * from customer where address_id in (224,510) order by last_name;`,\n\t`select city from city where country_id = 44;`,\n\t`select city,city_id from city where country_id = 44 and last_update='2006-02-15 04:45:25';`,\n\t`select city from city where country_id > 44 and last_update > '2006-02-15 04:45:25';`,\n\t`select * from city where country_id=1 and city='Kabul' order by last_update;`,\n\t`select * from city where country_id>1 and city='Kabul' order by last_update;`,\n\t`select * from city where city_id>251 order by last_update;`,\n\t`select * from city i inner join country o on i.country_id=o.country_id;`,\n\t`select * from city i left join country o on i.city_id=o.country_id;`,\n\t`select * from city i right join country o on i.city_id=o.country_id;`,\n\t`select * from city i left join country o on i.city_id=o.country_id where o.country_id is null;`,\n\t`select * from city i right join country o on i.city_id=o.country_id where i.city_id is null;`,\n\t`select * from city i left join country o on i.city_id=o.country_id union select * from city i right join country o on i.city_id=o.country_id;`,\n\t`select * from city i left join country o on i.city_id=o.country_id where o.country_id is null union select * from city i right join country o on i.city_id=o.country_id where i.city_id is null;`,\n\t`select first_name,last_name,email from customer natural left join address;`,\n\t`select first_name,last_name,email from customer natural left join address;`,\n\t`select first_name,last_name,email from customer natural right join address;`,\n\t`select first_name,last_name,email from customer STRAIGHT_JOIN address on customer.address_id=address.address_id;`,\n\t`select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;`,\n\t`SELECT a.table_name 表名, a.table_comment 表说明, b.COLUMN_NAME 字段名, b.column_comment 字段说明, b.column_type 字段类型, b.column_key 约束 FROM information_schema.TABLES a LEFT JOIN information_schema. COLUMNS b ON a.table_name = b.TABLE_NAME WHERE a.table_schema IN ('a') AND b.column_comment LIKE '%一%' ORDER BY a.table_name`,\n}\n\nvar exp = []string{\n\t`+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+\n| id | select_type | table   | type  | possible_keys                                           | key               | key_len | ref                       | rows | Extra       |\n+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+\n|  1 | SIMPLE      | country | index | PRIMARY,country_id                                      | country           | 152     | NULL                      |  109 | Using index |\n|  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 |\n+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+`,\n\t`+----+-------------+---------+------------+-------+-------------------+-------------------+---------+---------------------------+------+----------+-------------+\n| id | select_type | table   | partitions | type  | possible_keys     | key               | key_len | ref                       | rows | filtered | Extra       |\n+----+-------------+---------+------------+-------+-------------------+-------------------+---------+---------------------------+------+----------+-------------+\n|  1 | SIMPLE      | country | NULL       | index | PRIMARY           | PRIMARY           | 2       | NULL                      |  109 |   100.00 | Using index |\n|  1 | SIMPLE      | city    | NULL       | ref   | idx_fk_country_id | idx_fk_country_id | 2       | sakila.country.country_id |    5 |   100.00 | Using index |\n+----+-------------+---------+------------+-------+-------------------+-------------------+---------+---------------------------+------+----------+-------------+`,\n\t`*************************** 1. row ***************************\n           id: 1\n  select_type: SIMPLE\n        table: country\n         type: index\npossible_keys: PRIMARY,country_id\n          key: country\n      key_len: 152\n          ref: NULL\n         rows: 109\n        Extra: Using index\n*************************** 2. row ***************************\n           id: 1\n  select_type: SIMPLE\n        table: city\n         type: ref\npossible_keys: idx_fk_country_id,idx_country_id_city,idx_all,idx_other\n          key: idx_fk_country_id\n      key_len: 2\n          ref: sakila.country.country_id\n         rows: 2\n        Extra: Using index`,\n\t`+----+-------------+---------+------------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+\n| id | select_type | table   | partitions | type  | possible_keys                                           | key               | key_len | ref                       | rows | Extra       |\n+----+-------------+---------+------------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+\n|  1 | SIMPLE      | country | NULL       | index | PRIMARY,country_id                                      | country           | 152     | NULL                      |  109 | Using index |\n|  1 | SIMPLE      | city    | NULL       | 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 |\n+----+-------------+---------+------------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"message\": \"No tables used\"\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"message\": \"no matching row in const table\"\n  }\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"table\": {\n      \"insert\": true,\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\"\n    } /* table */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"message\": \"no matching row in const table\"\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"message\": \"no matching row in const table\"\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"13.50\"\n    } /* cost_info */,\n    \"table\": {\n      \"table_name\": \"a4\",\n      \"access_type\": \"ALL\",\n      \"rows_examined_per_scan\": 14,\n      \"rows_produced_per_join\": 14,\n      \"filtered\": \"100.00\",\n      \"cost_info\": {\n        \"read_cost\": \"10.70\",\n        \"eval_cost\": \"2.80\",\n        \"prefix_cost\": \"13.50\",\n        \"data_read_per_join\": \"224\"\n      } /* cost_info */,\n      \"used_columns\": [\n        \"i\"\n      ] /* used_columns */,\n      \"materialized_from_subquery\": {\n        \"using_temporary_table\": true,\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 2,\n          \"cost_info\": {\n            \"query_cost\": \"13.50\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"a3\",\n            \"access_type\": \"ALL\",\n            \"rows_examined_per_scan\": 14,\n            \"rows_produced_per_join\": 14,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"10.70\",\n              \"eval_cost\": \"2.80\",\n              \"prefix_cost\": \"13.50\",\n              \"data_read_per_join\": \"224\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */,\n            \"materialized_from_subquery\": {\n              \"using_temporary_table\": true,\n              \"dependent\": false,\n              \"cacheable\": true,\n              \"query_block\": {\n                \"select_id\": 3,\n                \"cost_info\": {\n                  \"query_cost\": \"13.50\"\n                } /* cost_info */,\n                \"table\": {\n                  \"table_name\": \"a2\",\n                  \"access_type\": \"ALL\",\n                  \"rows_examined_per_scan\": 14,\n                  \"rows_produced_per_join\": 14,\n                  \"filtered\": \"100.00\",\n                  \"cost_info\": {\n                    \"read_cost\": \"10.70\",\n                    \"eval_cost\": \"2.80\",\n                    \"prefix_cost\": \"13.50\",\n                    \"data_read_per_join\": \"224\"\n                  } /* cost_info */,\n                  \"used_columns\": [\n                    \"i\"\n                  ] /* used_columns */,\n                  \"materialized_from_subquery\": {\n                    \"using_temporary_table\": true,\n                    \"dependent\": false,\n                    \"cacheable\": true,\n                    \"query_block\": {\n                      \"select_id\": 4,\n                      \"cost_info\": {\n                        \"query_cost\": \"15.55\"\n                      } /* cost_info */,\n                      \"nested_loop\": [\n                        {\n                          \"table\": {\n                            \"table_name\": \"t2\",\n                            \"access_type\": \"ALL\",\n                            \"rows_examined_per_scan\": 2,\n                            \"rows_produced_per_join\": 2,\n                            \"filtered\": \"100.00\",\n                            \"cost_info\": {\n                              \"read_cost\": \"2.00\",\n                              \"eval_cost\": \"0.40\",\n                              \"prefix_cost\": \"2.40\",\n                              \"data_read_per_join\": \"16\"\n                            } /* cost_info */\n                          } /* table */\n                        },\n                        {\n                          \"table\": {\n                            \"table_name\": \"a1\",\n                            \"access_type\": \"ALL\",\n                            \"rows_examined_per_scan\": 7,\n                            \"rows_produced_per_join\": 14,\n                            \"filtered\": \"100.00\",\n                            \"using_join_buffer\": \"Block Nested Loop\",\n                            \"cost_info\": {\n                              \"read_cost\": \"10.35\",\n                              \"eval_cost\": \"2.80\",\n                              \"prefix_cost\": \"15.55\",\n                              \"data_read_per_join\": \"224\"\n                            } /* cost_info */,\n                            \"used_columns\": [\n                              \"i\"\n                            ] /* used_columns */,\n                            \"materialized_from_subquery\": {\n                              \"using_temporary_table\": true,\n                              \"dependent\": false,\n                              \"cacheable\": true,\n                              \"query_block\": {\n                                \"select_id\": 5,\n                                \"cost_info\": {\n                                  \"query_cost\": \"3.41\"\n                                } /* cost_info */,\n                                \"table\": {\n                                  \"table_name\": \"t1\",\n                                  \"access_type\": \"ALL\",\n                                  \"rows_examined_per_scan\": 7,\n                                  \"rows_produced_per_join\": 7,\n                                  \"filtered\": \"100.00\",\n                                  \"cost_info\": {\n                                    \"read_cost\": \"2.01\",\n                                    \"eval_cost\": \"1.40\",\n                                    \"prefix_cost\": \"3.41\",\n                                    \"data_read_per_join\": \"56\"\n                                  } /* cost_info */,\n                                  \"used_columns\": [\n                                    \"i\"\n                                  ] /* used_columns */\n                                } /* table */\n                              } /* query_block */\n                            } /* materialized_from_subquery */\n                          } /* table */\n                        }\n                      ] /* nested_loop */\n                    } /* query_block */\n                  } /* materialized_from_subquery */\n                } /* table */\n              } /* query_block */\n            } /* materialized_from_subquery */\n          } /* table */\n        } /* query_block */\n      } /* materialized_from_subquery */\n    } /* table */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"5.81\"\n    } /* cost_info */,\n    \"nested_loop\": [\n      {\n        \"table\": {\n          \"table_name\": \"t1\",\n          \"access_type\": \"ALL\",\n          \"rows_examined_per_scan\": 7,\n          \"rows_produced_per_join\": 0,\n          \"filtered\": \"14.29\",\n          \"cost_info\": {\n            \"read_cost\": \"3.21\",\n            \"eval_cost\": \"0.20\",\n            \"prefix_cost\": \"3.41\",\n            \"data_read_per_join\": \"7\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"i\"\n          ] /* used_columns */,\n          \"attached_condition\": \"(test.t1.i = 10)\"\n        } /* table */\n      },\n      {\n        \"table\": {\n          \"table_name\": \"t2\",\n          \"access_type\": \"ALL\",\n          \"rows_examined_per_scan\": 2,\n          \"rows_produced_per_join\": 0,\n          \"filtered\": \"50.00\",\n          \"first_match\": \"t1\",\n          \"using_join_buffer\": \"Block Nested Loop\",\n          \"cost_info\": {\n            \"read_cost\": \"2.20\",\n            \"eval_cost\": \"0.20\",\n            \"prefix_cost\": \"5.82\",\n            \"data_read_per_join\": \"7\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"i\"\n          ] /* used_columns */,\n          \"attached_condition\": \"(test.t2.i = 10)\"\n        } /* table */\n      }\n    ] /* nested_loop */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"3.41\"\n    } /* cost_info */,\n    \"table\": {\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\",\n      \"rows_examined_per_scan\": 7,\n      \"rows_produced_per_join\": 7,\n      \"filtered\": \"100.00\",\n      \"cost_info\": {\n        \"read_cost\": \"2.01\",\n        \"eval_cost\": \"1.40\",\n        \"prefix_cost\": \"3.41\",\n        \"data_read_per_join\": \"56\"\n      } /* cost_info */,\n      \"used_columns\": [\n        \"i\"\n      ] /* used_columns */,\n      \"attached_condition\": \"(<in_optimizer>(test.t1.i ,<exists>(/* select#2 */ select 1 from test.t2 where ((test.t1.i = 10) and (<cache>(test.t1.i) = test.t2.i)))) or <in_optimizer>(test.t1.i.test.t1.i in ( <materialize> (/* select#3 */ select NULL from test.t4 where 1 ), <primary_index_lookup>(test.t1.i in <temporary table> on <auto_key> where ((test.t1.i = materialized-subquery.i))))))\",\n      \"attached_subqueries\": [\n        {\n          \"table\": {\n            \"table_name\": \"<materialized_subquery>\",\n            \"access_type\": \"eq_ref\",\n            \"key\": \"<auto_key>\",\n            \"key_length\": \"5\",\n            \"rows_examined_per_scan\": 1,\n            \"materialized_from_subquery\": {\n              \"using_temporary_table\": true,\n              \"dependent\": true,\n              \"cacheable\": false,\n              \"query_block\": {\n                \"select_id\": 3,\n                \"message\": \"no matching row in const table\"\n              } /* query_block */\n            } /* materialized_from_subquery */\n          } /* table */\n        },\n        {\n          \"dependent\": true,\n          \"cacheable\": false,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"cost_info\": {\n              \"query_cost\": \"2.40\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t2\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 2,\n              \"rows_produced_per_join\": 1,\n              \"filtered\": \"50.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.00\",\n                \"eval_cost\": \"0.20\",\n                \"prefix_cost\": \"2.40\",\n                \"data_read_per_join\": \"8\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"i\"\n              ] /* used_columns */,\n              \"attached_condition\": \"((test.t1.i = 10) and (<cache>(test.t1.i) = test.t2.i))\"\n            } /* table */\n          } /* query_block */\n        }\n      ] /* attached_subqueries */\n    } /* table */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"union_result\": {\n      \"using_temporary_table\": true,\n      \"table_name\": \"<union1,2,3>\",\n      \"access_type\": \"ALL\",\n      \"query_specifications\": [\n        {\n          \"dependent\": false,\n          \"cacheable\": true,\n          \"query_block\": {\n            \"select_id\": 1,\n            \"cost_info\": {\n              \"query_cost\": \"3.41\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t1\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 7,\n              \"rows_produced_per_join\": 7,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.01\",\n                \"eval_cost\": \"1.40\",\n                \"prefix_cost\": \"3.41\",\n                \"data_read_per_join\": \"56\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"i\"\n              ] /* used_columns */\n            } /* table */\n          } /* query_block */\n        },\n        {\n          \"dependent\": false,\n          \"cacheable\": true,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"cost_info\": {\n              \"query_cost\": \"2.40\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t2\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 2,\n              \"rows_produced_per_join\": 2,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.00\",\n                \"eval_cost\": \"0.40\",\n                \"prefix_cost\": \"2.40\",\n                \"data_read_per_join\": \"16\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"i\"\n              ] /* used_columns */\n            } /* table */\n          } /* query_block */\n        },\n        {\n          \"dependent\": false,\n          \"cacheable\": true,\n          \"query_block\": {\n            \"select_id\": 3,\n            \"message\": \"no matching row in const table\"\n          } /* query_block */\n        }\n      ] /* query_specifications */\n    } /* union_result */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"union_result\": {\n      \"using_temporary_table\": false,\n      \"query_specifications\": [\n        {\n          \"dependent\": false,\n          \"cacheable\": true,\n          \"query_block\": {\n            \"select_id\": 1,\n            \"cost_info\": {\n              \"query_cost\": \"7.21\"\n            } /* cost_info */,\n            \"nested_loop\": [\n              {\n                \"table\": {\n                  \"table_name\": \"t2\",\n                  \"access_type\": \"ALL\",\n                  \"rows_examined_per_scan\": 2,\n                  \"rows_produced_per_join\": 2,\n                  \"filtered\": \"100.00\",\n                  \"cost_info\": {\n                    \"read_cost\": \"2.00\",\n                    \"eval_cost\": \"0.40\",\n                    \"prefix_cost\": \"2.40\",\n                    \"data_read_per_join\": \"16\"\n                  } /* cost_info */\n                } /* table */\n              },\n              {\n                \"table\": {\n                  \"table_name\": \"t1\",\n                  \"access_type\": \"ALL\",\n                  \"rows_examined_per_scan\": 7,\n                  \"rows_produced_per_join\": 14,\n                  \"filtered\": \"100.00\",\n                  \"using_join_buffer\": \"Block Nested Loop\",\n                  \"cost_info\": {\n                    \"read_cost\": \"2.01\",\n                    \"eval_cost\": \"2.80\",\n                    \"prefix_cost\": \"7.22\",\n                    \"data_read_per_join\": \"112\"\n                  } /* cost_info */,\n                  \"used_columns\": [\n                    \"i\"\n                  ] /* used_columns */\n                } /* table */\n              }\n            ] /* nested_loop */\n          } /* query_block */\n        },\n        {\n          \"dependent\": false,\n          \"cacheable\": true,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"message\": \"no matching row in const table\"\n          } /* query_block */\n        }\n      ] /* query_specifications */\n    } /* union_result */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"ordering_operation\": {\n      \"using_filesort\": true,\n      \"union_result\": {\n        \"using_temporary_table\": true,\n        \"table_name\": \"<union1,2>\",\n        \"access_type\": \"ALL\",\n        \"query_specifications\": [\n          {\n            \"dependent\": false,\n            \"cacheable\": true,\n            \"query_block\": {\n              \"select_id\": 1,\n              \"cost_info\": {\n                \"query_cost\": \"3.41\"\n              } /* cost_info */,\n              \"table\": {\n                \"table_name\": \"t1\",\n                \"access_type\": \"ALL\",\n                \"rows_examined_per_scan\": 7,\n                \"rows_produced_per_join\": 7,\n                \"filtered\": \"100.00\",\n                \"cost_info\": {\n                  \"read_cost\": \"2.01\",\n                  \"eval_cost\": \"1.40\",\n                  \"prefix_cost\": \"3.41\",\n                  \"data_read_per_join\": \"56\"\n                } /* cost_info */,\n                \"used_columns\": [\n                  \"i\"\n                ] /* used_columns */\n              } /* table */\n            } /* query_block */\n          },\n          {\n            \"dependent\": false,\n            \"cacheable\": true,\n            \"query_block\": {\n              \"select_id\": 2,\n              \"cost_info\": {\n                \"query_cost\": \"2.40\"\n              } /* cost_info */,\n              \"table\": {\n                \"table_name\": \"t2\",\n                \"access_type\": \"ALL\",\n                \"rows_examined_per_scan\": 2,\n                \"rows_produced_per_join\": 2,\n                \"filtered\": \"100.00\",\n                \"cost_info\": {\n                  \"read_cost\": \"2.00\",\n                  \"eval_cost\": \"0.40\",\n                  \"prefix_cost\": \"2.40\",\n                  \"data_read_per_join\": \"16\"\n                } /* cost_info */,\n                \"used_columns\": [\n                  \"i\"\n                ] /* used_columns */\n              } /* table */\n            } /* query_block */\n          }\n        ] /* query_specifications */\n      } /* union_result */,\n      \"order_by_subqueries\": [\n        {\n          \"dependent\": true,\n          \"cacheable\": false,\n          \"query_block\": {\n            \"select_id\": 3,\n            \"message\": \"No tables used\"\n          } /* query_block */\n        }\n      ] /* order_by_subqueries */\n    } /* ordering_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"3.41\"\n    } /* cost_info */,\n    \"ordering_operation\": {\n      \"using_filesort\": false,\n      \"table\": {\n        \"table_name\": \"t1\",\n        \"access_type\": \"ALL\",\n        \"rows_examined_per_scan\": 7,\n        \"rows_produced_per_join\": 7,\n        \"filtered\": \"100.00\",\n        \"cost_info\": {\n          \"read_cost\": \"2.01\",\n          \"eval_cost\": \"1.40\",\n          \"prefix_cost\": \"3.41\",\n          \"data_read_per_join\": \"56\"\n        } /* cost_info */,\n        \"used_columns\": [\n          \"i\"\n        ] /* used_columns */\n      } /* table */,\n      \"optimized_away_subqueries\": [\n        {\n          \"dependent\": false,\n          \"cacheable\": true,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"cost_info\": {\n              \"query_cost\": \"2.40\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t2\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 2,\n              \"rows_produced_per_join\": 2,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.00\",\n                \"eval_cost\": \"0.40\",\n                \"prefix_cost\": \"2.40\",\n                \"data_read_per_join\": \"16\"\n              } /* cost_info */\n            } /* table */\n          } /* query_block */\n        }\n      ] /* optimized_away_subqueries */\n    } /* ordering_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"3.41\"\n    } /* cost_info */,\n    \"table\": {\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\",\n      \"rows_examined_per_scan\": 7,\n      \"rows_produced_per_join\": 7,\n      \"filtered\": \"100.00\",\n      \"cost_info\": {\n        \"read_cost\": \"2.01\",\n        \"eval_cost\": \"1.40\",\n        \"prefix_cost\": \"3.41\",\n        \"data_read_per_join\": \"56\"\n      } /* cost_info */,\n      \"used_columns\": [\n        \"i\"\n      ] /* used_columns */\n    } /* table */,\n    \"having_subqueries\": [\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 3,\n          \"cost_info\": {\n            \"query_cost\": \"2.40\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"t2\",\n            \"access_type\": \"ALL\",\n            \"rows_examined_per_scan\": 2,\n            \"rows_produced_per_join\": 2,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"2.00\",\n              \"eval_cost\": \"0.40\",\n              \"prefix_cost\": \"2.40\",\n              \"data_read_per_join\": \"16\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */\n          } /* table */\n        } /* query_block */\n      },\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 2,\n          \"cost_info\": {\n            \"query_cost\": \"2.40\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"t2\",\n            \"access_type\": \"ALL\",\n            \"rows_examined_per_scan\": 2,\n            \"rows_produced_per_join\": 2,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"2.00\",\n              \"eval_cost\": \"0.40\",\n              \"prefix_cost\": \"2.40\",\n              \"data_read_per_join\": \"16\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */\n          } /* table */\n        } /* query_block */\n      }\n    ] /* having_subqueries */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"10.41\"\n    } /* cost_info */,\n    \"grouping_operation\": {\n      \"using_temporary_table\": true,\n      \"using_filesort\": true,\n      \"cost_info\": {\n        \"sort_cost\": \"7.00\"\n      } /* cost_info */,\n      \"table\": {\n        \"table_name\": \"t1\",\n        \"access_type\": \"ALL\",\n        \"rows_examined_per_scan\": 7,\n        \"rows_produced_per_join\": 7,\n        \"filtered\": \"100.00\",\n        \"cost_info\": {\n          \"read_cost\": \"2.01\",\n          \"eval_cost\": \"1.40\",\n          \"prefix_cost\": \"3.41\",\n          \"data_read_per_join\": \"56\"\n        } /* cost_info */,\n        \"used_columns\": [\n          \"i\"\n        ] /* used_columns */\n      } /* table */,\n      \"group_by_subqueries\": [\n        {\n          \"dependent\": true,\n          \"cacheable\": false,\n          \"query_block\": {\n            \"select_id\": 3,\n            \"cost_info\": {\n              \"query_cost\": \"2.40\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t2\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 2,\n              \"rows_produced_per_join\": 2,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.00\",\n                \"eval_cost\": \"0.40\",\n                \"prefix_cost\": \"2.40\",\n                \"data_read_per_join\": \"16\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"i\"\n              ] /* used_columns */,\n              \"attached_condition\": \"<if>(outer_field_is_not_null, ((<cache>(test.t1.i) >= test.t2.i) or isnull(test.t2.i)), true)\"\n            } /* table */\n          } /* query_block */\n        },\n        {\n          \"dependent\": true,\n          \"cacheable\": false,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"cost_info\": {\n              \"query_cost\": \"2.40\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t2\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 2,\n              \"rows_produced_per_join\": 2,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.00\",\n                \"eval_cost\": \"0.40\",\n                \"prefix_cost\": \"2.40\",\n                \"data_read_per_join\": \"16\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"i\"\n              ] /* used_columns */,\n              \"attached_condition\": \"<if>(outer_field_is_not_null, ((<cache>(test.t1.i) <= test.t2.i) or isnull(test.t2.i)), true)\"\n            } /* table */\n          } /* query_block */\n        }\n      ] /* group_by_subqueries */\n    } /* grouping_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"3.41\"\n    } /* cost_info */,\n    \"table\": {\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\",\n      \"rows_examined_per_scan\": 7,\n      \"rows_produced_per_join\": 7,\n      \"filtered\": \"100.00\",\n      \"cost_info\": {\n        \"read_cost\": \"2.01\",\n        \"eval_cost\": \"1.40\",\n        \"prefix_cost\": \"3.41\",\n        \"data_read_per_join\": \"56\"\n      } /* cost_info */,\n      \"used_columns\": [\n        \"i\"\n      ] /* used_columns */\n    } /* table */,\n    \"select_list_subqueries\": [\n      {\n        \"dependent\": false,\n        \"cacheable\": false,\n        \"query_block\": {\n          \"select_id\": 2,\n          \"cost_info\": {\n            \"query_cost\": \"3.41\"\n          } /* cost_info */,\n          \"ordering_operation\": {\n            \"using_temporary_table\": true,\n            \"using_filesort\": true,\n            \"table\": {\n              \"table_name\": \"t1\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 7,\n              \"rows_produced_per_join\": 7,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.01\",\n                \"eval_cost\": \"1.40\",\n                \"prefix_cost\": \"3.41\",\n                \"data_read_per_join\": \"56\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"i\"\n              ] /* used_columns */\n            } /* table */\n          } /* ordering_operation */\n        } /* query_block */\n      }\n    ] /* select_list_subqueries */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"message\": \"no matching row in const table\"\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"5.21\"\n    } /* cost_info */,\n    \"nested_loop\": [\n      {\n        \"table\": {\n          \"table_name\": \"t1\",\n          \"access_type\": \"ALL\",\n          \"rows_examined_per_scan\": 2,\n          \"rows_produced_per_join\": 2,\n          \"filtered\": \"100.00\",\n          \"cost_info\": {\n            \"read_cost\": \"2.00\",\n            \"eval_cost\": \"0.40\",\n            \"prefix_cost\": \"2.40\",\n            \"data_read_per_join\": \"32\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"a\",\n            \"b\"\n          ] /* used_columns */,\n          \"attached_condition\": \"<nop>(<in_optimizer>((/* select#3 */ select test.t3.e from test.t3),<exists>(/* select#4 */ select 1 from test.t3 where (test.t1.b and <if>(outer_field_is_not_null, ((<cache>((/* select#3 */ select test.t3.e from test.t3)) < test.t3.e) or isnull(test.t3.e)), true)) having <if>(outer_field_is_not_null, <is_not_null_test>(test.t3.e), true))))\",\n          \"attached_subqueries\": [\n            {\n              \"dependent\": true,\n              \"cacheable\": false,\n              \"query_block\": {\n                \"select_id\": 4,\n                \"cost_info\": {\n                  \"query_cost\": \"2.40\"\n                } /* cost_info */,\n                \"table\": {\n                  \"table_name\": \"t3\",\n                  \"access_type\": \"ALL\",\n                  \"rows_examined_per_scan\": 2,\n                  \"rows_produced_per_join\": 2,\n                  \"filtered\": \"100.00\",\n                  \"cost_info\": {\n                    \"read_cost\": \"2.00\",\n                    \"eval_cost\": \"0.40\",\n                    \"prefix_cost\": \"2.40\",\n                    \"data_read_per_join\": \"16\"\n                  } /* cost_info */,\n                  \"used_columns\": [\n                    \"e\"\n                  ] /* used_columns */,\n                  \"attached_condition\": \"(test.t1.b and <if>(outer_field_is_not_null, ((<cache>((/* select#3 */ select test.t3.e from test.t3)) < test.t3.e) or isnull(test.t3.e)), true))\"\n                } /* table */\n              } /* query_block */\n            },\n            {\n              \"dependent\": false,\n              \"cacheable\": true,\n              \"query_block\": {\n                \"select_id\": 3,\n                \"cost_info\": {\n                  \"query_cost\": \"2.40\"\n                } /* cost_info */,\n                \"table\": {\n                  \"table_name\": \"t3\",\n                  \"access_type\": \"ALL\",\n                  \"rows_examined_per_scan\": 2,\n                  \"rows_produced_per_join\": 2,\n                  \"filtered\": \"100.00\",\n                  \"cost_info\": {\n                    \"read_cost\": \"2.00\",\n                    \"eval_cost\": \"0.40\",\n                    \"prefix_cost\": \"2.40\",\n                    \"data_read_per_join\": \"16\"\n                  } /* cost_info */,\n                  \"used_columns\": [\n                    \"e\"\n                  ] /* used_columns */\n                } /* table */\n              } /* query_block */\n            }\n          ] /* attached_subqueries */\n        } /* table */\n      },\n      {\n        \"table\": {\n          \"table_name\": \"t2\",\n          \"access_type\": \"ALL\",\n          \"rows_examined_per_scan\": 2,\n          \"rows_produced_per_join\": 2,\n          \"filtered\": \"50.00\",\n          \"first_match\": \"t1\",\n          \"using_join_buffer\": \"Block Nested Loop\",\n          \"cost_info\": {\n            \"read_cost\": \"2.00\",\n            \"eval_cost\": \"0.40\",\n            \"prefix_cost\": \"5.21\",\n            \"data_read_per_join\": \"32\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"c\"\n          ] /* used_columns */,\n          \"attached_condition\": \"(test.t2.c = test.t1.a)\"\n        } /* table */\n      }\n    ] /* nested_loop */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"35.44\"\n    } /* cost_info */,\n    \"nested_loop\": [\n      {\n        \"table\": {\n          \"table_name\": \"t1\",\n          \"access_type\": \"ALL\",\n          \"rows_examined_per_scan\": 12,\n          \"rows_produced_per_join\": 12,\n          \"filtered\": \"100.00\",\n          \"cost_info\": {\n            \"read_cost\": \"2.02\",\n            \"eval_cost\": \"2.40\",\n            \"prefix_cost\": \"4.42\",\n            \"data_read_per_join\": \"96\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"a\"\n          ] /* used_columns */,\n          \"attached_condition\": \"((test.t1.a is not null) and (test.t1.a is not null))\"\n        } /* table */\n      },\n      {\n        \"table\": {\n          \"table_name\": \"<subquery3>\",\n          \"access_type\": \"eq_ref\",\n          \"key\": \"<auto_key>\",\n          \"key_length\": \"5\",\n          \"ref\": [\n            \"test.t1.a\"\n          ] /* ref */,\n          \"rows_examined_per_scan\": 1,\n          \"materialized_from_subquery\": {\n            \"using_temporary_table\": true,\n            \"query_block\": {\n              \"nested_loop\": [\n                {\n                  \"table\": {\n                    \"table_name\": \"t4\",\n                    \"access_type\": \"ALL\",\n                    \"rows_examined_per_scan\": 12,\n                    \"rows_produced_per_join\": 3,\n                    \"filtered\": \"33.33\",\n                    \"cost_info\": {\n                      \"read_cost\": \"3.62\",\n                      \"eval_cost\": \"0.80\",\n                      \"prefix_cost\": \"4.42\",\n                      \"data_read_per_join\": \"31\"\n                    } /* cost_info */,\n                    \"used_columns\": [\n                      \"a\"\n                    ] /* used_columns */,\n                    \"attached_condition\": \"(test.t4.a > 0)\"\n                  } /* table */\n                },\n                {\n                  \"table\": {\n                    \"table_name\": \"t3\",\n                    \"access_type\": \"ALL\",\n                    \"rows_examined_per_scan\": 12,\n                    \"rows_produced_per_join\": 4,\n                    \"filtered\": \"10.00\",\n                    \"using_join_buffer\": \"Block Nested Loop\",\n                    \"cost_info\": {\n                      \"read_cost\": \"2.02\",\n                      \"eval_cost\": \"0.96\",\n                      \"prefix_cost\": \"16.04\",\n                      \"data_read_per_join\": \"38\"\n                    } /* cost_info */,\n                    \"used_columns\": [\n                      \"a\"\n                    ] /* used_columns */,\n                    \"attached_condition\": \"(test.t3.a = test.t4.a)\"\n                  } /* table */\n                }\n              ] /* nested_loop */\n            } /* query_block */\n          } /* materialized_from_subquery */\n        } /* table */\n      },\n      {\n        \"table\": {\n          \"table_name\": \"<subquery2>\",\n          \"access_type\": \"eq_ref\",\n          \"key\": \"<auto_key>\",\n          \"key_length\": \"5\",\n          \"ref\": [\n            \"test.t1.a\"\n          ] /* ref */,\n          \"rows_examined_per_scan\": 1,\n          \"materialized_from_subquery\": {\n            \"using_temporary_table\": true,\n            \"query_block\": {\n              \"table\": {\n                \"table_name\": \"t2\",\n                \"access_type\": \"ALL\",\n                \"rows_examined_per_scan\": 12,\n                \"rows_produced_per_join\": 3,\n                \"filtered\": \"33.33\",\n                \"cost_info\": {\n                  \"read_cost\": \"3.62\",\n                  \"eval_cost\": \"0.80\",\n                  \"prefix_cost\": \"4.42\",\n                  \"data_read_per_join\": \"31\"\n                } /* cost_info */,\n                \"used_columns\": [\n                  \"a\"\n                ] /* used_columns */,\n                \"attached_condition\": \"(test.t2.a > 0)\"\n              } /* table */\n            } /* query_block */\n          } /* materialized_from_subquery */\n        } /* table */\n      }\n    ] /* nested_loop */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"1.20\"\n    } /* cost_info */,\n    \"table\": {\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\",\n      \"rows_examined_per_scan\": 1,\n      \"rows_produced_per_join\": 1,\n      \"filtered\": \"100.00\",\n      \"cost_info\": {\n        \"read_cost\": \"1.00\",\n        \"eval_cost\": \"0.20\",\n        \"prefix_cost\": \"1.20\",\n        \"data_read_per_join\": \"8\"\n      } /* cost_info */,\n      \"used_columns\": [\n        \"i1\",\n        \"c1\"\n      ] /* used_columns */,\n      \"attached_condition\": \"exists(/* select#2 */ select test.t2.c1 from test.t2 join test.t3 where ((test.t2.c1 = test.t3.c1) and (test.t2.c2 = (/* select#3 */ select min(test.t3.c1) from test.t3)) and ((/* select#3 */ select min(test.t3.c1) from test.t3) <> test.t1.c1)))\",\n      \"attached_subqueries\": [\n        {\n          \"dependent\": true,\n          \"cacheable\": false,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"cost_info\": {\n              \"query_cost\": \"2.40\"\n            } /* cost_info */,\n            \"nested_loop\": [\n              {\n                \"table\": {\n                  \"table_name\": \"t3\",\n                  \"access_type\": \"ALL\",\n                  \"rows_examined_per_scan\": 1,\n                  \"rows_produced_per_join\": 1,\n                  \"filtered\": \"100.00\",\n                  \"cost_info\": {\n                    \"read_cost\": \"1.00\",\n                    \"eval_cost\": \"0.20\",\n                    \"prefix_cost\": \"1.20\",\n                    \"data_read_per_join\": \"8\"\n                  } /* cost_info */,\n                  \"used_columns\": [\n                    \"c1\"\n                  ] /* used_columns */,\n                  \"attached_condition\": \"((/* select#3 */ select min(test.t3.c1) from test.t3) <> test.t1.c1)\",\n                  \"attached_subqueries\": [\n                    {\n                      \"dependent\": false,\n                      \"cacheable\": true,\n                      \"query_block\": {\n                        \"select_id\": 3,\n                        \"cost_info\": {\n                          \"query_cost\": \"1.20\"\n                        } /* cost_info */,\n                        \"table\": {\n                          \"table_name\": \"t3\",\n                          \"access_type\": \"ALL\",\n                          \"rows_examined_per_scan\": 1,\n                          \"rows_produced_per_join\": 1,\n                          \"filtered\": \"100.00\",\n                          \"cost_info\": {\n                            \"read_cost\": \"1.00\",\n                            \"eval_cost\": \"0.20\",\n                            \"prefix_cost\": \"1.20\",\n                            \"data_read_per_join\": \"8\"\n                          } /* cost_info */,\n                          \"used_columns\": [\n                            \"c1\"\n                          ] /* used_columns */\n                        } /* table */\n                      } /* query_block */\n                    }\n                  ] /* attached_subqueries */\n                } /* table */\n              },\n              {\n                \"table\": {\n                  \"table_name\": \"t2\",\n                  \"access_type\": \"ref\",\n                  \"possible_keys\": [\n                    \"c1\"\n                  ] /* possible_keys */,\n                  \"key\": \"c1\",\n                  \"used_key_parts\": [\n                    \"c1\"\n                  ] /* used_key_parts */,\n                  \"key_length\": \"3\",\n                  \"ref\": [\n                    \"test.t3.c1\"\n                  ] /* ref */,\n                  \"rows_examined_per_scan\": 1,\n                  \"rows_produced_per_join\": 0,\n                  \"filtered\": \"50.00\",\n                  \"cost_info\": {\n                    \"read_cost\": \"1.00\",\n                    \"eval_cost\": \"0.10\",\n                    \"prefix_cost\": \"2.40\",\n                    \"data_read_per_join\": \"8\"\n                  } /* cost_info */,\n                  \"used_columns\": [\n                    \"c1\",\n                    \"c2\"\n                  ] /* used_columns */,\n                  \"attached_condition\": \"(test.t2.c2 = (/* select#3 */ select min(test.t3.c1) from test.t3))\",\n                  \"attached_subqueries\": [\n                    {\n                      \"dependent\": false,\n                      \"cacheable\": true,\n                      \"query_block\": {\n                        \"select_id\": 3,\n                        \"cost_info\": {\n                          \"query_cost\": \"1.20\"\n                        } /* cost_info */,\n                        \"table\": {\n                          \"table_name\": \"t3\",\n                          \"access_type\": \"ALL\",\n                          \"rows_examined_per_scan\": 1,\n                          \"rows_produced_per_join\": 1,\n                          \"filtered\": \"100.00\",\n                          \"cost_info\": {\n                            \"read_cost\": \"1.00\",\n                            \"eval_cost\": \"0.20\",\n                            \"prefix_cost\": \"1.20\",\n                            \"data_read_per_join\": \"8\"\n                          } /* cost_info */,\n                          \"used_columns\": [\n                            \"c1\"\n                          ] /* used_columns */\n                        } /* table */\n                      } /* query_block */\n                    }\n                  ] /* attached_subqueries */\n                } /* table */\n              }\n            ] /* nested_loop */\n          } /* query_block */\n        }\n      ] /* attached_subqueries */\n    } /* table */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"20.82\"\n    } /* cost_info */,\n    \"duplicates_removal\": {\n      \"using_temporary_table\": true,\n      \"nested_loop\": [\n        {\n          \"table\": {\n            \"table_name\": \"t5\",\n            \"access_type\": \"ALL\",\n            \"rows_examined_per_scan\": 3,\n            \"rows_produced_per_join\": 3,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"2.01\",\n              \"eval_cost\": \"0.60\",\n              \"prefix_cost\": \"2.61\",\n              \"data_read_per_join\": \"24\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"c\"\n            ] /* used_columns */\n          } /* table */\n        },\n        {\n          \"table\": {\n            \"table_name\": \"t2\",\n            \"access_type\": \"ALL\",\n            \"rows_examined_per_scan\": 3,\n            \"rows_produced_per_join\": 3,\n            \"filtered\": \"33.33\",\n            \"using_join_buffer\": \"Block Nested Loop\",\n            \"cost_info\": {\n              \"read_cost\": \"2.01\",\n              \"eval_cost\": \"0.60\",\n              \"prefix_cost\": \"6.41\",\n              \"data_read_per_join\": \"48\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"c\",\n              \"c_key\"\n            ] /* used_columns */,\n            \"attached_condition\": \"(test.t2.c = test.t5.c)\"\n          } /* table */\n        },\n        {\n          \"table\": {\n            \"table_name\": \"t1\",\n            \"access_type\": \"index\",\n            \"possible_keys\": [\n              \"c_key\"\n            ] /* possible_keys */,\n            \"key\": \"c_key\",\n            \"used_key_parts\": [\n              \"c_key\"\n            ] /* used_key_parts */,\n            \"key_length\": \"5\",\n            \"rows_examined_per_scan\": 3,\n            \"rows_produced_per_join\": 3,\n            \"filtered\": \"33.33\",\n            \"using_index\": true,\n            \"using_join_buffer\": \"Block Nested Loop\",\n            \"cost_info\": {\n              \"read_cost\": \"2.01\",\n              \"eval_cost\": \"0.60\",\n              \"prefix_cost\": \"12.22\",\n              \"data_read_per_join\": \"24\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"c_key\"\n            ] /* used_columns */,\n            \"attached_condition\": \"(test.t1.c_key = test.t2.c_key)\"\n          } /* table */\n        },\n        {\n          \"table\": {\n            \"table_name\": \"t4\",\n            \"access_type\": \"ALL\",\n            \"rows_examined_per_scan\": 3,\n            \"rows_produced_per_join\": 3,\n            \"filtered\": \"33.33\",\n            \"using_join_buffer\": \"Block Nested Loop\",\n            \"cost_info\": {\n              \"read_cost\": \"2.01\",\n              \"eval_cost\": \"0.60\",\n              \"prefix_cost\": \"16.02\",\n              \"data_read_per_join\": \"48\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"c\",\n              \"c_key\"\n            ] /* used_columns */,\n            \"attached_condition\": \"((test.t4.c = test.t5.c) and (test.t4.c_key is not null))\"\n          } /* table */\n        },\n        {\n          \"table\": {\n            \"table_name\": \"t3\",\n            \"access_type\": \"ref\",\n            \"possible_keys\": [\n              \"c_key\"\n            ] /* possible_keys */,\n            \"key\": \"c_key\",\n            \"used_key_parts\": [\n              \"c_key\"\n            ] /* used_key_parts */,\n            \"key_length\": \"5\",\n            \"ref\": [\n              \"test.t4.c_key\"\n            ] /* ref */,\n            \"rows_examined_per_scan\": 1,\n            \"rows_produced_per_join\": 3,\n            \"filtered\": \"100.00\",\n            \"using_index\": true,\n            \"cost_info\": {\n              \"read_cost\": \"3.00\",\n              \"eval_cost\": \"0.60\",\n              \"prefix_cost\": \"20.82\",\n              \"data_read_per_join\": \"24\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"c_key\"\n            ] /* used_columns */\n          } /* table */\n        }\n      ] /* nested_loop */\n    } /* duplicates_removal */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"table\": {\n      \"update\": true,\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\",\n      \"rows_examined_per_scan\": 1,\n      \"filtered\": \"100.00\"\n    } /* table */,\n    \"update_value_subqueries\": [\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 2,\n          \"cost_info\": {\n            \"query_cost\": \"1.00\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"t2\",\n            \"access_type\": \"system\",\n            \"rows_examined_per_scan\": 1,\n            \"rows_produced_per_join\": 1,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"0.00\",\n              \"eval_cost\": \"0.20\",\n              \"prefix_cost\": \"0.00\",\n              \"data_read_per_join\": \"8\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */\n          } /* table */\n        } /* query_block */\n      }\n    ] /* update_value_subqueries */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"1.00\"\n    } /* cost_info */,\n    \"nested_loop\": [\n      {\n        \"table\": {\n          \"update\": true,\n          \"table_name\": \"t1\",\n          \"access_type\": \"system\",\n          \"rows_examined_per_scan\": 1,\n          \"rows_produced_per_join\": 1,\n          \"filtered\": \"100.00\",\n          \"cost_info\": {\n            \"read_cost\": \"0.00\",\n            \"eval_cost\": \"0.20\",\n            \"prefix_cost\": \"0.00\",\n            \"data_read_per_join\": \"8\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"i\"\n          ] /* used_columns */\n        } /* table */\n      },\n      {\n        \"table\": {\n          \"table_name\": \"t2\",\n          \"access_type\": \"system\",\n          \"rows_examined_per_scan\": 1,\n          \"rows_produced_per_join\": 1,\n          \"filtered\": \"100.00\",\n          \"cost_info\": {\n            \"read_cost\": \"0.00\",\n            \"eval_cost\": \"0.20\",\n            \"prefix_cost\": \"0.00\",\n            \"data_read_per_join\": \"8\"\n          } /* cost_info */\n        } /* table */\n      }\n    ] /* nested_loop */,\n    \"update_value_subqueries\": [\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 2,\n          \"cost_info\": {\n            \"query_cost\": \"1.00\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"t3\",\n            \"access_type\": \"system\",\n            \"rows_examined_per_scan\": 1,\n            \"rows_produced_per_join\": 1,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"0.00\",\n              \"eval_cost\": \"0.20\",\n              \"prefix_cost\": \"0.00\",\n              \"data_read_per_join\": \"8\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */\n          } /* table */\n        } /* query_block */\n      }\n    ] /* update_value_subqueries */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"1.00\"\n    } /* cost_info */,\n    \"table\": {\n      \"insert\": true,\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\"\n    } /* table */,\n    \"insert_from\": {\n      \"table\": {\n        \"table_name\": \"t2\",\n        \"access_type\": \"system\",\n        \"rows_examined_per_scan\": 1,\n        \"rows_produced_per_join\": 1,\n        \"filtered\": \"100.00\",\n        \"cost_info\": {\n          \"read_cost\": \"0.00\",\n          \"eval_cost\": \"0.20\",\n          \"prefix_cost\": \"0.00\",\n          \"data_read_per_join\": \"8\"\n        } /* cost_info */,\n        \"used_columns\": [\n          \"i\"\n        ] /* used_columns */\n      } /* table */\n    } /* insert_from */,\n    \"update_value_subqueries\": [\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 2,\n          \"cost_info\": {\n            \"query_cost\": \"1.00\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"t2\",\n            \"access_type\": \"system\",\n            \"rows_examined_per_scan\": 1,\n            \"rows_produced_per_join\": 1,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"0.00\",\n              \"eval_cost\": \"0.20\",\n              \"prefix_cost\": \"0.00\",\n              \"data_read_per_join\": \"8\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */\n          } /* table */\n        } /* query_block */\n      }\n    ] /* update_value_subqueries */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"table\": {\n      \"insert\": true,\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\"\n    } /* table */,\n    \"update_value_subqueries\": [\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 2,\n          \"cost_info\": {\n            \"query_cost\": \"1.00\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"t2\",\n            \"access_type\": \"system\",\n            \"rows_examined_per_scan\": 1,\n            \"rows_produced_per_join\": 1,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"0.00\",\n              \"eval_cost\": \"0.20\",\n              \"prefix_cost\": \"0.00\",\n              \"data_read_per_join\": \"8\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */\n          } /* table */\n        } /* query_block */\n      }\n    ] /* update_value_subqueries */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"table\": {\n      \"insert\": true,\n      \"table_name\": \"t3\",\n      \"access_type\": \"ALL\"\n    } /* table */,\n    \"optimized_away_subqueries\": [\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 3,\n          \"cost_info\": {\n            \"query_cost\": \"1.00\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"t2\",\n            \"access_type\": \"system\",\n            \"rows_examined_per_scan\": 1,\n            \"rows_produced_per_join\": 1,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"0.00\",\n              \"eval_cost\": \"0.20\",\n              \"prefix_cost\": \"0.00\",\n              \"data_read_per_join\": \"8\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */\n          } /* table */\n        } /* query_block */\n      },\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 2,\n          \"cost_info\": {\n            \"query_cost\": \"1.00\"\n          } /* cost_info */,\n          \"table\": {\n            \"table_name\": \"t1\",\n            \"access_type\": \"system\",\n            \"rows_examined_per_scan\": 1,\n            \"rows_produced_per_join\": 1,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"0.00\",\n              \"eval_cost\": \"0.20\",\n              \"prefix_cost\": \"0.00\",\n              \"data_read_per_join\": \"8\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"i\"\n            ] /* used_columns */\n          } /* table */\n        } /* query_block */\n      }\n    ] /* optimized_away_subqueries */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"10.50\"\n    } /* cost_info */,\n    \"ordering_operation\": {\n      \"using_filesort\": true,\n      \"grouping_operation\": {\n        \"using_temporary_table\": true,\n        \"using_filesort\": false,\n        \"table\": {\n          \"table_name\": \"t1\",\n          \"access_type\": \"ALL\",\n          \"rows_examined_per_scan\": 2,\n          \"rows_produced_per_join\": 2,\n          \"filtered\": \"100.00\",\n          \"cost_info\": {\n            \"read_cost\": \"10.10\",\n            \"eval_cost\": \"0.40\",\n            \"prefix_cost\": \"10.50\",\n            \"data_read_per_join\": \"48\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"a\",\n            \"b\"\n          ] /* used_columns */,\n          \"materialized_from_subquery\": {\n            \"using_temporary_table\": true,\n            \"dependent\": false,\n            \"cacheable\": true,\n            \"query_block\": {\n              \"union_result\": {\n                \"using_temporary_table\": false,\n                \"query_specifications\": [\n                  {\n                    \"dependent\": false,\n                    \"cacheable\": true,\n                    \"query_block\": {\n                      \"select_id\": 2,\n                      \"message\": \"No tables used\"\n                    } /* query_block */\n                  },\n                  {\n                    \"dependent\": false,\n                    \"cacheable\": true,\n                    \"query_block\": {\n                      \"select_id\": 3,\n                      \"message\": \"No tables used\"\n                    } /* query_block */\n                  }\n                ] /* query_specifications */\n              } /* union_result */\n            } /* query_block */\n          } /* materialized_from_subquery */\n        } /* table */\n      } /* grouping_operation */\n    } /* ordering_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"4.40\"\n    } /* cost_info */,\n    \"grouping_operation\": {\n      \"using_temporary_table\": true,\n      \"using_filesort\": true,\n      \"cost_info\": {\n        \"sort_cost\": \"2.00\"\n      } /* cost_info */,\n      \"table\": {\n        \"table_name\": \"t1\",\n        \"access_type\": \"ALL\",\n        \"rows_examined_per_scan\": 2,\n        \"rows_produced_per_join\": 2,\n        \"filtered\": \"100.00\",\n        \"cost_info\": {\n          \"read_cost\": \"2.00\",\n          \"eval_cost\": \"0.40\",\n          \"prefix_cost\": \"2.40\",\n          \"data_read_per_join\": \"32\"\n        } /* cost_info */,\n        \"used_columns\": [\n          \"a\"\n        ] /* used_columns */\n      } /* table */,\n      \"group_by_subqueries\": [\n        {\n          \"dependent\": false,\n          \"cacheable\": true,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"cost_info\": {\n              \"query_cost\": \"1.00\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"d\",\n              \"access_type\": \"system\",\n              \"rows_examined_per_scan\": 1,\n              \"rows_produced_per_join\": 1,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"0.00\",\n                \"eval_cost\": \"0.20\",\n                \"prefix_cost\": \"0.00\",\n                \"data_read_per_join\": \"16\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"b\"\n              ] /* used_columns */,\n              \"materialized_from_subquery\": {\n                \"using_temporary_table\": true,\n                \"dependent\": false,\n                \"cacheable\": true,\n                \"query_block\": {\n                  \"select_id\": 3,\n                  \"cost_info\": {\n                    \"query_cost\": \"5.21\"\n                  } /* cost_info */,\n                  \"ordering_operation\": {\n                    \"using_temporary_table\": true,\n                    \"using_filesort\": true,\n                    \"nested_loop\": [\n                      {\n                        \"table\": {\n                          \"table_name\": \"t1\",\n                          \"access_type\": \"ALL\",\n                          \"rows_examined_per_scan\": 2,\n                          \"rows_produced_per_join\": 2,\n                          \"filtered\": \"100.00\",\n                          \"cost_info\": {\n                            \"read_cost\": \"2.00\",\n                            \"eval_cost\": \"0.40\",\n                            \"prefix_cost\": \"2.40\",\n                            \"data_read_per_join\": \"32\"\n                          } /* cost_info */,\n                          \"used_columns\": [\n                            \"a\",\n                            \"b\"\n                          ] /* used_columns */\n                        } /* table */\n                      },\n                      {\n                        \"table\": {\n                          \"table_name\": \"t2\",\n                          \"access_type\": \"ALL\",\n                          \"rows_examined_per_scan\": 2,\n                          \"rows_produced_per_join\": 4,\n                          \"filtered\": \"100.00\",\n                          \"using_join_buffer\": \"Block Nested Loop\",\n                          \"cost_info\": {\n                            \"read_cost\": \"2.00\",\n                            \"eval_cost\": \"0.80\",\n                            \"prefix_cost\": \"5.21\",\n                            \"data_read_per_join\": \"64\"\n                          } /* cost_info */\n                        } /* table */\n                      }\n                    ] /* nested_loop */\n                  } /* ordering_operation */\n                } /* query_block */\n              } /* materialized_from_subquery */\n            } /* table */\n          } /* query_block */\n        }\n      ] /* group_by_subqueries */\n    } /* grouping_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"2.40\"\n    } /* cost_info */,\n    \"table\": {\n      \"table_name\": \"t1\",\n      \"access_type\": \"ALL\",\n      \"rows_examined_per_scan\": 2,\n      \"rows_produced_per_join\": 2,\n      \"filtered\": \"100.00\",\n      \"cost_info\": {\n        \"read_cost\": \"2.00\",\n        \"eval_cost\": \"0.40\",\n        \"prefix_cost\": \"2.40\",\n        \"data_read_per_join\": \"16\"\n      } /* cost_info */\n    } /* table */,\n    \"optimized_away_subqueries\": [\n      {\n        \"dependent\": false,\n        \"cacheable\": true,\n        \"query_block\": {\n          \"select_id\": 3,\n          \"cost_info\": {\n            \"query_cost\": \"4.40\"\n          } /* cost_info */,\n          \"grouping_operation\": {\n            \"using_temporary_table\": true,\n            \"using_filesort\": true,\n            \"cost_info\": {\n              \"sort_cost\": \"2.00\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t1\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 2,\n              \"rows_produced_per_join\": 2,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.00\",\n                \"eval_cost\": \"0.40\",\n                \"prefix_cost\": \"2.40\",\n                \"data_read_per_join\": \"16\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"f1\"\n              ] /* used_columns */\n            } /* table */\n          } /* grouping_operation */\n        } /* query_block */\n      }\n    ] /* optimized_away_subqueries */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"4.02\"\n    } /* cost_info */,\n    \"ordering_operation\": {\n      \"using_filesort\": true,\n      \"table\": {\n        \"table_name\": \"t1\",\n        \"access_type\": \"ALL\",\n        \"rows_examined_per_scan\": 10,\n        \"rows_produced_per_join\": 10,\n        \"filtered\": \"100.00\",\n        \"cost_info\": {\n          \"read_cost\": \"2.02\",\n          \"eval_cost\": \"2.00\",\n          \"prefix_cost\": \"4.02\",\n          \"data_read_per_join\": \"80\"\n        } /* cost_info */,\n        \"used_columns\": [\n          \"i\"\n        ] /* used_columns */\n      } /* table */,\n      \"order_by_subqueries\": [\n        {\n          \"dependent\": true,\n          \"cacheable\": false,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"cost_info\": {\n              \"query_cost\": \"4.02\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t2\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 10,\n              \"rows_produced_per_join\": 1,\n              \"filtered\": \"10.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.02\",\n                \"eval_cost\": \"0.20\",\n                \"prefix_cost\": \"4.02\",\n                \"data_read_per_join\": \"16\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"i\",\n                \"j\"\n              ] /* used_columns */,\n              \"attached_condition\": \"(test.t2.i = test.t1.i)\"\n            } /* table */\n          } /* query_block */\n        }\n      ] /* order_by_subqueries */\n    } /* ordering_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"4.02\"\n    } /* cost_info */,\n    \"grouping_operation\": {\n      \"using_temporary_table\": true,\n      \"using_filesort\": true,\n      \"table\": {\n        \"table_name\": \"t1\",\n        \"access_type\": \"ALL\",\n        \"rows_examined_per_scan\": 10,\n        \"rows_produced_per_join\": 10,\n        \"filtered\": \"100.00\",\n        \"cost_info\": {\n          \"read_cost\": \"2.02\",\n          \"eval_cost\": \"2.00\",\n          \"prefix_cost\": \"4.02\",\n          \"data_read_per_join\": \"80\"\n        } /* cost_info */,\n        \"used_columns\": [\n          \"i\"\n        ] /* used_columns */\n      } /* table */,\n      \"group_by_subqueries\": [\n        {\n          \"dependent\": true,\n          \"cacheable\": false,\n          \"query_block\": {\n            \"select_id\": 2,\n            \"cost_info\": {\n              \"query_cost\": \"4.02\"\n            } /* cost_info */,\n            \"table\": {\n              \"table_name\": \"t2\",\n              \"access_type\": \"ALL\",\n              \"rows_examined_per_scan\": 10,\n              \"rows_produced_per_join\": 1,\n              \"filtered\": \"10.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.02\",\n                \"eval_cost\": \"0.20\",\n                \"prefix_cost\": \"4.02\",\n                \"data_read_per_join\": \"16\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"i\",\n                \"j\"\n              ] /* used_columns */,\n              \"attached_condition\": \"(test.t2.i = test.t1.i)\"\n            } /* table */\n          } /* query_block */\n        }\n      ] /* group_by_subqueries */\n    } /* grouping_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"6.50\"\n    } /* cost_info */,\n    \"ordering_operation\": {\n      \"using_temporary_table\": true,\n      \"using_filesort\": true,\n      \"grouping_operation\": {\n        \"using_filesort\": false,\n        \"table\": {\n          \"table_name\": \"t1\",\n          \"access_type\": \"range\",\n          \"possible_keys\": [\n            \"k1\"\n          ] /* possible_keys */,\n          \"key\": \"k1\",\n          \"used_key_parts\": [\n            \"a\"\n          ] /* used_key_parts */,\n          \"key_length\": \"4\",\n          \"rows_examined_per_scan\": 11,\n          \"rows_produced_per_join\": 11,\n          \"filtered\": \"100.00\",\n          \"using_index_for_group_by\": true,\n          \"cost_info\": {\n            \"read_cost\": \"4.30\",\n            \"eval_cost\": \"2.20\",\n            \"prefix_cost\": \"6.50\",\n            \"data_read_per_join\": \"176\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"a\",\n            \"b\"\n          ] /* used_columns */\n        } /* table */\n      } /* grouping_operation */\n    } /* ordering_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"6.20\"\n    } /* cost_info */,\n    \"ordering_operation\": {\n      \"using_temporary_table\": true,\n      \"using_filesort\": true,\n      \"grouping_operation\": {\n        \"using_filesort\": true,\n        \"nested_loop\": [\n          {\n            \"table\": {\n              \"table_name\": \"t1\",\n              \"access_type\": \"ALL\",\n              \"possible_keys\": [\n                \"PRIMARY\"\n              ] /* possible_keys */,\n              \"rows_examined_per_scan\": 3,\n              \"rows_produced_per_join\": 3,\n              \"filtered\": \"100.00\",\n              \"cost_info\": {\n                \"read_cost\": \"2.01\",\n                \"eval_cost\": \"0.60\",\n                \"prefix_cost\": \"2.61\",\n                \"data_read_per_join\": \"48\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"a\",\n                \"b\"\n              ] /* used_columns */\n            } /* table */\n          },\n          {\n            \"table\": {\n              \"table_name\": \"t2\",\n              \"access_type\": \"ref\",\n              \"possible_keys\": [\n                \"PRIMARY\"\n              ] /* possible_keys */,\n              \"key\": \"PRIMARY\",\n              \"used_key_parts\": [\n                \"a\"\n              ] /* used_key_parts */,\n              \"key_length\": \"4\",\n              \"ref\": [\n                \"test.t1.a\"\n              ] /* ref */,\n              \"rows_examined_per_scan\": 1,\n              \"rows_produced_per_join\": 3,\n              \"filtered\": \"100.00\",\n              \"using_index\": true,\n              \"cost_info\": {\n                \"read_cost\": \"3.00\",\n                \"eval_cost\": \"0.60\",\n                \"prefix_cost\": \"6.21\",\n                \"data_read_per_join\": \"48\"\n              } /* cost_info */,\n              \"used_columns\": [\n                \"a\",\n                \"b\"\n              ] /* used_columns */\n            } /* table */\n          }\n        ] /* nested_loop */\n      } /* grouping_operation */\n    } /* ordering_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"12.82\"\n    } /* cost_info */,\n    \"grouping_operation\": {\n      \"using_filesort\": true,\n      \"cost_info\": {\n        \"sort_cost\": \"9.00\"\n      } /* cost_info */,\n      \"table\": {\n        \"table_name\": \"t1\",\n        \"access_type\": \"ALL\",\n        \"rows_examined_per_scan\": 9,\n        \"rows_produced_per_join\": 9,\n        \"filtered\": \"100.00\",\n        \"cost_info\": {\n          \"read_cost\": \"2.02\",\n          \"eval_cost\": \"1.80\",\n          \"prefix_cost\": \"3.82\",\n          \"data_read_per_join\": \"144\"\n        } /* cost_info */,\n        \"used_columns\": [\n          \"a\",\n          \"b\"\n        ] /* used_columns */\n      } /* table */\n    } /* grouping_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"3.01\"\n    } /* cost_info */,\n    \"ordering_operation\": {\n      \"using_filesort\": true,\n      \"duplicates_removal\": {\n        \"using_temporary_table\": true,\n        \"using_filesort\": false,\n        \"grouping_operation\": {\n          \"using_temporary_table\": true,\n          \"using_filesort\": false,\n          \"table\": {\n            \"table_name\": \"t1\",\n            \"access_type\": \"ALL\",\n            \"rows_examined_per_scan\": 5,\n            \"rows_produced_per_join\": 5,\n            \"filtered\": \"100.00\",\n            \"cost_info\": {\n              \"read_cost\": \"2.01\",\n              \"eval_cost\": \"1.00\",\n              \"prefix_cost\": \"3.01\",\n              \"data_read_per_join\": \"80\"\n            } /* cost_info */,\n            \"used_columns\": [\n              \"a\",\n              \"b\"\n            ] /* used_columns */\n          } /* table */\n        } /* grouping_operation */\n      } /* duplicates_removal */\n    } /* ordering_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"2.40\"\n    } /* cost_info */,\n    \"ordering_operation\": {\n      \"using_filesort\": false,\n      \"duplicates_removal\": {\n        \"using_temporary_table\": true,\n        \"using_filesort\": false,\n        \"buffer_result\": {\n          \"using_temporary_table\": true,\n          \"nested_loop\": [\n            {\n              \"table\": {\n                \"table_name\": \"t1\",\n                \"access_type\": \"system\",\n                \"rows_examined_per_scan\": 1,\n                \"rows_produced_per_join\": 1,\n                \"filtered\": \"100.00\",\n                \"cost_info\": {\n                  \"read_cost\": \"0.00\",\n                  \"eval_cost\": \"0.20\",\n                  \"prefix_cost\": \"0.00\",\n                  \"data_read_per_join\": \"8\"\n                } /* cost_info */,\n                \"used_columns\": [\n                  \"a\"\n                ] /* used_columns */\n              } /* table */\n            },\n            {\n              \"table\": {\n                \"table_name\": \"t2\",\n                \"access_type\": \"index\",\n                \"key\": \"PRIMARY\",\n                \"used_key_parts\": [\n                  \"a\"\n                ] /* used_key_parts */,\n                \"key_length\": \"4\",\n                \"rows_examined_per_scan\": 2,\n                \"rows_produced_per_join\": 2,\n                \"filtered\": \"100.00\",\n                \"using_index\": true,\n                \"distinct\": true,\n                \"cost_info\": {\n                  \"read_cost\": \"2.00\",\n                  \"eval_cost\": \"0.40\",\n                  \"prefix_cost\": \"2.40\",\n                  \"data_read_per_join\": \"16\"\n                } /* cost_info */,\n                \"used_columns\": [\n                  \"a\"\n                ] /* used_columns */\n              } /* table */\n            }\n          ] /* nested_loop */\n        } /* buffer_result */\n      } /* duplicates_removal */\n    } /* ordering_operation */\n  } /* query_block */\n}`,\n\t`{\n  \"query_block\": {\n    \"select_id\": 1,\n    \"cost_info\": {\n      \"query_cost\": \"6.41\"\n    } /* cost_info */,\n    \"nested_loop\": [\n      {\n        \"table\": {\n          \"table_name\": \"t1\",\n          \"access_type\": \"ALL\",\n          \"possible_keys\": [\n            \"PRIMARY\"\n          ] /* possible_keys */,\n          \"rows_examined_per_scan\": 4,\n          \"rows_produced_per_join\": 3,\n          \"filtered\": \"75.00\",\n          \"cost_info\": {\n            \"read_cost\": \"2.21\",\n            \"eval_cost\": \"0.60\",\n            \"prefix_cost\": \"2.81\",\n            \"data_read_per_join\": \"48\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"a\",\n            \"b\"\n          ] /* used_columns */,\n          \"attached_condition\": \"(test.t1.b <> 30)\"\n        } /* table */\n      },\n      {\n        \"table\": {\n          \"table_name\": \"t2\",\n          \"access_type\": \"eq_ref\",\n          \"possible_keys\": [\n            \"PRIMARY\"\n          ] /* possible_keys */,\n          \"key\": \"PRIMARY\",\n          \"used_key_parts\": [\n            \"a\"\n          ] /* used_key_parts */,\n          \"key_length\": \"4\",\n          \"ref\": [\n            \"test.t1.a\"\n          ] /* ref */,\n          \"rows_examined_per_scan\": 1,\n          \"rows_produced_per_join\": 3,\n          \"filtered\": \"100.00\",\n          \"using_index\": true,\n          \"cost_info\": {\n            \"read_cost\": \"3.00\",\n            \"eval_cost\": \"0.60\",\n            \"prefix_cost\": \"6.41\",\n            \"data_read_per_join\": \"24\"\n          } /* cost_info */,\n          \"used_columns\": [\n            \"a\"\n          ] /* used_columns */\n        } /* table */\n      }\n    ] /* nested_loop */\n  } /* query_block */\n}`,\n}\n\nfunc TestExplain(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\t// TraditionalFormatExplain\n\tfor idx, sql := range sqls {\n\t\texp, err := connTest.Explain(sql, TraditionalExplainType, TraditionalFormatExplain)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tpretty.Println(\"No.:\", idx, \"\\nOld: \", sql, \"\\nNew: \", exp.SQL)\n\t\tpretty.Println(exp)\n\t}\n\t// JSONFormatExplain\n\tfor idx, sql := range sqls {\n\t\texp, err := connTest.Explain(sql, TraditionalExplainType, JSONFormatExplain)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tpretty.Println(\"No.:\", idx, \"\\nOld: \", sql, \"\\nNew: \", exp.SQL)\n\t\tpretty.Println(exp)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestParseExplainText(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tfor _, content := range exp {\n\t\tpretty.Println(RemoveSQLComments(content))\n\t\tpretty.Println(ParseExplainText(content))\n\t}\n\t/*\n\t\t//length := len(exp)\n\t\tpretty.Println(string(RemoveSQLComments([]byte(exp[9]))))\n\t\texplainInfo, err := ParseExplainText(exp[9])\n\t\tpretty.Println(explainInfo)\n\t\tfmt.Println(err)\n\t*/\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindTablesInJson(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tidx := 9\n\tfor _, j := range exp[idx : idx+1] {\n\t\tpretty.Println(j)\n\t\tfindTablesInJSON(j, 0)\n\t}\n\tpretty.Println(len(explainJSONTables), explainJSONTables)\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFormatJsonIntoTraditional(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tidx := 11\n\tfor _, j := range exp[idx : idx+1] {\n\t\tpretty.Println(j)\n\t\tpretty.Println(FormatJSONIntoTraditional(j))\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestPrintMarkdownExplainTable(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\texpInfo, err := connTest.Explain(\"select 1\", TraditionalExplainType, TraditionalFormatExplain)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\terr = common.GoldenDiff(func() {\n\t\tPrintMarkdownExplainTable(expInfo)\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestExplainInfoTranslator(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\texpInfo, err := connTest.Explain(\"select 1\", TraditionalExplainType, TraditionalFormatExplain)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = common.GoldenDiff(func() {\n\t\tExplainInfoTranslator(expInfo)\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestMySQLExplainWarnings(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\texpInfo, err := connTest.Explain(\"select 1\", TraditionalExplainType, TraditionalFormatExplain)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = common.GoldenDiff(func() {\n\t\tMySQLExplainWarnings(expInfo)\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestMySQLExplainQueryCost(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := common.GoldenDiff(func() {\n\t\texpInfo, err := connTest.Explain(\"select 1\", TraditionalExplainType, TraditionalFormatExplain)\n\t\tfmt.Println(err, MySQLExplainQueryCost(expInfo))\n\t\texpInfo, err = connTest.Explain(\"select 1\", ExtendedExplainType, TraditionalFormatExplain)\n\t\tfmt.Println(err, MySQLExplainQueryCost(expInfo))\n\t\texpInfo, err = connTest.Explain(\"select 1\", TraditionalExplainType, JSONFormatExplain)\n\t\tfmt.Println(err, MySQLExplainQueryCost(expInfo))\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestSupportExplainWrite(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\t_, err := connTest.supportExplainWrite()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestExplainAbleSQL(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tfor _, sql := range sqls {\n\t\tif _, err := connTest.explainAbleSQL(sql); err != nil {\n\t\t\tt.Errorf(\"SQL: %s, not explain able\", sql)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "database/mysql.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"bytes\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t// for database/sql\n\t_ \"github.com/go-sql-driver/mysql\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// Connector 数据库连接基本对象\ntype Connector struct {\n\tAddr     string\n\tUser     string\n\tPass     string\n\tDatabase string\n\tCharset  string\n\tConn     *sql.DB\n}\n\n// QueryResult 数据库查询返回值\ntype QueryResult struct {\n\tRows      *sql.Rows\n\tError     error\n\tWarning   *sql.Rows\n\tQueryCost float64\n}\n\n// NewConnector 创建新连接\nfunc NewConnector(dsn *common.Dsn) (*Connector, error) {\n\tconn, err := sql.Open(\"mysql\", common.FormatDSN(dsn))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconnector := &Connector{\n\t\tAddr:     dsn.Addr,\n\t\tUser:     dsn.User,\n\t\tPass:     dsn.Password,\n\t\tDatabase: dsn.Schema,\n\t\tCharset:  dsn.Charset,\n\t\tConn:     conn,\n\t}\n\treturn connector, err\n}\n\n// Query 执行SQL\nfunc (db *Connector) Query(sql string, params ...interface{}) (QueryResult, error) {\n\tvar res QueryResult\n\tvar err error\n\t// 测试环境如果检查是关闭的，则SQL不会被执行\n\tif common.Config.TestDSN.Disable {\n\t\treturn res, errors.New(\"dsn is disable\")\n\t}\n\t// 数据库安全性检查：如果 Connector 的 IP 端口与 TEST 环境不一致，则启用SQL白名单\n\t// 不在白名单中的SQL不允许执行\n\t// 执行环境与test环境不相同\n\tif db.Addr != common.Config.TestDSN.Addr && db.dangerousQuery(sql) {\n\t\treturn res, fmt.Errorf(\"query execution deny: execute SQL with DSN(%s/%s) '%s'\",\n\t\t\tdb.Addr, db.Database, fmt.Sprintf(sql, params...))\n\t}\n\n\tif db.Database == \"\" {\n\t\tdb.Database = \"information_schema\"\n\t}\n\n\tcommon.Log.Debug(\"Execute SQL with DSN(%s/%s) : %s\", db.Addr, db.Database, fmt.Sprintf(sql, params...))\n\t_, err = db.Conn.Exec(\"USE `\" + db.Database + \"`\")\n\tif err != nil {\n\t\tcommon.Log.Error(err.Error())\n\t\treturn res, err\n\t}\n\tres.Rows, res.Error = db.Conn.Query(sql, params...)\n\n\tif common.Config.ShowWarnings {\n\t\tres.Warning, err = db.Conn.Query(\"SHOW WARNINGS\")\n\t\tcommon.LogIfError(err, \"\")\n\t}\n\n\t// SHOW WARNINGS 并不会影响 last_query_cost\n\tif common.Config.ShowLastQueryCost {\n\t\tcost, err := db.Conn.Query(\"SHOW SESSION STATUS LIKE 'last_query_cost'\")\n\t\tif err == nil {\n\t\t\tvar varName string\n\t\t\tif cost.Next() {\n\t\t\t\terr = cost.Scan(&varName, &res.QueryCost)\n\t\t\t\tcommon.LogIfError(err, \"\")\n\t\t\t}\n\t\t\tif err := cost.Close(); err != nil {\n\t\t\t\tcommon.Log.Error(err.Error())\n\t\t\t}\n\t\t}\n\t}\n\n\tif res.Error != nil && err == nil {\n\t\terr = res.Error\n\t}\n\treturn res, err\n}\n\n// Version 获取MySQL数据库版本\nfunc (db *Connector) Version() (int, error) {\n\tversion := 99999\n\t// 从数据库中获取版本信息\n\tres, err := db.Query(\"select @@version\")\n\tif err != nil {\n\t\tcommon.Log.Warn(\"(db *Connector) Version() Error: %v\", err)\n\t\treturn version, err\n\t}\n\n\t// MariaDB https://mariadb.com/kb/en/library/comment-syntax/\n\t// MySQL https://dev.mysql.com/doc/refman/8.0/en/comments.html\n\tvar versionStr string\n\tvar versionSeg []string\n\tif res.Rows.Next() {\n\t\terr = res.Rows.Scan(&versionStr)\n\t}\n\tif err := res.Rows.Close(); err != nil {\n\t\tcommon.Log.Error(err.Error())\n\t}\n\tversionStr = strings.Split(versionStr, \"-\")[0]\n\tversionSeg = strings.Split(versionStr, \".\")\n\tif len(versionSeg) == 3 {\n\t\tversionStr = fmt.Sprintf(\"%s%02s%02s\", versionSeg[0], versionSeg[1], versionSeg[2])\n\t\tversion, err = strconv.Atoi(versionStr)\n\t}\n\treturn version, err\n}\n\n// SingleIntValue 获取某个int型变量的值\nfunc (db *Connector) SingleIntValue(option string) (int, error) {\n\t// 从数据库中获取信息\n\tres, err := db.Query(\"select @@\" + option)\n\tif err != nil {\n\t\tcommon.Log.Warn(\"(db *Connector) SingleIntValue() Error: %v\", err)\n\t\treturn -1, err\n\t}\n\n\tvar intVal int\n\tif res.Rows.Next() {\n\t\terr = res.Rows.Scan(&intVal)\n\t}\n\tif err := res.Rows.Close(); err != nil {\n\t\tcommon.Log.Error(err.Error())\n\t}\n\treturn intVal, err\n}\n\n// ColumnCardinality 粒度计算\nfunc (db *Connector) ColumnCardinality(tb, col string) float64 {\n\t// 获取该表上的已有的索引\n\n\t// show table status 获取总行数（近似）\n\tcommon.Log.Debug(\"ColumnCardinality, ShowTableStatus check `%s` status Rows\", tb)\n\ttbStatus, err := db.ShowTableStatus(tb)\n\tif err != nil {\n\t\tcommon.Log.Warn(\"(db *Connector) ColumnCardinality() ShowTableStatus Error: %v\", err)\n\t\treturn 0\n\t}\n\n\t// 如果是视图或表中无数据，rowTotal 都为 0\n\t// 视图不需要加索引，无数据相当于散粒度为 1\n\tif len(tbStatus.Rows) == 0 {\n\t\tcommon.Log.Debug(\"(db *Connector) ColumnCardinality() No table status: %s\", tb)\n\t\treturn 1\n\t}\n\tif tbStatus.Rows[0].Rows == nil {\n\t\tcommon.Log.Debug(\"(db *Connector) ColumnCardinality() No table status: %s\", tb)\n\t\treturn 1\n\t}\n\trowTotal, err := strconv.ParseUint(string(tbStatus.Rows[0].Rows), 10, 64)\n\tif rowTotal == 0 || err != nil {\n\t\tif common.Config.Sampling {\n\t\t\tcommon.Log.Debug(\"ColumnCardinality, %s rowTotal == 0\", tb)\n\t\t}\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"ColumnCardinality, ParseUint: \" + string(tbStatus.Rows[0].Rows) + \" Error: \" + err.Error())\n\t\t}\n\t\treturn 1\n\t}\n\n\t// rowTotal > xxx 时保护数据库，不对该值计算散粒度，xxx可以在配置中设置\n\tif rowTotal > common.Config.MaxTotalRows {\n\t\treturn 0.5\n\t}\n\n\t// 计算该列散粒度\n\tdb.Conn.Stats()\n\tres, err := db.Query(fmt.Sprintf(\"select count(distinct `%s`) from `%s`.`%s`\",\n\t\tEscape(col, false),\n\t\tEscape(db.Database, false),\n\t\tEscape(tb, false)))\n\tif err != nil {\n\t\tcommon.Log.Warn(\"(db *Connector) ColumnCardinality() Query Error: %v\", err)\n\t\treturn 0\n\t}\n\n\tvar colNum float64\n\tif res.Rows.Next() {\n\t\terr = res.Rows.Scan(&colNum)\n\t\tif err != nil {\n\t\t\tcommon.Log.Warn(\"(db *Connector) ColumnCardinality() Query Error: %v\", err)\n\t\t\treturn 0\n\t\t}\n\t}\n\tres.Rows.Close()\n\n\t// 当table status元数据不准确时 rowTotal 可能远小于count(*)，导致散粒度大于1\n\tif colNum > float64(rowTotal) {\n\t\treturn 1\n\t}\n\t// 散粒度区间：[0,1]\n\treturn colNum / float64(rowTotal)\n}\n\n// IsView 判断表是否是视图\nfunc (db *Connector) IsView(tbName string) bool {\n\tcommon.Log.Debug(\"IsView, ShowTableStatus check if `%s` is view\", tbName)\n\ttbStatus, err := db.ShowTableStatus(tbName)\n\tif err != nil {\n\t\tcommon.Log.Error(\"(db *Connector) IsView Error: %v:\", err)\n\t\treturn false\n\t}\n\n\tif len(tbStatus.Rows) > 0 {\n\t\t/*\n\t\t\t\t// mysql 8.0.23\n\t\t\tmysql> show table status like \"actor_info\"\\G\n\t\t\t*************************** 1. row ***************************\n\t\t\t           Name: actor_info\n\t\t\t         Engine: NULL\n\t\t\t        Version: NULL\n\t\t\t     Row_format: NULL\n\t\t\t           Rows: NULL\n\t\t\t Avg_row_length: NULL\n\t\t\t    Data_length: NULL\n\t\t\tMax_data_length: NULL\n\t\t\t   Index_length: NULL\n\t\t\t      Data_free: NULL\n\t\t\t Auto_increment: NULL\n\t\t\t    Create_time: 2021-06-01 10:56:47\n\t\t\t    Update_time: NULL\n\t\t\t     Check_time: NULL\n\t\t\t      Collation: NULL\n\t\t\t       Checksum: NULL\n\t\t\t Create_options: NULL\n\t\t\t        Comment: VIEW\n\n\t\t\t\t\tmysql> show table status like \"film\"\\G\n\t\t\t\t\t*************************** 1. row ***************************\n\t\t\t\t\tName: film\n\t\t\t\t\tEngine: InnoDB\n\t\t\t\t\tVersion: 10\n\t\t\t\t\tRow_format: Dynamic\n\t\t\t\t\tRows: 1000\n\t\t\t\t\tAvg_row_length: 196\n\t\t\t\t\tData_length: 196608\n\t\t\t\t\tMax_data_length: 0\n\t\t\t\t\tIndex_length: 81920\n\t\t\t\t\tData_free: 0\n\t\t\t\t\tAuto_increment: 1001\n\t\t\t\t\tCreate_time: 2021-06-01 10:56:47\n\t\t\t\t\tUpdate_time: NULL\n\t\t\t\t\tCheck_time: NULL\n\t\t\t\t\tCollation: utf8_general_ci\n\t\t\t\t\tChecksum: NULL\n\t\t\t\t\tCreate_options:\n\t\t\t\t\tComment:\n\t\t*/\n\t\tif string(tbStatus.Rows[0].Comment) == \"VIEW\" {\n\t\t\treturn true\n\t\t}\n\t\tif string(tbStatus.Rows[0].Comment) == \"TABLE\" {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn false\n}\n\n// RemoveSQLComments 去除SQL中的注释\nfunc RemoveSQLComments(sql string) string {\n\tbuf := []byte(sql)\n\t// (\"(\"\"|[^\"]|(\\\"))*\") 双引号中的内容, \"\", \"\\\"\"\n\t// ('(''|[^']|(\\'))*') 单引号中的内容, '', '\\''\n\t// (--[^\\n\\r]*) 双减号注释\n\t// (#.*) 井号注释\n\t// (/\\*([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+/) 多行注释\n\tcommentRegex := regexp.MustCompile(`(\"(\"\"|[^\"]|(\\\"))*\")|('(''|[^']|(\\'))*')|(--[^\\n\\r]*)|(#.*)|(/\\*([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+/)`)\n\n\tres := commentRegex.ReplaceAllFunc(buf, func(s []byte) []byte {\n\t\tif (s[0] == '\"' && s[len(s)-1] == '\"') ||\n\t\t\t(s[0] == '\\'' && s[len(s)-1] == '\\'') ||\n\t\t\t(string(s[:3]) == \"/*!\") {\n\t\t\treturn s\n\t\t}\n\t\treturn []byte(\"\")\n\t})\n\treturn strings.TrimSpace(string(res))\n}\n\n// 为了防止在 Online 环境进行误操作，通过 dangerousQuery 来判断能否在 Online 执行\nfunc (db *Connector) dangerousQuery(query string) bool {\n\tqueries, err := sqlparser.SplitStatementToPieces(strings.TrimSpace(strings.ToLower(query)))\n\tif err != nil {\n\t\treturn true\n\t}\n\n\tfor _, query := range queries {\n\t\tdangerous := true\n\t\twhiteList := []string{\n\t\t\t\"select\",\n\t\t\t\"show\",\n\t\t\t\"explain\",\n\t\t\t\"describe\",\n\t\t\t\"desc\",\n\t\t}\n\n\t\tfor _, prefix := range whiteList {\n\t\t\tif strings.HasPrefix(query, prefix) {\n\t\t\t\tdangerous = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif dangerous {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// TimeFormat standard MySQL datetime format\nconst TimeFormat = \"2006-01-02 15:04:05.000000000\"\n\n// TimeString returns t as string in MySQL format Converts time.Time zero to MySQL zero.\nfunc TimeString(t time.Time) string {\n\tif t.IsZero() {\n\t\treturn \"0000-00-00 00:00:00\"\n\t}\n\tif t.Nanosecond() == 0 {\n\t\treturn t.Format(TimeFormat[:19])\n\t}\n\treturn t.Format(TimeFormat)\n}\n\n// NullString null able string\nfunc NullString(buf []byte) string {\n\tif buf == nil {\n\t\treturn \"NULL\"\n\t}\n\treturn string(buf)\n}\n\n// NullFloat null able float\nfunc NullFloat(buf []byte) float64 {\n\tif buf == nil {\n\t\treturn 0\n\t}\n\tf, _ := strconv.ParseFloat(string(buf), 64)\n\treturn f\n}\n\n// NullInt null able int\nfunc NullInt(buf []byte) int64 {\n\tif buf == nil {\n\t\treturn 0\n\t}\n\ti, _ := strconv.ParseInt(string(buf), 10, 64)\n\treturn i\n}\n\n// quoteEscape sql_mode=no_backslash_escapes\nfunc quoteEscape(source string) string {\n\tvar buf bytes.Buffer\n\tlast := 0\n\tfor ii, bb := range source {\n\t\tif bb == '\\'' {\n\t\t\t_, err := io.WriteString(&buf, source[last:ii])\n\t\t\tcommon.LogIfWarn(err, \"\")\n\t\t\t_, err = io.WriteString(&buf, `''`)\n\t\t\tcommon.LogIfWarn(err, \"\")\n\t\t\tlast = ii + 1\n\t\t}\n\t}\n\t_, err := io.WriteString(&buf, source[last:])\n\tcommon.LogIfWarn(err, \"\")\n\treturn buf.String()\n}\n\n// stringEscape mysql_escape_string\n// https://github.com/liule/golang_escape\nfunc stringEscape(source string) string {\n\tvar j int\n\tif source == \"\" {\n\t\treturn source\n\t}\n\ttempStr := source[:]\n\tdesc := make([]byte, len(tempStr)*2)\n\tfor i, b := range tempStr {\n\t\tflag := false\n\t\tvar escape byte\n\t\tswitch b {\n\t\tcase '\\000':\n\t\t\tflag = true\n\t\t\tescape = '\\000'\n\t\tcase '\\r':\n\t\t\tflag = true\n\t\t\tescape = '\\r'\n\t\tcase '\\n':\n\t\t\tflag = true\n\t\t\tescape = '\\n'\n\t\tcase '\\\\':\n\t\t\tflag = true\n\t\t\tescape = '\\\\'\n\t\tcase '\\'':\n\t\t\tflag = true\n\t\t\tescape = '\\''\n\t\tcase '\"':\n\t\t\tflag = true\n\t\t\tescape = '\"'\n\t\tcase '\\032':\n\t\t\tflag = true\n\t\t\tescape = 'Z'\n\t\tdefault:\n\t\t}\n\t\tif flag {\n\t\t\tdesc[j] = '\\\\'\n\t\t\tdesc[j+1] = escape\n\t\t\tj = j + 2\n\t\t} else {\n\t\t\tdesc[j] = tempStr[i]\n\t\t\tj = j + 1\n\t\t}\n\t}\n\treturn string(desc[0:j])\n}\n\n// Escape like C API mysql_escape_string()\nfunc Escape(source string, NoBackslashEscapes bool) string {\n\t// NoBackslashEscapes https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_no_backslash_escapes\n\t// TODO: NoBackslashEscapes always false\n\tif NoBackslashEscapes {\n\t\treturn quoteEscape(source)\n\t}\n\treturn stringEscape(source)\n}\n"
  },
  {
    "path": "database/mysql_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n)\n\nvar connTest *Connector\nvar update = flag.Bool(\"update\", false, \"update .golden files\")\n\nfunc TestMain(m *testing.M) {\n\t// 初始化 init\n\tif common.DevPath == \"\" {\n\t\t_, file, _, _ := runtime.Caller(0)\n\t\tcommon.DevPath, _ = filepath.Abs(filepath.Dir(filepath.Join(file, \"..\"+string(filepath.Separator))))\n\t}\n\tcommon.BaseDir = common.DevPath\n\terr := common.ParseConfig(\"\")\n\tcommon.LogIfError(err, \"init ParseConfig\")\n\tcommon.Log.Debug(\"mysql_test init\")\n\tconnTest, err = NewConnector(common.Config.TestDSN)\n\tif err != nil {\n\t\tcommon.Log.Critical(\"Test env Error: %v\", err)\n\t\tos.Exit(0)\n\t}\n\n\tif _, err := connTest.Version(); err != nil {\n\t\tcommon.Log.Critical(\"Test env Error: %v\", err)\n\t\tos.Exit(0)\n\t}\n\n\t// 分割线\n\tflag.Parse()\n\tm.Run()\n\n\t// 环境清理\n\t//\n}\n\nfunc TestQuery(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tres, err := connTest.Query(\"select 0\")\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\tfor res.Rows.Next() {\n\t\tvar val int\n\t\terr = res.Rows.Scan(&val)\n\t\tif err != nil {\n\t\t\tt.Error(err.Error())\n\t\t}\n\t\tif val != 0 {\n\t\t\tt.Error(\"should return 0\")\n\t\t}\n\t}\n\tres.Rows.Close()\n\t// TODO: timeout test\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestColumnCardinality(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgDatabase := connTest.Database\n\tconnTest.Database = \"sakila\"\n\ta := connTest.ColumnCardinality(\"actor\", \"first_name\")\n\tif a > 1 || a <= 0 {\n\t\tt.Error(\"sakila.actor.first_name cardinality should in [0, 1], now it's\", a)\n\t}\n\tconnTest.Database = orgDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestDangerousSQL(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestCase := map[string]bool{\n\t\t\"select * from tb;delete from tb;\": true,\n\t\t\"show database;\":                   false,\n\t\t\"select * from t;\":                 false,\n\t\t\"explain delete from t;\":           false,\n\t}\n\n\tdb := Connector{}\n\tfor sql, want := range testCase {\n\t\tgot := db.dangerousQuery(sql)\n\t\tif got != want {\n\t\t\tt.Errorf(\"SQL:%s got:%v want:%v\", sql, got, want)\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestWarningsAndQueryCost(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tcommon.Config.ShowWarnings = true\n\tcommon.Config.ShowLastQueryCost = true\n\tres, err := connTest.Query(\"explain select * from sakila.film\")\n\tif err != nil {\n\t\tt.Error(\"Query Error: \", err)\n\t} else {\n\t\tfor res.Warning.Next() {\n\t\t\tvar level, msg string\n\t\t\tvar code int\n\t\t\terr = res.Warning.Scan(&level, &code, &msg)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err.Error())\n\t\t\t}\n\t\t\tpretty.Println(msg)\n\t\t}\n\t\tres.Warning.Close()\n\t\tfmt.Println(res.QueryCost, err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestVersion(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tversion, err := connTest.Version()\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\tfmt.Println(version)\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestRemoveSQLComments(t *testing.T) {\n\t// Notice: double dash without space not comment, eg. `--not comment`\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tSQLs := []string{\n\t\t// FIXME: comments in two quotes string won't be remove\n\t\t// \t\t`\"abc\" /* comment */ \"abc\"`,\n\t\t// \t\t`\"abc\"\n\t\t// # comment\n\t\t// \"abc\"`,\n\t\t// \t\t`\"abc\"\n\t\t// -- comment\n\t\t// \"abc\"`,\n\t\t`select 'c#\\'#not comment'`,\n\t\t`select \"c#\\\"#not comment\"`,\n\t\t`-- comment`,\n\t\t`--`,\n\t\t`# comment`,\n\t\t`#comment`,\n\t\t`/* multi-line\ncomment*/`,\n\t\t`--\n-- comment`,\n\t}\n\n\t// fmt.Println(RemoveSQLComments(SQLs[0]))\n\t// return\n\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range SQLs {\n\t\t\tfmt.Println(RemoveSQLComments(sql))\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestSingleIntValue(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tval, err := connTest.SingleIntValue(\"read_only\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif val < 0 {\n\t\tt.Error(\"SingleIntValue, return should large than zero\")\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestIsView(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\toriginalDatabase := connTest.Database\n\tconnTest.Database = \"sakila\"\n\tif !connTest.IsView(\"actor_info\") {\n\t\tt.Error(\"actor_info should be a VIEW\")\n\t}\n\n\tif connTest.IsView(\"film\") {\n\t\tt.Error(\"film should be a TABLE\")\n\t}\n\tconnTest.Database = originalDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestNullString(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tcases := [][]byte{\n\t\tnil,\n\t\t[]byte(\"NULL\"),\n\t}\n\tfor _, buf := range cases {\n\t\tif NullString(buf) != \"NULL\" {\n\t\t\tt.Errorf(\"%s want NULL\", string(buf))\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestEscape(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tcases := []string{\n\t\t\"\",\n\t\t\"hello world\",\n\t\t\"hello' world\",\n\t\t`hello\" world`,\n\t\t\"hello\\000world\",\n\t\t`hello\\ world`,\n\t\t\"hello\\032world\",\n\t\t\"hello\\rworld\",\n\t\t\"hello\\nworld\",\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, str := range cases {\n\t\t\tfmt.Println(Escape(str, false))\n\t\t\tfmt.Println(Escape(str, true))\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "database/privilege.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\n// CurrentUser get current user with current_user() function\nfunc (db *Connector) CurrentUser() (string, string, error) {\n\tvar user, host string\n\tres, err := db.Query(\"select current_user()\")\n\tif err != nil {\n\t\treturn user, host, err\n\t}\n\tif res.Rows.Next() {\n\t\tvar currentUser string\n\t\terr = res.Rows.Scan(&currentUser)\n\t\tif err != nil {\n\t\t\treturn user, host, err\n\t\t}\n\t\tres.Rows.Close()\n\n\t\tcols := strings.Split(currentUser, \"@\")\n\t\tif len(cols) == 2 {\n\t\t\tuser = strings.Trim(cols[0], \"'\")\n\t\t\thost = strings.Trim(cols[1], \"'\")\n\t\t\tif strings.Contains(user, \"'\") || strings.Contains(host, \"'\") {\n\t\t\t\treturn \"\", \"\", errors.New(\"user or host contains irregular character\")\n\t\t\t}\n\t\t\treturn user, host, nil\n\t\t}\n\t\treturn user, host, errors.New(\"user or host contains irregular character\")\n\t}\n\treturn user, host, errors.New(\"no privilege info\")\n}\n\n// HasSelectPrivilege if user has select privilege\nfunc (db *Connector) HasSelectPrivilege() bool {\n\tuser, host, err := db.CurrentUser()\n\tif err != nil {\n\t\tcommon.Log.Error(\"User: %s, HasSelectPrivilege: %s\", db.User, err.Error())\n\t\treturn false\n\t}\n\tres, err := db.Query(fmt.Sprintf(\"select Select_priv from mysql.user where user='%s' and host='%s'\", user, host))\n\tif err != nil {\n\t\tcommon.Log.Error(\"HasSelectPrivilege, DSN: %s, Error: %s\", db.Addr, err.Error())\n\t\treturn false\n\t}\n\t// Select_priv\n\tif res.Rows.Next() {\n\t\tvar selectPrivilege string\n\t\terr = res.Rows.Scan(&selectPrivilege)\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"HasSelectPrivilege, Scan Error: %s\", err.Error())\n\t\t\treturn false\n\t\t}\n\t\tres.Rows.Close()\n\n\t\tif selectPrivilege == \"Y\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// HasAllPrivilege if user has all privileges\nfunc (db *Connector) HasAllPrivilege() bool {\n\tuser, host, err := db.CurrentUser()\n\tif err != nil {\n\t\tcommon.Log.Error(\"User: %s, HasAllPrivilege: %s\", db.User, err.Error())\n\t\treturn false\n\t}\n\n\t// concat privilege columns\n\tres, err := db.Query(\"SELECT GROUP_CONCAT(COLUMN_NAME) from information_schema.COLUMNS where TABLE_SCHEMA='mysql' and TABLE_NAME='user' and COLUMN_NAME like '%%_priv'\")\n\tif err != nil {\n\t\tcommon.Log.Error(\"HasAllPrivilege, DSN: %s, Error: %s\", db.Addr, err.Error())\n\t\treturn false\n\t}\n\n\tvar priv string\n\tif res.Rows.Next() {\n\t\terr = res.Rows.Scan(&priv)\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"HasAllPrivilege, DSN: %s, Scan error\", db.Addr)\n\t\t\treturn false\n\t\t}\n\t\tres.Rows.Close()\n\t}\n\n\t// get all privilege status\n\tres, err = db.Query(fmt.Sprintf(\"select concat(\"+priv+\") from mysql.user where user='%s' and host='%s'\", user, host))\n\tif err != nil {\n\t\tcommon.Log.Error(\"HasAllPrivilege, DSN: %s, Error: %s\", db.Addr, err.Error())\n\t\treturn false\n\t}\n\n\t// %_priv\n\tif res.Rows.Next() {\n\t\terr = res.Rows.Scan(&priv)\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"HasAllPrivilege, DSN: %s, Scan error\", db.Addr)\n\t\t\treturn false\n\t\t}\n\t\tres.Rows.Close()\n\t\tif strings.Replace(priv, \"Y\", \"\", -1) == \"\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "database/privilege_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\nfunc TestCurrentUser(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tuser, host, err := connTest.CurrentUser()\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\tif user != \"root\" || host != \"%\" {\n\t\tt.Errorf(\"Want user: root, host: %%. Get user: %s, host: %s\", user, host)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestHasSelectPrivilege(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tif !connTest.HasSelectPrivilege() {\n\t\tt.Errorf(\"DSN: %s, User: %s, should has select privilege\", connTest.Addr, connTest.User)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestHasAllPrivilege(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tif !connTest.HasAllPrivilege() {\n\t\tt.Errorf(\"DSN: %s, User: %s, should has all privilege\", connTest.Addr, connTest.User)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "database/profiling.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// Profiling show profile 输出的结果\ntype Profiling struct {\n\tRows []ProfilingRow\n}\n\n// ProfilingRow show profile每一行信息\ntype ProfilingRow struct {\n\tStatus   string\n\tDuration float64\n\t// TODO: 支持show profile all, 不过目前看所有的信息过多有点眼花缭乱\n}\n\n// Profiling 执行SQL，并对其 Profile\nfunc (db *Connector) Profiling(sql string, params ...interface{}) ([]ProfilingRow, error) {\n\tvar rows []ProfilingRow\n\t// 过滤不需要 profiling 的 SQL\n\tswitch sqlparser.Preview(sql) {\n\tcase sqlparser.StmtSelect, sqlparser.StmtUpdate, sqlparser.StmtDelete:\n\tdefault:\n\t\treturn rows, errors.New(\"no need profiling\")\n\t}\n\n\t// 测试环境如果检查是关闭的，则 SQL 不会被执行\n\tif common.Config.TestDSN.Disable {\n\t\treturn rows, errors.New(\"dsn is disable\")\n\t}\n\n\t// 数据库安全性检查：如果 Connector 的 IP 端口与 TEST 环境不一致，则启用 SQL 白名单\n\t// 不在白名单中的 SQL 不允许执行\n\t// 执行环境与 test 环境不相同\n\tif db.Addr != common.Config.TestDSN.Addr && db.dangerousQuery(sql) {\n\t\treturn rows, fmt.Errorf(\"query execution deny: Execute SQL with DSN(%s/%s) '%s'\",\n\t\t\tdb.Addr, db.Database, fmt.Sprintf(sql, params...))\n\t}\n\n\tcommon.Log.Debug(\"Execute SQL with DSN(%s/%s) : %s\", db.Addr, db.Database, sql)\n\t// Keep connection\n\t// https://github.com/go-sql-driver/mysql/issues/208\n\ttrx, err := db.Conn.Begin()\n\tif err != nil {\n\t\treturn rows, err\n\t}\n\tdefer func() {\n\t\ttrxErr := trx.Rollback()\n\t\tif trxErr != nil {\n\t\t\tcommon.Log.Debug(trxErr.Error())\n\t\t}\n\t}()\n\n\t// 开启 Profiling\n\t_, err = trx.Query(\"set @@profiling=1\")\n\tcommon.LogIfError(err, \"\")\n\n\t// 执行 SQL，抛弃返回结果\n\ttmpRes, err := trx.Query(sql, params...)\n\tif err != nil {\n\t\treturn rows, err\n\t}\n\tfor tmpRes.Next() {\n\t\tcontinue\n\t}\n\n\t// 返回 Profiling 结果\n\tres, err := trx.Query(\"show profile\")\n\tif err != nil {\n\t\ttrxErr := trx.Rollback()\n\t\tif trxErr != nil {\n\t\t\tcommon.Log.Debug(trxErr.Error())\n\t\t}\n\t\treturn rows, err\n\t}\n\tvar profileRow ProfilingRow\n\tfor res.Next() {\n\t\terr = res.Scan(&profileRow.Status, &profileRow.Duration)\n\t\tif err != nil {\n\t\t\tcommon.LogIfError(err, \"\")\n\t\t\tbreak\n\t\t}\n\t\trows = append(rows, profileRow)\n\t}\n\tres.Close()\n\n\t// 关闭 Profiling\n\t_, err = trx.Query(\"set @@profiling=0\")\n\tcommon.LogIfError(err, \"\")\n\treturn rows, err\n}\n\n// FormatProfiling 格式化输出 Profiling 信息\nfunc FormatProfiling(rows []ProfilingRow) string {\n\tstr := []string{\"| Status | Duration |\"}\n\tstr = append(str, \"| --- | --- |\")\n\tfor _, row := range rows {\n\t\tstr = append(str, fmt.Sprintf(\"| %s | %f |\", row.Status, row.Duration))\n\t}\n\treturn strings.Join(str, \"\\n\")\n}\n"
  },
  {
    "path": "database/profiling_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n)\n\nfunc TestProfiling(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\trows, err := connTest.Profiling(\"select 1\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tpretty.Println(rows)\n\t_, err = connTest.Profiling(\"delete from film\")\n\tif err == nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFormatProfiling(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tres, err := connTest.Profiling(\"select 1\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tpretty.Println(FormatProfiling(res))\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "database/sampling.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\n/*--------------------\n* The following choice of minrows is based on the paper\n* \"Random sampling for histogram construction: how much is enough?\"\n* by Surajit Chaudhuri, Rajeev Motwani and Vivek Narasayya, in\n* Proceedings of ACM SIGMOD International Conference on Management\n* of Data, 1998, Pages 436-447.  Their Corollary 1 to Theorem 5\n* says that for table size n, histogram size k, maximum relative\n* error in bin size f, and error probability gamma, the minimum\n* random sample size is\n*      r = 4 * k * ln(2*n/gamma) / f^2\n* Taking f = 0.5, gamma = 0.01, n = 10^6 rows, we obtain\n*      r = 305.82 * k\n* Note that because of the log function, the dependence on n is\n* quite weak; even at n = 10^12, a 300*k sample gives <= 0.66\n* bin size error with probability 0.99.  So there's no real need to\n* scale for n, which is a good thing because we don't necessarily\n* know it at this point.\n*--------------------\n */\n\n// SamplingData 将数据从 onlineConn 拉取到 db 中\nfunc (db *Connector) SamplingData(onlineConn *Connector, tables ...string) error {\n\tvar err error\n\tif onlineConn.Database == db.Database {\n\t\treturn fmt.Errorf(\"SamplingData the same database, From: %s/%s, To: %s/%s\", onlineConn.Addr, onlineConn.Database, db.Addr, db.Database)\n\t}\n\n\t// 计算需要泵取的数据量\n\twantRowsCount := 300 * common.Config.SamplingStatisticTarget\n\n\tfor _, table := range tables {\n\t\t// 表类型检查\n\t\tif onlineConn.IsView(table) {\n\t\t\treturn nil\n\t\t}\n\n\t\t// generate where condition\n\t\tvar where string\n\t\tif common.Config.SamplingCondition == \"\" {\n\t\t\ttableStatus, err := onlineConn.ShowTableStatus(table)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif len(tableStatus.Rows) == 0 {\n\t\t\t\tcommon.Log.Info(\"SamplingData, Table %s with no data, stop sampling\", table)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\ttableRows, err := strconv.ParseUint(string(tableStatus.Rows[0].Rows), 10, 64)\n\t\t\tif tableRows == 0 || err != nil {\n\t\t\t\tcommon.Log.Info(\"SamplingData, Table %s with no data, stop sampling\", table)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcommon.Log.Error(\"SamplingData, Error: \", err.Error())\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tfactor := float64(wantRowsCount) / float64(tableRows)\n\t\t\tcommon.Log.Debug(\"SamplingData, tableRows: %d, wantRowsCount: %d, factor: %f\", tableRows, wantRowsCount, factor)\n\t\t\twhere = fmt.Sprintf(\"where RAND() <= %f LIMIT %d\", factor, wantRowsCount)\n\t\t\tif factor >= 1 {\n\t\t\t\twhere = \"\"\n\t\t\t}\n\t\t} else {\n\t\t\twhere = common.Config.SamplingCondition\n\t\t}\n\t\terr = db.startSampling(onlineConn.Conn, onlineConn.Database, table, where)\n\t}\n\treturn err\n}\n\n// startSampling sampling data from OnlineDSN to TestDSN\nfunc (db *Connector) startSampling(onlineConn *sql.DB, database, table string, where string) error {\n\tsamplingQuery := fmt.Sprintf(\"select * from `%s`.`%s` %s\",\n\t\tEscape(database, false),\n\t\tEscape(table, false),\n\t\tEscape(where, false))\n\tcommon.Log.Debug(\"startSampling with Query: %s\", samplingQuery)\n\tres, err := onlineConn.Query(samplingQuery)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// columns list\n\tcolumns, err := res.Columns()\n\tif err != nil {\n\t\treturn err\n\t}\n\trow := make([][]byte, len(columns))\n\ttableFields := make([]interface{}, 0)\n\tfor i := range columns {\n\t\ttableFields = append(tableFields, &row[i])\n\t}\n\tcolumnTypes, err := res.ColumnTypes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// sampling data\n\tvar valuesCount int\n\tvar valuesStr []string\n\tmaxValuesCount := 200 // one time insert values count, TODO: config able\n\tcolumnsStr := \"`\" + strings.Join(columns, \"`,`\") + \"`\"\n\tfor res.Next() {\n\t\tvar values []string\n\t\terr = res.Scan(tableFields...)\n\t\tif err != nil {\n\t\t\tcommon.Log.Debug(err.Error())\n\t\t}\n\t\tfor i, val := range row {\n\t\t\tif val == nil {\n\t\t\t\tvalues = append(values, \"NULL\")\n\t\t\t} else {\n\t\t\t\tswitch columnTypes[i].DatabaseTypeName() {\n\t\t\t\tcase \"JSON\":\n\t\t\t\t\t// https://github.com/XiaoMi/soar/issues/178\n\t\t\t\t\tvalues = append(values, fmt.Sprintf(`convert(X'%s' using utf8mb4)`, fmt.Sprintf(\"%x\", val)))\n\t\t\t\tcase \"TIMESTAMP\", \"DATETIME\":\n\t\t\t\t\tt, err := time.Parse(time.RFC3339, string(val))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tvalues = append(values, fmt.Sprintf(`\"%s\"`, string(val)))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvalues = append(values, fmt.Sprintf(`\"%s\"`, TimeString(t)))\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tvalues = append(values, fmt.Sprintf(`unhex(\"%s\")`, fmt.Sprintf(\"%x\", val)))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tvaluesStr = append(valuesStr, \"(\"+strings.Join(values, `,`)+\")\")\n\t\tvaluesCount++\n\t\tif maxValuesCount <= valuesCount {\n\t\t\terr = db.doSampling(table, columnsStr, strings.Join(valuesStr, `,`))\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvaluesStr = make([]string, 0)\n\t\t\tvaluesCount = 0\n\t\t}\n\t}\n\tif len(valuesStr) > 0 {\n\t\terr = db.doSampling(table, columnsStr, strings.Join(valuesStr, `,`))\n\t\tif err != nil {\n\t\t\tcommon.LogIfWarn(err, \"\")\n\t\t}\n\t}\n\tres.Close()\n\treturn err\n}\n\n// 将泵取的数据转换成 insert 语句并在 testConn 数据库中执行\nfunc (db *Connector) doSampling(table, colDef, values string) error {\n\t// db.Database is hashed database name\n\tquery := fmt.Sprintf(\"insert into `%s`.`%s` (%s) values %s;\",\n\t\tEscape(db.Database, false),\n\t\tEscape(table, false),\n\t\tEscape(colDef, false), values)\n\tres, err := db.Query(query)\n\tif res.Rows != nil {\n\t\tres.Rows.Close()\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "database/show.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n)\n\n// SHOW TABLE STATUS Syntax\n// https://dev.mysql.com/doc/refman/5.7/en/show-table-status.html\n\n// TableStatInfo 用以保存 show table status 之后获取的table信息\ntype TableStatInfo struct {\n\tName string\n\tRows []tableStatusRow\n}\n\n// tableStatusRow 用于 show table status value\n// use []byte instead of string, because []byte allow to be null, string not\ntype tableStatusRow struct {\n\tName         string // 表名\n\tEngine       []byte // 该表使用的存储引擎\n\tVersion      []byte // 该表的 .frm 文件版本号\n\tRowFormat    []byte // 该表使用的行存储格式\n\tRows         []byte // 表行数, InnoDB 引擎中为预估值，甚至可能会有40%~50%的数值偏差\n\tAvgRowLength []byte // 平均行长度\n\n\t// MyISAM: Data_length 为数据文件的大小，单位为 bytes\n\t// InnoDB: Data_length 为聚簇索引分配的近似内存量，单位为 bytes, 计算方式为聚簇索引数量乘以 InnoDB 页面大小\n\t// 其他不同的存储引擎中该值的意义可能不尽相同\n\tDataLength []byte\n\n\t// MyISAM: Max_data_length 为数据文件长度的最大值。这是在给定使用的数据指针大小的情况下，可以存储在表中的数据的最大字节数\n\t// InnoDB: 未使用\n\t// 其他不同的存储引擎中该值的意义可能不尽相同\n\tMaxDataLength []byte\n\n\t// MyISAM: Index_length 为 index 文件的大小，单位为 bytes\n\t// InnoDB: Index_length 为非聚簇索引分配的近似内存量，单位为 bytes，计算方式为非聚簇索引数量乘以 InnoDB 页面大小\n\t// 其他不同的存储引擎中该值的意义可能不尽相同\n\tIndexLength []byte\n\n\tDataFree      []byte // 已分配但未使用的字节数\n\tAutoIncrement []byte // 下一个自增值\n\tCreateTime    []byte // 创建时间\n\tUpdateTime    []byte // 最近一次更新时间，该值不准确\n\tCheckTime     []byte // 上次检查时间\n\tCollation     []byte // 字符集及排序规则信息\n\tChecksum      []byte // 校验和\n\tCreateOptions []byte // 创建表的时候的时候一切其他属性\n\tComment       []byte // 注释\n}\n\n// 记录去除逗号类型是外健还是分区表\ntype deleteComaType int8\n\nconst (\n\t_ deleteComaType = iota\n\tCS\n\tPART\n)\n\n// newTableStat 构造 table Stat 对象\nfunc newTableStat(tableName string) *TableStatInfo {\n\treturn &TableStatInfo{\n\t\tName: tableName,\n\t\tRows: make([]tableStatusRow, 0),\n\t}\n}\n\n// ShowTables 执行 show tables\nfunc (db *Connector) ShowTables() ([]string, error) {\n\tdefer func() {\n\t\terr := recover()\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"recover ShowTables()\", err)\n\t\t}\n\t}()\n\n\t// 执行 show table status\n\tres, err := db.Query(\"show tables\")\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\n\t// 获取值\n\tvar tables []string\n\tfor res.Rows.Next() {\n\t\tvar table string\n\t\terr = res.Rows.Scan(&table)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\ttables = append(tables, table)\n\t}\n\tres.Rows.Close()\n\treturn tables, err\n}\n\n// ShowTableStatus 执行 show table status\nfunc (db *Connector) ShowTableStatus(tableName string) (*TableStatInfo, error) {\n\t// 初始化struct\n\ttbStatus := newTableStat(tableName)\n\n\t// 执行 show table status\n\tres, err := db.Query(fmt.Sprintf(\"show table status where name = '%s'\", Escape(tbStatus.Name, false)))\n\tif err != nil {\n\t\treturn tbStatus, err\n\t}\n\n\t// columns info\n\tts := tableStatusRow{}\n\tstatusFields := make([]interface{}, 0)\n\tfields := map[string]interface{}{\n\t\t\"Name\":            &ts.Name,\n\t\t\"Engine\":          &ts.Engine,\n\t\t\"Version\":         &ts.Version,\n\t\t\"Row_format\":      &ts.RowFormat,\n\t\t\"Rows\":            &ts.Rows,\n\t\t\"Avg_row_length\":  &ts.AvgRowLength,\n\t\t\"Data_length\":     &ts.DataLength,\n\t\t\"Max_data_length\": &ts.MaxDataLength,\n\t\t\"Index_length\":    &ts.IndexLength,\n\t\t\"Data_free\":       &ts.DataFree,\n\t\t\"Auto_increment\":  &ts.AutoIncrement,\n\t\t\"Create_time\":     &ts.CreateTime,\n\t\t\"Update_time\":     &ts.UpdateTime,\n\t\t\"Check_time\":      &ts.CheckTime,\n\t\t\"Collation\":       &ts.Collation,\n\t\t\"Checksum\":        &ts.Checksum,\n\t\t\"Create_options\":  &ts.CreateOptions,\n\t\t\"Comment\":         &ts.Comment,\n\t}\n\tcols, err := res.Rows.Columns()\n\tcommon.LogIfError(err, \"\")\n\tvar colByPass []byte\n\tfor _, col := range cols {\n\t\tif _, ok := fields[col]; ok {\n\t\t\tstatusFields = append(statusFields, fields[col])\n\t\t} else {\n\t\t\tcommon.Log.Debug(\"ShowTableStatus by pass column %s\", col)\n\t\t\tstatusFields = append(statusFields, &colByPass)\n\t\t}\n\t}\n\t// 获取值\n\tfor res.Rows.Next() {\n\t\terr := res.Rows.Scan(statusFields...)\n\t\tif err != nil {\n\t\t\t// MariaDB 中视图的 STATUS 信息大部分表都为 NULL，此时会打印如下 DEBUG 级别日志信息，看到后忽略即可。\n\t\t\t// sql: Scan error on column index 4: converting driver.Value type <nil> (\"<nil>\") to uint64: invalid syntax\n\t\t\tcommon.Log.Debug(err.Error())\n\t\t}\n\t\ttbStatus.Rows = append(tbStatus.Rows, ts)\n\t}\n\tres.Rows.Close()\n\treturn tbStatus, err\n}\n\n// https://dev.mysql.com/doc/refman/5.7/en/show-index.html\n\n// TableIndexInfo 用以保存 show index 之后获取的 index 信息\ntype TableIndexInfo struct {\n\tTableName string\n\tRows      []TableIndexRow\n}\n\n// TableIndexRow 用以存放show index 之后获取的每一条 index 信息\ntype TableIndexRow struct {\n\tTable        string // 表名\n\tNonUnique    int    // 0：unique key，1：not unique\n\tKeyName      string // index的名称，如果是主键则为 \"PRIMARY\"\n\tSeqInIndex   int    // 该列在索引中的位置。计数从 1 开始\n\tColumnName   string // 列名\n\tCollation    string // A or Null\n\tCardinality  int    // 索引中唯一值的数量，\"ANALYZE TABLE\" 可更新该值\n\tSubPart      int    // 索引前缀字节数\n\tPacked       int\n\tNull         string // 表示该列是否可以为空，如果可以为 'YES'，反之''\n\tIndexType    string // BTREE, FULLTEXT, HASH, RTREE\n\tComment      string\n\tIndexComment string\n\tVisible      string\n\tExpression   []byte\n}\n\n// NewTableIndexInfo 构造 TableIndexInfo\nfunc NewTableIndexInfo(tableName string) *TableIndexInfo {\n\treturn &TableIndexInfo{\n\t\tTableName: tableName,\n\t\tRows:      make([]TableIndexRow, 0),\n\t}\n}\n\n// ShowIndex show Index\nfunc (db *Connector) ShowIndex(tableName string) (*TableIndexInfo, error) {\n\ttbIndex := NewTableIndexInfo(tableName)\n\n\tif db.Database == \"\" || tableName == \"\" {\n\t\treturn nil, fmt.Errorf(\"database('%s') or table('%s') name should not empty\", db.Database, tableName)\n\t}\n\n\t// 执行 show create table\n\tres, err := db.Query(fmt.Sprintf(\"show index from `%s`.`%s`\", Escape(db.Database, false), Escape(tableName, false)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// columns info\n\tti := TableIndexRow{}\n\tindexFields := make([]interface{}, 0)\n\tfields := map[string]interface{}{\n\t\t\"Table\":         &ti.Table,\n\t\t\"Non_unique\":    &ti.NonUnique,\n\t\t\"Key_name\":      &ti.KeyName,\n\t\t\"Seq_in_index\":  &ti.SeqInIndex,\n\t\t\"Column_name\":   &ti.ColumnName,\n\t\t\"Collation\":     &ti.Collation,\n\t\t\"Cardinality\":   &ti.Cardinality,\n\t\t\"Sub_part\":      &ti.SubPart,\n\t\t\"Packed\":        &ti.Packed,\n\t\t\"Null\":          &ti.Null,\n\t\t\"Index_type\":    &ti.IndexType,\n\t\t\"Comment\":       &ti.Comment,\n\t\t\"Index_comment\": &ti.IndexComment,\n\t\t\"Visible\":       &ti.Visible,\n\t\t\"Expression\":    &ti.Expression,\n\t}\n\tcols, err := res.Rows.Columns()\n\tcommon.LogIfError(err, \"\")\n\tvar colByPass []byte\n\tfor _, col := range cols {\n\t\tif _, ok := fields[col]; ok {\n\t\t\tindexFields = append(indexFields, fields[col])\n\t\t} else {\n\t\t\tcommon.Log.Debug(\"ShowIndex by pass column %s\", col)\n\t\t\tindexFields = append(indexFields, &colByPass)\n\t\t}\n\t}\n\t// 获取值\n\tfor res.Rows.Next() {\n\t\terr := res.Rows.Scan(indexFields...)\n\t\tif err != nil {\n\t\t\tcommon.Log.Debug(err.Error())\n\t\t}\n\t\ttbIndex.Rows = append(tbIndex.Rows, ti)\n\t}\n\tres.Rows.Close()\n\treturn tbIndex, err\n}\n\n// IndexSelectKey 用以对 TableIndexInfo 进行查询\ntype IndexSelectKey string\n\n// 索引相关\nconst (\n\tIndexKeyName    = IndexSelectKey(\"KeyName\")    // 索引名称\n\tIndexColumnName = IndexSelectKey(\"ColumnName\") // 索引列名称\n\tIndexIndexType  = IndexSelectKey(\"IndexType\")  // 索引类型\n\tIndexNonUnique  = IndexSelectKey(\"NonUnique\")  // 唯一索引\n)\n\n// FindIndex 获取 TableIndexInfo 中需要的索引\nfunc (tbIndex *TableIndexInfo) FindIndex(arg IndexSelectKey, value string) []TableIndexRow {\n\tvar result []TableIndexRow\n\tif tbIndex == nil {\n\t\treturn result\n\t}\n\n\tvalue = strings.ToLower(value)\n\n\tswitch arg {\n\tcase IndexKeyName:\n\t\tfor _, index := range tbIndex.Rows {\n\t\t\tif strings.ToLower(index.KeyName) == value {\n\t\t\t\tresult = append(result, index)\n\t\t\t}\n\t\t}\n\n\tcase IndexColumnName:\n\t\tfor _, index := range tbIndex.Rows {\n\t\t\tif strings.ToLower(index.ColumnName) == value {\n\t\t\t\tresult = append(result, index)\n\t\t\t}\n\t\t}\n\n\tcase IndexIndexType:\n\t\tfor _, index := range tbIndex.Rows {\n\t\t\tif strings.ToLower(index.IndexType) == value {\n\t\t\t\tresult = append(result, index)\n\t\t\t}\n\t\t}\n\n\tcase IndexNonUnique:\n\t\tfor _, index := range tbIndex.Rows {\n\t\t\tunique := strconv.Itoa(index.NonUnique)\n\t\t\tif unique == value {\n\t\t\t\tresult = append(result, index)\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\tcommon.Log.Error(\"no such args: TableIndexRow\")\n\t}\n\n\treturn result\n}\n\n// desc table\n// https://dev.mysql.com/doc/refman/5.7/en/show-columns.html\n\n// TableDesc show columns from rental;\ntype TableDesc struct {\n\tName       string\n\tDescValues []TableDescValue\n}\n\n// TableDescValue 含有每一列的属性\ntype TableDescValue struct {\n\tField      string // 列名\n\tType       string // 数据类型\n\tCollation  []byte // 字符集\n\tNull       string // 是否有NULL（NO、YES）\n\tKey        string // 键类型\n\tDefault    []byte // 默认值\n\tExtra      string // 其他\n\tPrivileges string // 权限\n\tComment    string // 备注\n}\n\n// NewTableDesc 初始化一个*TableDesc\nfunc NewTableDesc(tableName string) *TableDesc {\n\treturn &TableDesc{\n\t\tName:       tableName,\n\t\tDescValues: make([]TableDescValue, 0),\n\t}\n}\n\n// ShowColumns 获取 DB 中所有的 columns\nfunc (db *Connector) ShowColumns(tableName string) (*TableDesc, error) {\n\ttbDesc := NewTableDesc(tableName)\n\n\t// 执行 show create table\n\tres, err := db.Query(fmt.Sprintf(\"show full columns from `%s`.`%s`\", Escape(db.Database, false), Escape(tableName, false)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// columns info\n\ttc := TableDescValue{}\n\tcolumnFields := make([]interface{}, 0)\n\tfields := map[string]interface{}{\n\t\t\"Field\":      &tc.Field,\n\t\t\"Type\":       &tc.Type,\n\t\t\"Collation\":  &tc.Collation,\n\t\t\"Null\":       &tc.Null,\n\t\t\"Key\":        &tc.Key,\n\t\t\"Default\":    &tc.Default,\n\t\t\"Extra\":      &tc.Extra,\n\t\t\"Privileges\": &tc.Privileges,\n\t\t\"Comment\":    &tc.Comment,\n\t}\n\tcols, err := res.Rows.Columns()\n\tcommon.LogIfError(err, \"\")\n\tvar colByPass []byte\n\tfor _, col := range cols {\n\t\tif _, ok := fields[col]; ok {\n\t\t\tcolumnFields = append(columnFields, fields[col])\n\t\t} else {\n\t\t\tcommon.Log.Debug(\"ShowColumns by pass column %s\", col)\n\t\t\tcolumnFields = append(columnFields, &colByPass)\n\t\t}\n\t}\n\t// 获取值\n\tfor res.Rows.Next() {\n\t\terr := res.Rows.Scan(columnFields...)\n\t\tif err != nil {\n\t\t\tcommon.Log.Debug(err.Error())\n\t\t}\n\t\ttbDesc.DescValues = append(tbDesc.DescValues, tc)\n\t}\n\tres.Rows.Close()\n\treturn tbDesc, err\n}\n\n// Columns 用于获取TableDesc中所有列的名称\nfunc (td TableDesc) Columns() []string {\n\tvar cols []string\n\tfor _, col := range td.DescValues {\n\t\tcols = append(cols, col.Field)\n\t}\n\treturn cols\n}\n\n// showCreate show create\nfunc (db *Connector) showCreate(createType, name string) (string, error) {\n\t// SHOW CREATE DATABASE db_name\n\t// SHOW CREATE EVENT event_name\n\t// SHOW CREATE FUNCTION func_name\n\t// SHOW CREATE PROCEDURE proc_name\n\t// SHOW CREATE TABLE tbl_name\n\t// SHOW CREATE TRIGGER trigger_name\n\t// SHOW CREATE VIEW view_name\n\tres, err := db.Query(fmt.Sprintf(\"SHOW CREATE %s `%s`\", createType, Escape(name, false)))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// columns info\n\tvar create string\n\tcreateFields := make([]interface{}, 0)\n\tfields := map[string]interface{}{\n\t\t\"Create View\":      &create,\n\t\t\"Create Table\":     &create,\n\t\t\"Create Database\":  &create,\n\t\t\"Create Event\":     &create,\n\t\t\"Statement\":        &create, // show create trigger\n\t\t\"Create Function\":  &create,\n\t\t\"Create Procedure\": &create,\n\t}\n\tcols, err := res.Rows.Columns()\n\tcommon.LogIfError(err, \"\")\n\tvar colByPass []byte\n\tfor _, col := range cols {\n\t\tif _, ok := fields[col]; ok {\n\t\t\tcreateFields = append(createFields, fields[col])\n\t\t} else {\n\t\t\tcommon.Log.Debug(\"showCreate Type: %s, Name: %s, by pass column `%s`\", createType, name, col)\n\t\t\tcreateFields = append(createFields, &colByPass)\n\t\t}\n\t}\n\n\t// 获取 CREATE 语句\n\tfor res.Rows.Next() {\n\t\terr := res.Rows.Scan(createFields...)\n\t\tif err != nil {\n\t\t\tcommon.Log.Debug(err.Error())\n\t\t}\n\t}\n\tres.Rows.Close()\n\treturn create, err\n}\n\n// ShowCreateDatabase show create database\nfunc (db *Connector) ShowCreateDatabase(dbName string) (string, error) {\n\tdefer func() {\n\t\terr := recover()\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"recover ShowCreateDatabase()\", err)\n\t\t}\n\t}()\n\treturn db.showCreate(\"database\", dbName)\n}\n\n// ShowCreateTable show create table\nfunc (db *Connector) ShowCreateTable(tableName string) (string, error) {\n\tdefer func() {\n\t\terr := recover()\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"recover ShowCreateTable()\", err)\n\t\t}\n\t}()\n\n\tddl, err := db.showCreate(\"TABLE\", tableName)\n\n\t// 去除外键关联条件\n\tlines := strings.Split(ddl, \"\\n\")\n\t// CREATE VIEW ONLY 1 LINE\n\tif len(lines) > 2 {\n\t\tvar noConstraint []string\n\t\trelationReg, _ := regexp.Compile(\"CONSTRAINT\")\n\t\tpartitionReg, _ := regexp.Compile(\"PARTITIONS\")\n\t\tvar DeleteComaT deleteComaType\n\t\tfor _, line := range lines[1 : len(lines)-1] {\n\t\t\tif relationReg.Match([]byte(line)) {\n\t\t\t\tDeleteComaT = CS\n\t\t\t\tcontinue\n\t\t\t} else if partitionReg.Match([]byte(line)) {\n\t\t\t\tDeleteComaT = PART\n\t\t\t}\n\t\t\tline = strings.TrimSuffix(line, \",\")\n\t\t\tnoConstraint = append(noConstraint, line)\n\t\t}\n\n\t\t// 去除外键语句会使DDL中多一个','导致语法错误，要把多余的逗号去除\n\t\t// len(lines) > 2的判断方式有问题，如果是分区表也会判断成为外键语句，导致建表语句的逗号错乱\n\t\tif DeleteComaT == CS {\n\t\t\tddl = fmt.Sprint(\n\t\t\t\tlines[0], \"\\n\",\n\t\t\t\tstrings.Join(noConstraint, \",\\n\"), \"\\n\",\n\t\t\t\tlines[len(lines)-1],\n\t\t\t)\n\t\t} else if DeleteComaT == PART {\n\t\t\tddl = fmt.Sprint(\n\t\t\t\tlines[0], \"\\n\",\n\t\t\t\tstrings.Join(noConstraint, \",\\n\"), \"\\n\",\n\t\t\t\tlines[len(lines)-3],\n\t\t\t)\n\t\t}\n\n\t}\n\n\treturn ddl, err\n}\n\n// FindColumn find column\nfunc (db *Connector) FindColumn(name, dbName string, tables ...string) ([]*common.Column, error) {\n\t// 执行 show create table\n\tvar columns []*common.Column\n\tsql := fmt.Sprintf(\"SELECT \"+\n\t\t\"c.TABLE_NAME,c.TABLE_SCHEMA,c.COLUMN_TYPE,c.CHARACTER_SET_NAME, c.COLLATION_NAME \"+\n\t\t\"FROM `INFORMATION_SCHEMA`.`COLUMNS` as c where c.COLUMN_NAME = '%s' \", Escape(name, false))\n\n\tif dbName != \"\" {\n\t\tsql += fmt.Sprintf(\" and c.table_schema = '%s'\", Escape(dbName, false))\n\t}\n\n\tif len(tables) > 0 {\n\t\tvar tmp []string\n\t\tfor _, table := range tables {\n\t\t\ttmp = append(tmp, \"'\"+Escape(table, false)+\"'\")\n\t\t}\n\t\tsql += fmt.Sprintf(\" and c.table_name in (%s)\", strings.Join(tmp, \",\"))\n\t}\n\n\tcommon.Log.Debug(\"FindColumn, execute SQL: %s\", sql)\n\tres, err := db.Query(sql)\n\tif err != nil {\n\t\tcommon.Log.Error(\"(db *Connector) FindColumn Error : \", err)\n\t\treturn columns, err\n\t}\n\n\tvar col common.Column\n\tfor res.Rows.Next() {\n\t\tvar character, collation []byte\n\t\terr = res.Rows.Scan(&col.Table, &col.DB, &col.DataType, &character, &collation)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tcol.Name = name\n\t\tcol.Character = string(character)\n\t\tcol.Collation = string(collation)\n\t\t// 填充字符集和排序规则\n\t\tif col.Character == \"\" {\n\t\t\t// 当从 `INFORMATION_SCHEMA`.`COLUMNS` 表中查询不到相关列的 character 和 collation 的信息时\n\t\t\t// 认为该列使用的 character 和 collation 与其所处的表一致\n\t\t\t// 由于 `INFORMATION_SCHEMA`.`TABLES` 表中未找到表的 character，所以从按照 MySQL 中 collation 的规则从中截取 character\n\n\t\t\tsql = fmt.Sprintf(\"SELECT `t`.`TABLE_COLLATION` FROM `INFORMATION_SCHEMA`.`TABLES` AS `t` \"+\n\t\t\t\t\"WHERE `t`.`TABLE_NAME`='%s' AND `t`.`TABLE_SCHEMA` = '%s'\", Escape(col.Table, false), Escape(col.DB, false))\n\n\t\t\tcommon.Log.Debug(\"FindColumn, execute SQL: %s\", sql)\n\t\t\tvar newRes QueryResult\n\t\t\tnewRes, err = db.Query(sql)\n\t\t\tif err != nil {\n\t\t\t\tcommon.Log.Error(\"(db *Connector) FindColumn Error : \", err)\n\t\t\t\treturn columns, err\n\t\t\t}\n\n\t\t\tvar tbCollation []byte\n\t\t\tif newRes.Rows.Next() {\n\t\t\t\terr = newRes.Rows.Scan(&tbCollation)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tnewRes.Rows.Close()\n\t\t\tif string(tbCollation) != \"\" {\n\t\t\t\tcol.Character = strings.Split(string(tbCollation), \"_\")[0]\n\t\t\t\tcol.Collation = string(tbCollation)\n\t\t\t}\n\t\t}\n\t\tcolumns = append(columns, &col)\n\t}\n\tres.Rows.Close()\n\treturn columns, err\n}\n\n// IsForeignKey 判断列是否是外键\nfunc (db *Connector) IsForeignKey(dbName, tbName, column string) bool {\n\tsql := fmt.Sprintf(\"SELECT REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE C \"+\n\t\t\"WHERE REFERENCED_TABLE_SCHEMA <> 'NULL' AND\"+\n\t\t\" TABLE_NAME='%s' AND\"+\n\t\t\" TABLE_SCHEMA='%s' AND\"+\n\t\t\" COLUMN_NAME='%s'\", Escape(tbName, false), Escape(dbName, false), Escape(column, false))\n\n\tcommon.Log.Debug(\"IsForeignKey, execute SQL: %s\", sql)\n\tres, err := db.Query(sql)\n\tif err != nil {\n\t\tcommon.Log.Error(\"IsForeignKey, Error: %s\", err.Error())\n\t\treturn false\n\t}\n\tif res.Rows.Next() {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// Reference 用于存储关系\ntype Reference map[string][]ReferenceValue\n\n// ReferenceValue 用于处理表之间的关系\ntype ReferenceValue struct {\n\tReferencedTableSchema string // 夫表所属数据库\n\tReferencedTableName   string // 父表\n\tTableSchema           string // 子表所属数据库\n\tTableName             string // 子表\n\tConstraintName        string // 关系名称\n}\n\n// ShowReference 查找所有的外键信息\nfunc (db *Connector) ShowReference(dbName string, tbName ...string) ([]ReferenceValue, error) {\n\tvar referenceValues []ReferenceValue\n\tsql := `SELECT DISTINCT C.REFERENCED_TABLE_SCHEMA,C.REFERENCED_TABLE_NAME,C.TABLE_SCHEMA,C.TABLE_NAME,C.CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE C JOIN INFORMATION_SCHEMA. TABLES T ON T.TABLE_NAME = C.TABLE_NAME WHERE C.REFERENCED_TABLE_NAME IS NOT NULL`\n\tsql = sql + fmt.Sprintf(` AND C.TABLE_SCHEMA = \"%s\"`, Escape(dbName, false))\n\n\tvar tables []string\n\tfor _, tb := range tbName {\n\t\ttables = append(tables, \"'\"+Escape(tb, false)+\"'\")\n\t}\n\tif len(tbName) > 0 {\n\t\textra := fmt.Sprintf(` AND C.TABLE_NAME IN (\"%s\")`, strings.Join(tables, \",\"))\n\t\tsql = sql + extra\n\t}\n\n\tcommon.Log.Debug(\"ShowReference, execute SQL: %s\", sql)\n\t// 执行SQL查找外键关联关系\n\tres, err := db.Query(sql)\n\tif err != nil {\n\t\treturn referenceValues, err\n\t}\n\n\t// 获取值\n\tfor res.Rows.Next() {\n\t\tvar rv ReferenceValue\n\t\terr = res.Rows.Scan(&rv.ReferencedTableSchema, &rv.ReferencedTableName, &rv.TableSchema, &rv.TableName, &rv.ConstraintName)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\treferenceValues = append(referenceValues, rv)\n\t}\n\tres.Rows.Close()\n\treturn referenceValues, err\n}\n"
  },
  {
    "path": "database/show_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n)\n\nfunc TestShowTableStatus(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgDatabase := connTest.Database\n\tconnTest.Database = \"sakila\"\n\tts, err := connTest.ShowTableStatus(\"film\")\n\tif err != nil {\n\t\tt.Error(\"ShowTableStatus Error: \", err)\n\t}\n\tif string(ts.Rows[0].Engine) != \"InnoDB\" {\n\t\tt.Error(\"film table should be InnoDB engine\")\n\t}\n\tpretty.Println(ts)\n\n\tconnTest.Database = \"sakila\"\n\tts, err = connTest.ShowTableStatus(\"actor_info\")\n\tif err != nil {\n\t\tt.Error(\"ShowTableStatus Error: \", err)\n\t}\n\tif string(ts.Rows[0].Comment) != \"VIEW\" {\n\t\tt.Error(\"actor_info should be VIEW\", ts.Rows[0].Comment)\n\t}\n\tpretty.Println(ts)\n\tconnTest.Database = orgDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestShowTables(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgDatabase := connTest.Database\n\tconnTest.Database = \"sakila\"\n\tts, err := connTest.ShowTables()\n\tif err != nil {\n\t\tt.Error(\"ShowTableStatus Error: \", err)\n\t}\n\n\terr = common.GoldenDiff(func() {\n\t\tfor _, table := range ts {\n\t\t\tfmt.Println(table)\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tconnTest.Database = orgDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestShowCreateDatabase(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\terr := common.GoldenDiff(func() {\n\t\tfmt.Println(connTest.ShowCreateDatabase(\"sakila\"))\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestShowCreateTable(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgDatabase := connTest.Database\n\tconnTest.Database = \"sakila\"\n\ttables := []string{\n\t\t\"film\",\n\t\t\"category\",\n\t\t\"customer_list\",\n\t\t\"inventory\",\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, table := range tables {\n\t\t\tts, err := connTest.ShowCreateTable(table)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"ShowCreateTable Error: \", err)\n\t\t\t}\n\t\t\tfmt.Println(ts)\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tconnTest.Database = orgDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestShowIndex(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgDatabase := connTest.Database\n\tconnTest.Database = \"sakila\"\n\tti, err := connTest.ShowIndex(\"film\")\n\tif err != nil {\n\t\tt.Error(\"ShowIndex Error: \", err)\n\t}\n\n\terr = common.GoldenDiff(func() {\n\t\tpretty.Println(ti)\n\t\tpretty.Println(ti.FindIndex(IndexKeyName, \"idx_title\"))\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tconnTest.Database = orgDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestShowColumns(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgDatabase := connTest.Database\n\tconnTest.Database = \"sakila\"\n\tti, err := connTest.ShowColumns(\"actor_info\")\n\tif err != nil {\n\t\tt.Error(\"ShowColumns Error: \", err)\n\t}\n\n\terr = common.GoldenDiff(func() {\n\t\tpretty.Println(ti)\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tconnTest.Database = orgDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFindColumn(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tti, err := connTest.FindColumn(\"film_id\", \"sakila\", \"film\")\n\tif err != nil {\n\t\tt.Error(\"FindColumn Error: \", err)\n\t}\n\terr = common.GoldenDiff(func() {\n\t\tpretty.Println(ti)\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestIsFKey(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tif !connTest.IsForeignKey(\"sakila\", \"film\", \"language_id\") {\n\t\tt.Error(\"want True. got false\")\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestShowReference(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\trv, err := connTest.ShowReference(\"sakila\", \"film\")\n\tif err != nil {\n\t\tt.Error(\"ShowReference Error: \", err)\n\t}\n\n\terr = common.GoldenDiff(func() {\n\t\tpretty.Println(rv)\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "database/testdata/TestExplain.golden",
    "content": "&database.ExplainInfo{\n    SQL:           \"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n    ExplainFormat: 0,\n    ExplainRows:   {\n        &database.ExplainRow{\n            ID:           1,\n            SelectType:   \"PRIMARY\",\n            TableName:    \"country\",\n            Partitions:   \"NULL\",\n            AccessType:   \"index\",\n            PossibleKeys: {\"PRIMARY\"},\n            Key:          \"PRIMARY\",\n            KeyLen:       \"2\",\n            Ref:          {\"\"},\n            Rows:         109,\n            Filtered:     100,\n            Scalability:  \"O(n)\",\n            Extra:        \"Using index; Using temporary; Using filesort\",\n        },\n        &database.ExplainRow{\n            ID:           1,\n            SelectType:   \"PRIMARY\",\n            TableName:    \"city\",\n            Partitions:   \"NULL\",\n            AccessType:   \"ref\",\n            PossibleKeys: {\"PRIMARY\", \"idx_fk_country_id\"},\n            Key:          \"idx_fk_country_id\",\n            KeyLen:       \"2\",\n            Ref:          {\"sakila.country.country_id\"},\n            Rows:         5,\n            Filtered:     100,\n            Scalability:  \"O(log n)\",\n            Extra:        \"NULL\",\n        },\n        &database.ExplainRow{\n            ID:           1,\n            SelectType:   \"PRIMARY\",\n            TableName:    \"c\",\n            Partitions:   \"NULL\",\n            AccessType:   \"ALL\",\n            PossibleKeys: {\"\"},\n            Key:          \"NULL\",\n            KeyLen:       \"\",\n            Ref:          {\"\"},\n            Rows:         600,\n            Filtered:     10,\n            Scalability:  \"O(n)\",\n            Extra:        \"Using where; Using join buffer (Block Nested Loop)\",\n        },\n        &database.ExplainRow{\n            ID:           1,\n            SelectType:   \"PRIMARY\",\n            TableName:    \"a\",\n            Partitions:   \"NULL\",\n            AccessType:   \"ref\",\n            PossibleKeys: {\"PRIMARY\", \"idx_fk_city_id\"},\n            Key:          \"idx_fk_city_id\",\n            KeyLen:       \"2\",\n            Ref:          {\"sakila.city.city_id\"},\n            Rows:         1,\n            Filtered:     100,\n            Scalability:  \"O(log n)\",\n            Extra:        \"NULL\",\n        },\n        &database.ExplainRow{\n            ID:           1,\n            SelectType:   \"PRIMARY\",\n            TableName:    \"cu\",\n            Partitions:   \"NULL\",\n            AccessType:   \"ref\",\n            PossibleKeys: {\"idx_fk_address_id\"},\n            Key:          \"idx_fk_address_id\",\n            KeyLen:       \"2\",\n            Ref:          {\"sakila.a.address_id\"},\n            Rows:         1,\n            Filtered:     100,\n            Scalability:  \"O(log n)\",\n            Extra:        \"NULL\",\n        },\n        &database.ExplainRow{\n            ID:           1,\n            SelectType:   \"PRIMARY\",\n            TableName:    \"<derived2>\",\n            Partitions:   \"NULL\",\n            AccessType:   \"ref\",\n            PossibleKeys: {\"<auto_key0>\"},\n            Key:          \"<auto_key0>\",\n            KeyLen:       \"152\",\n            Ref:          {\"sakila.a.address\"},\n            Rows:         6,\n            Filtered:     100,\n            Scalability:  \"O(log n)\",\n            Extra:        \"Using index\",\n        },\n        &database.ExplainRow{\n            ID:           2,\n            SelectType:   \"DERIVED\",\n            TableName:    \"a\",\n            Partitions:   \"NULL\",\n            AccessType:   \"ALL\",\n            PossibleKeys: {\"PRIMARY\", \"idx_fk_city_id\"},\n            Key:          \"NULL\",\n            KeyLen:       \"\",\n            Ref:          {\"\"},\n            Rows:         603,\n            Filtered:     100,\n            Scalability:  \"O(n)\",\n            Extra:        \"Using filesort\",\n        },\n        &database.ExplainRow{\n            ID:           2,\n            SelectType:   \"DERIVED\",\n            TableName:    \"cu\",\n            Partitions:   \"NULL\",\n            AccessType:   \"ref\",\n            PossibleKeys: {\"idx_fk_store_id\", \"idx_fk_address_id\"},\n            Key:          \"idx_fk_address_id\",\n            KeyLen:       \"2\",\n            Ref:          {\"sakila.a.address_id\"},\n            Rows:         1,\n            Filtered:     54.42,\n            Scalability:  \"O(log n)\",\n            Extra:        \"Using where\",\n        },\n        &database.ExplainRow{\n            ID:           2,\n            SelectType:   \"DERIVED\",\n            TableName:    \"city\",\n            Partitions:   \"NULL\",\n            AccessType:   \"eq_ref\",\n            PossibleKeys: {\"PRIMARY\", \"idx_fk_country_id\"},\n            Key:          \"PRIMARY\",\n            KeyLen:       \"2\",\n            Ref:          {\"sakila.a.city_id\"},\n            Rows:         1,\n            Filtered:     100,\n            Scalability:  \"O(log n)\",\n            Extra:        \"NULL\",\n        },\n        &database.ExplainRow{\n            ID:           2,\n            SelectType:   \"DERIVED\",\n            TableName:    \"country\",\n            Partitions:   \"NULL\",\n            AccessType:   \"eq_ref\",\n            PossibleKeys: {\"PRIMARY\"},\n            Key:          \"PRIMARY\",\n            KeyLen:       \"2\",\n            Ref:          {\"sakila.city.country_id\"},\n            Rows:         1,\n            Filtered:     100,\n            Scalability:  \"O(log n)\",\n            Extra:        \"Using index\",\n        },\n    },\n    ExplainJSON: (*database.ExplainJSON)(nil),\n    Warnings:    nil,\n    QueryCost:   0,\n}\n"
  },
  {
    "path": "database/testdata/TestExplainInfoTranslator.golden",
    "content": ""
  },
  {
    "path": "database/testdata/TestFindColumn.golden",
    "content": "[]*common.Column{\n    &common.Column{\n        Name:        \"film_id\",\n        Alias:       nil,\n        Table:       \"film\",\n        DB:          \"sakila\",\n        DataType:    \"smallint unsigned\",\n        Character:   \"utf8mb3\",\n        Collation:   \"utf8mb3_general_ci\",\n        Cardinality: 0,\n        Null:        \"\",\n        Key:         \"\",\n        Default:     \"\",\n        Extra:       \"\",\n        Comment:     \"\",\n        Privileges:  \"\",\n    },\n}\n"
  },
  {
    "path": "database/testdata/TestFormatProfiling.golden",
    "content": ""
  },
  {
    "path": "database/testdata/TestFormatTrace.golden",
    "content": "\n```sql\nselect 1\n```\n\n```json\n{\n  \"steps\": [\n    {\n      \"join_preparation\": {\n        \"select#\": 1,\n        \"steps\": [\n          {\n            \"expanded_query\": \"/* select#1 */ select 1 AS `1`\"\n          }\n        ]\n      }\n    },\n    {\n      \"join_optimization\": {\n        \"select#\": 1,\n        \"steps\": [\n        ]\n      }\n    },\n    {\n      \"join_explain\": {\n        \"select#\": 1,\n        \"steps\": [\n        ]\n      }\n    }\n  ]\n}\n```\n\n"
  },
  {
    "path": "database/testdata/TestMySQLExplainQueryCost.golden",
    "content": "<nil> \n<nil> \n<nil> \n"
  },
  {
    "path": "database/testdata/TestMySQLExplainWarnings.golden",
    "content": ""
  },
  {
    "path": "database/testdata/TestPrintMarkdownExplainTable.golden",
    "content": ""
  },
  {
    "path": "database/testdata/TestRemoveSQLComments.golden",
    "content": "select 'c#\\'#not comment'\nselect \"c#\\\"#not comment\"\n\n\n\n\n\n\n"
  },
  {
    "path": "database/testdata/TestShowColumns.golden",
    "content": "&database.TableDesc{\n    Name:       \"actor_info\",\n    DescValues: {\n        {\n            Field:      \"actor_id\",\n            Type:       \"smallint unsigned\",\n            Collation:  nil,\n            Null:       \"NO\",\n            Key:        \"\",\n            Default:    {0x30},\n            Extra:      \"\",\n            Privileges: \"select,insert,update,references\",\n            Comment:    \"\",\n        },\n        {\n            Field:      \"first_name\",\n            Type:       \"varchar(45)\",\n            Collation:  {0x75, 0x74, 0x66, 0x38, 0x6d, 0x62, 0x33, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x69},\n            Null:       \"NO\",\n            Key:        \"\",\n            Default:    nil,\n            Extra:      \"\",\n            Privileges: \"select,insert,update,references\",\n            Comment:    \"\",\n        },\n        {\n            Field:      \"last_name\",\n            Type:       \"varchar(45)\",\n            Collation:  {0x75, 0x74, 0x66, 0x38, 0x6d, 0x62, 0x33, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x69},\n            Null:       \"NO\",\n            Key:        \"\",\n            Default:    nil,\n            Extra:      \"\",\n            Privileges: \"select,insert,update,references\",\n            Comment:    \"\",\n        },\n        {\n            Field:      \"film_info\",\n            Type:       \"text\",\n            Collation:  {0x75, 0x74, 0x66, 0x38, 0x6d, 0x62, 0x33, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x69},\n            Null:       \"YES\",\n            Key:        \"\",\n            Default:    nil,\n            Extra:      \"\",\n            Privileges: \"select,insert,update,references\",\n            Comment:    \"\",\n        },\n    },\n}\n"
  },
  {
    "path": "database/testdata/TestShowCreateDatabase.golden",
    "content": "CREATE DATABASE `sakila` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ <nil>\n"
  },
  {
    "path": "database/testdata/TestShowCreateTable.golden",
    "content": "CREATE TABLE `film` (\n  `film_id` smallint unsigned NOT NULL AUTO_INCREMENT,\n  `title` varchar(255) NOT NULL,\n  `description` text,\n  `release_year` year DEFAULT NULL,\n  `language_id` tinyint unsigned NOT NULL,\n  `original_language_id` tinyint unsigned DEFAULT NULL,\n  `rental_duration` tinyint unsigned NOT NULL DEFAULT '3',\n  `rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',\n  `length` smallint unsigned DEFAULT NULL,\n  `replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',\n  `rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G',\n  `special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL,\n  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  PRIMARY KEY (`film_id`),\n  KEY `idx_title` (`title`),\n  KEY `idx_fk_language_id` (`language_id`),\n  KEY `idx_fk_original_language_id` (`original_language_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb3\nCREATE TABLE `category` (\n  `category_id` tinyint unsigned NOT NULL AUTO_INCREMENT,\n  `name` varchar(25) NOT NULL,\n  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  PRIMARY KEY (`category_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb3\nCREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `customer_list` AS select `cu`.`customer_id` AS `ID`,concat(`cu`.`first_name`,_utf8mb3' ',`cu`.`last_name`) AS `name`,`a`.`address` AS `address`,`a`.`postal_code` AS `zip code`,`a`.`phone` AS `phone`,`city`.`city` AS `city`,`country`.`country` AS `country`,if(`cu`.`active`,_utf8mb3'active',_utf8mb3'') AS `notes`,`cu`.`store_id` AS `SID` from (((`customer` `cu` join `address` `a` on((`cu`.`address_id` = `a`.`address_id`))) join `city` on((`a`.`city_id` = `city`.`city_id`))) join `country` on((`city`.`country_id` = `country`.`country_id`)))\nCREATE TABLE `inventory` (\n  `inventory_id` mediumint unsigned NOT NULL AUTO_INCREMENT,\n  `film_id` smallint unsigned NOT NULL,\n  `store_id` tinyint unsigned NOT NULL,\n  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  PRIMARY KEY (`inventory_id`),\n  KEY `idx_fk_film_id` (`film_id`),\n  KEY `idx_store_id_film_id` (`store_id`,`film_id`)\n) ENGINE=InnoDB AUTO_INCREMENT=4582 DEFAULT CHARSET=utf8mb3\n"
  },
  {
    "path": "database/testdata/TestShowIndex.golden",
    "content": "&database.TableIndexInfo{\n    TableName: \"film\",\n    Rows:      {\n        {\n            Table:        \"film\",\n            NonUnique:    0,\n            KeyName:      \"PRIMARY\",\n            SeqInIndex:   1,\n            ColumnName:   \"film_id\",\n            Collation:    \"A\",\n            Cardinality:  1000,\n            SubPart:      0,\n            Packed:       0,\n            Null:         \"\",\n            IndexType:    \"\",\n            Comment:      \"\",\n            IndexComment: \"\",\n            Visible:      \"\",\n            Expression:   nil,\n        },\n        {\n            Table:        \"film\",\n            NonUnique:    1,\n            KeyName:      \"idx_title\",\n            SeqInIndex:   1,\n            ColumnName:   \"title\",\n            Collation:    \"A\",\n            Cardinality:  1000,\n            SubPart:      0,\n            Packed:       0,\n            Null:         \"\",\n            IndexType:    \"\",\n            Comment:      \"\",\n            IndexComment: \"\",\n            Visible:      \"\",\n            Expression:   nil,\n        },\n        {\n            Table:        \"film\",\n            NonUnique:    1,\n            KeyName:      \"idx_fk_language_id\",\n            SeqInIndex:   1,\n            ColumnName:   \"language_id\",\n            Collation:    \"A\",\n            Cardinality:  1,\n            SubPart:      0,\n            Packed:       0,\n            Null:         \"\",\n            IndexType:    \"\",\n            Comment:      \"\",\n            IndexComment: \"\",\n            Visible:      \"\",\n            Expression:   nil,\n        },\n        {\n            Table:        \"film\",\n            NonUnique:    1,\n            KeyName:      \"idx_fk_original_language_id\",\n            SeqInIndex:   1,\n            ColumnName:   \"original_language_id\",\n            Collation:    \"A\",\n            Cardinality:  1,\n            SubPart:      0,\n            Packed:       0,\n            Null:         \"\",\n            IndexType:    \"\",\n            Comment:      \"\",\n            IndexComment: \"\",\n            Visible:      \"\",\n            Expression:   nil,\n        },\n    },\n}\n[]database.TableIndexRow{\n    {\n        Table:        \"film\",\n        NonUnique:    1,\n        KeyName:      \"idx_title\",\n        SeqInIndex:   1,\n        ColumnName:   \"title\",\n        Collation:    \"A\",\n        Cardinality:  1000,\n        SubPart:      0,\n        Packed:       0,\n        Null:         \"\",\n        IndexType:    \"\",\n        Comment:      \"\",\n        IndexComment: \"\",\n        Visible:      \"\",\n        Expression:   nil,\n    },\n}\n"
  },
  {
    "path": "database/testdata/TestShowReference.golden",
    "content": "[]database.ReferenceValue(nil)\n"
  },
  {
    "path": "database/testdata/TestShowTables.golden",
    "content": "actor\nactor_info\naddress\ncategory\ncity\ncountry\ncustomer\ncustomer_list\nfilm\nfilm_actor\nfilm_category\nfilm_list\nfilm_text\ninventory\nlanguage\nnicer_but_slower_film_list\npayment\nrental\nsales_by_film_category\nsales_by_store\nstaff\nstaff_list\nstore\n"
  },
  {
    "path": "database/testdata/TestTrace.golden",
    "content": "select 1 []database.TraceRow{\n    {Query:\"explain select 1\", Trace:\"{\\n  \\\"steps\\\": [\\n    {\\n      \\\"join_preparation\\\": {\\n        \\\"select#\\\": 1,\\n        \\\"steps\\\": [\\n          {\\n            \\\"expanded_query\\\": \\\"/* select#1 */ select 1 AS `1`\\\"\\n          }\\n        ]\\n      }\\n    },\\n    {\\n      \\\"join_optimization\\\": {\\n        \\\"select#\\\": 1,\\n        \\\"steps\\\": [\\n        ]\\n      }\\n    },\\n    {\\n      \\\"join_explain\\\": {\\n        \\\"select#\\\": 1,\\n        \\\"steps\\\": [\\n        ]\\n      }\\n    }\\n  ]\\n}\", MissingBytesBeyondMaxMemSize:0, InsufficientPrivileges:0},\n} nil\nexplain select 1 []database.TraceRow(nil) &errors.errorString{s:\"no need trace\"}\nshow create table film []database.TraceRow(nil) &errors.errorString{s:\"no need trace\"}\n"
  },
  {
    "path": "database/trace.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// Trace 用于存放 Select * From Information_Schema.Optimizer_Trace;输出的结果\ntype Trace struct {\n\tRows []TraceRow\n}\n\n// TraceRow 中含有trace的基本信息\ntype TraceRow struct {\n\tQuery                        string\n\tTrace                        string\n\tMissingBytesBeyondMaxMemSize int\n\tInsufficientPrivileges       int\n}\n\n// Trace 执行SQL，并对其Trace\nfunc (db *Connector) Trace(sql string, params ...interface{}) ([]TraceRow, error) {\n\tcommon.Log.Debug(\"Trace SQL: %s\", sql)\n\tvar rows []TraceRow\n\tif common.Config.TestDSN.Version < 50600 {\n\t\treturn rows, errors.New(\"version < 5.6, not support trace\")\n\t}\n\n\t// 过滤不需要 Trace 的 SQL\n\tswitch sqlparser.Preview(sql) {\n\tcase sqlparser.StmtSelect, sqlparser.StmtUpdate, sqlparser.StmtDelete:\n\t\tsql = \"explain \" + sql\n\tcase sqlparser.EXPLAIN:\n\tdefault:\n\t\treturn rows, errors.New(\"no need trace\")\n\t}\n\n\t// 测试环境如果检查是关闭的，则SQL不会被执行\n\tif common.Config.TestDSN.Disable {\n\t\treturn rows, errors.New(\"dsn is disable\")\n\t}\n\n\t// 数据库安全性检查：如果 Connector 的 IP 端口与 TEST 环境不一致，则启用SQL白名单\n\t// 不在白名单中的SQL不允许执行\n\t// 执行环境与test环境不相同\n\tif db.Addr != common.Config.TestDSN.Addr && db.dangerousQuery(sql) {\n\t\treturn rows, fmt.Errorf(\"query Execution Deny: Execute SQL with DSN(%s/%s) '%s'\",\n\t\t\tdb.Addr, db.Database, fmt.Sprintf(sql, params...))\n\t}\n\n\tcommon.Log.Debug(\"Execute SQL with DSN(%s/%s) : %s\", db.Addr, db.Database, sql)\n\t// 开启Trace\n\tcommon.Log.Debug(\"SET SESSION OPTIMIZER_TRACE='enabled=on'\")\n\ttrx, err := db.Conn.Begin()\n\tif err != nil {\n\t\treturn rows, err\n\t}\n\tdefer func() {\n\t\ttrxErr := trx.Rollback()\n\t\tif trxErr != nil {\n\t\t\tcommon.Log.Debug(trxErr.Error())\n\t\t}\n\t}()\n\t_, err = trx.Query(\"SET SESSION OPTIMIZER_TRACE='enabled=on'\")\n\tcommon.LogIfError(err, \"\")\n\n\t// 执行SQL，抛弃返回结果\n\ttmpRes, err := trx.Query(sql, params...)\n\tif err != nil {\n\t\treturn rows, err\n\t}\n\tfor tmpRes.Next() {\n\t\tcontinue\n\t}\n\n\t// 返回Trace结果\n\tres, err := trx.Query(\"SELECT * FROM information_schema.OPTIMIZER_TRACE\")\n\tif err != nil {\n\t\ttrxErr := trx.Rollback()\n\t\tif trxErr != nil {\n\t\t\tcommon.Log.Debug(trxErr.Error())\n\t\t}\n\t\treturn rows, err\n\t}\n\tfor res.Next() {\n\t\tvar traceRow TraceRow\n\t\terr = res.Scan(&traceRow.Query, &traceRow.Trace, &traceRow.MissingBytesBeyondMaxMemSize, &traceRow.InsufficientPrivileges)\n\t\tif err != nil {\n\t\t\tcommon.LogIfError(err, \"\")\n\t\t\tbreak\n\t\t}\n\t\trows = append(rows, traceRow)\n\t}\n\tres.Close()\n\n\t// 关闭Trace\n\tcommon.Log.Debug(\"SET SESSION OPTIMIZER_TRACE='enabled=off'\")\n\t_, err = trx.Query(\"SET SESSION OPTIMIZER_TRACE='enabled=off'\")\n\tcommon.LogIfError(err, \"\")\n\treturn rows, err\n}\n\n// FormatTrace 格式化输出Trace信息\nfunc FormatTrace(rows []TraceRow) string {\n\texplainReg := regexp.MustCompile(`(?i)^explain\\s+`)\n\tstr := []string{\"\"}\n\tfor _, row := range rows {\n\t\tstr = append(str, \"```sql\")\n\t\tsql := explainReg.ReplaceAllString(row.Query, \"\")\n\t\tstr = append(str, sql)\n\t\tstr = append(str, \"```\\n\")\n\t\tstr = append(str, \"```json\")\n\t\tstr = append(str, row.Trace)\n\t\tstr = append(str, \"```\\n\")\n\t}\n\treturn strings.Join(str, \"\\n\")\n}\n"
  },
  {
    "path": "database/trace_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage database\n\nimport (\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\n\t\"github.com/kr/pretty\"\n)\n\nfunc TestTrace(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tsqls := []string{\n\t\t\"select 1\",\n\t\t\"explain select 1\",\n\t\t\"show create table film\",\n\t}\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range sqls {\n\t\t\tres, err := connTest.Trace(sql)\n\t\t\tpretty.Println(sql, res, err)\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestFormatTrace(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tres, err := connTest.Trace(\"select 1\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\terr = common.GoldenDiff(func() {\n\t\tpretty.Println(FormatTrace(res))\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "deps.sh",
    "content": "#!/bin/bash\n\nNEEDED_COMMANDS=\"docker git go retool bats\"\n\nfor cmd in ${NEEDED_COMMANDS} ; do\n    if ! command -v \"${cmd}\" &> /dev/null ; then\n        echo -e \"\\033[91m${cmd} missing\\033[0m\"\n        exit 1\n    else\n        echo \"${cmd} found\"\n    fi\ndone\n\n# Docker\n## https://www.docker.com\n\n# Git\n## https://git-scm.com/\n\n# Go\n## https://golang.org/\n\n# Govendor\n## go get github.com/kardianos/govendor\n\n# retool\n## go get github.com/twitchtv/retool\n\n# bats https://github.com/sstephenson/bats\n## Ubuntu: apt-get install bats\n## Mac: brew install bats\n"
  },
  {
    "path": "doc/FAQ.md",
    "content": "# 常见问题\n\n## 软件依赖\n\n* [git](https://git-scm.co) 项目代码管理工具\n* [go](https://golang.org/) 源码编译依赖\n* [govendor](https://github.com/kardianos/govendor) 管理第三方包\n* [docker](https://www.docker.com) 主要用于构建测试环境\n* [mysql](https://www.mysql.com/) 测试时用来连接测试环境\n* [retool](https://github.com/twitchtv/retool): 管理测试开发工具,首次安装耗时会比较长,如: `gometalinter.v2`, `revive`, `golangci-lint`\n\n## Web 界面支持\n\n官方不会提供 Web 界面支持，但社区已经有相当多的同学基于 SOAR 开发了衍生的 Web 平台。可以参考如下 ISSUE 的讨论。\n\n> https://github.com/XiaoMi/soar/issues/51\n\n## 命令行参数 `test-dsn`, `online-dsn` 中包含特殊字符怎么办？\n\n如果 `test-dsn` 或 `online-dsn` 中包含':', '@', '/', '!'等特殊字符建议在配置文件中配置相关信息，配置文件为YAML格式，需要遵守YAML格式的要求规范。\n\n## Windows 环境如下安装使用？\n\nSOAR是命令行工具，开源版本无UI界面需要在 `cmd.exe` 下运行。下图为社区同学帮忙录制的 Windows 环境下载、安装及使用演示。\n\n![SOAR__Windows_Installation](https://wx4.sinaimg.cn/large/7143d93fly1fx9z0lw0k8g211j0jlkjo.gif)\n\n## Windows 环境下 -query 文件找不到问题\n\n![](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/windows_query_error.png)\n\n将 Window 文件后缀名显示打开，并检查文件名是否正确。\n\n![](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/windows_query_check.png)\n\n## Windows环境下双击`soar.windows-amd64`文件无反应。\n\n`soar` 是命令行工具，不是图形化桌面工具，Windows环境需要在 `cmd.exe` 下以命令行方式运行。使用 `soar` 前您需要先熟悉Windows命令行使用。\n\n## Windows 用户的引号问题\n\n![windows_quote](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/windows_quote.png)\n\n从上图可以看出 Windows 环境下引号（单引号或双引号）也成为了SQL的一部分传递给 soar 进行分析。因此 Windows 环境下使用如下方式读取 SQL 来解决 Windows 的引号问题。\n\n```bash\ntype query.sql | soar.windows-amd64\n```\n\n## 文件无法执行\n\n```bash\n./soar.linux-amd64\nbash: ./soar.linux-amd64: cannot execute binary file\n```\n\n请注意您操作系统类型，`soar.linux-amd64` 为 Linux 系统使用的二进制文件，`soar.darwin-amd64` 为苹果系统使用的二进制文件，`soar.windows-amd64` 是微软用户使用的二进制文件。下载文件后 Linux 和苹果用户需要为文件添加可执行权限 `chmod a+x filename`。\n\n## 命令无法找到\n\n```bash\nbash: soar: command not found\n```\n\n直接执行 `soar` 命令提示命令无法找到，请先将 soar 文件添加可执行权限 `chmod a+x soar` 然后将可以将 soar 所在路径加到[PATH](https://linuxconfig.org/linux-path-environment-variable)中，也可以将 soar 移动到已有 PATH 中。\n\n当然在 Linux 环境下，在 soar 二进制文件所在路径运行 `./soar` 也同样可以解决您的问题。\n\n## 提示语法错误\n\n* 请检查SQL语句中是否出现了不配对的引号,如 `, \", '\n\n## 输出结果返回慢\n\n* 如果配置了 online-dsn 或 test-dsn SOAR 会请求这些数据库以支持更多的功能，这时评审一条SQL就会耗时变长。\n* 如果又开启了 `-sampling=true` 的话会将线上的数据导入到测试环境，数据采样也会消耗一些时间。\n\n## 如何搭建测试环境\n\n```bash\n# 创建测试数据库\nwget http://downloads.mysql.com/doc/sakila-db.tar.gz\ntar zxf sakila-db.tar.gz && cd sakila-db\nmysql -u root -p -f < sakila-schema.sql\nmysql -u root -p -f < sakila-data.sql\n\n# 创建测试用户\nCREATE USER root@'hostname' IDENTIFIED BY \"1t'sB1g3rt\";\nGRANT ALL ON  *.* TO root@'hostname';\n```\n\n## 更新vitess依赖\n\n使用`govendor fetch`或`git clone` [vitess](https://github.com/vitessio/vitess) 在某些地区更新vitess可能会比较慢，导致项目编译不过，所以将vitess整个代码库加到了代码仓库。\n\n如属更新vitess仓库可以使用如下命令。\n\n```bash\nmake vitess\n```\n\n## 生成报告并发邮件\n\n```bash\n#!/bin/bash\n\nsoar -query \"select * from film\" > ./index.html\n\n(\n  echo To: youmail@example.com\n  echo From: robot@example.com\n  echo \"Content-Type: text/html; \"\n  echo Subject: SQL Analyze Report\n  echo\n  cat ./index.html\n) | sendmail -t\n\n```\n\n## 如何新增一条启发式建议\n\n```bash\nadvisor/rules.go HeuristicRules 加一个条新的规则\nadvisor/heuristic.go 实现一个规则函数\nadvisor/heuristic_test.go 添加相应规则函数的测试用例\nmake heuristic\nmake daily\n```\n"
  },
  {
    "path": "doc/FAQ_en.md",
    "content": "## FAQ\n\n### Dependency Tools\n\n* [git](https://git-scm.co): clone code from git repository\n* [go](https://golang.org/): build source\n* [govendor](https://github.com/kardianos/govendor): manager third party dependency\n* [docker](https://www.docker.com): manager test environment\n* [mysql](https://www.mysql.com/): connect test environment\n* [retool](https://github.com/twitchtv/retool): manager test tools such as `gometalinter.v2`, `revive`, `golangci-lint`\n\n### Syntax Error\n\n* Unexpected quote, like `, \", '\n* vitess syntax not supported yet\n\n### Program running slowly\n\n* SOAR will use online-dsn, test-dsn for data sampling and testing if they are on a different host to access these instance will cost much time. This may cause analyze slowly, especially when you are optimizing lots of queries.\n* As mentioned above, if you set `-sampling=true`(by default), data sampling will take some time for more accurate suggestions.\n\n## build test env\n\n```bash\n# create test database\nwget http://downloads.mysql.com/doc/sakila-db.tar.gz\ntar zxf sakila-db.tar.gz && cd sakila-db\nmysql -u root -p -f < sakila-schema.sql\nmysql -u root -p -f < sakila-data.sql\n\n# create test user\nCREATE USER root@'hostname' IDENTIFIED BY \"1t'sB1g3rt\";\nGRANT ALL ON  *.* TO root@'hostname';\n```\n\n## update vitess in vendor\n\n`govendor fetch` or `git clone` [vitess](https://github.com/vitessio/vitess) in somewhere maybe very slow or be blocked, so we add vitess source code in vendor directory.\n\nIf you what to update vitess package, you should bypass that block using yourself method.\n\n```bash\n$ make vitess\n```\n\n## HTML Format Report\n\n```bash\n#!/bin/bash\n\nsoar -query \"select * from film\" > ./index.html\n\n(\n  echo To: youmail@example.com\n  echo From: robot@example.com\n  echo \"Content-Type: text/html; \"\n  echo Subject: SQL Analyze Report\n  echo\n  cat ./index.html\n) | sendmail -t\n\n```\n\n## Add a new heuristic rule\n\n```bash\nadvisor/rules.go HeuristicRules add a new item\nadvisor/heuristic.go add a new rule function\nadvisor/heuristic_test.go add a new test function\nmake doc\ngo test github.com/XiaoMi/soar/advisor -v -update -run TestListHeuristicRules\ngo test github.com/XiaoMi/soar/advisor -v -update -run TestMergeConflictHeuristicRules\nmake daily\n```\n"
  },
  {
    "path": "doc/cheatsheet.md",
    "content": "# 常用命令\n\n[toc]\n\n## 基本用法\n\n```bash\necho \"select title from sakila.film\" | ./soar -log-output=soar.log\n```\n\n## 指定输入源\n\n```bash\n# 从文件读取SQL\n./soar -query file.sql\n\n# 从管道读取SQL\ncat file.sql | ./soar\n```\n\n## 指定配置文件\n\n```bash\nvi soar.yaml\n# yaml format config file\nonline-dsn:\n    addr:     127.0.0.1:3306\n    schema:   sakila\n    user:     root\n    password: \"1t'sB1g3rt\"\n    disable:  false\n\ntest-dsn:\n    addr:     127.0.0.1:3306\n    schema:   sakila\n    user:     root\n    password: \"1t'sB1g3rt\"\n    disable:  false\n```\n\n```bash\necho \"select title from sakila.film\" | ./soar -test-dsn=\"root:1t'sB1g3rt@127.0.0.1:3306/sakila\" -allow-online-as-test -log-output=soar.log\n```\n\n## 打印所有的启发式规则\n\n```bash\nsoar -list-heuristic-rules\n```\n\n## 忽略某些规则\n\n```bash\nsoar -ignore-rules \"ALI.001,IDX.*\"\n```\n\n## 打印支持的报告格式\n\n```bash\nsoar -list-report-types\n```\n\n## 以指定格式输出报告\n\n```bash\nsoar -report-type json\n```\n\n## 语法检查工具\n\n```bash\necho \"select * from tb\" | soar -only-syntax-check\necho $?\n0\n\necho \"select * frm tb\" | soar -only-syntax-check\nAt SQL 1 : syntax error at position 13 near 'frm'\necho $?\n1\n```\n\n## 慢日志进行分析示例\n\n```bash\npt-query-digest slow.log > slow.log.digest\n# parse pt-query-digest's output which example script\npython2.7 doc/example/digest_pt.py slow.log.digest > slow.md\n```\n\n## SQL指纹\n\n```bash\necho \"select * from film where col='abc'\" | soar -report-type=fingerprint\n```\n\n输出\n\n```sql\nselect * from film where col=?\n```\n\n## 将 UPDATE/DELETE/INSERT 语法转为 SELECT\n\n```bash\necho \"update film set title = 'abc'\" | soar -rewrite-rules dml2select,delimiter  -report-type rewrite\n```\n\n输出\n\n```sql\nselect * from film;\n```\n\n## 合并多条ALTER语句\n\n```bash\necho \"alter table tb add column a int; alter table tb add column b int;\" | soar -report-type rewrite -rewrite-rules mergealter\n```\n\n输出\n\n```sql\nALTER TABLE `tb` add column a int, add column b int ;\n```\n\n## SQL美化\n\n```bash\necho \"select * from tbl where col = 'val'\" | ./soar -report-type=pretty\n```\n\n输出\n\n```sql\nSELECT\n  *\nFROM\n  tbl\nWHERE\n  col  = 'val';\n```\n\n## EXPLAIN信息分析报告\n\n```bash\nsoar -report-type explain-digest << EOF\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\n| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\n|  1 | SIMPLE      | film  | ALL  | NULL          | NULL | NULL    | NULL | 1131 |       |\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\nEOF\n```\n\n```text\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** |  |\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n```\n\n## markdown 转 HTML\n\n通过指定-report-css, -report-javascript, -markdown-extensions, -markdown-html-flags这些参数，你还可以控制HTML的显示格式。\n\n```bash\ncat test.md | soar -report-type md2html > test.html\n```\n\n## 清理测试环境残余的临时库表\n\n如配置了`-drop-test-temporary=false`或`soar`异常中止，`-test-dsn`中会残余以`optimizer_`为前缀的临时库表。手工清理这些库表可以使用如下命令。\n\n注意：为了不影响正在进行的其他SQL评审，`-cleanup-test-database`中会删除1小时前生成的临时库表。\n\n```bash\n./soar -cleanup-test-database\n```\n"
  },
  {
    "path": "doc/cheatsheet_en.md",
    "content": "\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```\n\n## Analyze SQL with test environment\n\n```bash\nvi soar.yaml\n# yaml format config file\nonline-dsn:\n    addr:     127.0.0.1:3306\n    schema:   sakila\n    user:     root\n    password: \"1t'sB1g3rt\"\n    disable:  false\n\ntest-dsn:\n    addr:     127.0.0.1:3306\n    schema:   sakila\n    user:     root\n    password: \"1t'sB1g3rt\"\n    disable:  false\n```\n\n```bash\necho \"select title from sakila.film\" | ./soar -test-dsn=\"root:1t'sB1g3rt@127.0.0.1:3306/sakila\" -allow-online-as-test -log-output=soar.log\n```\n\n## List supported heuristic rules\n\n```bash\nsoar -list-heuristic-rules\n```\n\n## Ignore Rules\n\n```bash\nsoar -ignore-rules \"ALI.001,IDX.*\"\n```\n\n## List supported report-type\n\n```bash\nsoar -list-report-types\n```\n\n## Set report-type for output\n\n```bash\nsoar -report-type json\n```\n\n## Syntax Check\n\n```bash\necho \"select * from tb\" | soar -only-syntax-check\necho $?\n0\n\necho \"select * frm tb\" | soar -only-syntax-check\nAt SQL 1 : syntax error at position 13 near 'frm'\n$ echo $?\n1\n\n```\n\n## Slow log analyzing\n\n```bash\npt-query-digest slow.log > slow.log.digest\n# parse pt-query-digest's output which example script\npython2.7 doc/example/digest_pt.py slow.log.digest > slow.md\n```\n\n## SQL FingerPrint\n\n```bash\necho \"select * from film where col='abc'\" | soar -report-type=fingerprint\n```\n\nOutput\n\n```sql\nselect * from film where col=?\n```\n\n## Convert UPDATE/DELETE/INSERT into SELECT\n\n```bash\necho \"update film set title = 'abc'\" | soar -rewrite-rules dml2select,delimiter  -report-type rewrite\n```\n\nOutput\n\n```sql\nselect * from film;\n```\n\n## Merge ALTER SQLs\n\n```bash\necho \"alter table tb add column a int; alter table tb add column b int;\" | soar -report-type rewrite -rewrite-rules mergealter\n```\n\nOutput\n\n```sql\nALTER TABLE `tb` add column a int, add column b int ;\n```\n\n## SQL Pretty\n\n```bash\necho \"select * from tbl where col = 'val'\" | ./soar -report-type=pretty\n```\n\nOutput\n\n```sql\nSELECT\n  *\nFROM\n  tbl\nWHERE\n  col  = 'val';\n```\n\n## Convert markdown to HTML\n\nmd2html comes with other flags, such as `-report-css`, `-report-javascript`, `-markdown-extensions`, `-markdown-html-flags`, you can get more self control HTML report.\n\n```bash\ncat test.md | soar -report-type md2html > test.html\n```\n"
  },
  {
    "path": "doc/comparison.md",
    "content": "## 业内其他优秀产品对比\n\n|              | SOAR | sqlcheck | pt-query-advisor | SQL Advisor | Inception | sqlautoreview |\n| ---          | ---  | ---      | ---              | ---         | ---       | ---           |\n| 启发式建议     | ✔️   | ✔️       | ✔️             | ❌          | ✔️        | ✔️             |\n| 索引建议       | ✔️   | ❌      | ❌               | ✔️         | ❌       | ✔️             |\n| 查询重写       | ✔️   | ❌      | ❌               | ❌          | ❌      | ❌            |\n| 执行计划展示    | ✔️   | ❌      | ❌               | ❌          | ❌      | ❌            |\n| Profiling     | ✔️   | ❌      | ❌               | ❌          | ❌      | ❌            |\n| Trace         | ✔️   | ❌      | ❌               | ❌          | ❌      | ❌            |\n| SQL在线执行    | ❌   | ❌       | ❌               | ❌          | ✔️       | ❌            |\n| 数据备份       | ❌   | ❌       | ❌               | ❌          | ✔️       | ❌            |\n"
  },
  {
    "path": "doc/comparison_en.md",
    "content": "## Compare with other wonderful product\n\n|                    | SOAR | sqlcheck | pt-query-advisor | SQL Advisor | Inception | sqlautoreview |\n| ---                | ---  | ---      | ---              | ---         | ---       | ---           |\n| Heuristic Rules    | ✔️    | ✔️        | ✔️                | ❌          | ✔️         | ✔️             |\n| Index Suggest      | ✔️    | ❌       | ❌               | ✔️           | ❌        | ✔️             |\n| Rewrite Query      | ✔️    | ❌       | ❌               | ❌          | ❌        | ❌            |\n| Explain            | ✔️    | ❌       | ❌               | ❌          | ❌        | ❌            |\n| Profiling          | ✔️    | ❌       | ❌               | ❌          | ❌        | ❌            |\n| Trace              | ✔️    | ❌       | ❌               | ❌          | ❌        | ❌            |\n| Execute SQL Online | ❌   | ❌       | ❌               | ❌          | ✔️         | ❌            |\n| Backup Data        | ❌   | ❌       | ❌               | ❌          | ✔️         | ❌            |\n"
  },
  {
    "path": "doc/config.md",
    "content": "## 配置文件说明\n\n配置文件为[yaml](https://en.wikipedia.org/wiki/YAML)格式。一般情况下只需要配置online-dsn, test-dsn, log-output等少数几个参数。即使不创建配置文件SOAR仍然会给出基本的启发式建议。\n\n默认文件会按照`/etc/soar.yaml`, `./etc/soar.yaml`, `./soar.yaml`顺序加载，找到第一个后不再继续加载后面的配置文件。如需指定其他配置文件可以通过`-config`参数指定。\n\n关于数据库权限`online-dsn`需要相应库表的SELECT权限，`test-dsn`需要root最高权限。\n\n```text\n# 线上环境配置\nonline-dsn:\n  addr: 127.0.0.1:3306\n  schema: sakila\n  user: root\n  password: 1t'sB1g3rt\n  disable: false\n# 测试环境配置\ntest-dsn:\n  addr: 127.0.0.1:3307\n  schema: test\n  user: root\n  password: 1t'sB1g3rt\n  disable: false\n# 是否允许测试环境与线上环境配置相同\nallow-online-as-test: true\n# 是否禁用环境检测，开启后表示允许测试环境版本低于线上环境 不建议开启，可能会导致语句执行异常\ndisable-version-check: false\n# 是否清理测试时产生的临时文件\ndrop-test-temporary: true\n# 语法检查小工具\nonly-syntax-check: false\nsampling-statistic-target: 100\nsampling: false\n# 日志级别，[0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug]\nlog-level: 7\nlog-output: ${your_log_dir}/soar.log\n# 优化建议输出格式\nreport-type: markdown\nignore-rules:\n- \"\"\n# 黑名单中的 SQL 将不会给评审意见。一行一条 SQL，可以是正则也可以是指纹，填写指纹时注意问号需要加反斜线转义。\nblacklist: ${your_config_dir}/soar.blacklist\n# 启发式算法相关配置\nmax-join-table-count: 5\nmax-group-by-cols-count: 5\nmax-distinct-count: 5\nmax-index-cols-count: 5\nmax-total-rows: 9999999\nspaghetti-query-length: 2048\nallow-drop-index: false\n# EXPLAIN相关配置\nexplain-sql-report-type: pretty\nexplain-type: extended\nexplain-format: traditional\nexplain-warn-select-type:\n- \"\"\nexplain-warn-access-type:\n- ALL\nexplain-max-keys: 3\nexplain-min-keys: 0\nexplain-max-rows: 10000\nexplain-warn-extra:\n- \"\"\nexplain-max-filtered: 100\nexplain-warn-scalability:\n- O(n)\nquery: \"\"\nlist-heuristic-rules: false\nlist-test-sqls: false\nverbose: true\n```\n\n## 命令行参数\n\n几乎所有配置文件中指定的参数都通通过命令行参数进行修改，且命令行参数优先级较配置文件优先级高。\n\n```bash\nsoar -h\n```\n\n### 命令行参数配置DSN\n\nSOAR 最新版本已经使用`go-sql-driver`替代了`mymysql`，DSN将使用`go-sql-driver`格式并且保持向前兼容，请参考[go-sql-driver](https://github.com/go-sql-driver/mysql#dsn-data-source-name)文档。\n\n**以下DSN格式不再推荐使用**\n\n> 账号密码中如包含特殊符号(如：'@',':','/'等)可在配置文件中设置，存在特殊字符的情况不适合在命令行中使用。目前`soar`只支持 tcp 协议的 MySQL 数据库连接方式，如需要配置本机MySQL环境建议将`localhost`修改为'127.0.0.1'，并检查对应的 'user'@'127.0.0.1' 账号是否存在。\n\n```bash\nsoar -online-dsn \"user:password@ip:port/database\"\n\nsoar -test-dsn \"user:password@ip:port/database\"\n```\n\n#### DSN格式支持\n\n* \"user:password@127.0.0.1:3307/database\"\n* \"user:password@127.0.0.1:3307\"\n* \"user:password@127.0.0.1:/database\"\n* \"user:password@:3307/database\"\n* \"user:password@\"\n* \"127.0.0.1:3307/database\"\n* \"@127.0.0.1:3307/database\"\n* \"@127.0.0.1\"\n* \"127.0.0.1\"\n* \"@/database\"\n* \"@127.0.0.1:3307\"\n* \"@:3307/database\"\n* \":3307/database\"\n* \"/database\"\n\n### SQL评分\n\n不同类型的建议指定的Severity不同，严重程度数字由低到高依次排序。满分100分，扣到0分为止。L0不扣分只给出建议，L1扣5分，L2扣10分，每级多扣5分以此类推。当由时给出L1, L2两要建议时扣分叠加，即扣15分。\n\n如果您想给出不同的扣分建议或者对指引中的文字内容不满意可以为在 git 中提 ISSUE，也可直接修改 rules.go 的相应配置然后重新编译自己的版本。\n\n注意：目前只有`markdown`和`html`两种`-report-type`支持评分输出显示，其他输出格式如有评分需求可以按上述规则自行计算。\n"
  },
  {
    "path": "doc/editor_plugin.md",
    "content": "## Vim插件安装\n\n* 首先安装Syntastic，安装方法参见[官方文档](https://github.com/vim-syntastic/syntastic#installation)\n* 将`soar`二进制文件拷贝到可执行文件的查找路径($PATH)下，添加可执行权限`chmod a+x soar`\n* 将doc/example/[soar.vim](http://github.com/XiaoMi/soar/raw/master/doc/example/soar.vim)文件拷贝至${SyntasticInstalledPath}/syntax_checkers/sql目录下\n* 修改${SyntasticInstalledPath}/plugin/syntastic/registry.vim文件，增加sql文件的检查工具，`'sql':['soar', 'sqlint']`\n\n### 插件演示\n\n![Vim插件示例](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/vim_plugin.png)\n\n### 常见问题\n\n#### 安装插件后无任何变化\n\n安装了 Syntastic 没有任何显示，官方推荐通过如下配置来开启自动提示，不然用户无法看到SOAR给出的建议。\n\n```vim\nset statusline+=%#warningmsg#\nset statusline+=%{SyntasticStatuslineFlag()}\nset statusline+=%*\n\nlet g:syntastic_always_populate_loc_list = 1\nlet g:syntastic_auto_loc_list = 1\nlet g:syntastic_check_on_open = 1\nlet g:syntastic_check_on_wq = 0\n```\n\n如果soar二进制未在可执行文件查找路径下，或未添加可执行文件也会导致无法提供建议，可通过如下命令确认。\n\n```bash\n$ which soar\n/usr/local/bin/soar\n```\n"
  },
  {
    "path": "doc/environment.md",
    "content": "## 集成环境\n\n![集成环境](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/env.png)\n\n| 线上环境 | 测试环境 |   场景                          |\n| ---      | ---      | ---                             |\n|    有    |    有    | 日常优化，完整的建议，推荐      |\n|    无    |    有    | 新申请资源，环境初始化测试      |\n|    无    |    无    | 盲测，试用，无EXPLAIN和索引建议 |\n|    有    |    无    | 用线上环境当测试环境，不推荐    |\n\n## 线上环境\n\n* 数据字典\n* 数据采样\n* EXPLAIN\n\n## 测试环境\n\n* 库表映射\n* 语法检查\n* 模拟执行\n* 索引建议/去重\n\n## 注意\n* 测试环境 MySQL 版本必须高于或等于线上环境\n* 测试环境需要所有权限(建议通过[docker](https://hub.docker.com/_/mysql/)启动)，线上环境需要只读(SELECT)权限。\n"
  },
  {
    "path": "doc/example/digest_pt.py",
    "content": "#!/usr/bin/python -u\n#-*- coding: utf-8 -*-\n\nimport sys, re, subprocess\nimport os.path\nreload(sys)\nsys.setdefaultencoding(\"utf-8\")\n\nSOAR_ARGS=[\"-ignore-rules=OK\"]\nUSE_DATABASE=\"\"\n\n# 打印pt-query-digest的统计信息\ndef printStatInfo(buf):\n    if buf.strip() == \"\":\n        return\n    if re.match(\"^# Query [0-9]\", buf):\n        sys.stdout.write(buf.split(\":\", 1)[0] + \"\\n\")\n    sys.stdout.write(\"\\n```text\\n\")\n    sys.stdout.write(buf)\n    sys.stdout.write(\"```\\n\")\n\n# 打印每条SQL的SOAR结果\ndef printSqlAdvisor(buf):\n    global USE_DATABASE\n    buf = re.sub(\"\\\\\\G$\", \"\", USE_DATABASE + buf)\n    if buf.strip() == \"\":\n        return\n\n    cmd = [\"soar\"]\n    if len(SOAR_ARGS) > 0:\n        cmd = cmd + SOAR_ARGS\n\n    p = subprocess.Popen([\"soar\"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)\n    adv = p.communicate(input=buf)[0]\n\n    # 清理环境\n    USE_DATABASE = \"\"\n\n    # 删除第一行\"# Query: xxxxx\"\n    try:\n        adv = adv.split('\\n', 1)[1]\n    except:\n        pass\n    sys.stdout.write(adv + \"\\n\")\n\n# 从统计信息中获取database信息\ndef getUseDB(line):\n    global USE_DATABASE\n    USE_DATABASE = \"USE \" + re.sub(' +', \" \", line).split(\" \")[2] + \";\"\n\ndef parsePtQueryDisget(f):\n    statBuf = \"\"\n    sqlBuf = \"\"\n    for line in f:\n        if line.strip() == \"\":\n            continue\n\n        if line.startswith(\"#\"):\n            if line.startswith(\"# Databases \") and not line.strip().endswith(\"more\"):\n                getUseDB(line)\n            if re.match(\"^# Query [0-9]\", line):\n                # pt-query-digest的头部统计信息\n                if line.startswith(\"# Query 1:\"):\n                    sys.stdout.write(\"# pt-query-digest统计信息\" + \"\\n\")\n                printStatInfo(statBuf)\n                statBuf = line\n            else:\n                statBuf += line\n        else:\n            if not line.strip().endswith(\"\\G\"):\n                sqlBuf += line \n            else:\n                sqlBuf += line\n                printStatInfo(statBuf)\n                statBuf = \"\"\n                printSqlAdvisor(sqlBuf)\n                sqlBuf = \"\"\n\ndef main():\n    global SOAR_ARGS\n    if len(sys.argv) == 1:\n        f = sys.stdin\n        parsePtQueryDisget(f)\n    else:\n        if os.path.isfile(sys.argv[-1]):\n            SOAR_ARGS = sys.argv[1:-1]\n            f = open(sys.argv[-1])\n        else:\n            SOAR_ARGS = sys.argv[1:]\n            f = sys.stdin\n        parsePtQueryDisget(f)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "doc/example/metalinter.json",
    "content": "{\n    \"Vendor\": true,\n    \"DisableAll\": true,\n    \"Enable\": [\n        \"gofmt\",\n        \"goimports\",\n        \"interfacer\",\n        \"misspell\",\n        \"unconvert\",\n        \"gosimple\",\n        \"golint\",\n        \"structcheck\",\n        \"deadcode\",\n        \"ineffassign\",\n        \"varcheck\",\n        \"vet\"\n    ],\n    \"Exclude\": [\n        \"MagicWordSZjYPIDgod1M8XqYEwhsdlzv2SyAtjy8\",\n        \"src/internal\",\n        \"src/runtime\"\n    ],\n    \"Deadline\": \"5m\"\n}\n"
  },
  {
    "path": "doc/example/metalinter.sh",
    "content": "#!/bin/bash\n\nMETABIN=$(which gometalinter.v1)\nPROJECT_PATH=${GOPATH}/src/github.com/XiaoMi/soar/\n\nif [ \"x$METABIN\" == \"x\" ]; then\n\tgo get -u gopkg.in/alecthomas/gometalinter.v1\n\t${GOPATH}/bin/gometalinter.v1 --install\nfi\n\nUPDATE=$1\n\nif [ \"${UPDATE}X\" != \"X\" ]; then\n\t${GOPATH}/bin/gometalinter.v1 --config ${PROJECT_PATH}/doc/example/metalinter.json ./... | tr -d [0-9] | sort > ${PROJECT_PATH}/doc/example/metalinter.txt\t\nelse\n\tcd ${PROJECT_PATH} && diff <(${GOPATH}/bin/gometalinter.v1 --config ${PROJECT_PATH}/doc/example/metalinter.json ./... | tr -d [0-9] | sort) <(cat ${PROJECT_PATH}/doc/example/metalinter.txt)\nfi\n\n"
  },
  {
    "path": "doc/example/metalinter.txt",
    "content": ""
  },
  {
    "path": "doc/example/revive.toml",
    "content": "ignoreGeneratedHeader = false\nseverity = \"error\"\nconfidence = 0.8\nerrorCode = 0\nwarningCode = 0\n\n[rule.blank-imports]\n[rule.context-as-argument]\n[rule.dot-imports]\n[rule.error-return]\n[rule.error-strings]\n[rule.error-naming]\n[rule.exported]\n[rule.if-return]\n[rule.var-naming]\n[rule.package-comments]\n[rule.range]\n[rule.receiver-naming]\n[rule.indent-error-flow]\n[rule.superfluous-else]\n[rule.modifies-parameter]\n\n# This can be checked by other tools like megacheck\n[rule.unreachable-code]\n\n\n# Currently this makes too much noise, but should add it in\n# and perhaps ignore it in a few files\n#[rule.confusing-naming]\n#  severity = \"warning\"\n#[rule.confusing-results]\n#  severity = \"warning\"\n#[rule.unused-parameter]\n#  severity = \"warning\"\n#[rule.deep-exit]\n#  severity = \"warning\"\n#[rule.flag-parameter]\n#  severity = \"warning\"\n\n\n\n# Adding these will slow down the linter\n# They are already provided by megacheck\n# [rule.unexported-return]\n# [rule.time-naming]\n# [rule.errorf]\n\n# Adding these will slow down the linter\n# Not sure if they are already provided by megacheck\n# [rule.var-declaration]\n# [rule.context-keys-type]\n"
  },
  {
    "path": "doc/example/slow.log.digest",
    "content": "\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.0.1\n# Files: slow.log\n# Overall: 75.28k total, 21 unique, 1.36 QPS, 0.22x concurrency __________\n# Time range: 2018-05-17 00:01:47 to 15:24:47\n# Attribute          total     min     max     avg     95%  stddev  median\n# ============     ======= ======= ======= ======= ======= ======= =======\n# Exec time         12368s    20ms      6s   164ms   501ms   208ms    95ms\n# Lock time             2s       0   311us    30us    38us     5us    27us\n# Rows sent         21.79M       0  28.49k  303.58   49.17   2.44k    0.99\n# Rows examine     103.77M       0  31.41k   1.41k   4.49k   2.96k  621.67\n# Query size         8.58M      17   7.78k  119.54  143.84   32.94  112.70\n\n# Profile\n# Rank Query ID           Response time    Calls R/Call V/M   Item\n# ==== ================== ================ ===== ====== ===== ============\n#    1 0x6F837C9DA962A07D 11374.6099 92.0% 67535 0.1684  0.27 SELECT test.table_?\n#    2 0x0B991403AD4E8932   803.2640  6.5%  5993 0.1340  0.24 SELECT test.table_?\n# MISC 0xMISC               190.1791  1.5%  1751 0.1086   0.0 <19 ITEMS>\n\n# Query 1: 1.22 QPS, 0.21x concurrency, ID 0x6F837C9DA962A07D at byte 6821409\n# This item is included in the report because it matches --limit.\n# Scores: V/M = 0.27\n# Time range: 2018-05-17 00:01:47 to 15:24:47\n# Attribute    pct   total     min     max     avg     95%  stddev  median\n# ============ === ======= ======= ======= ======= ======= ======= =======\n# Count         89   67535\n# Exec time     91  11375s    20ms      6s   168ms   501ms   212ms   100ms\n# Lock time     88      2s    20us   221us    29us    38us     5us    27us\n# Rows sent      0  65.95k       1       1       1       1       0       1\n# Rows examine  72  75.13M       0  31.41k   1.14k   3.52k   1.89k  592.07\n# Query size    88   7.61M     114     119  118.23  118.34    2.50  112.70\n# String:\n# Databases    test... (50646/74%)... 2 more\n# Hosts        127.0.0.1 (13617/20%)... 4 more\n# Users        test_r\n# Query_time distribution\n#   1us\n#  10us\n# 100us\n#   1ms\n#  10ms  ############################################################\n# 100ms  ################################################################\n#    1s  #\n#  10s+\n# Tables\n#    SHOW TABLE STATUS FROM `test` LIKE 'table_78'\\G\n#    SHOW CREATE TABLE `test`.`table_78`\\G\n# EXPLAIN /*!50100 PARTITIONS*/\nSELECT COUNT(*) AS `count` FROM test.table_78 WHERE `id` = 824076488 AND `last_modify` > 1526044213 AND `type` = 6\\G\n\n# Query 2: 0.11 QPS, 0.01x concurrency, ID 0x0B991403AD4E8932 at byte 1691609\n# This item is included in the report because it matches --limit.\n# Scores: V/M = 0.24\n# Time range: 2018-05-17 00:01:54 to 15:24:43\n# Attribute    pct   total     min     max     avg     95%  stddev  median\n# ============ === ======= ======= ======= ======= ======= ======= =======\n# Count          7    5993\n# Exec time      6    803s    20ms      1s   134ms   552ms   181ms    56ms\n# Lock time      9   206ms    26us   179us    34us    44us     5us    31us\n# Rows sent      1 290.64k       7      50   49.66   49.17    2.98   49.17\n# Rows examine   6   7.13M       7   5.79k   1.22k   4.27k   1.40k  563.87\n# Query size     9 850.97k     142     154  145.40  143.84    1.98  143.84\n# String:\n# Databases    test... (4280/71%)... 2 more\n# Hosts        127.0.0.1 (1246/20%), 127.0.0.2 (1229/20%)... 3 more\n# Users        test_r\n# Query_time distribution\n#   1us\n#  10us\n# 100us\n#   1ms\n#  10ms  ################################################################\n# 100ms  ####################################\n#    1s  #\n#  10s+\n# Tables\n#    SHOW TABLE STATUS FROM `test` LIKE 'table_83'\\G\n#    SHOW CREATE TABLE `test`.`table_83`\\G\n# EXPLAIN /*!50100 PARTITIONS*/\nSELECT * FROM test.table_83 WHERE `id` = 68211602 AND `last_modify` < 1526495341 AND `type` in ('6') order by `last_modify` desc  LIMIT 0,50\\G\n"
  },
  {
    "path": "doc/example/soar.vim",
    "content": "\"============================================================================\n\"File:        soar.vim\n\"Description: Syntax checking plugin for syntastic\n\"Maintainer:  Pengxiang Li<lipengxiang@xiaomi.com>\n\"License:     MIT\n\"============================================================================\n\nif exists('g:loaded_syntastic_sql_soar_checker')\n    finish\nendif\nlet g:loaded_syntastic_sql_soar_checker= 1\n\nlet s:save_cpo = &cpo\nset cpo&vim\n\nfunction! SyntaxCheckers_sql_soar_GetLocList() dict\n    let makeprg = self.makeprgBuild({\n    \\ 'args_after': '-report-type lint -query '})\n\n    let errorformat = '%f:%l:%m'\n\n    return SyntasticMake({\n        \\ 'makeprg': makeprg,\n        \\ 'errorformat': errorformat,\n        \\ 'defaults': {'type': 'W'},\n        \\ 'subtype': 'Style',\n        \\ 'returns': [0, 1] })\nendfunction\n\ncall g:SyntasticRegistry.CreateAndRegisterChecker({\n    \\ 'filetype': 'sql',\n    \\ 'name': 'soar'})\n\nlet &cpo = s:save_cpo\nunlet s:save_cpo\n\n\" vim: set sw=4 sts=4 et fdm=marker:\n"
  },
  {
    "path": "doc/explain.md",
    "content": "\n## EXPLAIN信息解读\n\n* [EXPLAIN语法](https://dev.mysql.com/doc/refman/5.7/en/explain.html)\n* [EXPLAIN输出信息](https://dev.mysql.com/doc/refman/5.7/en/explain-output.html)\n\n### SELECT转换\n\n指定了线上环境时SOAR会到线上环境进行EXPLAIN，然后对线上执行EXPLAIN的结果进行分析。由于低版本的MySQL不支持对INSERT， UPDATE， DELETE， REPLACE进行分析，SOAR会自动将这些类型的查询请求转换为SELECT请求再执行EXPLAIN信息。\n\n另外当线上环境设置了read\\_only或super\\_readonly时即使是高版本的MySQL也无法对更新请求执行EXPLAIN。需要进行SELECT转换。\n\n### 文本格式\n\nSOAR也支持用户直接拷贝粘贴已有的EXPLAIN文本信息，格式可以是传统格式，\\G输出的Verical格式，也可以是JSON格式。\n\nJSON格式的EXPLAIN包含的内容很丰富，但不便于人查看，信息解读的时候会将JSON和Vertical格式统一转换成传统格式。Golang处理JSON格式需要提前定义结构体，这里不得不向[gojson](https://github.com/ChimeraCoder/gojson)献出膝盖，要是没有这个工具也许我们暂时会放弃对JSON格式的支持。\n\n### Filtered\n\n表示此查询条件所过滤的数据的百分比。低版本的MySQL EXPLAIN信息不包含Filtered字段，SOAR会按 `filtered = rows/total_rows` 计算补充。\n\n5.7之前的版本Filtered计算可能出现大于100%的[BUG](https://bugs.mysql.com/bug.php?id=34124)，为了不对用户产生困扰，soar会将大于100%的Filered化整为100%。\n\n### Scalability\n\nScalability表示单表查询的运算复杂度，是参考[explain-analyzer](https://github.com/Preetam/explain-analyzer)项目添加的。Scalability是对access\\_type的映射表，由于是单表查询，所以最大复杂度为O(n)。\n\n| Access Type      | Scalability |\n| ---              | ---         |\n| ALL              | O(n)        |\n| index            | O(n)        |\n| range            | O(log n)+   |\n| index\\_subquery  | O(log n)+   |\n| unique\\_subquery | O(log n)+   |\n| index\\_merge     | O(log n)+   |\n| ref\\_or\\_null    | O(log n)+   |\n| fulltext         | O(log n)+   |\n| ref              | O(log n)    |\n| eq\\_ref          | O(log n)    |\n| const            | O(1)        |\n| system           | O(1)        |\n"
  },
  {
    "path": "doc/heuristic.md",
    "content": "# 启发式规则建议\n\n[toc]\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item**:ALI.001\n* **Severity**:L0\n* **Content**:在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n* **Case**:\n\n```sql\nselect name from tbl t1 where id < 1000\n```\n## 不建议给列通配符'\\*'设置别名\n\n* **Item**:ALI.002\n* **Severity**:L8\n* **Content**:例: \"SELECT tbl.\\* col1, col2\"上面这条 SQL 给列通配符设置了别名，这样的SQL可能存在逻辑错误。您可能意在查询 col1, 但是代替它的是重命名的是 tbl 的最后一列。\n* **Case**:\n\n```sql\nselect tbl.* as c1,c2,c3 from tbl where id < 1000\n```\n## 别名不要与表或列的名字相同\n\n* **Item**:ALI.003\n* **Severity**:L1\n* **Content**:表或列的别名与其真实名称相同, 这样的别名会使得查询更难去分辨。\n* **Case**:\n\n```sql\nselect name from tbl as tbl where id < 1000\n```\n## 修改表的默认字符集不会改表各个字段的字符集\n\n* **Item**:ALT.001\n* **Severity**:L4\n* **Content**:很多初学者会将 ALTER TABLE tbl\\_name [DEFAULT] CHARACTER SET 'UTF8' 误认为会修改所有字段的字符集，但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用 ALTER TABLE tbl\\_name CONVERT TO CHARACTER SET charset\\_name;\n* **Case**:\n\n```sql\nALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;\n```\n## 同一张表的多条 ALTER 请求建议合为一条\n\n* **Item**:ALT.002\n* **Severity**:L2\n* **Content**:每次表结构变更对线上服务都会产生影响，即使是能够通过在线工具进行调整也请尽量通过合并 ALTER 请求的试减少操作次数。\n* **Case**:\n\n```sql\nALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);\n```\n## 删除列为高危操作，操作前请注意检查业务逻辑是否还有依赖\n\n* **Item**:ALT.003\n* **Severity**:L0\n* **Content**:如业务逻辑依赖未完全消除，列被删除后可能导致数据无法写入或无法查询到已删除列数据导致程序异常的情况。这种情况下即使通过备份数据回滚也会丢失用户请求写入的数据。\n* **Case**:\n\n```sql\nALTER TABLE tbl DROP COLUMN col;\n```\n## 删除主键和外键为高危操作，操作前请与 DBA 确认影响\n\n* **Item**:ALT.004\n* **Severity**:L0\n* **Content**:主键和外键为关系型数据库中两种重要约束，删除已有约束会打破已有业务逻辑，操作前请业务开发与 DBA 确认影响，三思而行。\n* **Case**:\n\n```sql\nALTER TABLE tbl DROP PRIMARY KEY;\n```\n## 不建议使用前项通配符查找\n\n* **Item**:ARG.001\n* **Severity**:L4\n* **Content**:例如 \"％foo\"，查询参数有一个前项通配符的情况无法使用已有索引。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from tbl where name like '%foo'\n```\n## 没有通配符的 LIKE 查询\n\n* **Item**:ARG.002\n* **Severity**:L1\n* **Content**:不包含通配符的 LIKE 查询可能存在逻辑错误，因为逻辑上它与等值查询相同。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from tbl where name like 'foo'\n```\n## 参数比较包含隐式转换，无法使用索引\n\n* **Item**:ARG.003\n* **Severity**:L4\n* **Content**:隐式类型转换有无法命中索引的风险，在高并发、大数据量的情况下，命不中索引带来的后果非常严重。\n* **Case**:\n\n```sql\nSELECT * FROM sakila.film WHERE length >= '60';\n```\n## IN (NULL)/NOT IN (NULL) 永远非真\n\n* **Item**:ARG.004\n* **Severity**:L4\n* **Content**:正确的作法是 col IN ('val1', 'val2', 'val3') OR col IS NULL\n* **Case**:\n\n```sql\nSELECT * FROM tb WHERE col IN (NULL);\n```\n## IN 要慎用，元素过多会导致全表扫描\n\n* **Item**:ARG.005\n* **Severity**:L1\n* **Content**: 如：select id from t where num in(1,2,3)对于连续的数值，能用 BETWEEN 就不要用 IN 了：select id from t where num between 1 and 3。而当 IN 值过多时 MySQL 也可能会进入全表扫描导致性能急剧下降。\n* **Case**:\n\n```sql\nselect id from t where num in(1,2,3)\n```\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item**:ARG.006\n* **Severity**:L1\n* **Content**:使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n* **Case**:\n\n```sql\nselect id from t where num is null\n```\n## 避免使用模式匹配\n\n* **Item**:ARG.007\n* **Severity**:L3\n* **Content**:性能问题是使用模式匹配操作符的最大缺点。使用 LIKE 或正则表达式进行模式匹配进行查询的另一个问题，是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代 SQL，比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL，请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说，您不一定要使用SQL来解决所有问题。\n* **Case**:\n\n```sql\nselect c_id,c2,c3 from tbl where c2 like 'test%'\n```\n## OR 查询索引列时请尽量使用 IN 谓词\n\n* **Item**:ARG.008\n* **Severity**:L1\n* **Content**:IN-list 谓词可以用于索引检索，并且优化器可以对 IN-list 进行排序，以匹配索引的排序序列，从而获得更有效的检索。请注意，IN-list 必须只包含常量，或在查询块执行期间保持常量的值，例如外引用。\n* **Case**:\n\n```sql\nSELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17\n```\n## 引号中的字符串开头或结尾包含空格\n\n* **Item**:ARG.009\n* **Severity**:L1\n* **Content**:如果 VARCHAR 列的前后存在空格将可能引起逻辑问题，如在 MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。\n* **Case**:\n\n```sql\nSELECT 'abc '\n```\n## 不要使用 hint，如：sql\\_no\\_cache, force index, ignore key, straight join等\n\n* **Item**:ARG.010\n* **Severity**:L1\n* **Content**:hint 是用来强制 SQL 按照某个执行计划来执行，但随着数据量变化我们无法保证自己当初的预判是正确的。\n* **Case**:\n\n```sql\nSELECT * FROM t1 USE INDEX (i1) ORDER BY a;\n```\n## 不要使用负向查询，如：NOT IN/NOT LIKE\n\n* **Item**:ARG.011\n* **Severity**:L3\n* **Content**:请尽量不要使用负向查询，这将导致全表扫描，对查询性能影响较大。\n* **Case**:\n\n```sql\nselect id from t where num not in(1,2,3);\n```\n## 一次性 INSERT/REPLACE 的数据过多\n\n* **Item**:ARG.012\n* **Severity**:L2\n* **Content**:单条 INSERT/REPLACE 语句批量插入大量数据性能较差，甚至可能导致从库同步延迟。为了提升性能，减少批量写入数据对从库同步延时的影响，建议采用分批次插入的方法。\n* **Case**:\n\n```sql\nINSERT INTO tb (a) VALUES (1), (2)\n```\n## DDL 语句中使用了中文全角引号\n\n* **Item**:ARG.013\n* **Severity**:L0\n* **Content**:DDL 语句中使用了中文全角引号“”或‘’，这可能是书写错误，请确认是否符合预期。\n* **Case**:\n\n```sql\nCREATE TABLE tb (a varchar(10) default '“”'\n```\n## IN 条件中存在列名，可能导致数据匹配范围扩大\n\n* **Item**:ARG.014\n* **Severity**:L4\n* **Content**:如：delete from t where id in(1, 2, id) 可能会导致全表数据误删除。请仔细检查 IN 条件的正确性。\n* **Case**:\n\n```sql\nselect id from t where id in(1, 2, id)\n```\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item**:CLA.001\n* **Severity**:L4\n* **Content**:SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n* **Case**:\n\n```sql\nselect id from tbl\n```\n## 不建议使用 ORDER BY RAND()\n\n* **Item**:CLA.002\n* **Severity**:L3\n* **Content**:ORDER BY RAND() 是从结果集中检索随机行的一种非常低效的方法，因为它会对整个结果进行排序并丢弃其大部分数据。\n* **Case**:\n\n```sql\nselect name from tbl where id < 1000 order by rand(number)\n```\n## 不建议使用带 OFFSET 的LIMIT 查询\n\n* **Item**:CLA.003\n* **Severity**:L2\n* **Content**:使用 LIMIT 和 OFFSET 对结果集分页的复杂度是 O(n^2)，并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。\n* **Case**:\n\n```sql\nselect c1,c2 from tbl where name=xx order by number limit 1 offset 20\n```\n## 不建议对常量进行 GROUP BY\n\n* **Item**:CLA.004\n* **Severity**:L2\n* **Content**:GROUP BY 1 表示按第一列进行 GROUP BY。如果在 GROUP BY 子句中使用数字，而不是表达式或列名称，当查询列顺序改变时，可能会导致问题。\n* **Case**:\n\n```sql\nselect col1,col2 from tbl group by 1\n```\n## ORDER BY 常数列没有任何意义\n\n* **Item**:CLA.005\n* **Severity**:L2\n* **Content**:SQL 逻辑上可能存在错误; 最多只是一个无用的操作，不会更改查询结果。\n* **Case**:\n\n```sql\nselect id from test where id=1 order by id\n```\n## 在不同的表中 GROUP BY 或 ORDER BY\n\n* **Item**:CLA.006\n* **Severity**:L4\n* **Content**:这将强制使用临时表和 filesort，可能产生巨大性能隐患，并且可能消耗大量内存和磁盘上的临时空间。\n* **Case**:\n\n```sql\nselect tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col\n```\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item**:CLA.008\n* **Severity**:L2\n* **Content**:默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1 where c1='foo' group by c2\n```\n## ORDER BY 的条件为表达式\n\n* **Item**:CLA.009\n* **Severity**:L2\n* **Content**:当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n* **Case**:\n\n```sql\nselect description from film where title ='ACADEMY DINOSAUR' order by length-language_id;\n```\n## GROUP BY 的条件为表达式\n\n* **Item**:CLA.010\n* **Severity**:L2\n* **Content**:当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n* **Case**:\n\n```sql\nselect description from film where title ='ACADEMY DINOSAUR' GROUP BY length-language_id;\n```\n## 建议为表添加注释\n\n* **Item**:CLA.011\n* **Severity**:L1\n* **Content**:为表添加注释能够使得表的意义更明确，从而为日后的维护带来极大的便利。\n* **Case**:\n\n```sql\nCREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128) DEFAULT NULL,PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8\n```\n## 将复杂的裹脚布式查询分解成几个简单的查询\n\n* **Item**:CLA.012\n* **Severity**:L2\n* **Content**:SQL是一门极具表现力的语言，您可以在单个SQL查询或者单条语句中完成很多事情。但这并不意味着必须强制只使用一行代码，或者认为使用一行代码就搞定每个任务是个好主意。通过一个查询来获得所有结果的常见后果是得到了一个笛卡儿积。当查询中的两张表之间没有条件限制它们的关系时，就会发生这种情况。没有对应的限制而直接使用两张表进行联结查询，就会得到第一张表中的每一行和第二张表中的每一行的一个组合。每一个这样的组合就会成为结果集中的一行，最终您就会得到一个行数很多的结果集。重要的是要考虑这些查询很难编写、难以修改和难以调试。数据库查询请求的日益增加应该是预料之中的事。经理们想要更复杂的报告以及在用户界面上添加更多的字段。如果您的设计很复杂，并且是一个单一查询，要扩展它们就会很费时费力。不论对您还是项目来说，时间花在这些事情上面不值得。将复杂的意大利面条式查询分解成几个简单的查询。当您拆分一个复杂的SQL查询时，得到的结果可能是很多类似的查询，可能仅仅在数据类型上有所不同。编写所有的这些查询是很乏味的，因此，最好能够有个程序自动生成这些代码。SQL代码生成是一个很好的应用。尽管SQL支持用一行代码解决复杂的问题，但也别做不切实际的事情。\n* **Case**:\n\n```sql\n这是一条很长很长的 SQL，案例略。\n```\n## 不建议使用 HAVING 子句\n\n* **Item**:CLA.013\n* **Severity**:L3\n* **Content**:将查询的 HAVING 子句改写为 WHERE 中的查询条件，可以在查询处理期间使用索引。\n* **Case**:\n\n```sql\nSELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id\n```\n## 删除全表时建议使用 TRUNCATE 替代 DELETE\n\n* **Item**:CLA.014\n* **Severity**:L2\n* **Content**:删除全表时建议使用 TRUNCATE 替代 DELETE\n* **Case**:\n\n```sql\ndelete from tbl\n```\n## UPDATE 未指定 WHERE 条件\n\n* **Item**:CLA.015\n* **Severity**:L4\n* **Content**:UPDATE 不指定 WHERE 条件一般是致命的，请您三思后行\n* **Case**:\n\n```sql\nupdate tbl set col=1\n```\n## 不要 UPDATE 主键\n\n* **Item**:CLA.016\n* **Severity**:L2\n* **Content**:主键是数据表中记录的唯一标识符，不建议频繁更新主键列，这将影响元数据统计信息进而影响正常的查询。\n* **Case**:\n\n```sql\nupdate tbl set col=1\n```\n## 不建议使用 SELECT \\* 类型查询\n\n* **Item**:COL.001\n* **Severity**:L1\n* **Content**:当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n* **Case**:\n\n```sql\nselect * from tbl where id=1\n```\n## INSERT/REPLACE 未指定列名\n\n* **Item**:COL.002\n* **Severity**:L2\n* **Content**:当表结构发生变更，如果 INSERT 或 REPLACE 请求不明确指定列名，请求的结果将会与预想的不同; 建议使用 “INSERT INTO tbl(col1，col2)VALUES ...” 代替。\n* **Case**:\n\n```sql\ninsert into tbl values(1,'name')\n```\n## 建议修改自增 ID 为无符号类型\n\n* **Item**:COL.003\n* **Severity**:L2\n* **Content**:建议修改自增 ID 为无符号类型\n* **Case**:\n\n```sql\ncreate table test(`id` int(11) NOT NULL AUTO_INCREMENT)\n```\n## 请为列添加默认值\n\n* **Item**:COL.004\n* **Severity**:L1\n* **Content**:请为列添加默认值，如果是 ALTER 操作，请不要忘记将原字段的默认值写上。字段无默认值，当表较大时无法在线变更表结构。\n* **Case**:\n\n```sql\nCREATE TABLE tbl (col int) ENGINE=InnoDB;\n```\n## 列未添加注释\n\n* **Item**:COL.005\n* **Severity**:L1\n* **Content**:建议对表中每个列添加注释，来明确每个列在表中的含义及作用。\n* **Case**:\n\n```sql\nCREATE TABLE tbl (col int) ENGINE=InnoDB;\n```\n## 表中包含有太多的列\n\n* **Item**:COL.006\n* **Severity**:L3\n* **Content**:表中包含有太多的列\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( cols ....);\n```\n## 表中包含有太多的 text/blob 列\n\n* **Item**:COL.007\n* **Severity**:L3\n* **Content**:表中包含超过2个的 text/blob 列\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( cols ....);\n```\n## 可使用 VARCHAR 代替 CHAR， VARBINARY 代替 BINARY\n\n* **Item**:COL.008\n* **Severity**:L1\n* **Content**:为首先变长字段存储空间小，可以节省存储空间。其次对于查询来说，在一个相对较小的字段内搜索效率显然要高些。\n* **Case**:\n\n```sql\ncreate table t1(id int,name char(20),last_time date)\n```\n## 建议使用精确的数据类型\n\n* **Item**:COL.009\n* **Severity**:L2\n* **Content**:实际上，任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时，非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。\n* **Case**:\n\n```sql\nCREATE TABLE tab2 (p_id  BIGINT UNSIGNED NOT NULL,a_id  BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))\n```\n## 不建议使用 ENUM/BIT/SET 数据类型\n\n* **Item**:COL.010\n* **Severity**:L2\n* **Content**:ENUM 定义了列中值的类型，使用字符串表示 ENUM 里的值时，实际存储在列中的数据是这些值在定义时的序数。因此，这列的数据是字节对齐的，当您进行一次排序查询时，结果是按照实际存储的序数值排序的，而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值；您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项，您可能会为历史数据而烦恼。作为一种策略，改变元数据——也就是说，改变表和列的定义——应该是不常见的，并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表，每一行包含一个允许在列中出现的候选值；然后在引用新表的旧表上声明一个外键约束。\n* **Case**:\n\n```sql\ncreate table tab1(status ENUM('new','in progress','fixed'))\n```\n## 当需要唯一约束时才使用 NULL，仅当列不能有缺失值时才使用 NOT NULL\n\n* **Item**:COL.011\n* **Severity**:L0\n* **Content**:NULL 和0是不同的，10乘以 NULL 还是 NULL。NULL 和空字符串是不一样的。将一个字符串和标准 SQL 中的 NULL 联合起来的结果还是 NULL。NULL 和 FALSE 也是不同的。AND、OR 和 NOT 这三个布尔操作如果涉及 NULL，其结果也让很多人感到困惑。当您将一列声明为 NOT NULL 时，也就是说这列中的每一个值都必须存在且是有意义的。使用 NULL 来表示任意类型不存在的空值。 当您将一列声明为 NOT NULL 时，也就是说这列中的每一个值都必须存在且是有意义的。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from tbl where c4 is null or c4 <> 1\n```\n## TEXT、BLOB 和 JSON 类型的字段不建议设置为 NOT NULL\n\n* **Item**:COL.012\n* **Severity**:L5\n* **Content**:TEXT、BLOB 和 JSON 类型的字段无法指定非 NULL 的默认值，如果添加了 NOT NULL 限制，写入数据时又未对该字段指定值可能导致写入失败。\n* **Case**:\n\n```sql\nCREATE TABLE `tb`(`c` longblob NOT NULL);\n```\n## TIMESTAMP 类型默认值检查异常\n\n* **Item**:COL.013\n* **Severity**:L4\n* **Content**:TIMESTAMP 类型建议设置默认值，且不建议使用 0 或 0000-00-00 00:00:00 作为默认值。可以考虑使用 1970-08-02 01:01:01\n* **Case**:\n\n```sql\nCREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);\n```\n## 为列指定了字符集\n\n* **Item**:COL.014\n* **Severity**:L5\n* **Content**:建议列与表使用同一个字符集，不要单独指定列的字符集。\n* **Case**:\n\n```sql\nCREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)\n```\n## TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值\n\n* **Item**:COL.015\n* **Severity**:L4\n* **Content**:MySQL 数据库中 TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值。TEXT最大长度为2^16-1个字符，MEDIUMTEXT最大长度为2^32-1个字符，LONGTEXT最大长度为2^64-1个字符。\n* **Case**:\n\n```sql\nCREATE TABLE `tbl` (`c` blob DEFAULT NULL);\n```\n## 整型定义建议采用 INT(10) 或 BIGINT(20)\n\n* **Item**:COL.016\n* **Severity**:L1\n* **Content**:INT(M) 在 integer 数据类型中，M 表示最大显示宽度。 在 INT(M) 中，M 的值跟 INT(M) 所占多少存储空间并无任何关系。 INT(3)、INT(4)、INT(8) 在磁盘上都是占用 4 bytes 的存储空间。高版本 MySQL 已经不推荐设置整数显示宽度。\n* **Case**:\n\n```sql\nCREATE TABLE tab (a INT(1));\n```\n## VARCHAR 定义长度过长\n\n* **Item**:COL.017\n* **Severity**:L2\n* **Content**:varchar 是可变长字符串，不预先分配存储空间，长度不要超过1024，如果存储长度过长 MySQL 将定义字段类型为 text，独立出来一张表，用主键来对应，避免影响其它字段索引效率。\n* **Case**:\n\n```sql\nCREATE TABLE tab (a varchar(3500));\n```\n## 建表语句中使用了不推荐的字段类型\n\n* **Item**:COL.018\n* **Severity**:L9\n* **Content**:以下字段类型不被推荐使用：boolean\n* **Case**:\n\n```sql\nCREATE TABLE tab (a BOOLEAN);\n```\n## 不建议使用精度在秒级以下的时间数据类型\n\n* **Item**:COL.019\n* **Severity**:L1\n* **Content**:使用高精度的时间数据类型带来的存储空间消耗相对较大；MySQL 在5.6.4以上才可以支持精确到微秒的时间数据类型，使用时需要考虑版本兼容问题。\n* **Case**:\n\n```sql\nCREATE TABLE t1 (t TIME(3), dt DATETIME(6));\n```\n## 消除不必要的 DISTINCT 条件\n\n* **Item**:DIS.001\n* **Severity**:L1\n* **Content**:太多DISTINCT条件是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少DISTINCT条件的数量。如果主键列是列的结果集的一部分，则DISTINCT条件可能没有影响。\n* **Case**:\n\n```sql\nSELECT DISTINCT c.c_id,count(DISTINCT c.c_name),count(DISTINCT c.c_e),count(DISTINCT c.c_n),count(DISTINCT c.c_me),c.c_d FROM (select distinct id, name from B) as e WHERE e.country_id = c.country_id\n```\n## COUNT(DISTINCT) 多列时结果可能和你预想的不同\n\n* **Item**:DIS.002\n* **Severity**:L3\n* **Content**:COUNT(DISTINCT col) 计算该列除NULL之外的不重复行数，注意 COUNT(DISTINCT col, col2) 如果其中一列全为 NULL 那么即使另一列有不同的值，也返回0。\n* **Case**:\n\n```sql\nSELECT COUNT(DISTINCT col, col2) FROM tbl;\n```\n## DISTINCT \\* 对有主键的表没有意义\n\n* **Item**:DIS.003\n* **Severity**:L3\n* **Content**:当表已经有主键时，对所有列进行 DISTINCT 的输出结果与不进行 DISTINCT 操作的结果相同，请不要画蛇添足。\n* **Case**:\n\n```sql\nSELECT DISTINCT * FROM film;\n```\n## 避免在 WHERE 条件中使用函数或其他运算符\n\n* **Item**:FUN.001\n* **Severity**:L2\n* **Content**:虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\n* **Case**:\n\n```sql\nselect id from t where substring(name,1,3)='abc'\n```\n## 指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(\\*) 操作性能不佳\n\n* **Item**:FUN.002\n* **Severity**:L1\n* **Content**:COUNT(\\*) 的作用是统计表行数，COUNT(COL) 的作用是统计指定列非 NULL 的行数。MyISAM 表对于 COUNT(\\*) 统计全表行数进行了特殊的优化，通常情况下非常快。但对于非 MyISAM 表或指定了某些 WHERE 条件，COUNT(\\*) 操作需要扫描大量的行才能获取精确的结果，性能也因此不佳。有时候某些业务场景并不需要完全精确的 COUNT 值，此时可以用近似值来代替。EXPLAIN 出来的优化器估算的行数就是一个不错的近似值，执行 EXPLAIN 并不需要真正去执行查询，所以成本很低。\n* **Case**:\n\n```sql\nSELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY num\n```\n## 使用了合并为可空列的字符串连接\n\n* **Item**:FUN.003\n* **Severity**:L3\n* **Content**:在一些查询请求中，您需要强制让某一列或者某个表达式返回非 NULL 的值，从而让查询逻辑变得更简单，但又不想将这个值存下来。可以使用 COALESCE() 函数来构造连接的表达式，这样即使是空值列也不会使整表达式变为 NULL。\n* **Case**:\n\n```sql\nselect c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl\n```\n## 不建议使用 SYSDATE() 函数\n\n* **Item**:FUN.004\n* **Severity**:L4\n* **Content**:SYSDATE() 函数可能导致主从数据不一致，请使用 NOW() 函数替代 SYSDATE()。\n* **Case**:\n\n```sql\nSELECT SYSDATE();\n```\n## 不建议使用 COUNT(col) 或 COUNT(常量)\n\n* **Item**:FUN.005\n* **Severity**:L1\n* **Content**:不要使用 COUNT(col) 或 COUNT(常量) 来替代 COUNT(\\*), COUNT(\\*) 是 SQL92 定义的标准统计行数的方法，跟数据无关，跟 NULL 和非 NULL 也无关。\n* **Case**:\n\n```sql\nSELECT COUNT(1) FROM tbl;\n```\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item**:FUN.006\n* **Severity**:L1\n* **Content**:当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n* **Case**:\n\n```sql\nSELECT SUM(COL) FROM tbl;\n```\n## 不建议使用触发器\n\n* **Item**:FUN.007\n* **Severity**:L1\n* **Content**:触发器的执行没有反馈和日志，隐藏了实际的执行步骤，当数据库出现问题是，不能通过慢日志分析触发器的具体执行情况，不易发现问题。在MySQL中，触发器不能临时关闭或打开，在数据迁移或数据恢复等场景下，需要临时drop触发器，可能影响到生产环境。\n* **Case**:\n\n```sql\nCREATE TRIGGER t1 AFTER INSERT ON work FOR EACH ROW INSERT INTO time VALUES(NOW());\n```\n## 不建议使用存储过程\n\n* **Item**:FUN.008\n* **Severity**:L1\n* **Content**:存储过程无版本控制，配合业务的存储过程升级很难做到业务无感知。存储过程在拓展和移植上也存在问题。\n* **Case**:\n\n```sql\nCREATE PROCEDURE simpleproc (OUT param1 INT);\n```\n## 不建议使用自定义函数\n\n* **Item**:FUN.009\n* **Severity**:L1\n* **Content**:不建议使用自定义函数\n* **Case**:\n\n```sql\nCREATE FUNCTION hello (s CHAR(20));\n```\n## 不建议对等值查询列使用 GROUP BY\n\n* **Item**:GRP.001\n* **Severity**:L2\n* **Content**:GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询，对这样的列进行 GROUP BY 意义不大。\n* **Case**:\n\n```sql\nselect film_id, title from film where release_year='2006' group by release_year\n```\n## JOIN 语句混用逗号和 ANSI 模式\n\n* **Item**:JOI.001\n* **Severity**:L2\n* **Content**:表连接的时候混用逗号和 ANSI JOIN 不便于人类理解，并且MySQL不同版本的表连接行为和优先级均有所不同，当 MySQL 版本变化后可能会引入错误。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000\n```\n## 同一张表被连接两次\n\n* **Item**:JOI.002\n* **Severity**:L4\n* **Content**:相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\n* **Case**:\n\n```sql\nselect tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1\n```\n## OUTER JOIN 失效\n\n* **Item**:JOI.003\n* **Severity**:L4\n* **Content**:由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回，这会将查询隐式转换为 INNER JOIN 。如：select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解，因为 LEFT/RIGHT JOIN 是 LEFT/RIGHT OUTER JOIN 的缩写。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4\n```\n## 不建议使用排它 JOIN\n\n* **Item**:JOI.004\n* **Severity**:L4\n* **Content**:只在右侧表为 NULL 的带 WHERE 子句的 LEFT OUTER JOIN 语句，有可能是在WHERE子句中使用错误的列，如：“... FROM l LEFT OUTER JOIN r ON l.l = r.r WHERE r.z IS NULL”，这个查询正确的逻辑可能是 WHERE r.r IS NULL。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null\n```\n## 减少 JOIN 的数量\n\n* **Item**:JOI.005\n* **Severity**:L2\n* **Content**:太多的 JOIN 是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少 JOIN 的数量。\n* **Case**:\n\n```sql\nselect 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\n```\n## 将嵌套查询重写为 JOIN 通常会导致更高效的执行和更有效的优化\n\n* **Item**:JOI.006\n* **Severity**:L4\n* **Content**:一般来说，非嵌套子查询总是用于关联子查询，最多是来自FROM子句中的一个表，这些子查询用于 ANY, ALL 和 EXISTS 的谓词。如果可以根据查询语义决定子查询最多返回一个行，那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。\n* **Case**:\n\n```sql\nSELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 100996 AND s.q = 1 )\n```\n## 不建议使用联表删除或更新\n\n* **Item**:JOI.007\n* **Severity**:L4\n* **Content**:当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n* **Case**:\n\n```sql\nUPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano';\n```\n## 不要使用跨数据库的 JOIN 查询\n\n* **Item**:JOI.008\n* **Severity**:L4\n* **Content**:一般来说，跨数据库的 JOIN 查询意味着查询语句跨越了两个不同的子系统，这可能意味着系统耦合度过高或库表结构设计不合理。\n* **Case**:\n\n```sql\nSELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 100996 AND s.q = 1 )\n```\n## 建议使用自增列作为主键，如使用联合自增主键时请将自增键作为第一列\n\n* **Item**:KEY.001\n* **Severity**:L2\n* **Content**:建议使用自增列作为主键，如使用联合自增主键时请将自增键作为第一列\n* **Case**:\n\n```sql\ncreate table test(`id` int(11) NOT NULL PRIMARY KEY (`id`))\n```\n## 无主键或唯一键，无法在线变更表结构\n\n* **Item**:KEY.002\n* **Severity**:L4\n* **Content**:无主键或唯一键，无法在线变更表结构\n* **Case**:\n\n```sql\ncreate table test(col varchar(5000))\n```\n## 避免外键等递归关系\n\n* **Item**:KEY.003\n* **Severity**:L4\n* **Content**:存在递归关系的数据很常见，数据常会像树或者以层级方式组织。然而，创建一个外键约束来强制执行同一表中两列之间的关系，会导致笨拙的查询。树的每一层对应着另一个连接。您将需要发出递归查询，以获得节点的所有后代或所有祖先。解决方案是构造一个附加的闭包表。它记录了树中所有节点间的关系，而不仅仅是那些具有直接的父子关系。您也可以比较不同层次的数据设计：闭包表，路径枚举，嵌套集。然后根据应用程序的需要选择一个。\n* **Case**:\n\n```sql\nCREATE 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))\n```\n## 提醒：请将索引属性顺序与查询对齐\n\n* **Item**:KEY.004\n* **Severity**:L0\n* **Content**:如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\n* **Case**:\n\n```sql\ncreate index idx1 on tbl (last_name,first_name)\n```\n## 表建的索引过多\n\n* **Item**:KEY.005\n* **Severity**:L2\n* **Content**:表建的索引过多\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c(`c`));\n```\n## 主键中的列过多\n\n* **Item**:KEY.006\n* **Severity**:L4\n* **Content**:主键中的列过多\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));\n```\n## 未指定主键或主键非 int 或 bigint\n\n* **Item**:KEY.007\n* **Severity**:L4\n* **Content**:未指定主键或主键非 int 或 bigint，建议将主键设置为 int unsigned 或 bigint unsigned。\n* **Case**:\n\n```sql\nCREATE TABLE tbl (a int);\n```\n## ORDER BY 多个列但排序方向不同时可能无法使用索引\n\n* **Item**:KEY.008\n* **Severity**:L4\n* **Content**:在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\n* **Case**:\n\n```sql\nSELECT * FROM tbl ORDER BY a DESC, b ASC;\n```\n## 添加唯一索引前请注意检查数据唯一性\n\n* **Item**:KEY.009\n* **Severity**:L0\n* **Content**:请提前检查添加唯一索引列的数据唯一性，如果数据不唯一在线表结构调整时将有可能自动将重复列删除，这有可能导致数据丢失。\n* **Case**:\n\n```sql\nCREATE UNIQUE INDEX part_of_name ON customer (name(10));\n```\n## 全文索引不是银弹\n\n* **Item**:KEY.010\n* **Severity**:L0\n* **Content**:全文索引主要用于解决模糊查询的性能问题，但需要控制好查询的频率和并发度。同时注意调整 ft\\_min\\_word\\_len, ft\\_max\\_word\\_len, ngram\\_token\\_size 等参数。\n* **Case**:\n\n```sql\nCREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) ) ENGINE=InnoDB;\n```\n## SQL\\_CALC\\_FOUND\\_ROWS 效率低下\n\n* **Item**:KWR.001\n* **Severity**:L2\n* **Content**:因为 SQL\\_CALC\\_FOUND\\_ROWS 不能很好地扩展，所以可能导致性能问题; 建议业务使用其他策略来替代 SQL\\_CALC\\_FOUND\\_ROWS 提供的计数功能，比如：分页结果展示等。\n* **Case**:\n\n```sql\nselect SQL_CALC_FOUND_ROWS col from tbl where id>1000\n```\n## 不建议使用 MySQL 关键字做列名或表名\n\n* **Item**:KWR.002\n* **Severity**:L2\n* **Content**:当使用关键字做为列名或表名时程序需要对列名和表名进行转义，如果疏忽被将导致请求无法执行。\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( `select` int )\n```\n## 不建议使用复数做列名或表名\n\n* **Item**:KWR.003\n* **Severity**:L1\n* **Content**:表名应该仅仅表示表里面的实体内容，不应该表示实体数量，对应于 DO 类名也是单数形式，符合表达习惯。\n* **Case**:\n\n```sql\nCREATE TABLE tbl ( `books` int )\n```\n## 不建议使用使用多字节编码字符(中文)命名\n\n* **Item**:KWR.004\n* **Severity**:L1\n* **Content**:为库、表、列、别名命名时建议使用英文，数字，下划线等字符，不建议使用中文或其他多字节编码字符。\n* **Case**:\n\n```sql\nselect col as 列 from tb\n```\n## SQL 中包含 unicode 特殊字符\n\n* **Item**:KWR.005\n* **Severity**:L1\n* **Content**:部分 IDE 会自动在 SQL 插入肉眼不可见的 unicode 字符。如：non-break space, zero-width space 等。Linux 下可使用 \\`cat -A file.sql\\` 命令查看不可见字符。\n* **Case**:\n\n```sql\nupdate tb set status = 1 where id = 1;\n```\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item**:LCK.001\n* **Severity**:L3\n* **Content**:INSERT INTO xx SELECT 加锁粒度较大请谨慎\n* **Case**:\n\n```sql\nINSERT INTO tbl SELECT * FROM tbl2;\n```\n## 请慎用 INSERT ON DUPLICATE KEY UPDATE\n\n* **Item**:LCK.002\n* **Severity**:L3\n* **Content**:当主键为自增键时使用 INSERT ON DUPLICATE KEY UPDATE 可能会导致主键出现大量不连续快速增长，导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。\n* **Case**:\n\n```sql\nINSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;\n```\n## 用字符类型存储IP地址\n\n* **Item**:LIT.001\n* **Severity**:L2\n* **Content**:字符串字面上看起来像IP地址，但不是 INET\\_ATON() 的参数，表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。\n* **Case**:\n\n```sql\ninsert into tbl (IP,name) values('10.20.306.122','test')\n```\n## 日期/时间未使用引号括起\n\n* **Item**:LIT.002\n* **Severity**:L4\n* **Content**:诸如“WHERE col <2010-02-12”之类的查询是有效的SQL，但可能是一个错误，因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号，且引号前后不应有空格。\n* **Case**:\n\n```sql\nselect col1,col2 from tbl where time < 2018-01-10\n```\n## 一列中存储一系列相关数据的集合\n\n* **Item**:LIT.003\n* **Severity**:L3\n* **Content**:将 ID 存储为一个列表，作为 VARCHAR/TEXT 列，这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证 ID 更加困难。考虑一下，列表最多支持存放多少数据呢？将 ID 存储在一张单独的表中，代替使用多值属性，从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询，也更有效地验证ID。\n* **Case**:\n\n```sql\nselect c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'\n```\n## 请使用分号或已设定的 DELIMITER 结尾\n\n* **Item**:LIT.004\n* **Severity**:L1\n* **Content**:USE database, SHOW DATABASES 等命令也需要使用使用分号或已设定的 DELIMITER 结尾。\n* **Case**:\n\n```sql\nUSE db\n```\n## 非确定性的 GROUP BY\n\n* **Item**:RES.001\n* **Severity**:L4\n* **Content**:SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n* **Case**:\n\n```sql\nselect c1,c2,c3 from t1 where c2='foo' group by c2\n```\n## 未使用 ORDER BY 的 LIMIT 查询\n\n* **Item**:RES.002\n* **Severity**:L4\n* **Content**:没有 ORDER BY 的 LIMIT 会导致非确定性的结果，这取决于查询执行计划。\n* **Case**:\n\n```sql\nselect col1,col2 from tbl where name=xx limit 10\n```\n## UPDATE/DELETE 操作使用了 LIMIT 条件\n\n* **Item**:RES.003\n* **Severity**:L4\n* **Content**:UPDATE/DELETE 操作使用 LIMIT 条件和不添加 WHERE 条件一样危险，它可将会导致主从数据不一致或从库同步中断。\n* **Case**:\n\n```sql\nUPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;\n```\n## UPDATE/DELETE 操作指定了 ORDER BY 条件\n\n* **Item**:RES.004\n* **Severity**:L4\n* **Content**:UPDATE/DELETE 操作不要指定 ORDER BY 条件。\n* **Case**:\n\n```sql\nUPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title\n```\n## UPDATE 语句可能存在逻辑错误，导致数据损坏\n\n* **Item**:RES.005\n* **Severity**:L4\n* **Content**:在一条 UPDATE 语句中，如果要更新多个字段，字段间不能使用 AND ，而应该用逗号分隔。\n* **Case**:\n\n```sql\nupdate tbl set col = 1 and cl = 2 where col=3;\n```\n## 永远不真的比较条件\n\n* **Item**:RES.006\n* **Severity**:L4\n* **Content**:查询条件永远非真，如果该条件出现在 where 中可能导致查询无匹配到的结果。\n* **Case**:\n\n```sql\nselect * from tbl where 1 != 1;\n```\n## 永远为真的比较条件\n\n* **Item**:RES.007\n* **Severity**:L4\n* **Content**:查询条件永远为真，可能导致 WHERE 条件失效进行全表查询。\n* **Case**:\n\n```sql\nselect * from tbl where 1 = 1;\n```\n## 不建议使用LOAD DATA/SELECT ... INTO OUTFILE\n\n* **Item**:RES.008\n* **Severity**:L2\n* **Content**:SELECT INTO OUTFILE 需要授予 FILE 权限，这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度，但同时也可能导致从库同步延迟过大。\n* **Case**:\n\n```sql\nLOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;\n```\n## 不建议使用连续判断\n\n* **Item**:RES.009\n* **Severity**:L2\n* **Content**:类似这样的 SELECT \\* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误，您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。\n* **Case**:\n\n```sql\nSELECT * FROM tbl WHERE col = col = 'abc'\n```\n## 建表语句中定义为 ON UPDATE CURRENT\\_TIMESTAMP 的字段不建议包含业务逻辑\n\n* **Item**:RES.010\n* **Severity**:L2\n* **Content**:定义为 ON UPDATE CURRENT\\_TIMESTAMP 的字段在该表其他字段更新时会联动修改，如果包含业务逻辑用户可见会埋下隐患。后续如有批量修改数据却又不想修改该字段时会导致数据错误。\n* **Case**:\n\n```sql\nCREATE TABLE category (category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,\tname VARCHAR(25) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY  (category_id)\n```\n## 更新请求操作的表包含 ON UPDATE CURRENT\\_TIMESTAMP 字段\n\n* **Item**:RES.011\n* **Severity**:L2\n* **Content**:定义为 ON UPDATE CURRENT\\_TIMESTAMP 的字段在该表其他字段更新时会联动修改，请注意检查。如不想修改字段的更新时间可以使用如下方法：UPDATE category SET name='ActioN', last\\_update=last\\_update WHERE category\\_id=1\n* **Case**:\n\n```sql\nUPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1\n```\n## 请谨慎使用TRUNCATE操作\n\n* **Item**:SEC.001\n* **Severity**:L0\n* **Content**:一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl\\_name;语句。但TRUNCATE操作也并非是毫无代价的，TRUNCATE TABLE无法返回被删除的准确行数，如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO\\_INCREMENT，如果不想重置该值建议使用 DELETE FROM tbl\\_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL)，当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求，因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。\n* **Case**:\n\n```sql\nTRUNCATE TABLE tbl_name\n```\n## 不使用明文存储密码\n\n* **Item**:SEC.002\n* **Severity**:L0\n* **Content**:使用明文存储密码或者使用明文在网络上传递密码都是不安全的。如果攻击者能够截获您用来插入密码的SQL语句，他们就能直接读到密码。另外，将用户输入的字符串以明文的形式插入到纯SQL语句中，也会让攻击者发现它。如果您能够读取密码，黑客也可以。解决方案是使用单向哈希函数对原始密码进行加密编码。哈希是指将输入字符串转化成另一个新的、不可识别的字符串的函数。对密码加密表达式加点随机串来防御“字典攻击”。不要将明文密码输入到SQL查询语句中。在应用程序代码中计算哈希串，只在SQL查询中使用哈希串。\n* **Case**:\n\n```sql\ncreate table test(id int,name varchar(20) not null,password varchar(200)not null)\n```\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item**:SEC.003\n* **Severity**:L0\n* **Content**:在执行高危操作之前对数据进行备份是十分有必要的。\n* **Case**:\n\n```sql\ndelete from table where col = 'condition'\n```\n## 发现常见 SQL 注入函数\n\n* **Item**:SEC.004\n* **Severity**:L0\n* **Content**:SLEEP(), BENCHMARK(), GET\\_LOCK(), RELEASE\\_LOCK() 等函数通常出现在 SQL 注入语句中，会严重影响数据库性能。\n* **Case**:\n\n```sql\nSELECT BENCHMARK(10, RAND())\n```\n## '!=' 运算符是非标准的\n\n* **Item**:STA.001\n* **Severity**:L0\n* **Content**:\"<>\"才是标准SQL中的不等于运算符。\n* **Case**:\n\n```sql\nselect col1,col2 from tbl where type!=0\n```\n## 库名或表名点后建议不要加空格\n\n* **Item**:STA.002\n* **Severity**:L1\n* **Content**:当使用 db.table 或 table.column 格式访问表或字段时，请不要在点号后面添加空格，虽然这样语法正确。\n* **Case**:\n\n```sql\nselect col from sakila. film\n```\n## 索引起名不规范\n\n* **Item**:STA.003\n* **Severity**:L1\n* **Content**:建议普通二级索引以idx\\_为前缀，唯一索引以uk\\_为前缀。\n* **Case**:\n\n```sql\nselect col from now where type!=0\n```\n## 起名时请不要使用字母、数字和下划线之外的字符\n\n* **Item**:STA.004\n* **Severity**:L1\n* **Content**:以字母或下划线开头，名字只允许使用字母、数字和下划线。请统一大小写，不要使用驼峰命名法。不要在名字中出现连续下划线'\\_\\_'，这样很难辨认。\n* **Case**:\n\n```sql\nCREATE TABLE ` abc` (a int);\n```\n## MySQL 对子查询的优化效果不佳\n\n* **Item**:SUB.001\n* **Severity**:L4\n* **Content**:MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n* **Case**:\n\n```sql\nselect col1,col2,col3 from table1 where col2 in(select col from table2)\n```\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item**:SUB.002\n* **Severity**:L2\n* **Content**:与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n* **Case**:\n\n```sql\nselect teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id\n```\n## 考虑使用 EXISTS 而不是 DISTINCT 子查询\n\n* **Item**:SUB.003\n* **Severity**:L3\n* **Content**:DISTINCT 关键字在对元组排序后删除重复。相反，考虑使用一个带有 EXISTS 关键字的子查询，您可以避免返回整个表。\n* **Case**:\n\n```sql\nSELECT DISTINCT c.c_id, c.c_name FROM c,e WHERE e.c_id = c.c_id\n```\n## 执行计划中嵌套连接深度过深\n\n* **Item**:SUB.004\n* **Severity**:L3\n* **Content**:MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。\n* **Case**:\n\n```sql\nSELECT * from tb where id in (select id from (select id from tb))\n```\n## 子查询不支持LIMIT\n\n* **Item**:SUB.005\n* **Severity**:L8\n* **Content**:当前 MySQL 版本不支持在子查询中进行 'LIMIT & IN/ALL/ANY/SOME'。\n* **Case**:\n\n```sql\nSELECT * FROM staff WHERE name IN (SELECT NAME FROM customer ORDER BY name LIMIT 1)\n```\n## 不建议在子查询中使用函数\n\n* **Item**:SUB.006\n* **Severity**:L2\n* **Content**:MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\n* **Case**:\n\n```sql\nSELECT * FROM staff WHERE name IN (SELECT max(NAME) FROM customer)\n```\n## 外层带有 LIMIT 输出限制的 UNION 联合查询，其内层查询建议也添加 LIMIT 输出限制\n\n* **Item**:SUB.007\n* **Severity**:L2\n* **Content**:有时 MySQL 无法将限制条件从外层“下推”到内层，这会使得原本可以限制能够限制部分返回结果的条件无法应用到内层查询的优化上。比如：(SELECT \\* FROM tb1 ORDER BY name) UNION ALL (SELECT \\* FROM tb2 ORDER BY name) LIMIT 20;  MySQL 会将两个子查询的结果放在一个临时表中，然后取出 20 条结果，可以通过在两个子查询中添加 LIMIT 20 来减少临时表中的数据。(SELECT \\* FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT \\* FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;\n* **Case**:\n\n```sql\n(SELECT * FROM tb1 ORDER BY name LIMIT 20) UNION ALL (SELECT * FROM tb2 ORDER BY name LIMIT 20) LIMIT 20;\n```\n## 不建议使用分区表\n\n* **Item**:TBL.001\n* **Severity**:L4\n* **Content**:不建议使用分区表\n* **Case**:\n\n```sql\nCREATE TABLE trb3(id INT, name VARCHAR(50), purchased DATE) PARTITION BY RANGE(YEAR(purchased)) (PARTITION p0 VALUES LESS THAN (1990), PARTITION p1 VALUES LESS THAN (1995), PARTITION p2 VALUES LESS THAN (2000), PARTITION p3 VALUES LESS THAN (2005) );\n```\n## 请为表选择合适的存储引擎\n\n* **Item**:TBL.002\n* **Severity**:L4\n* **Content**:建表或修改表的存储引擎时建议使用推荐的存储引擎，如：innodb\n* **Case**:\n\n```sql\ncreate table test(`id` int(11) NOT NULL AUTO_INCREMENT)\n```\n## 以DUAL命名的表在数据库中有特殊含义\n\n* **Item**:TBL.003\n* **Severity**:L8\n* **Content**:DUAL表为虚拟表，不需要创建即可使用，也不建议服务以DUAL命名表。\n* **Case**:\n\n```sql\ncreate table dual(id int, primary key (id));\n```\n## 表的初始AUTO\\_INCREMENT值不为0\n\n* **Item**:TBL.004\n* **Severity**:L2\n* **Content**:AUTO\\_INCREMENT不为0会导致数据空洞。\n* **Case**:\n\n```sql\nCREATE TABLE tbl (a int) AUTO_INCREMENT = 10;\n```\n## 请使用推荐的字符集\n\n* **Item**:TBL.005\n* **Severity**:L4\n* **Content**:表字符集只允许设置为'utf8,utf8mb4'\n* **Case**:\n\n```sql\nCREATE TABLE tbl (a int) DEFAULT CHARSET = latin1;\n```\n## 不建议使用视图\n\n* **Item**:TBL.006\n* **Severity**:L1\n* **Content**:不建议使用视图\n* **Case**:\n\n```sql\ncreate view v_today (today) AS SELECT CURRENT_DATE;\n```\n## 不建议使用临时表\n\n* **Item**:TBL.007\n* **Severity**:L1\n* **Content**:不建议使用临时表\n* **Case**:\n\n```sql\nCREATE TEMPORARY TABLE `work` (`time` time DEFAULT NULL) ENGINE=InnoDB;\n```\n## 请使用推荐的COLLATE\n\n* **Item**:TBL.008\n* **Severity**:L4\n* **Content**:COLLATE 只允许设置为''\n* **Case**:\n\n```sql\nCREATE TABLE tbl (a int) DEFAULT COLLATE = latin1_bin;\n```\n"
  },
  {
    "path": "doc/images/logo.ascii",
    "content": ",adPPYba,  ,adPPYba,  ,adPPYYba, 8b,dPPYba,\nI8[    \"\" a8\"     \"8a \"\"     `Y8 88P'   \"Y8\n `\"Y8ba,  8b       d8 ,adPPPPP88 88        \naa    ]8I \"8a,   ,a8\" 88,    ,88 88        \n`\"YbbdP\"'  `\"YbbdP\"'  `\"8bbdP\"Y8 88        \n"
  },
  {
    "path": "doc/indexing.md",
    "content": "\n# 索引优化建议\n\n以下优化算法基于个人当前理解，能力有限，如有偏颇还请斧正。\n\n## 简单查询索引优化\n\n### 等值查询优化\n\n* 单列等值查询，为该等值列加索引\n* 多列等值查询，每列求取散粒度，按从大到小排序取前N列添加到索引（N可配置）\n\n```sql\nSELECT * FROM tbl WHERE a = 123;\nSELECT * FROM tbl WHERE a = 123 AND b = 456;\nSELECT * FROM tbl WHERE a IS NULL;\nSELECT * FROM tbl WHERE a <=> 123;\nSELECT * FROM tbl WHERE a IS TRUE;\nSELECT * FROM tbl WHERE a IS FALSE;\nSELECT * FROM tbl WHERE a IS NOT TRUE;\nSELECT * FROM tbl WHERE a IS NOT FALSE;\nSELECT * FROM tbl WHERE a IN (\"xxx\"); -- IN单值\n```\n\n### 非等值查询优化\n\n* 单列非等值查询，为该非等值列加索引\n* 多列非等值查询，每列求取散粒度，为散粒度最大的列加索引。\n\n思考：对于多列非等值，为filtered最小列加索引可能比较好。因为输入可变，所以现在只按散粒度排序。对于高版本MySQL如果开启了Index Merge，考虑为非等值列加单列索引可能会比较好。\n\n```sql\nSELECT * FROM tbl WHERE a >= 123 -- <, <=, >=, >, !=, <>\nSELECT * FROM tbl WHERE a BETWEEN 22 AND 44; -- NOT BETWEEN\nSELECT * FROM tbl WHERE a LIKE 'blah%'; -- NOT LIKE\nSELECT * FROM tbl WHERE a IS NOT NULL;\nSELECT * FROM tbl WHERE a IN (\"xxx\"); -- IN多值\n```\n\n### 等值 & 非等值组合查询优化\n\n1. 先按`等值查询优化`为等值列添加索引\n2. 再将`非等值查询优化`的列追加在等值列索引后\n\n```sql\nSELECT * FROM tbl WHERE c = 9 AND a > 12 AND b > 345; -- INDEX(c, a)或INDEX(c, b)\n```\n\n### OR操作符\n\n如果使用了OR操作符，即使OR两边是简单的查询条件也会对优化器带来很大的困难。一般对OR的优化需要依赖UNION ALL或Index Merge等多索引访问技术来实现。SOAR目前不会对使用OR操作符连接的字段进行索引优化。\n\n### GROUP BY子句\n\nGROUP BY相关字段能否加入索引列表需要依赖WHERE子句中的条件。当查询指定了WHERE条件，在满足WHERE子句只有等值查询时，可以对GROUP BY字段添加索引。当查询未指定WHERE条件，可以直接对GROUP BY字段添加索引。\n\n* 按照GROPU BY的先后顺序添加索引\n* GROUP BY字段出现常量，数学运算或函数运算时会给出警告\n\n### ORDER BY子句\n\nORDER BY相关字段能否加入索引列表需要依赖WHERE子句和GROUP BY子句中的条件。当查询指定了WHERE条件，在满足WHERE子句只有等值查询且无GROUP BY子句时，可以对ORDER BY字段添加索引。当查询未指定WHERE条件，在满足无GROUP BY子句时，可以对ORDER BY字段添加索引。\n\n* 多个字段之间如果指定顺序相同，按照ORDER BY的先后顺序添加索引\n* 多个字段之间如果指定顺序不同，所有ORDER BY字段都不添加索引\n* ORDER BY字段出现常量，数学运算或函数运算时会给出警告\n\n## 复杂查询索引优化\n\n### JOIN索引优化算法\n\n* LEFT JOIN为右表加索引\n* RIGHT JOIN为左表加索引\n* INNER JOIN两张表都加索引\n* NATURAL的处理方法参考前三条\n* STRAIGHT_JOIN为后面的表加索引\n\n### SUBQUERY和UNION的复杂查询\n\n对于使用了IN，EXIST等词的SUBQUERY或UNION类型的SQL，先将其拆成多条独立的SELECT语句。然后基于上面简单查询索引优化算法，对单条SELECT查询进行优化。SUBQUERY的连接列暂不考虑添加索引。\n\n\n```sql\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);\n\n1. SELECT * FROM film;\n2. SELECT language_id FROM language LIMIT 1;\n```\n\n```sql\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id\nUNION\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n\n1. SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;\n2. SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;\n```\n\n## 无法使用索引的情况\n\n如下类型的查询条件无法使用索引或SOAR无法给出正确的索引建议。\n\n```sql\n-- MySQL无法使用索引\nSELECT * FROM tbl WHERE a LIKE '%blah%';\nSELECT * FROM tbl WHERE a IN (SELECT...)\nSELECT * FROM tbl WHERE DATE(dt) = 'xxx'\nSELECT * FROM tbl WHERE LOWER(s) = 'xxx'\nSELECT * FROM tbl WHERE CAST(s …) = 'xxx'\nSELECT * FROM tbl where a NOT IN()\n-- SOAR不支持的索引建议\nSELECT * FROM tbl WHERE a = 'xxx' COLLATE xxx -- vitess语法暂不支持\nSELECT * FROM tbl ORDER BY a ASC, b DESC -- 8.0+支持\nSELECT * FROM tbl WHERE `date` LIKE '2016-12%' -- 时间数据类型隐式类型转换\n```\n\n## 索引长度限制\n\n由于索引长度受数据库版本及不同配置参数影响，参考[InnoDB限制](https://dev.mysql.com/doc/refman/8.0/en/innodb-restrictions.html)。这里将索引长度限制定义为可配置值，用户可以根据实际情况进行设置。\n\n* 通过-max-index-bytes配置每列索引最大长度，默认为767 Bytes\n* 超过单列索引最大长度限制后程序会自动添加该列的前缀索引（max-index-bytes/CHARSET_Maxlen）\n* 通过-max-index-bytes-percolumn配置多列索引加各最大长度，默认为3072 Bytes\n* 超过多列索引最大长度限制后，由程序生成的ALTER语句会将每列前缀索引长度指定为N，用户自行调整\n\n```sql\nALTER TABLE `sakila`.`film_text` add index `idx_description` (`description`(255)) ;\n\n```\n\n## 更新语句转换为只读查询\n\nSOAR支持将DELETE， UPDATE， INSERT， REPLACE四种类型语句转换为SELECT查询。对转换后的SELECT查询进行索引优化。以下为转换示例。\n\n```sql\nUPDATE film SET length = 10 WHERE language_id = 20;\n\nSELECT * FROM film WHERE language_id = 20;\n```\n\n```sql\nDELETE FROM film WHERE length > 100;\n\nSELECT * FROM film WHERE length > 100;\n```\n\n```sql\nINSERT INTO city (country_id) SELECT country_id FROM country;\n\nSELECT country_id FROM country;\n```\n\n```sql\nREPLACE INTO city (country_id) SELECT country_id FROM country;\n\nSELECT country_id FROM country;\n```\n\n## 散粒度计算\n\n### 计算公式\n\n`Cardinality = ColumnDistinctCount/TableTotalRows * 100%`\n\n由于直接对线上表进行COUNT(DISTINCT)操作会影响数据库请求执行效率，因此默认各列的散粒度均为1。用户可以通过指定`-sampling`参数开启数据采样。SOAR会将线上数据随机采样至测试环境求取散粒度。\n\n### 数据采样算法\n\n以下说明摘抄自PostgreSQL数据直方图采样算法。默认k(-sampling-statistic-target)设置为100，即最多采样3万行记录。\n\n```text\n The following choice of minrows is based on the paper\n \"Random sampling for histogram construction: how much is enough?\"\n by Surajit Chaudhuri, Rajeev Motwani and Vivek Narasayya, in\n Proceedings of ACM SIGMOD International Conference on Management\n of Data, 1998, Pages 436-447.  Their Corollary 1 to Theorem 5\n says that for table size n, histogram size k, maximum relative\n error in bin size f, and error probability gamma, the minimum\n random sample size is\n      r = 4 * k * ln(2*n/gamma) / f^2\n Taking f = 0.5, gamma = 0.01, n = 10^6 rows, we obtain\n      r = 305.82 * k\n Note that because of the log function, the dependence on n is\n quite weak; even at n = 10^12, a 300*k sample gives <= 0.66\n bin size error with probability 0.99.  So there's no real need to\n scale for n, which is a good thing because we don't necessarily\n know it at this point.\n```\n\n### 随机采样\n\n随机采样使用的SQL如下，其中变量`r`, `n`的含义见上面的说明。\n\n```sql\nSELECT * FROM `tbl` WHERE RAND() < r LIMIT n;\n```\n\n## 索引去重\n\n### 检查步骤\n1. 为查询语句可能使用索引的字段添加索引\n2. 枚举用到的所有库表的已知索引\n3. 判断所有新加的索引是否与已知索引重复\n4. 判断所有新加的索引之间是否存在索引重复\n\n\n### 检查规则\n\n* PRIMARY > UNIQUE > KEY\n* 索引名称相同，即: idxA == idxA\n* (a, b) > (a)\n* (a, b), (b, a) 会给出警告，用户自行判断是否重复\n\n## 不足\n\n* 目前只支持针对InnoDB引擎添加索引建议，不支持FULLTEXT, SPATIAL等其他类型索引\n* 暂不支持索引覆盖（Covering）\n* 暂不支持Index Merge情况下的索引建议\n"
  },
  {
    "path": "doc/install.md",
    "content": "## 下载二进制安装包\n\n```bash\nwget https://github.com/XiaoMi/soar/releases/download/${tag}/soar.${OS}-amd64 -O soar\nchmod a+x soar\n如：\nwget https://github.com/XiaoMi/soar/releases/download/0.9.0/soar.linux-amd64 -O soar\nchmod a+x soar\n```\n\n## 源码安装\n\n### 依赖软件\n\n一般依赖\n\n* Go 1.12+\n* git\n\n高级依赖（仅面向开发人员）\n\n* [mysql](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) 客户端版本需要与容器中MySQL版本相同，避免出现由于认证原因导致无法连接问题\n* [docker](https://docs.docker.com/engine/reference/commandline/cli/) MySQL Server测试容器管理\n* [govendor](https://github.com/kardianos/govendor) Go包管理\n* [retool](https://github.com/twitchtv/retool) 依赖外部代码质量静态检查工具二进制文件管理\n\n### 生成二进制文件\n\n```bash\ngo get -d github.com/XiaoMi/soar\ncd ${GOPATH}/src/github.com/XiaoMi/soar && make\n```\n\n### 开发调试\n\n如下指令如果您没有精力参与SOAR的开发可以跳过。\n\n* make deps 依赖检查\n* make vitess 升级Vitess Parser依赖\n* make tidb 升级TiDB Parser依赖\n* make fmt 代码格式化，统一风格\n* make lint 代码质量检查\n* make docker 启动一个MySQL测试容器，可用于测试依赖元数据检查的功能或不同版本MySQL差异\n* make test 运行所有的测试用例\n* make cover 代码测试覆盖度检查\n* make doc 自动生成命令行参数中-list-XX相关文档\n* make daily 每日构建，时刻跟进Vitess, TiDB依赖变化\n* make release 生成Linux, Windows, Mac发布版本\n\n## 安装验证\n\n```bash\necho 'select * from film' | ./soar\n```\n"
  },
  {
    "path": "doc/install_en.md",
    "content": "## Get Released Binary\n\n```bash\nwget https://github.com/XiaoMi/soar/releases/download/${tag}/soar.${OS}-amd64 -O soar\nchmod a+x soar\neg.\nwget https://github.com/XiaoMi/soar/releases/download/0.9.0/soar.linux-amd64 -O soar\nchmod a+x soar\n```\n\n## Build From Source\n\n```bash\ngo get -d github.com/XiaoMi/soar\ncd ${GOPATH}/src/github.com/XiaoMi/soar && make\n```\n\n## Simple Test Case\n\n```bash\necho 'select * from film' | ./soar\n```\n"
  },
  {
    "path": "doc/js/pretty.js",
    "content": "! function(e, E) {\n    \"object\" == typeof exports && \"object\" == typeof module ? module.exports = E() : \"function\" == typeof define && define.amd ? define([], E) : \"object\" == typeof exports ? exports.sqlFormatter = E() : e.sqlFormatter = E()\n}(this, function() {\n    return function(e) {\n        function E(n) {\n            if (t[n]) return t[n].exports;\n            var r = t[n] = {\n                exports: {},\n                id: n,\n                loaded: !1\n            };\n            return e[n].call(r.exports, r, r.exports, E), r.loaded = !0, r.exports\n        }\n        var t = {};\n        return E.m = e, E.c = t, E.p = \"\", E(0)\n    }([function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(18),\n            T = n(r),\n            R = t(19),\n            o = n(R),\n            N = t(20),\n            A = n(N),\n            I = t(21),\n            O = n(I);\n        E[\"default\"] = {\n            format: function(e, E) {\n                switch (E = E || {}, E.language) {\n                    case \"db2\":\n                        return new T[\"default\"](E).format(e);\n                    case \"n1ql\":\n                        return new o[\"default\"](E).format(e);\n                    case \"pl/sql\":\n                        return new A[\"default\"](E).format(e);\n                    case \"sql\":\n                    case void 0:\n                        return new O[\"default\"](E).format(e);\n                    default:\n                        throw Error(\"Unsupported SQL dialect: \" + E.language)\n                }\n            }\n        }, e.exports = E[\"default\"]\n    }, function(e, E) {\n        \"use strict\";\n        E.__esModule = !0, E[\"default\"] = function(e, E) {\n            if (!(e instanceof E)) throw new TypeError(\"Cannot call a class as a function\")\n        }\n    }, function(e, E, t) {\n        var n = t(39),\n            r = \"object\" == typeof self && self && self.Object === Object && self,\n            T = n || r || Function(\"return this\")();\n        e.exports = T\n    }, function(e, E, t) {\n        function n(e, E) {\n            var t = T(e, E);\n            return r(t) ? t : void 0\n        }\n        var r = t(33),\n            T = t(41);\n        e.exports = n\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = t(66),\n            o = n(R),\n            N = t(7),\n            A = n(N),\n            I = t(15),\n            O = n(I),\n            i = t(16),\n            S = n(i),\n            u = t(17),\n            L = n(u),\n            C = function() {\n                function e(E, t) {\n                    (0, T[\"default\"])(this, e), this.cfg = E || {}, this.indentation = new O[\"default\"](this.cfg.indent), this.inlineBlock = new S[\"default\"], this.params = new L[\"default\"](this.cfg.params), this.tokenizer = t, this.previousReservedWord = {}\n                }\n                return e.prototype.format = function(e) {\n                    var E = this.tokenizer.tokenize(e),\n                        t = this.getFormattedQueryFromTokens(E);\n                    return t.trim()\n                }, e.prototype.getFormattedQueryFromTokens = function(e) {\n                    var E = this,\n                        t = \"\";\n                    return e.forEach(function(n, r) {\n                        n.type !== A[\"default\"].WHITESPACE && (n.type === A[\"default\"].LINE_COMMENT ? t = E.formatLineComment(n, t) : n.type === A[\"default\"].BLOCK_COMMENT ? t = E.formatBlockComment(n, t) : n.type === A[\"default\"].RESERVED_TOPLEVEL ? (t = E.formatToplevelReservedWord(n, t), E.previousReservedWord = n) : n.type === A[\"default\"].RESERVED_NEWLINE ? (t = E.formatNewlineReservedWord(n, t), E.previousReservedWord = n) : n.type === A[\"default\"].RESERVED ? (t = E.formatWithSpaces(n, t), E.previousReservedWord = n) : t = n.type === A[\"default\"].OPEN_PAREN ? E.formatOpeningParentheses(e, r, t) : n.type === A[\"default\"].CLOSE_PAREN ? E.formatClosingParentheses(n, t) : n.type === A[\"default\"].PLACEHOLDER ? E.formatPlaceholder(n, t) : \",\" === n.value ? E.formatComma(n, t) : \":\" === n.value ? E.formatWithSpaceAfter(n, t) : \".\" === n.value || \";\" === n.value ? E.formatWithoutSpaces(n, t) : E.formatWithSpaces(n, t))\n                    }), t\n                }, e.prototype.formatLineComment = function(e, E) {\n                    return this.addNewline(E + e.value)\n                }, e.prototype.formatBlockComment = function(e, E) {\n                    return this.addNewline(this.addNewline(E) + this.indentComment(e.value))\n                }, e.prototype.indentComment = function(e) {\n                    return e.replace(/\\n/g, \"\\n\" + this.indentation.getIndent())\n                }, e.prototype.formatToplevelReservedWord = function(e, E) {\n                    return this.indentation.decreaseTopLevel(), E = this.addNewline(E), this.indentation.increaseToplevel(), E += this.equalizeWhitespace(e.value), this.addNewline(E)\n                }, e.prototype.formatNewlineReservedWord = function(e, E) {\n                    return this.addNewline(E) + this.equalizeWhitespace(e.value) + \" \"\n                }, e.prototype.equalizeWhitespace = function(e) {\n                    return e.replace(/\\s+/g, \" \")\n                }, e.prototype.formatOpeningParentheses = function(e, E, t) {\n                    var n = e[E - 1];\n                    return n && n.type !== A[\"default\"].WHITESPACE && n.type !== A[\"default\"].OPEN_PAREN && (t = (0, o[\"default\"])(t)), t += e[E].value, this.inlineBlock.beginIfPossible(e, E), this.inlineBlock.isActive() || (this.indentation.increaseBlockLevel(), t = this.addNewline(t)), t\n                }, e.prototype.formatClosingParentheses = function(e, E) {\n                    return this.inlineBlock.isActive() ? (this.inlineBlock.end(), this.formatWithSpaceAfter(e, E)) : (this.indentation.decreaseBlockLevel(), this.formatWithSpaces(e, this.addNewline(E)))\n                }, e.prototype.formatPlaceholder = function(e, E) {\n                    return E + this.params.get(e) + \" \"\n                }, e.prototype.formatComma = function(e, E) {\n                    return E = (0, o[\"default\"])(E) + e.value + \" \", this.inlineBlock.isActive() ? E : /^LIMIT$/i.test(this.previousReservedWord.value) ? E : this.addNewline(E)\n                }, e.prototype.formatWithSpaceAfter = function(e, E) {\n                    return (0, o[\"default\"])(E) + e.value + \" \"\n                }, e.prototype.formatWithoutSpaces = function(e, E) {\n                    return (0, o[\"default\"])(E) + e.value\n                }, e.prototype.formatWithSpaces = function(e, E) {\n                    return E + e.value + \" \"\n                }, e.prototype.addNewline = function(e) {\n                    return (0, o[\"default\"])(e) + \"\\n\" + this.indentation.getIndent()\n                }, e\n            }();\n        E[\"default\"] = C, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = t(58),\n            o = n(R),\n            N = t(53),\n            A = n(N),\n            I = t(7),\n            O = n(I),\n            i = function() {\n                function e(E) {\n                    (0, T[\"default\"])(this, e), this.WHITESPACE_REGEX = /^(\\s+)/, this.NUMBER_REGEX = /^((-\\s*)?[0-9]+(\\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)\\b/, this.OPERATOR_REGEX = /^(!=|<>|==|<=|>=|!<|!>|\\|\\||::|->>|->|~~\\*|~~|!~~\\*|!~~|~\\*|!~\\*|!~|.)/, this.BLOCK_COMMENT_REGEX = /^(\\/\\*[^]*?(?:\\*\\/|$))/, this.LINE_COMMENT_REGEX = this.createLineCommentRegex(E.lineCommentTypes), this.RESERVED_TOPLEVEL_REGEX = this.createReservedWordRegex(E.reservedToplevelWords), this.RESERVED_NEWLINE_REGEX = this.createReservedWordRegex(E.reservedNewlineWords), this.RESERVED_PLAIN_REGEX = this.createReservedWordRegex(E.reservedWords), this.WORD_REGEX = this.createWordRegex(E.specialWordChars), this.STRING_REGEX = this.createStringRegex(E.stringTypes), this.OPEN_PAREN_REGEX = this.createParenRegex(E.openParens), this.CLOSE_PAREN_REGEX = this.createParenRegex(E.closeParens), this.INDEXED_PLACEHOLDER_REGEX = this.createPlaceholderRegex(E.indexedPlaceholderTypes, \"[0-9]*\"), this.IDENT_NAMED_PLACEHOLDER_REGEX = this.createPlaceholderRegex(E.namedPlaceholderTypes, \"[a-zA-Z0-9._$]+\"), this.STRING_NAMED_PLACEHOLDER_REGEX = this.createPlaceholderRegex(E.namedPlaceholderTypes, this.createStringPattern(E.stringTypes))\n                }\n                return e.prototype.createLineCommentRegex = function(e) {\n                    return RegExp(\"^((?:\" + e.map(function(e) {\n                        return (0, A[\"default\"])(e)\n                    }).join(\"|\") + \").*?(?:\\n|$))\")\n                }, e.prototype.createReservedWordRegex = function(e) {\n                    var E = e.join(\"|\").replace(/ /g, \"\\\\s+\");\n                    return RegExp(\"^(\" + E + \")\\\\b\", \"i\")\n                }, e.prototype.createWordRegex = function() {\n                    var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : [];\n                    return RegExp(\"^([\\\\w\" + e.join(\"\") + \"]+)\")\n                }, e.prototype.createStringRegex = function(e) {\n                    return RegExp(\"^(\" + this.createStringPattern(e) + \")\")\n                }, e.prototype.createStringPattern = function(e) {\n                    var E = {\n                        \"``\": \"((`[^`]*($|`))+)\",\n                        \"[]\": \"((\\\\[[^\\\\]]*($|\\\\]))(\\\\][^\\\\]]*($|\\\\]))*)\",\n                        '\"\"': '((\"[^\"\\\\\\\\]*(?:\\\\\\\\.[^\"\\\\\\\\]*)*(\"|$))+)',\n                        \"''\": \"(('[^'\\\\\\\\]*(?:\\\\\\\\.[^'\\\\\\\\]*)*('|$))+)\",\n                        \"N''\": \"((N'[^N'\\\\\\\\]*(?:\\\\\\\\.[^N'\\\\\\\\]*)*('|$))+)\"\n                    };\n                    return e.map(function(e) {\n                        return E[e]\n                    }).join(\"|\")\n                }, e.prototype.createParenRegex = function(e) {\n                    var E = this;\n                    return RegExp(\"^(\" + e.map(function(e) {\n                        return E.escapeParen(e)\n                    }).join(\"|\") + \")\", \"i\")\n                }, e.prototype.escapeParen = function(e) {\n                    return 1 === e.length ? (0, A[\"default\"])(e) : \"\\\\b\" + e + \"\\\\b\"\n                }, e.prototype.createPlaceholderRegex = function(e, E) {\n                    if ((0, o[\"default\"])(e)) return !1;\n                    var t = e.map(A[\"default\"]).join(\"|\");\n                    return RegExp(\"^((?:\" + t + \")(?:\" + E + \"))\")\n                }, e.prototype.tokenize = function(e) {\n                    for (var E = [], t = void 0; e.length;) t = this.getNextToken(e, t), e = e.substring(t.value.length), E.push(t);\n                    return E\n                }, e.prototype.getNextToken = function(e, E) {\n                    return this.getWhitespaceToken(e) || this.getCommentToken(e) || this.getStringToken(e) || this.getOpenParenToken(e) || this.getCloseParenToken(e) || this.getPlaceholderToken(e) || this.getNumberToken(e) || this.getReservedWordToken(e, E) || this.getWordToken(e) || this.getOperatorToken(e)\n                }, e.prototype.getWhitespaceToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].WHITESPACE,\n                        regex: this.WHITESPACE_REGEX\n                    })\n                }, e.prototype.getCommentToken = function(e) {\n                    return this.getLineCommentToken(e) || this.getBlockCommentToken(e)\n                }, e.prototype.getLineCommentToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].LINE_COMMENT,\n                        regex: this.LINE_COMMENT_REGEX\n                    })\n                }, e.prototype.getBlockCommentToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].BLOCK_COMMENT,\n                        regex: this.BLOCK_COMMENT_REGEX\n                    })\n                }, e.prototype.getStringToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].STRING,\n                        regex: this.STRING_REGEX\n                    })\n                }, e.prototype.getOpenParenToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].OPEN_PAREN,\n                        regex: this.OPEN_PAREN_REGEX\n                    })\n                }, e.prototype.getCloseParenToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].CLOSE_PAREN,\n                        regex: this.CLOSE_PAREN_REGEX\n                    })\n                }, e.prototype.getPlaceholderToken = function(e) {\n                    return this.getIdentNamedPlaceholderToken(e) || this.getStringNamedPlaceholderToken(e) || this.getIndexedPlaceholderToken(e)\n                }, e.prototype.getIdentNamedPlaceholderToken = function(e) {\n                    return this.getPlaceholderTokenWithKey({\n                        input: e,\n                        regex: this.IDENT_NAMED_PLACEHOLDER_REGEX,\n                        parseKey: function(e) {\n                            return e.slice(1)\n                        }\n                    })\n                }, e.prototype.getStringNamedPlaceholderToken = function(e) {\n                    var E = this;\n                    return this.getPlaceholderTokenWithKey({\n                        input: e,\n                        regex: this.STRING_NAMED_PLACEHOLDER_REGEX,\n                        parseKey: function(e) {\n                            return E.getEscapedPlaceholderKey({\n                                key: e.slice(2, -1),\n                                quoteChar: e.slice(-1)\n                            })\n                        }\n                    })\n                }, e.prototype.getIndexedPlaceholderToken = function(e) {\n                    return this.getPlaceholderTokenWithKey({\n                        input: e,\n                        regex: this.INDEXED_PLACEHOLDER_REGEX,\n                        parseKey: function(e) {\n                            return e.slice(1)\n                        }\n                    })\n                }, e.prototype.getPlaceholderTokenWithKey = function(e) {\n                    var E = e.input,\n                        t = e.regex,\n                        n = e.parseKey,\n                        r = this.getTokenOnFirstMatch({\n                            input: E,\n                            regex: t,\n                            type: O[\"default\"].PLACEHOLDER\n                        });\n                    return r && (r.key = n(r.value)), r\n                }, e.prototype.getEscapedPlaceholderKey = function(e) {\n                    var E = e.key,\n                        t = e.quoteChar;\n                    return E.replace(RegExp((0, A[\"default\"])(\"\\\\\") + t, \"g\"), t)\n                }, e.prototype.getNumberToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].NUMBER,\n                        regex: this.NUMBER_REGEX\n                    })\n                }, e.prototype.getOperatorToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].OPERATOR,\n                        regex: this.OPERATOR_REGEX\n                    })\n                }, e.prototype.getReservedWordToken = function(e, E) {\n                    if (!E || !E.value || \".\" !== E.value) return this.getToplevelReservedToken(e) || this.getNewlineReservedToken(e) || this.getPlainReservedToken(e)\n                }, e.prototype.getToplevelReservedToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].RESERVED_TOPLEVEL,\n                        regex: this.RESERVED_TOPLEVEL_REGEX\n                    })\n                }, e.prototype.getNewlineReservedToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].RESERVED_NEWLINE,\n                        regex: this.RESERVED_NEWLINE_REGEX\n                    })\n                }, e.prototype.getPlainReservedToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].RESERVED,\n                        regex: this.RESERVED_PLAIN_REGEX\n                    })\n                }, e.prototype.getWordToken = function(e) {\n                    return this.getTokenOnFirstMatch({\n                        input: e,\n                        type: O[\"default\"].WORD,\n                        regex: this.WORD_REGEX\n                    })\n                }, e.prototype.getTokenOnFirstMatch = function(e) {\n                    var E = e.input,\n                        t = e.type,\n                        n = e.regex,\n                        r = E.match(n);\n                    if (r) return {\n                        type: t,\n                        value: r[1]\n                    }\n                }, e\n            }();\n        E[\"default\"] = i, e.exports = E[\"default\"]\n    }, function(e, E) {\n        function t(e) {\n            var E = typeof e;\n            return null != e && (\"object\" == E || \"function\" == E)\n        }\n        e.exports = t\n    }, function(e, E) {\n        \"use strict\";\n        E.__esModule = !0, E[\"default\"] = {\n            WHITESPACE: \"whitespace\",\n            WORD: \"word\",\n            STRING: \"string\",\n            RESERVED: \"reserved\",\n            RESERVED_TOPLEVEL: \"reserved-toplevel\",\n            RESERVED_NEWLINE: \"reserved-newline\",\n            OPERATOR: \"operator\",\n            OPEN_PAREN: \"open-paren\",\n            CLOSE_PAREN: \"close-paren\",\n            LINE_COMMENT: \"line-comment\",\n            BLOCK_COMMENT: \"block-comment\",\n            NUMBER: \"number\",\n            PLACEHOLDER: \"placeholder\"\n        }, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        function n(e) {\n            return null != e && T(e.length) && !r(e)\n        }\n        var r = t(12),\n            T = t(59);\n        e.exports = n\n    }, function(e, E, t) {\n        function n(e) {\n            return null == e ? \"\" : r(e)\n        }\n        var r = t(10);\n        e.exports = n\n    }, function(e, E, t) {\n        function n(e) {\n            if (\"string\" == typeof e) return e;\n            if (T(e)) return N ? N.call(e) : \"\";\n            var E = e + \"\";\n            return \"0\" == E && 1 / e == -R ? \"-0\" : E\n        }\n        var r = t(26),\n            T = t(14),\n            R = 1 / 0,\n            o = r ? r.prototype : void 0,\n            N = o ? o.toString : void 0;\n        e.exports = n\n    }, function(e, E) {\n        function t(e) {\n            if (null != e) {\n                try {\n                    return r.call(e)\n                } catch (E) {}\n                try {\n                    return e + \"\"\n                } catch (E) {}\n            }\n            return \"\"\n        }\n        var n = Function.prototype,\n            r = n.toString;\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e) {\n            var E = r(e) ? N.call(e) : \"\";\n            return E == T || E == R\n        }\n        var r = t(6),\n            T = \"[object Function]\",\n            R = \"[object GeneratorFunction]\",\n            o = Object.prototype,\n            N = o.toString;\n        e.exports = n\n    }, function(e, E) {\n        function t(e) {\n            return null != e && \"object\" == typeof e\n        }\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e) {\n            return \"symbol\" == typeof e || r(e) && o.call(e) == T\n        }\n        var r = t(13),\n            T = \"[object Symbol]\",\n            R = Object.prototype,\n            o = R.toString;\n        e.exports = n\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = t(61),\n            o = n(R),\n            N = t(60),\n            A = n(N),\n            I = \"top-level\",\n            O = \"block-level\",\n            i = function() {\n                function e(E) {\n                    (0, T[\"default\"])(this, e), this.indent = E || \"  \", this.indentTypes = []\n                }\n                return e.prototype.getIndent = function() {\n                    return (0, o[\"default\"])(this.indent, this.indentTypes.length)\n                }, e.prototype.increaseToplevel = function() {\n                    this.indentTypes.push(I)\n                }, e.prototype.increaseBlockLevel = function() {\n                    this.indentTypes.push(O)\n                }, e.prototype.decreaseTopLevel = function() {\n                    (0, A[\"default\"])(this.indentTypes) === I && this.indentTypes.pop()\n                }, e.prototype.decreaseBlockLevel = function() {\n                    for (; this.indentTypes.length > 0;) {\n                        var e = this.indentTypes.pop();\n                        if (e !== I) break\n                    }\n                }, e\n            }();\n        E[\"default\"] = i, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = t(7),\n            o = n(R),\n            N = 50,\n            A = function() {\n                function e() {\n                    (0, T[\"default\"])(this, e), this.level = 0\n                }\n                return e.prototype.beginIfPossible = function(e, E) {\n                    0 === this.level && this.isInlineBlock(e, E) ? this.level = 1 : this.level > 0 ? this.level++ : this.level = 0\n                }, e.prototype.end = function() {\n                    this.level--\n                }, e.prototype.isActive = function() {\n                    return this.level > 0\n                }, e.prototype.isInlineBlock = function(e, E) {\n                    for (var t = 0, n = 0, r = E; e.length > r; r++) {\n                        var T = e[r];\n                        if (t += T.value.length, t > N) return !1;\n                        if (T.type === o[\"default\"].OPEN_PAREN) n++;\n                        else if (T.type === o[\"default\"].CLOSE_PAREN && (n--, 0 === n)) return !0;\n                        if (this.isForbiddenToken(T)) return !1\n                    }\n                    return !1\n                }, e.prototype.isForbiddenToken = function(e) {\n                    var E = e.type,\n                        t = e.value;\n                    return E === o[\"default\"].RESERVED_TOPLEVEL || E === o[\"default\"].RESERVED_NEWLINE || E === o[\"default\"].COMMENT || E === o[\"default\"].BLOCK_COMMENT || \";\" === t\n                }, e\n            }();\n        E[\"default\"] = A, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = function() {\n                function e(E) {\n                    (0, T[\"default\"])(this, e), this.params = E, this.index = 0\n                }\n                return e.prototype.get = function(e) {\n                    var E = e.key,\n                        t = e.value;\n                    return this.params ? E ? this.params[E] : this.params[this.index++] : t\n                }, e\n            }();\n        E[\"default\"] = R, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = t(4),\n            o = n(R),\n            N = t(5),\n            A = n(N),\n            I = [\"ABS\", \"ACTIVATE\", \"ALIAS\", \"ALL\", \"ALLOCATE\", \"ALLOW\", \"ALTER\", \"ANY\", \"ARE\", \"ARRAY\", \"AS\", \"ASC\", \"ASENSITIVE\", \"ASSOCIATE\", \"ASUTIME\", \"ASYMMETRIC\", \"AT\", \"ATOMIC\", \"ATTRIBUTES\", \"AUDIT\", \"AUTHORIZATION\", \"AUX\", \"AUXILIARY\", \"AVG\", \"BEFORE\", \"BEGIN\", \"BETWEEN\", \"BIGINT\", \"BINARY\", \"BLOB\", \"BOOLEAN\", \"BOTH\", \"BUFFERPOOL\", \"BY\", \"CACHE\", \"CALL\", \"CALLED\", \"CAPTURE\", \"CARDINALITY\", \"CASCADED\", \"CASE\", \"CAST\", \"CCSID\", \"CEIL\", \"CEILING\", \"CHAR\", \"CHARACTER\", \"CHARACTER_LENGTH\", \"CHAR_LENGTH\", \"CHECK\", \"CLOB\", \"CLONE\", \"CLOSE\", \"CLUSTER\", \"COALESCE\", \"COLLATE\", \"COLLECT\", \"COLLECTION\", \"COLLID\", \"COLUMN\", \"COMMENT\", \"COMMIT\", \"CONCAT\", \"CONDITION\", \"CONNECT\", \"CONNECTION\", \"CONSTRAINT\", \"CONTAINS\", \"CONTINUE\", \"CONVERT\", \"CORR\", \"CORRESPONDING\", \"COUNT\", \"COUNT_BIG\", \"COVAR_POP\", \"COVAR_SAMP\", \"CREATE\", \"CROSS\", \"CUBE\", \"CUME_DIST\", \"CURRENT\", \"CURRENT_DATE\", \"CURRENT_DEFAULT_TRANSFORM_GROUP\", \"CURRENT_LC_CTYPE\", \"CURRENT_PATH\", \"CURRENT_ROLE\", \"CURRENT_SCHEMA\", \"CURRENT_SERVER\", \"CURRENT_TIME\", \"CURRENT_TIMESTAMP\", \"CURRENT_TIMEZONE\", \"CURRENT_TRANSFORM_GROUP_FOR_TYPE\", \"CURRENT_USER\", \"CURSOR\", \"CYCLE\", \"DATA\", \"DATABASE\", \"DATAPARTITIONNAME\", \"DATAPARTITIONNUM\", \"DATE\", \"DAY\", \"DAYS\", \"DB2GENERAL\", \"DB2GENRL\", \"DB2SQL\", \"DBINFO\", \"DBPARTITIONNAME\", \"DBPARTITIONNUM\", \"DEALLOCATE\", \"DEC\", \"DECIMAL\", \"DECLARE\", \"DEFAULT\", \"DEFAULTS\", \"DEFINITION\", \"DELETE\", \"DENSERANK\", \"DENSE_RANK\", \"DEREF\", \"DESCRIBE\", \"DESCRIPTOR\", \"DETERMINISTIC\", \"DIAGNOSTICS\", \"DISABLE\", \"DISALLOW\", \"DISCONNECT\", \"DISTINCT\", \"DO\", \"DOCUMENT\", \"DOUBLE\", \"DROP\", \"DSSIZE\", \"DYNAMIC\", \"EACH\", \"EDITPROC\", \"ELEMENT\", \"ELSE\", \"ELSEIF\", \"ENABLE\", \"ENCODING\", \"ENCRYPTION\", \"END\", \"END-EXEC\", \"ENDING\", \"ERASE\", \"ESCAPE\", \"EVERY\", \"EXCEPTION\", \"EXCLUDING\", \"EXCLUSIVE\", \"EXEC\", \"EXECUTE\", \"EXISTS\", \"EXIT\", \"EXP\", \"EXPLAIN\", \"EXTENDED\", \"EXTERNAL\", \"EXTRACT\", \"FALSE\", \"FENCED\", \"FETCH\", \"FIELDPROC\", \"FILE\", \"FILTER\", \"FINAL\", \"FIRST\", \"FLOAT\", \"FLOOR\", \"FOR\", \"FOREIGN\", \"FREE\", \"FULL\", \"FUNCTION\", \"FUSION\", \"GENERAL\", \"GENERATED\", \"GET\", \"GLOBAL\", \"GOTO\", \"GRANT\", \"GRAPHIC\", \"GROUP\", \"GROUPING\", \"HANDLER\", \"HASH\", \"HASHED_VALUE\", \"HINT\", \"HOLD\", \"HOUR\", \"HOURS\", \"IDENTITY\", \"IF\", \"IMMEDIATE\", \"IN\", \"INCLUDING\", \"INCLUSIVE\", \"INCREMENT\", \"INDEX\", \"INDICATOR\", \"INDICATORS\", \"INF\", \"INFINITY\", \"INHERIT\", \"INNER\", \"INOUT\", \"INSENSITIVE\", \"INSERT\", \"INT\", \"INTEGER\", \"INTEGRITY\", \"INTERSECTION\", \"INTERVAL\", \"INTO\", \"IS\", \"ISOBID\", \"ISOLATION\", \"ITERATE\", \"JAR\", \"JAVA\", \"KEEP\", \"KEY\", \"LABEL\", \"LANGUAGE\", \"LARGE\", \"LATERAL\", \"LC_CTYPE\", \"LEADING\", \"LEAVE\", \"LEFT\", \"LIKE\", \"LINKTYPE\", \"LN\", \"LOCAL\", \"LOCALDATE\", \"LOCALE\", \"LOCALTIME\", \"LOCALTIMESTAMP\", \"LOCATOR\", \"LOCATORS\", \"LOCK\", \"LOCKMAX\", \"LOCKSIZE\", \"LONG\", \"LOOP\", \"LOWER\", \"MAINTAINED\", \"MATCH\", \"MATERIALIZED\", \"MAX\", \"MAXVALUE\", \"MEMBER\", \"MERGE\", \"METHOD\", \"MICROSECOND\", \"MICROSECONDS\", \"MIN\", \"MINUTE\", \"MINUTES\", \"MINVALUE\", \"MOD\", \"MODE\", \"MODIFIES\", \"MODULE\", \"MONTH\", \"MONTHS\", \"MULTISET\", \"NAN\", \"NATIONAL\", \"NATURAL\", \"NCHAR\", \"NCLOB\", \"NEW\", \"NEW_TABLE\", \"NEXTVAL\", \"NO\", \"NOCACHE\", \"NOCYCLE\", \"NODENAME\", \"NODENUMBER\", \"NOMAXVALUE\", \"NOMINVALUE\", \"NONE\", \"NOORDER\", \"NORMALIZE\", \"NORMALIZED\", \"NOT\", \"NULL\", \"NULLIF\", \"NULLS\", \"NUMERIC\", \"NUMPARTS\", \"OBID\", \"OCTET_LENGTH\", \"OF\", \"OFFSET\", \"OLD\", \"OLD_TABLE\", \"ON\", \"ONLY\", \"OPEN\", \"OPTIMIZATION\", \"OPTIMIZE\", \"OPTION\", \"ORDER\", \"OUT\", \"OUTER\", \"OVER\", \"OVERLAPS\", \"OVERLAY\", \"OVERRIDING\", \"PACKAGE\", \"PADDED\", \"PAGESIZE\", \"PARAMETER\", \"PART\", \"PARTITION\", \"PARTITIONED\", \"PARTITIONING\", \"PARTITIONS\", \"PASSWORD\", \"PATH\", \"PERCENTILE_CONT\", \"PERCENTILE_DISC\", \"PERCENT_RANK\", \"PIECESIZE\", \"PLAN\", \"POSITION\", \"POWER\", \"PRECISION\", \"PREPARE\", \"PREVVAL\", \"PRIMARY\", \"PRIQTY\", \"PRIVILEGES\", \"PROCEDURE\", \"PROGRAM\", \"PSID\", \"PUBLIC\", \"QUERY\", \"QUERYNO\", \"RANGE\", \"RANK\", \"READ\", \"READS\", \"REAL\", \"RECOVERY\", \"RECURSIVE\", \"REF\", \"REFERENCES\", \"REFERENCING\", \"REFRESH\", \"REGR_AVGX\", \"REGR_AVGY\", \"REGR_COUNT\", \"REGR_INTERCEPT\", \"REGR_R2\", \"REGR_SLOPE\", \"REGR_SXX\", \"REGR_SXY\", \"REGR_SYY\", \"RELEASE\", \"RENAME\", \"REPEAT\", \"RESET\", \"RESIGNAL\", \"RESTART\", \"RESTRICT\", \n            O = [\"ADD\", \"AFTER\", \"ALTER COLUMN\", \"ALTER TABLE\", \"DELETE FROM\", \"EXCEPT\", \"FETCH FIRST\", \"FROM\", \"GROUP BY\", \"GO\", \"HAVING\", \"INSERT INTO\", \"INTERSECT\", \"LIMIT\", \"ORDER BY\", \"SELECT\", \"SET CURRENT SCHEMA\", \"SET SCHEMA\", \"SET\", \"UNION ALL\", \"UPDATE\", \"VALUES\", \"WHERE\"],\n            i = [\"AND\", \"CROSS JOIN\", \"INNER JOIN\", \"JOIN\", \"LEFT JOIN\", \"LEFT OUTER JOIN\", \"OR\", \"OUTER JOIN\", \"RIGHT JOIN\", \"RIGHT OUTER JOIN\"],\n            S = void 0,\n            u = function() {\n                function e(E) {\n                    (0, T[\"default\"])(this, e), this.cfg = E\n                }\n                return e.prototype.format = function(e) {\n                    return S || (S = new A[\"default\"]({\n                        reservedWords: I,\n                        reservedToplevelWords: O,\n                        reservedNewlineWords: i,\n                        stringTypes: ['\"\"', \"''\", \"``\", \"[]\"],\n                        openParens: [\"(\"],\n                        closeParens: [\")\"],\n                        indexedPlaceholderTypes: [\"?\"],\n                        namedPlaceholderTypes: [\":\"],\n                        lineCommentTypes: [\"--\"],\n                        specialWordChars: [\"#\", \"@\"]\n                    })), new o[\"default\"](this.cfg, S).format(e)\n                }, e\n            }();\n        E[\"default\"] = u, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = t(4),\n            o = n(R),\n            N = t(5),\n            A = n(N),\n            I = [\"ALL\", \"ALTER\", \"ANALYZE\", \"AND\", \"ANY\", \"ARRAY\", \"AS\", \"ASC\", \"BEGIN\", \"BETWEEN\", \"BINARY\", \"BOOLEAN\", \"BREAK\", \"BUCKET\", \"BUILD\", \"BY\", \"CALL\", \"CASE\", \"CAST\", \"CLUSTER\", \"COLLATE\", \"COLLECTION\", \"COMMIT\", \"CONNECT\", \"CONTINUE\", \"CORRELATE\", \"COVER\", \"CREATE\", \"DATABASE\", \"DATASET\", \"DATASTORE\", \"DECLARE\", \"DECREMENT\", \"DELETE\", \"DERIVED\", \"DESC\", \"DESCRIBE\", \"DISTINCT\", \"DO\", \"DROP\", \"EACH\", \"ELEMENT\", \"ELSE\", \"END\", \"EVERY\", \"EXCEPT\", \"EXCLUDE\", \"EXECUTE\", \"EXISTS\", \"EXPLAIN\", \"FALSE\", \"FETCH\", \"FIRST\", \"FLATTEN\", \"FOR\", \"FORCE\", \"FROM\", \"FUNCTION\", \"GRANT\", \"GROUP\", \"GSI\", \"HAVING\", \"IF\", \"IGNORE\", \"ILIKE\", \"IN\", \"INCLUDE\", \"INCREMENT\", \"INDEX\", \"INFER\", \"INLINE\", \"INNER\", \"INSERT\", \"INTERSECT\", \"INTO\", \"IS\", \"JOIN\", \"KEY\", \"KEYS\", \"KEYSPACE\", \"KNOWN\", \"LAST\", \"LEFT\", \"LET\", \"LETTING\", \"LIKE\", \"LIMIT\", \"LSM\", \"MAP\", \"MAPPING\", \"MATCHED\", \"MATERIALIZED\", \"MERGE\", \"MINUS\", \"MISSING\", \"NAMESPACE\", \"NEST\", \"NOT\", \"NULL\", \"NUMBER\", \"OBJECT\", \"OFFSET\", \"ON\", \"OPTION\", \"OR\", \"ORDER\", \"OUTER\", \"OVER\", \"PARSE\", \"PARTITION\", \"PASSWORD\", \"PATH\", \"POOL\", \"PREPARE\", \"PRIMARY\", \"PRIVATE\", \"PRIVILEGE\", \"PROCEDURE\", \"PUBLIC\", \"RAW\", \"REALM\", \"REDUCE\", \"RENAME\", \"RETURN\", \"RETURNING\", \"REVOKE\", \"RIGHT\", \"ROLE\", \"ROLLBACK\", \"SATISFIES\", \"SCHEMA\", \"SELECT\", \"SELF\", \"SEMI\", \"SET\", \"SHOW\", \"SOME\", \"START\", \"STATISTICS\", \"STRING\", \"SYSTEM\", \"THEN\", \"TO\", \"TRANSACTION\", \"TRIGGER\", \"TRUE\", \"TRUNCATE\", \"UNDER\", \"UNION\", \"UNIQUE\", \"UNKNOWN\", \"UNNEST\", \"UNSET\", \"UPDATE\", \"UPSERT\", \"USE\", \"USER\", \"USING\", \"VALIDATE\", \"VALUE\", \"VALUED\", \"VALUES\", \"VIA\", \"VIEW\", \"WHEN\", \"WHERE\", \"WHILE\", \"WITH\", \"WITHIN\", \"WORK\", \"XOR\"],\n            O = [\"DELETE FROM\", \"EXCEPT ALL\", \"EXCEPT\", \"EXPLAIN DELETE FROM\", \"EXPLAIN UPDATE\", \"EXPLAIN UPSERT\", \"FROM\", \"GROUP BY\", \"HAVING\", \"INFER\", \"INSERT INTO\", \"INTERSECT ALL\", \"INTERSECT\", \"LET\", \"LIMIT\", \"MERGE\", \"NEST\", \"ORDER BY\", \"PREPARE\", \"SELECT\", \"SET CURRENT SCHEMA\", \"SET SCHEMA\", \"SET\", \"UNION ALL\", \"UNION\", \"UNNEST\", \"UPDATE\", \"UPSERT\", \"USE KEYS\", \"VALUES\", \"WHERE\"],\n            i = [\"AND\", \"INNER JOIN\", \"JOIN\", \"LEFT JOIN\", \"LEFT OUTER JOIN\", \"OR\", \"OUTER JOIN\", \"RIGHT JOIN\", \"RIGHT OUTER JOIN\", \"XOR\"],\n            S = void 0,\n            u = function() {\n                function e(E) {\n                    (0, T[\"default\"])(this, e), this.cfg = E\n                }\n                return e.prototype.format = function(e) {\n                    return S || (S = new A[\"default\"]({\n                        reservedWords: I,\n                        reservedToplevelWords: O,\n                        reservedNewlineWords: i,\n                        stringTypes: ['\"\"', \"''\", \"``\"],\n                        openParens: [\"(\", \"[\", \"{\"],\n                        closeParens: [\")\", \"]\", \"}\"],\n                        namedPlaceholderTypes: [\"$\"],\n                        lineCommentTypes: [\"#\", \"--\"]\n                    })), new o[\"default\"](this.cfg, S).format(e)\n                }, e\n            }();\n        E[\"default\"] = u, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = t(4),\n            o = n(R),\n            N = t(5),\n            A = n(N),\n            I = [\"A\", \"ACCESSIBLE\", \"AGENT\", \"AGGREGATE\", \"ALL\", \"ALTER\", \"ANY\", \"ARRAY\", \"AS\", \"ASC\", \"AT\", \"ATTRIBUTE\", \"AUTHID\", \"AVG\", \"BETWEEN\", \"BFILE_BASE\", \"BINARY_INTEGER\", \"BINARY\", \"BLOB_BASE\", \"BLOCK\", \"BODY\", \"BOOLEAN\", \"BOTH\", \"BOUND\", \"BULK\", \"BY\", \"BYTE\", \"C\", \"CALL\", \"CALLING\", \"CASCADE\", \"CASE\", \"CHAR_BASE\", \"CHAR\", \"CHARACTER\", \"CHARSET\", \"CHARSETFORM\", \"CHARSETID\", \"CHECK\", \"CLOB_BASE\", \"CLONE\", \"CLOSE\", \"CLUSTER\", \"CLUSTERS\", \"COALESCE\", \"COLAUTH\", \"COLLECT\", \"COLUMNS\", \"COMMENT\", \"COMMIT\", \"COMMITTED\", \"COMPILED\", \"COMPRESS\", \"CONNECT\", \"CONSTANT\", \"CONSTRUCTOR\", \"CONTEXT\", \"CONTINUE\", \"CONVERT\", \"COUNT\", \"CRASH\", \"CREATE\", \"CREDENTIAL\", \"CURRENT\", \"CURRVAL\", \"CURSOR\", \"CUSTOMDATUM\", \"DANGLING\", \"DATA\", \"DATE_BASE\", \"DATE\", \"DAY\", \"DECIMAL\", \"DEFAULT\", \"DEFINE\", \"DELETE\", \"DESC\", \"DETERMINISTIC\", \"DIRECTORY\", \"DISTINCT\", \"DO\", \"DOUBLE\", \"DROP\", \"DURATION\", \"ELEMENT\", \"ELSIF\", \"EMPTY\", \"ESCAPE\", \"EXCEPTIONS\", \"EXCLUSIVE\", \"EXECUTE\", \"EXISTS\", \"EXIT\", \"EXTENDS\", \"EXTERNAL\", \"EXTRACT\", \"FALSE\", \"FETCH\", \"FINAL\", \"FIRST\", \"FIXED\", \"FLOAT\", \"FOR\", \"FORALL\", \"FORCE\", \"FROM\", \"FUNCTION\", \"GENERAL\", \"GOTO\", \"GRANT\", \"GROUP\", \"HASH\", \"HEAP\", \"HIDDEN\", \"HOUR\", \"IDENTIFIED\", \"IF\", \"IMMEDIATE\", \"IN\", \"INCLUDING\", \"INDEX\", \"INDEXES\", \"INDICATOR\", \"INDICES\", \"INFINITE\", \"INSTANTIABLE\", \"INT\", \"INTEGER\", \"INTERFACE\", \"INTERVAL\", \"INTO\", \"INVALIDATE\", \"IS\", \"ISOLATION\", \"JAVA\", \"LANGUAGE\", \"LARGE\", \"LEADING\", \"LENGTH\", \"LEVEL\", \"LIBRARY\", \"LIKE\", \"LIKE2\", \"LIKE4\", \"LIKEC\", \"LIMITED\", \"LOCAL\", \"LOCK\", \"LONG\", \"MAP\", \"MAX\", \"MAXLEN\", \"MEMBER\", \"MERGE\", \"MIN\", \"MINUS\", \"MINUTE\", \"MLSLABEL\", \"MOD\", \"MODE\", \"MONTH\", \"MULTISET\", \"NAME\", \"NAN\", \"NATIONAL\", \"NATIVE\", \"NATURAL\", \"NATURALN\", \"NCHAR\", \"NEW\", \"NEXTVAL\", \"NOCOMPRESS\", \"NOCOPY\", \"NOT\", \"NOWAIT\", \"NULL\", \"NULLIF\", \"NUMBER_BASE\", \"NUMBER\", \"OBJECT\", \"OCICOLL\", \"OCIDATE\", \"OCIDATETIME\", \"OCIDURATION\", \"OCIINTERVAL\", \"OCILOBLOCATOR\", \"OCINUMBER\", \"OCIRAW\", \"OCIREF\", \"OCIREFCURSOR\", \"OCIROWID\", \"OCISTRING\", \"OCITYPE\", \"OF\", \"OLD\", \"ON\", \"ONLY\", \"OPAQUE\", \"OPEN\", \"OPERATOR\", \"OPTION\", \"ORACLE\", \"ORADATA\", \"ORDER\", \"ORGANIZATION\", \"ORLANY\", \"ORLVARY\", \"OTHERS\", \"OUT\", \"OVERLAPS\", \"OVERRIDING\", \"PACKAGE\", \"PARALLEL_ENABLE\", \"PARAMETER\", \"PARAMETERS\", \"PARENT\", \"PARTITION\", \"PASCAL\", \"PCTFREE\", \"PIPE\", \"PIPELINED\", \"PLS_INTEGER\", \"PLUGGABLE\", \"POSITIVE\", \"POSITIVEN\", \"PRAGMA\", \"PRECISION\", \"PRIOR\", \"PRIVATE\", \"PROCEDURE\", \"PUBLIC\", \"RAISE\", \"RANGE\", \"RAW\", \"READ\", \"REAL\", \"RECORD\", \"REF\", \"REFERENCE\", \"RELEASE\", \"RELIES_ON\", \"REM\", \"REMAINDER\", \"RENAME\", \"RESOURCE\", \"RESULT_CACHE\", \"RESULT\", \"RETURN\", \"RETURNING\", \"REVERSE\", \"REVOKE\", \"ROLLBACK\", \"ROW\", \"ROWID\", \"ROWNUM\", \"ROWTYPE\", \"SAMPLE\", \"SAVE\", \"SAVEPOINT\", \"SB1\", \"SB2\", \"SB4\", \"SECOND\", \"SEGMENT\", \"SELF\", \"SEPARATE\", \"SEQUENCE\", \"SERIALIZABLE\", \"SHARE\", \"SHORT\", \"SIZE_T\", \"SIZE\", \"SMALLINT\", \"SOME\", \"SPACE\", \"SPARSE\", \"SQL\", \"SQLCODE\", \"SQLDATA\", \"SQLERRM\", \"SQLNAME\", \"SQLSTATE\", \"STANDARD\", \"START\", \"STATIC\", \"STDDEV\", \"STORED\", \"STRING\", \"STRUCT\", \"STYLE\", \"SUBMULTISET\", \"SUBPARTITION\", \"SUBSTITUTABLE\", \"SUBTYPE\", \"SUCCESSFUL\", \"SUM\", \"SYNONYM\", \"SYSDATE\", \"TABAUTH\", \"TABLE\", \"TDO\", \"THE\", \"THEN\", \"TIME\", \"TIMESTAMP\", \"TIMEZONE_ABBR\", \"TIMEZONE_HOUR\", \"TIMEZONE_MINUTE\", \"TIMEZONE_REGION\", \"TO\", \"TRAILING\", \"TRANSACTION\", \"TRANSACTIONAL\", \"TRIGGER\", \"TRUE\", \"TRUSTED\", \"TYPE\", \"UB1\", \"UB2\", \"UB4\", \"UID\", \"UNDER\", \"UNIQUE\", \"UNPLUG\", \"UNSIGNED\", \"UNTRUSTED\", \"USE\", \"USER\", \"USING\", \"VALIDATE\", \"VALIST\", \"VALUE\", \"VARCHAR\", \"VARCHAR2\", \"VARIABLE\", \"VARIANCE\", \"VARRAY\", \"VARYING\", \"VIEW\", \"VIEWS\", \"VOID\", \"WHENEVER\", \"WHILE\", \"WITH\", \"WORK\", \"WRAPPED\", \"WRITE\", \"YEAR\", \"ZONE\"],\n            O = [\"ADD\", \"ALTER COLUMN\", \"ALTER TABLE\", \"BEGIN\", \"CONNECT BY\", \"DECLARE\", \"DELETE FROM\", \"DELETE\", \"END\", \"EXCEPT\", \"EXCEPTION\", \"FETCH FIRST\", \"FROM\", \"GROUP BY\", \"HAVING\", \"INSERT INTO\", \"INSERT\", \"INTERSECT\", \"LIMIT\", \"LOOP\", \"MODIFY\", \"ORDER BY\", \"SELECT\", \"SET CURRENT SCHEMA\", \"SET SCHEMA\", \"SET\", \"START WITH\", \"UNION ALL\", \"UNION\", \"UPDATE\", \"VALUES\", \"WHERE\"],\n            i = [\"AND\", \"CROSS APPLY\", \"CROSS JOIN\", \"ELSE\", \"END\", \"INNER JOIN\", \"JOIN\", \"LEFT JOIN\", \"LEFT OUTER JOIN\", \"OR\", \"OUTER APPLY\", \"OUTER JOIN\", \"RIGHT JOIN\", \"RIGHT OUTER JOIN\", \"WHEN\", \"XOR\"],\n            S = void 0,\n            u = function() {\n                function e(E) {\n                    (0, T[\"default\"])(this, e), this.cfg = E\n                }\n                return e.prototype.format = function(e) {\n                    return S || (S = new A[\"default\"]({\n                        reservedWords: I,\n                        reservedToplevelWords: O,\n                        reservedNewlineWords: i,\n                        stringTypes: ['\"\"', \"N''\", \"''\", \"``\"],\n                        openParens: [\"(\", \"CASE\"],\n                        closeParens: [\")\", \"END\"],\n                        indexedPlaceholderTypes: [\"?\"],\n                        namedPlaceholderTypes: [\":\"],\n                        lineCommentTypes: [\"--\"],\n                        specialWordChars: [\"_\", \"$\", \"#\", \".\", \"@\"]\n                    })), new o[\"default\"](this.cfg, S).format(e)\n                }, e\n            }();\n        E[\"default\"] = u, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        \"use strict\";\n\n        function n(e) {\n            return e && e.__esModule ? e : {\n                \"default\": e\n            }\n        }\n        E.__esModule = !0;\n        var r = t(1),\n            T = n(r),\n            R = t(4),\n            o = n(R),\n            N = t(5),\n            A = n(N),\n            I = [\"ACCESSIBLE\", \"ACTION\", \"AGAINST\", \"AGGREGATE\", \"ALGORITHM\", \"ALL\", \"ALTER\", \"ANALYSE\", \"ANALYZE\", \"AS\", \"ASC\", \"AUTOCOMMIT\", \"AUTO_INCREMENT\", \"BACKUP\", \"BEGIN\", \"BETWEEN\", \"BINLOG\", \"BOTH\", \"CASCADE\", \"CASE\", \"CHANGE\", \"CHANGED\", \"CHARACTER SET\", \"CHARSET\", \"CHECK\", \"CHECKSUM\", \"COLLATE\", \"COLLATION\", \"COLUMN\", \"COLUMNS\", \"COMMENT\", \"COMMIT\", \"COMMITTED\", \"COMPRESSED\", \"CONCURRENT\", \"CONSTRAINT\", \"CONTAINS\", \"CONVERT\", \"CREATE\", \"CROSS\", \"CURRENT_TIMESTAMP\", \"DATABASE\", \"DATABASES\", \"DAY\", \"DAY_HOUR\", \"DAY_MINUTE\", \"DAY_SECOND\", \"DEFAULT\", \"DEFINER\", \"DELAYED\", \"DELETE\", \"DESC\", \"DESCRIBE\", \"DETERMINISTIC\", \"DISTINCT\", \"DISTINCTROW\", \"DIV\", \"DO\", \"DROP\", \"DUMPFILE\", \"DUPLICATE\", \"DYNAMIC\", \"ELSE\", \"ENCLOSED\", \"END\", \"ENGINE\", \"ENGINES\", \"ENGINE_TYPE\", \"ESCAPE\", \"ESCAPED\", \"EVENTS\", \"EXEC\", \"EXECUTE\", \"EXISTS\", \"EXPLAIN\", \"EXTENDED\", \"FAST\", \"FETCH\", \"FIELDS\", \"FILE\", \"FIRST\", \"FIXED\", \"FLUSH\", \"FOR\", \"FORCE\", \"FOREIGN\", \"FULL\", \"FULLTEXT\", \"FUNCTION\", \"GLOBAL\", \"GRANT\", \"GRANTS\", \"GROUP_CONCAT\", \"HEAP\", \"HIGH_PRIORITY\", \"HOSTS\", \"HOUR\", \"HOUR_MINUTE\", \"HOUR_SECOND\", \"IDENTIFIED\", \"IF\", \"IFNULL\", \"IGNORE\", \"IN\", \"INDEX\", \"INDEXES\", \"INFILE\", \"INSERT\", \"INSERT_ID\", \"INSERT_METHOD\", \"INTERVAL\", \"INTO\", \"INVOKER\", \"IS\", \"ISOLATION\", \"KEY\", \"KEYS\", \"KILL\", \"LAST_INSERT_ID\", \"LEADING\", \"LEVEL\", \"LIKE\", \"LINEAR\", \"LINES\", \"LOAD\", \"LOCAL\", \"LOCK\", \"LOCKS\", \"LOGS\", \"LOW_PRIORITY\", \"MARIA\", \"MASTER\", \"MASTER_CONNECT_RETRY\", \"MASTER_HOST\", \"MASTER_LOG_FILE\", \"MATCH\", \"MAX_CONNECTIONS_PER_HOUR\", \"MAX_QUERIES_PER_HOUR\", \"MAX_ROWS\", \"MAX_UPDATES_PER_HOUR\", \"MAX_USER_CONNECTIONS\", \"MEDIUM\", \"MERGE\", \"MINUTE\", \"MINUTE_SECOND\", \"MIN_ROWS\", \"MODE\", \"MODIFY\", \"MONTH\", \"MRG_MYISAM\", \"MYISAM\", \"NAMES\", \"NATURAL\", \"NOT\", \"NOW()\", \"NULL\", \"OFFSET\", \"ON DELETE\", \"ON UPDATE\", \"ON\", \"ONLY\", \"OPEN\", \"OPTIMIZE\", \"OPTION\", \"OPTIONALLY\", \"OUTFILE\", \"PACK_KEYS\", \"PAGE\", \"PARTIAL\", \"PARTITION\", \"PARTITIONS\", \"PASSWORD\", \"PRIMARY\", \"PRIVILEGES\", \"PROCEDURE\", \"PROCESS\", \"PROCESSLIST\", \"PURGE\", \"QUICK\", \"RAID0\", \"RAID_CHUNKS\", \"RAID_CHUNKSIZE\", \"RAID_TYPE\", \"RANGE\", \"READ\", \"READ_ONLY\", \"READ_WRITE\", \"REFERENCES\", \"REGEXP\", \"RELOAD\", \"RENAME\", \"REPAIR\", \"REPEATABLE\", \"REPLACE\", \"REPLICATION\", \"RESET\", \"RESTORE\", \"RESTRICT\", \"RETURN\", \"RETURNS\", \"REVOKE\", \"RLIKE\", \"ROLLBACK\", \"ROW\", \"ROWS\", \"ROW_FORMAT\", \"SECOND\", \"SECURITY\", \"SEPARATOR\", \"SERIALIZABLE\", \"SESSION\", \"SHARE\", \"SHOW\", \"SHUTDOWN\", \"SLAVE\", \"SONAME\", \"SOUNDS\", \"SQL\", \"SQL_AUTO_IS_NULL\", \"SQL_BIG_RESULT\", \"SQL_BIG_SELECTS\", \"SQL_BIG_TABLES\", \"SQL_BUFFER_RESULT\", \"SQL_CACHE\", \"SQL_CALC_FOUND_ROWS\", \"SQL_LOG_BIN\", \"SQL_LOG_OFF\", \"SQL_LOG_UPDATE\", \"SQL_LOW_PRIORITY_UPDATES\", \"SQL_MAX_JOIN_SIZE\", \"SQL_NO_CACHE\", \"SQL_QUOTE_SHOW_CREATE\", \"SQL_SAFE_UPDATES\", \"SQL_SELECT_LIMIT\", \"SQL_SLAVE_SKIP_COUNTER\", \"SQL_SMALL_RESULT\", \"SQL_WARNINGS\", \"START\", \"STARTING\", \"STATUS\", \"STOP\", \"STORAGE\", \"STRAIGHT_JOIN\", \"STRING\", \"STRIPED\", \"SUPER\", \"TABLE\", \"TABLES\", \"TEMPORARY\", \"TERMINATED\", \"THEN\", \"TO\", \"TRAILING\", \"TRANSACTIONAL\", \"TRUE\", \"TRUNCATE\", \"TYPE\", \"TYPES\", \"UNCOMMITTED\", \"UNIQUE\", \"UNLOCK\", \"UNSIGNED\", \"USAGE\", \"USE\", \"USING\", \"VARIABLES\", \"VIEW\", \"WHEN\", \"WITH\", \"WORK\", \"WRITE\", \"YEAR_MONTH\"],\n            O = [\"ADD\", \"AFTER\", \"ALTER COLUMN\", \"ALTER TABLE\", \"DELETE FROM\", \"EXCEPT\", \"FETCH FIRST\", \"FROM\", \"GROUP BY\", \"GO\", \"HAVING\", \"INSERT INTO\", \"INSERT\", \"INTERSECT\", \"LIMIT\", \"MODIFY\", \"ORDER BY\", \"SELECT\", \"SET CURRENT SCHEMA\", \"SET SCHEMA\", \"SET\", \"UNION ALL\", \"UNION\", \"UPDATE\", \"VALUES\", \"WHERE\"],\n            i = [\"AND\", \"CROSS APPLY\", \"CROSS JOIN\", \"ELSE\", \"INNER JOIN\", \"JOIN\", \"LEFT JOIN\", \"LEFT OUTER JOIN\", \"OR\", \"OUTER APPLY\", \"OUTER JOIN\", \"RIGHT JOIN\", \"RIGHT OUTER JOIN\", \"WHEN\", \"XOR\"],\n            S = void 0,\n            u = function() {\n                function e(E) {\n                    (0, T[\"default\"])(this, e), this.cfg = E\n                }\n                return e.prototype.format = function(e) {\n                    return S || (S = new A[\"default\"]({\n                        reservedWords: I,\n                        reservedToplevelWords: O,\n                        reservedNewlineWords: i,\n                        stringTypes: ['\"\"', \"N''\", \"''\", \"``\", \"[]\"],\n                        openParens: [\"(\", \"CASE\"],\n                        closeParens: [\")\", \"END\"],\n                        indexedPlaceholderTypes: [\"?\"],\n                        namedPlaceholderTypes: [\"@\", \":\"],\n                        lineCommentTypes: [\"#\", \"--\"]\n                    })), new o[\"default\"](this.cfg, S).format(e)\n                }, e\n            }();\n        E[\"default\"] = u, e.exports = E[\"default\"]\n    }, function(e, E, t) {\n        var n = t(3),\n            r = t(2),\n            T = n(r, \"DataView\");\n        e.exports = T\n    }, function(e, E, t) {\n        var n = t(3),\n            r = t(2),\n            T = n(r, \"Map\");\n        e.exports = T\n    }, function(e, E, t) {\n        var n = t(3),\n            r = t(2),\n            T = n(r, \"Promise\");\n        e.exports = T\n    }, function(e, E, t) {\n        var n = t(3),\n            r = t(2),\n            T = n(r, \"Set\");\n        e.exports = T\n    }, function(e, E, t) {\n        var n = t(2),\n            r = n.Symbol;\n        e.exports = r\n    }, function(e, E, t) {\n        var n = t(3),\n            r = t(2),\n            T = n(r, \"WeakMap\");\n        e.exports = T\n    }, function(e, E) {\n        function t(e) {\n            return e.split(\"\")\n        }\n        e.exports = t\n    }, function(e, E) {\n        function t(e, E, t, n) {\n            for (var r = e.length, T = t + (n ? 1 : -1); n ? T-- : ++T < r;)\n                if (E(e[T], T, e)) return T;\n            return -1\n        }\n        e.exports = t\n    }, function(e, E) {\n        function t(e) {\n            return r.call(e)\n        }\n        var n = Object.prototype,\n            r = n.toString;\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e, E, t) {\n            return E === E ? R(e, E, t) : r(e, T, t)\n        }\n        var r = t(29),\n            T = t(32),\n            R = t(49);\n        e.exports = n\n    }, function(e, E) {\n        function t(e) {\n            return e !== e\n        }\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e) {\n            if (!R(e) || T(e)) return !1;\n            var E = r(e) ? u : A;\n            return E.test(o(e))\n        }\n        var r = t(12),\n            T = t(45),\n            R = t(6),\n            o = t(11),\n            N = /[\\\\^$.*+?()[\\]{}|]/g,\n            A = /^\\[object .+?Constructor\\]$/,\n            I = Function.prototype,\n            O = Object.prototype,\n            i = I.toString,\n            S = O.hasOwnProperty,\n            u = RegExp(\"^\" + i.call(S).replace(N, \"\\\\$&\").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, \"$1.*?\") + \"$\");\n        e.exports = n\n    }, function(e, E) {\n        function t(e, E) {\n            var t = \"\";\n            if (!e || 1 > E || E > n) return t;\n            do E % 2 && (t += e), E = r(E / 2), E && (e += e); while (E);\n            return t\n        }\n        var n = 9007199254740991,\n            r = Math.floor;\n        e.exports = t\n    }, function(e, E) {\n        function t(e, E, t) {\n            var n = -1,\n                r = e.length;\n            0 > E && (E = -E > r ? 0 : r + E), t = t > r ? r : t, 0 > t && (t += r), r = E > t ? 0 : t - E >>> 0, E >>>= 0;\n            for (var T = Array(r); ++n < r;) T[n] = e[n + E];\n            return T\n        }\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e, E, t) {\n            var n = e.length;\n            return t = void 0 === t ? n : t, E || n > t ? r(e, E, t) : e\n        }\n        var r = t(35);\n        e.exports = n\n    }, function(e, E, t) {\n        function n(e, E) {\n            for (var t = e.length; t-- && r(E, e[t], 0) > -1;);\n            return t\n        }\n        var r = t(31);\n        e.exports = n\n    }, function(e, E, t) {\n        var n = t(2),\n            r = n[\"__core-js_shared__\"];\n        e.exports = r\n    }, function(e, E) {\n        (function(E) {\n            var t = \"object\" == typeof E && E && E.Object === Object && E;\n            e.exports = t\n        }).call(E, function() {\n            return this\n        }())\n    }, function(e, E, t) {\n        var n = t(22),\n            r = t(23),\n            T = t(24),\n            R = t(25),\n            o = t(27),\n            N = t(30),\n            A = t(11),\n            I = \"[object Map]\",\n            O = \"[object Object]\",\n            i = \"[object Promise]\",\n            S = \"[object Set]\",\n            u = \"[object WeakMap]\",\n            L = \"[object DataView]\",\n            C = Object.prototype,\n            s = C.toString,\n            a = A(n),\n            f = A(r),\n            c = A(T),\n            p = A(R),\n            l = A(o),\n            D = N;\n        (n && D(new n(new ArrayBuffer(1))) != L || r && D(new r) != I || T && D(T.resolve()) != i || R && D(new R) != S || o && D(new o) != u) && (D = function(e) {\n            var E = s.call(e),\n                t = E == O ? e.constructor : void 0,\n                n = t ? A(t) : void 0;\n            if (n) switch (n) {\n                case a:\n                    return L;\n                case f:\n                    return I;\n                case c:\n                    return i;\n                case p:\n                    return S;\n                case l:\n                    return u\n            }\n            return E\n        }), e.exports = D\n    }, function(e, E) {\n        function t(e, E) {\n            return null == e ? void 0 : e[E]\n        }\n        e.exports = t\n    }, function(e, E) {\n        function t(e) {\n            return N.test(e)\n        }\n        var n = \"\\\\ud800-\\\\udfff\",\n            r = \"\\\\u0300-\\\\u036f\\\\ufe20-\\\\ufe23\",\n            T = \"\\\\u20d0-\\\\u20f0\",\n            R = \"\\\\ufe0e\\\\ufe0f\",\n            o = \"\\\\u200d\",\n            N = RegExp(\"[\" + o + n + r + T + R + \"]\");\n        e.exports = t\n    }, function(e, E) {\n        function t(e, E) {\n            return E = null == E ? n : E, !!E && (\"number\" == typeof e || r.test(e)) && e > -1 && e % 1 == 0 && E > e\n        }\n        var n = 9007199254740991,\n            r = /^(?:0|[1-9]\\d*)$/;\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e, E, t) {\n            if (!o(t)) return !1;\n            var n = typeof E;\n            return !!(\"number\" == n ? T(t) && R(E, t.length) : \"string\" == n && E in t) && r(t[E], e)\n        }\n        var r = t(52),\n            T = t(8),\n            R = t(43),\n            o = t(6);\n        e.exports = n\n    }, function(e, E, t) {\n        function n(e) {\n            return !!T && T in e\n        }\n        var r = t(38),\n            T = function() {\n                var e = /[^.]+$/.exec(r && r.keys && r.keys.IE_PROTO || \"\");\n                return e ? \"Symbol(src)_1.\" + e : \"\"\n            }();\n        e.exports = n\n    }, function(e, E) {\n        function t(e) {\n            var E = e && e.constructor,\n                t = \"function\" == typeof E && E.prototype || n;\n            return e === t\n        }\n        var n = Object.prototype;\n        e.exports = t\n    }, function(e, E, t) {\n        var n = t(48),\n            r = n(Object.keys, Object);\n        e.exports = r\n    }, function(e, E) {\n        function t(e, E) {\n            return function(t) {\n                return e(E(t))\n            }\n        }\n        e.exports = t\n    }, function(e, E) {\n        function t(e, E, t) {\n            for (var n = t - 1, r = e.length; ++n < r;)\n                if (e[n] === E) return n;\n            return -1\n        }\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e) {\n            return T(e) ? R(e) : r(e)\n        }\n        var r = t(28),\n            T = t(42),\n            R = t(51);\n        e.exports = n\n    }, function(e, E) {\n        function t(e) {\n            return e.match(c) || []\n        }\n        var n = \"\\\\ud800-\\\\udfff\",\n            r = \"\\\\u0300-\\\\u036f\\\\ufe20-\\\\ufe23\",\n            T = \"\\\\u20d0-\\\\u20f0\",\n            R = \"\\\\ufe0e\\\\ufe0f\",\n            o = \"[\" + n + \"]\",\n            N = \"[\" + r + T + \"]\",\n            A = \"\\\\ud83c[\\\\udffb-\\\\udfff]\",\n            I = \"(?:\" + N + \"|\" + A + \")\",\n            O = \"[^\" + n + \"]\",\n            i = \"(?:\\\\ud83c[\\\\udde6-\\\\uddff]){2}\",\n            S = \"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\",\n            u = \"\\\\u200d\",\n            L = I + \"?\",\n            C = \"[\" + R + \"]?\",\n            s = \"(?:\" + u + \"(?:\" + [O, i, S].join(\"|\") + \")\" + C + L + \")*\",\n            a = C + L + s,\n            f = \"(?:\" + [O + N + \"?\", N, i, S, o].join(\"|\") + \")\",\n            c = RegExp(A + \"(?=\" + A + \")|\" + f + a, \"g\");\n        e.exports = t\n    }, function(e, E) {\n        function t(e, E) {\n            return e === E || e !== e && E !== E\n        }\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e) {\n            return e = r(e), e && R.test(e) ? e.replace(T, \"\\\\$&\") : e\n        }\n        var r = t(9),\n            T = /[\\\\^$.*+?()[\\]{}|]/g,\n            R = RegExp(T.source);\n        e.exports = n\n    }, function(e, E, t) {\n        function n(e) {\n            return r(e) && o.call(e, \"callee\") && (!A.call(e, \"callee\") || N.call(e) == T)\n        }\n        var r = t(56),\n            T = \"[object Arguments]\",\n            R = Object.prototype,\n            o = R.hasOwnProperty,\n            N = R.toString,\n            A = R.propertyIsEnumerable;\n        e.exports = n\n    }, function(e, E) {\n        var t = Array.isArray;\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e) {\n            return T(e) && r(e)\n        }\n        var r = t(8),\n            T = t(13);\n        e.exports = n\n    }, function(e, E, t) {\n        (function(e) {\n            var n = t(2),\n                r = t(62),\n                T = \"object\" == typeof E && E && !E.nodeType && E,\n                R = T && \"object\" == typeof e && e && !e.nodeType && e,\n                o = R && R.exports === T,\n                N = o ? n.Buffer : void 0,\n                A = N ? N.isBuffer : void 0,\n                I = A || r;\n            e.exports = I\n        }).call(E, t(67)(e))\n    }, function(e, E, t) {\n        function n(e) {\n            if (o(e) && (R(e) || \"string\" == typeof e || \"function\" == typeof e.splice || N(e) || T(e))) return !e.length;\n            var E = r(e);\n            if (E == O || E == i) return !e.size;\n            if (A(e)) return !I(e).length;\n            for (var t in e)\n                if (u.call(e, t)) return !1;\n            return !0\n        }\n        var r = t(40),\n            T = t(54),\n            R = t(55),\n            o = t(8),\n            N = t(57),\n            A = t(46),\n            I = t(47),\n            O = \"[object Map]\",\n            i = \"[object Set]\",\n            S = Object.prototype,\n            u = S.hasOwnProperty;\n        e.exports = n\n    }, function(e, E) {\n        function t(e) {\n            return \"number\" == typeof e && e > -1 && e % 1 == 0 && n >= e\n        }\n        var n = 9007199254740991;\n        e.exports = t\n    }, function(e, E) {\n        function t(e) {\n            var E = e ? e.length : 0;\n            return E ? e[E - 1] : void 0\n        }\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e, E, t) {\n            return E = (t ? T(e, E, t) : void 0 === E) ? 1 : R(E), r(o(e), E)\n        }\n        var r = t(34),\n            T = t(44),\n            R = t(64),\n            o = t(9);\n        e.exports = n\n    }, function(e, E) {\n        function t() {\n            return !1\n        }\n        e.exports = t\n    }, function(e, E, t) {\n        function n(e) {\n            if (!e) return 0 === e ? e : 0;\n            if (e = r(e), e === T || e === -T) {\n                var E = 0 > e ? -1 : 1;\n                return E * R\n            }\n            return e === e ? e : 0\n        }\n        var r = t(65),\n            T = 1 / 0,\n            R = 1.7976931348623157e308;\n        e.exports = n\n    }, function(e, E, t) {\n        function n(e) {\n            var E = r(e),\n                t = E % 1;\n            return E === E ? t ? E - t : E : 0\n        }\n        var r = t(63);\n        e.exports = n\n    }, function(e, E, t) {\n        function n(e) {\n            if (\"number\" == typeof e) return e;\n            if (T(e)) return R;\n            if (r(e)) {\n                var E = \"function\" == typeof e.valueOf ? e.valueOf() : e;\n                e = r(E) ? E + \"\" : E\n            }\n            if (\"string\" != typeof e) return 0 === e ? e : +e;\n            e = e.replace(o, \"\");\n            var t = A.test(e);\n            return t || I.test(e) ? O(e.slice(2), t ? 2 : 8) : N.test(e) ? R : +e\n        }\n        var r = t(6),\n            T = t(14),\n            R = NaN,\n            o = /^\\s+|\\s+$/g,\n            N = /^[-+]0x[0-9a-f]+$/i,\n            A = /^0b[01]+$/i,\n            I = /^0o[0-7]+$/i,\n            O = parseInt;\n        e.exports = n\n    }, function(e, E, t) {\n        function n(e, E, t) {\n            if (e = N(e), e && (t || void 0 === E)) return e.replace(A, \"\");\n            if (!e || !(E = r(E))) return e;\n            var n = o(e),\n                I = R(n, o(E)) + 1;\n            return T(n, 0, I).join(\"\")\n        }\n        var r = t(10),\n            T = t(36),\n            R = t(37),\n            o = t(50),\n            N = t(9),\n            A = /\\s+$/;\n        e.exports = n\n    }, function(e, E) {\n        e.exports = function(e) {\n            return e.webpackPolyfill || (e.deprecate = function() {}, e.paths = [], e.children = [], e.webpackPolyfill = 1), e\n        }\n    }])\n});\n\nfunction escape2Html(str) {\n    var arrEntities = {\n        'lt': '<',\n        'gt': '>',\n        'nbsp': '',\n        'amp': '&',\n        'quot': '\"'\n    };\n    return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, function(all, t) {\n        return arrEntities[t];\n    });\n}\n\nfunction load() {\n    let codeList = document.getElementsByTagName('code');\n\n    for (let i = 0; i < codeList.length; i++) {\n        codeList[i].innerHTML = window.sqlFormatter.format(escape2Html(codeList[i].innerHTML))\n    }\n};\n"
  },
  {
    "path": "doc/report_type.md",
    "content": "# 支持的报告类型\n\n[toc]\n\n## lint\n* **Description**:参考sqlint格式，以插件形式集成到代码编辑器，显示输出更加友好\n\n* **Example**:\n\n```bash\nsoar -report-type lint -query test.sql\n```\n## markdown\n* **Description**:该格式为默认输出格式，以markdown格式展现，可以用网页浏览器插件直接打开，也可以用markdown编辑器打开\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar\n```\n## rewrite\n* **Description**:SQL重写功能，配合-rewrite-rules参数一起使用，可以通过-list-rewrite-rules 查看所有支持的 SQL 重写规则\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -rewrite-rules star2columns,delimiter -report-type rewrite\n```\n## ast\n* **Description**:输出 SQL 的抽象语法树，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type ast\n```\n## ast-json\n* **Description**:以 JSON 格式输出 SQL 的抽象语法树，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type ast-json\n```\n## tiast\n* **Description**:输出 SQL 的 TiDB抽象语法树，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type tiast\n```\n## tiast-json\n* **Description**:以 JSON 格式输出 SQL 的 TiDB抽象语法树，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type tiast-json\n```\n## tables\n* **Description**:以 JSON 格式输出 SQL 使用的库表名\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type tables\n```\n## query-type\n* **Description**:SQL 语句的请求类型\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type query-type\n```\n## fingerprint\n* **Description**:输出SQL的指纹\n\n* **Example**:\n\n```bash\necho \"select * from film where language_id=1\" | soar -report-type fingerprint\n```\n## md2html\n* **Description**:markdown 格式转 html 格式小工具\n\n* **Example**:\n\n```bash\nsoar -list-heuristic-rules | soar -report-type md2html > heuristic_rules.html\n```\n## explain-digest\n* **Description**:输入为EXPLAIN的表格，JSON 或 Vertical格式，对其进行分析，给出分析结果\n\n* **Example**:\n\n```bash\nsoar -report-type explain-digest << EOF\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\n| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\n|  1 | SIMPLE      | film  | ALL  | NULL          | NULL | NULL    | NULL | 1131 |       |\n+----+-------------+-------+------+---------------+------+---------+------+------+-------+\nEOF\n```\n## duplicate-key-checker\n* **Description**:对 OnlineDsn 中指定的 database 进行索引重复检查\n\n* **Example**:\n\n```bash\nsoar -report-type duplicate-key-checker -online-dsn user:password@127.0.0.1:3306/db\n```\n## html\n* **Description**:以HTML格式输出报表\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type html\n```\n## json\n* **Description**:输出JSON格式报表，方便应用程序处理\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type json\n```\n## tokenize\n* **Description**:对SQL进行切词，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type tokenize\n```\n## compress\n* **Description**:SQL压缩小工具，使用内置SQL压缩逻辑，测试中的功能\n\n* **Example**:\n\n```bash\necho \"select\n*\nfrom\n  film\" | soar -report-type compress\n```\n## pretty\n* **Description**:使用kr/pretty打印报告，主要用于测试\n\n* **Example**:\n\n```bash\necho \"select * from film\" | soar -report-type pretty\n```\n## remove-comment\n* **Description**:去除SQL语句中的注释，支持单行多行注释的去除\n\n* **Example**:\n\n```bash\necho \"select/*comment*/ * from film\" | soar -report-type remove-comment\n```\n## chardet\n* **Description**:猜测输入的 SQL 使用的字符集\n\n* **Example**:\n\n```bash\necho '中文' | soar -report-type chardet\n```\n"
  },
  {
    "path": "doc/rewrite.md",
    "content": "# 重写规则\n\n[toc]\n\n## dml2select\n* **Description**:将数据库更新请求转换为只读查询请求，便于执行EXPLAIN\n\n* **Original**:\n\n```sql\nDELETE FROM film WHERE length > 100\n```\n\n* **Suggest**:\n\n```sql\nselect * from film where length > 100\n```\n## reg2select\n* **Description**:使用正则的方式将数据库更新请求转换为只读查询请求，便于执行EXPLAIN\n\n* **Original**:\n\n```sql\nDELETE FROM film WHERE length > 100\n```\n\n* **Suggest**:\n\n```sql\nselect * from film where length > 100\n```\n## star2columns\n* **Description**:为SELECT *补全表的列信息\n\n* **Original**:\n\n```sql\nSELECT * FROM film\n```\n\n* **Suggest**:\n\n```sql\nselect film.film_id, film.title from film\n```\n## insertcolumns\n* **Description**:为INSERT补全表的列信息\n\n* **Original**:\n\n```sql\ninsert into film values(1,2,3,4,5)\n```\n\n* **Suggest**:\n\n```sql\ninsert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5)\n```\n## having\n* **Description**:将查询的 HAVING 子句改写为 WHERE 中的查询条件\n\n* **Original**:\n\n```sql\nSELECT state, COUNT(*) FROM Drivers GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state\n```\n\n* **Suggest**:\n\n```sql\nselect state, COUNT(*) from Drivers where state in ('GA', 'TX') group by state order by state asc\n```\n## orderbynull\n* **Description**:如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 ORDER BY NULL\n\n* **Original**:\n\n```sql\nSELECT sum(col1) FROM tbl GROUP BY col\n```\n\n* **Suggest**:\n\n```sql\nselect sum(col1) from tbl group by col order by null\n```\n## unionall\n* **Description**:可以接受重复的时间，使用 UNION ALL 替代 UNION 以提高查询效率\n\n* **Original**:\n\n```sql\nselect country_id from city union select country_id from country\n```\n\n* **Suggest**:\n\n```sql\nselect country_id from city union all select country_id from country\n```\n## or2in\n* **Description**:将同一列不同条件的 OR 查询转写为 IN 查询\n\n* **Original**:\n\n```sql\nselect country_id from city where col1 = 1 or (col2 = 1 or col2 = 2 ) or col1 = 3;\n```\n\n* **Suggest**:\n\n```sql\nselect country_id from city where (col2 in (1, 2)) or col1 in (1, 3);\n```\n## dmlorderby\n* **Description**:删除 DML 更新操作中无意义的 ORDER BY\n\n* **Original**:\n\n```sql\nDELETE FROM tbl WHERE col1=1 ORDER BY col\n```\n\n* **Suggest**:\n\n```sql\ndelete from tbl where col1 = 1\n```\n## distinctstar\n* **Description**:DISTINCT *对有主键的表没有意义，可以将DISTINCT删掉\n\n* **Original**:\n\n```sql\nSELECT DISTINCT * FROM film;\n```\n\n* **Suggest**:\n\n```sql\nSELECT * FROM film\n```\n## standard\n* **Description**:SQL标准化，如：关键字转换为小写\n\n* **Original**:\n\n```sql\nSELECT sum(col1) FROM tbl GROUP BY 1;\n```\n\n* **Suggest**:\n\n```sql\nselect sum(col1) from tbl group by 1\n```\n## mergealter\n* **Description**:合并同一张表的多条ALTER语句\n\n* **Original**:\n\n```sql\nALTER TABLE t2 DROP COLUMN c;ALTER TABLE t2 DROP COLUMN d;\n```\n\n* **Suggest**:\n\n```sql\nALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;\n```\n## alwaystrue\n* **Description**:删除无用的恒真判断条件\n\n* **Original**:\n\n```sql\nSELECT count(col) FROM tbl where 'a'= 'a' or ('b' = 'b' and a = 'b');\n```\n\n* **Suggest**:\n\n```sql\nselect count(col) from tbl where (a = 'b');\n```\n## countstar\n* **Description**:不建议使用COUNT(col)或COUNT(常量)，建议改写为COUNT(*)\n\n* **Original**:\n\n```sql\nSELECT count(col) FROM tbl GROUP BY 1;\n```\n\n* **Suggest**:\n\n```sql\nSELECT count(*) FROM tbl GROUP BY 1;\n```\n## innodb\n* **Description**:建表时建议使用InnoDB引擎，非 InnoDB 引擎表自动转 InnoDB\n\n* **Original**:\n\n```sql\nCREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT);\n```\n\n* **Suggest**:\n\n```sql\ncreate table t1 (\n\tid bigint(20) not null auto_increment\n) ENGINE=InnoDB;\n```\n## autoincrement\n* **Description**:将autoincrement初始化为1\n\n* **Original**:\n\n```sql\nCREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;\n```\n\n* **Suggest**:\n\n```sql\ncreate table t1(id bigint(20) not null auto_increment) ENGINE=InnoDB auto_increment=1;\n```\n## intwidth\n* **Description**:整型数据类型修改默认显示宽度\n\n* **Original**:\n\n```sql\ncreate table t1 (id int(20) not null auto_increment) ENGINE=InnoDB;\n```\n\n* **Suggest**:\n\n```sql\ncreate table t1 (id int(10) not null auto_increment) ENGINE=InnoDB;\n```\n## truncate\n* **Description**:不带 WHERE 条件的 DELETE 操作建议修改为 TRUNCATE\n\n* **Original**:\n\n```sql\nDELETE FROM tbl\n```\n\n* **Suggest**:\n\n```sql\ntruncate table tbl\n```\n## rmparenthesis\n* **Description**:去除没有意义的括号\n\n* **Original**:\n\n```sql\nselect col from table where (col = 1);\n```\n\n* **Suggest**:\n\n```sql\nselect col from table where col = 1;\n```\n## delimiter\n* **Description**:补全DELIMITER\n\n* **Original**:\n\n```sql\nuse sakila\n```\n\n* **Suggest**:\n\n```sql\nuse sakila;\n```\n"
  },
  {
    "path": "doc/roadmap.md",
    "content": "## 路线图\n\n* 语法支持方面，目前主要依赖vitess,TiDB对SQL语法的支持。\n* 目前仅针对MySQL语法族进行开发和测试，其他使用SQL的数据库产品暂不支持。\n* Profiling和Trace功能有待深入挖掘，供经验丰富的DBA分析使用。\n* 目前尚不支持直接线上自动执行评审通过的SQL，后续会努力支持。\n* 由于暂不支持线上自动执行，因此数据备份功能也未提供。\n* Vim, Sublime, Emacs等编辑器插件支持。\n* Currently, only support Chinese suggestion, if you can help us add multi-language support, it will be greatly appreciated.\n"
  },
  {
    "path": "doc/structure.md",
    "content": "\n# 体系架构\n\n![架构图](https://raw.githubusercontent.com/XiaoMi/soar/master/doc/images/structure.png)\n\nSOAR主要由语法解析器，集成环境，优化建议，重写逻辑，工具集五大模块组成。下面将对每个模块的作用及设计实现进行简述，更详细的算法及逻辑会在各个独立章节中详细讲解。\n\n## 语法解析和语法检查\n\n一条SQL从文件，标准输入或命令行参数等形式传递给SOAR后首先进入语法解析器，这里一开始我们选用了vitess的语法解析库作为SOAR的语法解析库，但随时需求的不断增加我们发现有些复杂需求使用vitess的语法解析实现起来比较逻辑比较复杂。于是参考业务其他数据库产品，我们引入了TiDB的语法解析器做为补充。我们发现这两个解析库还存在一定的盲区，于是又引入了MySQL执行返回结果作为多版本SQL方言的补充。大家也可以看到在语法解析器这里，SOAR的实现方案是松散的、可插拔的。SOAR并不直接维护庞大的语法解析库，它把各种优秀的语法解析库集成在一起，各取所长。\n\n## 集成环境\n\n集成环境区分`线上环境`和`测试环境`两种，分别用于解决不同场景下用户的SQL优化需求。一种常见的情况是已有表结构需要优化查询SQL的场景，可以从线上环境导出表结构和足够的采样数据到测试环境，在测试环境上就可以放心的执行各种高危操作而不用担心数据被损坏。另一种常见的情况是建一套全新的数据库，需要验证提供的数据字典中是否存在优化的可能。对于这种情况，很有可能你不需要知道线上环境在哪儿，完全只是想先试试看，如果报错了马上改对就是了。当然还有更多种组合的场景需求，将在[集成环境](http://github.com/XiaoMi/soar/blob/master/doc/environment.md)中介绍。\n\n## 优化建议\n\n目前SOAR可以提供的优化建议有基于启发式规则(通常也称之为经验)的优化建议，基于索引优化算法给出的索引优化建议，以及基于EXPLAIN信息给出的解读。\n\n### 启发式规则建议\n\n下面这段代码是启发式规则的的元数据结构，它由规则代号，危险等级，规则摘要，规则解释，SQL示例，建议位置，规则函数等7部分组成。每一条SQL经过语法解析后会经过数百个启发式规则的逐一检查，命中了的规则将会保存在一个叫heuristicSuggest的变量中传递下去，与其他优化建议合并输出。这里最核心的部分，也是代码最多的部分在heuristic.go，里面包含了所有的启发式规则实现的函数。所有的启发式规则列表保存在rules.go文件中。\n\n```Golang\n// Rule 评审规则元数据结构\ntype Rule struct {\n    Item     string                  `json:\"Item\"`     // 规则代号\n    Severity string                  `json:\"Severity\"` // 危险等级：L[0-8], 数字越大表示级别越高\n    Summary  string                  `json:\"Summary\"`  // 规则摘要\n    Content  string                  `json:\"Content\"`  // 规则解释\n    Case     string                  `json:\"Case\"`     // SQL示例\n    Position int                     `json:\"Position\"` // 建议所处SQL字符位置，默认0表示全局建议\n    Func     func(*Query4Audit) Rule `json:\"-\"`        // 函数名\n}\n```\n\n### 索引优化\n\n关于索引优化，数据库经过几十年的发展，DBA沉淀了很多宝贵的经验，怎样把这些感性的经验转化为覆盖全面、逻辑可推导的算法是这种模块最大的挑战。很幸运的是SOAR并不是第一个尝试做这类算法整理的产品，有很多前人的著作、论文、博客等的知识储备。毫不夸张的说，为了写成这个模块我们读了不下5百万字的著作和论文，还不包括网络上各种大神的博客，这些老师们的知识结晶收集整理在[鸣谢](http://github.com/XiaoMi/soar/blob/master/doc/thanks.md)章节。使用到的算法在[索引优化](http://github.com/XiaoMi/soar/blob/master/doc/indexing.md)章节有详细的描述，虽然在某些算法理解上可能还存在一定争议，很希望与同行们共同讨论，共同进步，不断完善SOAR的算法。\n\n### EXPLAIN解读\n\n做过SQL优化的人对EXPLAIN应该都不陌生，但对于新手来说要记住每一个列代表什么含义，每个关键字背后的奥秘是什么需要足够的脑容量来记忆才行。统计了一下SOAR只在EXPLAIN信息的注解一项差不多写了200行代码，按平均行长度120计算，算下来一个DBA要精通EXPLAIN优化就要记住不下2万字的文档。SOAR能帮每为DBA节约了这部分脑容量。不过关于EXPLAIN解读还远不止这些，想了解更多可以参考[EXPLAIN信息解读](http://github.com/XiaoMi/soar/blob/master/doc/explain.md)章节。\n\n## 重写逻辑\n\n上面提到的优化建议是我们早期实现的主要功能，早期的功能还只是停留在建议上，对于一些初级用户看到建议也不一定会改写。为了进一步简化SQL优化的成本，SOAR又进一步挖掘了自动SQL重写的功能。现在提供几十种常见场景下的SQL等价转写，不过相比SQL优化建议还有很大的改进空间。这部分的功能和逻辑将在[重写逻辑](http://github.com/XiaoMi/soar/blob/master/doc/rewrite.md)一章中详细说明。\n\n## 工具集\n\n除了SQL优化和改写以外，为了方便用户使用以及美化输出展现形式，SOAR还提供了一些辅助的小工具，比如markdown转HTML工具，SQL格式化输出工具等等。你可以在[常用命令](http://github.com/XiaoMi/soar/blob/master/doc/cheatsheet.md)中找到这些小工具的使用方法。\n"
  },
  {
    "path": "doc/thanks.md",
    "content": "## 鸣谢\n\n以下为SOAR的灵感及代码来源，我们站在伟人的肩膀上，让DBA的工作和生活更美好。\n\n* [vitess](https://github.com/vitessio/vitess)\n* [SQLAdvisor](https://github.com/Meituan-Dianping/SQLAdvisor)\n* [pt-query-advisor](https://www.percona.com/doc/percona-toolkit/2.1/pt-query-advisor.html)\n* [sqlcheck](https://github.com/jarulraj/sqlcheck)\n* [pg_idx_advisor](https://github.com/cohenjo/pg_idx_advisor)\n* [mysql-xplain-xplain](https://github.com/rap2hpoutre/mysql-xplain-xplain)\n* [explain-analyzer](https://github.com/Preetam/explain-analyzer)\n* [Explain](https://github.com/goghcrow/explain/blob/master/Explain.php)\n* [sql-beautify](https://github.com/jkramer/sql-beautify)\n* [go-mysql](https://github.com/percona/go-mysql)\n* [pretty](https://github.com/kr/pretty)\n* [golang_escape](https://github.com/liule/golang_escape)\n* [mymysql](https://github.com/ziutek/mymysql)\n* [beego/logs](https://github.com/astaxie/beego/logs)\n* [uniuri](https://github.com/dchest/uniuri)\n* [gjson](https://github.com/tidwall/gjson)\n\n## 参考博文\n\n* [MySQL Reference Manual Chapter 8 Optimization](https://dev.mysql.com/doc/refman/8.0/en/optimization.html)\n* [Indexing 101: Optimizing MySQL queries on a single table](https://www.percona.com/blog/2015/04/27/indexing-101-optimizing-mysql-queries-on-a-single-table/)\n* [MySQL: Building the best INDEX for a given SELECT](http://mysql.rjweb.org/doc.php/index_cookbook_mysql)\n* [MySQL INDEX Cookbook](http://mysql.rjweb.org/slides/cook.pdf)\n* [Random Sampling for Histogram Construction: How much is enough?](http://www.mathcs.emory.edu/~cheung/papers/StreamDB/Histogram/1998-Chaudhuri-Histo.pdf)\n* [10 Cool SQL Optimisations That do not Depend on the Cost Model](https://blog.jooq.org/2017/09/28/10-cool-sql-optimisations-that-do-not-depend-on-the-cost-model/)\n\n## 参考书目\n\n* 《高性能MySQL》/《High Performance MySQL》 \n* 《数据库索引设计与优化》/《Relational Database Index Design and the Optimizers》 \n* 《数据库系统概论》/《Database System Concepts》 \n* 《SQL反模式》/《SQL Antipatterns》 \n* 《数据库查询优化器的艺术》/《The Art of Database Query Optimizer》 \n* 《SQL优化最佳实践》/《SQL Optimization Best Practice》\n* 《SQL编程风格》/《Sql Programming Style》\n"
  },
  {
    "path": "doc/thanks_en.md",
    "content": "## Thanks\n\n以下为SOAR的灵感及代码来源，我们站在伟人的肩膀上，让DBA的工作和生活更美好。\n\n* [vitess](https://github.com/vitessio/vitess)\n* [SQLAdvisor](https://github.com/Meituan-Dianping/SQLAdvisor)\n* [pt-query-advisor](https://www.percona.com/doc/percona-toolkit/2.1/pt-query-advisor.html)\n* [sqlcheck](https://github.com/jarulraj/sqlcheck)\n* [pg_idx_advisor](https://github.com/cohenjo/pg_idx_advisor)\n* [mysql-xplain-xplain](https://github.com/rap2hpoutre/mysql-xplain-xplain)\n* [explain-analyzer](https://github.com/Preetam/explain-analyzer)\n* [Explain](https://github.com/goghcrow/explain/blob/master/Explain.php)\n* [sql-beautify](https://github.com/jkramer/sql-beautify)\n* [go-mysql](https://github.com/percona/go-mysql)\n* [pretty](https://github.com/kr/pretty)\n* [golang_escape](https://github.com/liule/golang_escape)\n* [mymysql](https://github.com/ziutek/mymysql)\n* [beego/logs](https://github.com/astaxie/beego/logs)\n* [uniuri](https://github.com/dchest/uniuri)\n* [gjson](https://github.com/tidwall/gjson)\n\n## Reference Articles\n\n* [MySQL Reference Manual Chapter 8 Optimization](https://dev.mysql.com/doc/refman/8.0/en/optimization.html)\n* [Indexing 101: Optimizing MySQL queries on a single table](https://www.percona.com/blog/2015/04/27/indexing-101-optimizing-mysql-queries-on-a-single-table/)\n* [MySQL: Building the best INDEX for a given SELECT](http://mysql.rjweb.org/doc.php/index_cookbook_mysql)\n* [MySQL INDEX Cookbook](http://mysql.rjweb.org/slides/cook.pdf)\n* [Random Sampling for Histogram Construction: How much is enough?](http://www.mathcs.emory.edu/~cheung/papers/StreamDB/Histogram/1998-Chaudhuri-Histo.pdf)\n* [10 Cool SQL Optimisations That do not Depend on the Cost Model](https://blog.jooq.org/2017/09/28/10-cool-sql-optimisations-that-do-not-depend-on-the-cost-model/)\n\n## Books\n\n* 《高性能MySQL》/《High Performance MySQL》 \n* 《数据库索引设计与优化》/《Relational Database Index Design and the Optimizers》 \n* 《数据库系统概论》/《Database System Concepts》 \n* 《SQL反模式》/《SQL Antipatterns》 \n* 《数据库查询优化器的艺术》/《The Art of Database Query Optimizer》 \n* 《SQL优化最佳实践》/《SQL Optimization Best Practice》\n* 《SQL编程风格》/《Sql Programming Style》\n"
  },
  {
    "path": "doc/themes/foghorn.css",
    "content": "\nhtml, body {\n        padding:1em;\n        margin:auto;\n        max-width:42em;\n        background:#fefefe;\n  }\nbody {\n  font: 1.3em \"Vollkorn\", Palatino, Times;\n  color: #333;\n  line-height: 1.4;\n  text-align: justify;\n  }\nheader, nav, article, footer {\n  width: 700px;\n  margin:0 auto;\n  }\narticle {\n  margin-top: 4em;\n  margin-bottom: 4em;\n  min-height: 400px;\n  }\nfooter {\n  margin-bottom:50px;\n  }\nvideo {\n  margin: 2em 0;\n  border:1px solid #ddd;\n  }\n\nnav {\n  font-size: .9em;\n  font-style: italic;\n  border-bottom: 1px solid #ddd;\n  padding: 1em 0;\n  }\nnav p {\n  margin: 0;\n  }\n\n/* Typography\n-------------------------------------------------------- */\n\nh1 {\n  margin-top: 0;\n  font-weight: normal;\n  }\nh2 {\n  font-weight: normal;\n  }\nh3 {\n  font-weight: normal;\n  font-style: italic;\n  margin-top:3em;\n  }\np {\n  margin-top:0;\n  -webkit-hypens:auto;\n  -moz-hypens:auto;\n  hyphens:auto;\n  }\nul {\n  list-style: square;\n  padding-left:1.2em;\n  }\nol {\n  padding-left:1.2em;\n  }\nblockquote {\n  margin-left: 1em;\n  padding-left: 1em;\n  border-left: 1px solid #ddd;\n  }\ncode {\n  font-family: \"Consolas\", \"Menlo\", \"Monaco\", monospace, serif;\n  font-size: .9em;\n  background: white;\n  }\na {\n  color: #2484c1;\n  text-decoration: none;\n  }\na:hover {\n  text-decoration: underline;\n  }\na img {\n  border:none;\n  }\nh1 a, h1 a:hover {\n  color: #333;\n  text-decoration: none;\n  }\nhr {\n  color : #ddd;\n  height : 1px;\n  margin: 2em 0;\n  border-top : solid 1px #ddd;\n  border-bottom : none;\n  border-left: 0;\n  border-right: 0;\n  }\np#heart{\n  font-size: 2em;\n  line-height: 1;\n  text-align: center;\n  color: #ccc;\n  }\n.red {\n  color:#B50000;\n  }\n\n/* Home Page\n--------------------------- */\n\nbody#index li {\n  margin-bottom: 1em;\n  }\n\n\n/* iPad\n-------------------------------------------------------- */\n@media only screen and (max-device-width: 1024px) {\nbody {\n  font-size: 120%;\n  line-height: 1.4;\n  }\n} /* @iPad */\n\n/* iPhone\n-------------------------------------------------------- */\n@media only screen and (max-device-width: 480px) {\nbody {\n  text-align: left;\n  }\narticle, footer {\n  width: auto;\n  }\narticle {\n  padding: 0 10px;\n  }\n} /* @iPhone */\n"
  },
  {
    "path": "doc/themes/ghostwriter.css",
    "content": "/* ============================================================ */\n/* Base */\n/* ============================================================ */\nhtml, body {\n  height: 100%;\n}\n\nbody {\n  background: #fefefe;\n  color: #424242;\n  font-family: \"Open Sans\", arial, sans-serif;\n  font-size: 18px;\n}\n\nh1, h2, h3, h4, h5, h6 {\n  margin-bottom: 33px;\n  text-transform: none;\n}\n\nh1 {\n  font-size: 26px;\n}\n\nh2 {\n  font-size: 24px;\n}\n\nh3 {\n  font-size: 20px;\n  margin-bottom: 20px;\n}\n\nh4 {\n  font-size: 18px;\n  margin-bottom: 18px;\n}\n\nh5 {\n  font-size: 16px;\n  margin-bottom: 15px;\n}\n\nh6 {\n  font-size: 14px;\n  margin-bottom: 12px;\n}\n\np {\n  line-height: 1.8;\n  margin: 0 0 30px;\n}\n\na {\n  color: #f03838;\n  text-decoration: none;\n}\n\nul, ol {\n  list-style-position: inside;\n  line-height: 1.8;\n  margin: 0 0 40px;\n  padding: 0;\n}\nul li, ol li {\n  margin: 0 0 10px;\n}\n\nblockquote {\n  border-left: 1px dotted #303030;\n  margin: 40px 0;\n  padding: 5px 30px;\n}\nblockquote p {\n  color: #AEADAD;\n  display: block;\n  font-style: italic;\n  margin: 0;\n  width: 100%;\n}\n\nimg {\n  display: block;\n  margin: 40px 0;\n  width: auto;\n  max-width: 100%;\n}\n\npre {\n  background: #F1F0EA;\n  border: 1px solid #DDDBCC;\n  border-radius: 3px;\n  margin: 0 0 40px;\n  padding: 15px 20px;\n}\n\n::selection {\n  background: #FFF5B8;\n  color: #000;\n  display: block;\n}\n\n::-moz-selection {\n  background: #FFF5B8;\n  color: #000;\n  display: block;\n}\n\n/* ============================================================ */\n/* General Appearance */\n/* ============================================================ */\n.container {\n  margin: 0 auto;\n  position: relative;\n  width: 100%;\n  max-width: 889px;\n}\n\n#wrapper {\n  height: auto;\n  min-height: 100%;\n  /* This must be the same as the height of the footer */\n  margin-bottom: -265px;\n}\n#wrapper:after {\n  content: \"\";\n  display: block;\n  /* This must be the same as the height of the footer */\n  height: 265px;\n}\n\n.button {\n  background: #303030;\n  border: none;\n  border-radius: 3px;\n  color: #FEFEFE;\n  font-size: 14px;\n  font-weight: 700;\n  padding: 10px 12px;\n  text-transform: uppercase;\n}\n.button:hover {\n  background: #f03838;\n}\n\n.button-square {\n  background: #f03838;\n  float: left;\n  margin: 0 0 0 10px;\n  padding: 8px;\n}\n.button-square:hover {\n  background: #303030;\n}\n\n/* ============================================================ */\n/* Site Header */\n/* ============================================================ */\n.site-header {\n  padding: 100px 0 35px;\n  overflow: auto;\n  text-align: center;\n  text-transform: uppercase;\n}\n\n.site-title-wrapper {\n  display: table;\n  margin: 0 auto;\n}\n\n.site-title {\n  float: left;\n  font-size: 14px;\n  font-weight: 600;\n  margin: 0;\n  text-transform: uppercase;\n}\n.site-title a {\n  float: left;\n  background: #f03838;\n  color: #FEFEFE;\n  padding: 5px 10px 6px;\n}\n.site-title a:hover {\n  background: #303030;\n}\n\n/* ============================================================ */\n/* Post */\n/* ============================================================ */\n.post {\n  margin: 0 40px;\n}\n\n.post-header {\n  border-bottom: 6px solid #303030;\n  margin: 0 0 50px;\n  padding: 0 0 80px;\n  text-align: center;\n  text-transform: uppercase;\n}\n\n.post-title {\n  font-size: 52px;\n  font-weight: 700;\n  margin: 15px 0;\n  text-transform: uppercase;\n}\n\n.post-date {\n  color: #AEADAD;\n  font-size: 14px;\n  font-weight: 600;\n  line-height: 1;\n  margin: 25px 0 0;\n}\n.post-date:after {\n  border-bottom: 1px dotted #303030;\n  content: \"\";\n  display: block;\n  margin: 40px auto 0;\n  width: 100px;\n}\n\n.post-content {\n  margin: 0 0 92px;\n}\n.post-content a:hover {\n  border-bottom: 1px dotted #f03838;\n  padding: 0 0 2px;\n}\n\n.post-tags {\n  color: #AEADAD;\n  font-size: 14px;\n}\n.post-tags span {\n  font-weight: 600;\n}\n\n.post-navigation {\n  display: table;\n  margin: 70px auto 100px;\n}\n\n.newer-posts,\n.older-posts {\n  float: left;\n  background: #f03838;\n  color: #FEFEFE;\n  font-size: 14px;\n  font-weight: 600;\n  margin: 0 5px;\n  padding: 5px 10px 6px;\n  text-transform: uppercase;\n}\n.newer-posts:hover,\n.older-posts:hover {\n  background: #303030;\n}\n\n.page-number {\n  display: none;\n}\n\n/* ============================================================ */\n/* Post Index */\n/* ============================================================ */\n.post-list {\n  border-top: 6px solid #303030;\n  list-style: none;\n  margin: 80px 40px 0;\n  padding: 35px 0 0;\n}\n\n.post-stub {\n  border-bottom: 1px dotted #303030;\n  margin: 0;\n}\n.post-stub:first-child {\n  padding-top: 0;\n}\n.post-stub a {\n  -webkit-transition: all 0.2s ease-in-out;\n  -moz-transition: all 0.2s ease-in-out;\n  transition: all 0.2s ease-in-out;\n  display: block;\n  color: #424242;\n  padding: 20px 5px;\n}\n.post-stub a:hover {\n  background: #FCF5F5;\n  color: #f03838;\n  padding: 20px 12px;\n}\n\n.post-stub-title {\n  display: inline-block;\n  margin: 0;\n  text-transform: none;\n}\n\n.post-stub-date {\n  display: inline-block;\n}\n.post-stub-date:before {\n  content: \"/ \";\n}\n\n.next-posts-link a,\n.previous-posts-link a {\n  display: block;\n  padding: 8px 11px;\n}\n\n/* ============================================================ */\n/* Icons */\n/* ============================================================ */\n.icon {\n  background-size: 14px 38px;\n  display: block;\n  height: 38px;\n  width: 14px;\n}\n\n.icon-menu {\n  background-position: 0 0;\n  height: 14px;\n  width: 14px;\n}\n\n.icon-up {\n  background-position: 0 -15px;\n  height: 8px;\n  width: 14px;\n}\n\n.icon-rss {\n  background-position: 0 -24px;\n  height: 14px;\n  width: 14px;\n}\n\n/* ============================================================ */\n/* Footer */\n/* ============================================================ */\n.footer {\n  background: #303030;\n  color: #D3D3D3;\n  height: 265px;\n  overflow: auto;\n}\n.footer .site-title-wrapper {\n  margin: 80px auto 35px;\n}\n.footer .site-title a:hover,\n.footer .button-square:hover {\n  background: #121212;\n}\n\n.button-jump-top {\n  padding-top: 11px;\n  padding-bottom: 11px;\n}\n\n.footer-copyright {\n  color: #656565;\n  font-size: 14px;\n  margin: 0;\n  text-align: center;\n  text-transform: uppercase;\n}\n.footer-copyright a {\n  color: #656565;\n  font-weight: 700;\n}\n.footer-copyright a:hover {\n  color: #FEFEFE;\n}\n\n/* ============================================================ */\n/* NProgress */\n/* ============================================================ */\n#nprogress .bar {\n  background: #f03838;\n}\n\n#nprogress .peg {\n  box-shadow: 0 0 10px #f03838, 0 0 5px #f03838;\n}\n\n#nprogress .spinner-icon {\n  border-top-color: #f03838;\n  border-left-color: #f03838;\n}\n\n/* ============================================================ */\n/* Media Queries */\n/* ============================================================ */\n@media only screen and (max-width: 600px) {\n  .post-stub-title {\n    display: block;\n  }\n\n  .post-stub-date:before {\n    content: \"\";\n    display: block;\n  }\n}\n@media only screen and (max-width: 400px) {\n  .post-title {\n    font-size: 32px;\n  }\n}\n"
  },
  {
    "path": "doc/themes/github-dark.css",
    "content": "@font-face {\n  font-family: octicons-link;\n  src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');\n}\n\n.markdown-body {\n  -ms-text-size-adjust: 100%;\n  -webkit-text-size-adjust: 100%;\n  line-height: 1.5;\n  color: #24292e;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n  font-size: 16px;\n  line-height: 1.5;\n  word-wrap: break-word;\n}\n\n.markdown-body .pl-c {\n  color: #6a737d;\n}\n\n.markdown-body .pl-c1,\n.markdown-body .pl-s .pl-v {\n  color: #005cc5;\n}\n\n.markdown-body .pl-e,\n.markdown-body .pl-en {\n  color: #6f42c1;\n}\n\n.markdown-body .pl-smi,\n.markdown-body .pl-s .pl-s1 {\n  color: #24292e;\n}\n\n.markdown-body .pl-ent {\n  color: #22863a;\n}\n\n.markdown-body .pl-k {\n  color: #d73a49;\n}\n\n.markdown-body .pl-s,\n.markdown-body .pl-pds,\n.markdown-body .pl-s .pl-pse .pl-s1,\n.markdown-body .pl-sr,\n.markdown-body .pl-sr .pl-cce,\n.markdown-body .pl-sr .pl-sre,\n.markdown-body .pl-sr .pl-sra {\n  color: #032f62;\n}\n\n.markdown-body .pl-v,\n.markdown-body .pl-smw {\n  color: #e36209;\n}\n\n.markdown-body .pl-bu {\n  color: #b31d28;\n}\n\n.markdown-body .pl-ii {\n  color: #fafbfc;\n  background-color: #b31d28;\n}\n\n.markdown-body .pl-c2 {\n  color: #fafbfc;\n  background-color: #d73a49;\n}\n\n.markdown-body .pl-c2::before {\n  content: \"^M\";\n}\n\n.markdown-body .pl-sr .pl-cce {\n  font-weight: bold;\n  color: #22863a;\n}\n\n.markdown-body .pl-ml {\n  color: #735c0f;\n}\n\n.markdown-body .pl-mh,\n.markdown-body .pl-mh .pl-en,\n.markdown-body .pl-ms {\n  font-weight: bold;\n  color: #005cc5;\n}\n\n.markdown-body .pl-mi {\n  font-style: italic;\n  color: #24292e;\n}\n\n.markdown-body .pl-mb {\n  font-weight: bold;\n  color: #24292e;\n}\n\n.markdown-body .pl-md {\n  color: #b31d28;\n  background-color: #ffeef0;\n}\n\n.markdown-body .pl-mi1 {\n  color: #22863a;\n  background-color: #f0fff4;\n}\n\n.markdown-body .pl-mc {\n  color: #e36209;\n  background-color: #ffebda;\n}\n\n.markdown-body .pl-mi2 {\n  color: #f6f8fa;\n  background-color: #005cc5;\n}\n\n.markdown-body .pl-mdr {\n  font-weight: bold;\n  color: #6f42c1;\n}\n\n.markdown-body .pl-ba {\n  color: #586069;\n}\n\n.markdown-body .pl-sg {\n  color: #959da5;\n}\n\n.markdown-body .pl-corl {\n  text-decoration: underline;\n  color: #032f62;\n}\n\n.markdown-body .octicon {\n  display: inline-block;\n  vertical-align: text-top;\n  fill: currentColor;\n}\n\n.markdown-body a {\n  background-color: transparent;\n  -webkit-text-decoration-skip: objects;\n}\n\n.markdown-body a:active,\n.markdown-body a:hover {\n  outline-width: 0;\n}\n\n.markdown-body strong {\n  font-weight: inherit;\n}\n\n.markdown-body strong {\n  font-weight: bolder;\n}\n\n.markdown-body h1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n.markdown-body img {\n  border-style: none;\n}\n\n.markdown-body svg:not(:root) {\n  overflow: hidden;\n}\n\n.markdown-body code,\n.markdown-body kbd,\n.markdown-body pre {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\n\n.markdown-body hr {\n  box-sizing: content-box;\n  height: 0;\n  overflow: visible;\n}\n\n.markdown-body input {\n  font: inherit;\n  margin: 0;\n}\n\n.markdown-body input {\n  overflow: visible;\n}\n\n.markdown-body [type=\"checkbox\"] {\n  box-sizing: border-box;\n  padding: 0;\n}\n\n.markdown-body * {\n  box-sizing: border-box;\n}\n\n.markdown-body input {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\n\n.markdown-body a {\n  color: #0366d6;\n  text-decoration: none;\n}\n\n.markdown-body a:hover {\n  text-decoration: underline;\n}\n\n.markdown-body strong {\n  font-weight: 600;\n}\n\n.markdown-body hr {\n  height: 0;\n  margin: 15px 0;\n  overflow: hidden;\n  background: transparent;\n  border: 0;\n  border-bottom: 1px solid #dfe2e5;\n}\n\n.markdown-body hr::before {\n  display: table;\n  content: \"\";\n}\n\n.markdown-body hr::after {\n  display: table;\n  clear: both;\n  content: \"\";\n}\n\n.markdown-body table {\n  border-spacing: 0;\n  border-collapse: collapse;\n}\n\n.markdown-body td,\n.markdown-body th {\n  padding: 0;\n}\n\n.markdown-body h1,\n.markdown-body h2,\n.markdown-body h3,\n.markdown-body h4,\n.markdown-body h5,\n.markdown-body h6 {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body h1 {\n  font-size: 32px;\n  font-weight: 600;\n}\n\n.markdown-body h2 {\n  font-size: 24px;\n  font-weight: 600;\n}\n\n.markdown-body h3 {\n  font-size: 20px;\n  font-weight: 600;\n}\n\n.markdown-body h4 {\n  font-size: 16px;\n  font-weight: 600;\n}\n\n.markdown-body h5 {\n  font-size: 14px;\n  font-weight: 600;\n}\n\n.markdown-body h6 {\n  font-size: 12px;\n  font-weight: 600;\n}\n\n.markdown-body p {\n  margin-top: 0;\n  margin-bottom: 10px;\n}\n\n.markdown-body blockquote {\n  margin: 0;\n}\n\n.markdown-body ul,\n.markdown-body ol {\n  padding-left: 0;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body ol ol,\n.markdown-body ul ol {\n  list-style-type: lower-roman;\n}\n\n.markdown-body ul ul ol,\n.markdown-body ul ol ol,\n.markdown-body ol ul ol,\n.markdown-body ol ol ol {\n  list-style-type: lower-alpha;\n}\n\n.markdown-body dd {\n  margin-left: 0;\n}\n\n.markdown-body code {\n  font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 12px;\n}\n\n.markdown-body pre {\n  margin-top: 0;\n  margin-bottom: 0;\n  font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 12px;\n}\n\n.markdown-body .octicon {\n  vertical-align: text-bottom;\n}\n\n.markdown-body .pl-0 {\n  padding-left: 0 !important;\n}\n\n.markdown-body .pl-1 {\n  padding-left: 4px !important;\n}\n\n.markdown-body .pl-2 {\n  padding-left: 8px !important;\n}\n\n.markdown-body .pl-3 {\n  padding-left: 16px !important;\n}\n\n.markdown-body .pl-4 {\n  padding-left: 24px !important;\n}\n\n.markdown-body .pl-5 {\n  padding-left: 32px !important;\n}\n\n.markdown-body .pl-6 {\n  padding-left: 40px !important;\n}\n\n.markdown-body::before {\n  display: table;\n  content: \"\";\n}\n\n.markdown-body::after {\n  display: table;\n  clear: both;\n  content: \"\";\n}\n\n.markdown-body>*:first-child {\n  margin-top: 0 !important;\n}\n\n.markdown-body>*:last-child {\n  margin-bottom: 0 !important;\n}\n\n.markdown-body a:not([href]) {\n  color: inherit;\n  text-decoration: none;\n}\n\n.markdown-body .anchor {\n  float: left;\n  padding-right: 4px;\n  margin-left: -20px;\n  line-height: 1;\n}\n\n.markdown-body .anchor:focus {\n  outline: none;\n}\n\n.markdown-body p,\n.markdown-body blockquote,\n.markdown-body ul,\n.markdown-body ol,\n.markdown-body dl,\n.markdown-body table,\n.markdown-body pre {\n  margin-top: 0;\n  margin-bottom: 16px;\n}\n\n.markdown-body hr {\n  height: 0.25em;\n  padding: 0;\n  margin: 24px 0;\n  background-color: #e1e4e8;\n  border: 0;\n}\n\n.markdown-body blockquote {\n  padding: 0 1em;\n  color: #6a737d;\n  border-left: 0.25em solid #dfe2e5;\n}\n\n.markdown-body blockquote>:first-child {\n  margin-top: 0;\n}\n\n.markdown-body blockquote>:last-child {\n  margin-bottom: 0;\n}\n\n.markdown-body kbd {\n  display: inline-block;\n  padding: 3px 5px;\n  font-size: 11px;\n  line-height: 10px;\n  color: #444d56;\n  vertical-align: middle;\n  background-color: #fafbfc;\n  border: solid 1px #c6cbd1;\n  border-bottom-color: #959da5;\n  border-radius: 3px;\n  box-shadow: inset 0 -1px 0 #959da5;\n}\n\n.markdown-body h1,\n.markdown-body h2,\n.markdown-body h3,\n.markdown-body h4,\n.markdown-body h5,\n.markdown-body h6 {\n  margin-top: 24px;\n  margin-bottom: 16px;\n  font-weight: 600;\n  line-height: 1.25;\n}\n\n.markdown-body h1 .octicon-link,\n.markdown-body h2 .octicon-link,\n.markdown-body h3 .octicon-link,\n.markdown-body h4 .octicon-link,\n.markdown-body h5 .octicon-link,\n.markdown-body h6 .octicon-link {\n  color: #1b1f23;\n  vertical-align: middle;\n  visibility: hidden;\n}\n\n.markdown-body h1:hover .anchor,\n.markdown-body h2:hover .anchor,\n.markdown-body h3:hover .anchor,\n.markdown-body h4:hover .anchor,\n.markdown-body h5:hover .anchor,\n.markdown-body h6:hover .anchor {\n  text-decoration: none;\n}\n\n.markdown-body h1:hover .anchor .octicon-link,\n.markdown-body h2:hover .anchor .octicon-link,\n.markdown-body h3:hover .anchor .octicon-link,\n.markdown-body h4:hover .anchor .octicon-link,\n.markdown-body h5:hover .anchor .octicon-link,\n.markdown-body h6:hover .anchor .octicon-link {\n  visibility: visible;\n}\n\n.markdown-body h1 {\n  padding-bottom: 0.3em;\n  font-size: 2em;\n  border-bottom: 1px solid #eaecef;\n}\n\n.markdown-body h2 {\n  padding-bottom: 0.3em;\n  font-size: 1.5em;\n  border-bottom: 1px solid #eaecef;\n}\n\n.markdown-body h3 {\n  font-size: 1.25em;\n}\n\n.markdown-body h4 {\n  font-size: 1em;\n}\n\n.markdown-body h5 {\n  font-size: 0.875em;\n}\n\n.markdown-body h6 {\n  font-size: 0.85em;\n  color: #6a737d;\n}\n\n.markdown-body ul,\n.markdown-body ol {\n  padding-left: 2em;\n}\n\n.markdown-body ul ul,\n.markdown-body ul ol,\n.markdown-body ol ol,\n.markdown-body ol ul {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body li>p {\n  margin-top: 16px;\n}\n\n.markdown-body li+li {\n  margin-top: 0.25em;\n}\n\n.markdown-body dl {\n  padding: 0;\n}\n\n.markdown-body dl dt {\n  padding: 0;\n  margin-top: 16px;\n  font-size: 1em;\n  font-style: italic;\n  font-weight: 600;\n}\n\n.markdown-body dl dd {\n  padding: 0 16px;\n  margin-bottom: 16px;\n}\n\n.markdown-body table {\n  display: block;\n  width: 100%;\n  overflow: auto;\n}\n\n.markdown-body table th {\n  font-weight: 600;\n}\n\n.markdown-body table th,\n.markdown-body table td {\n  padding: 6px 13px;\n  border: 1px solid #dfe2e5;\n}\n\n.markdown-body table tr {\n  background-color: #fff;\n  border-top: 1px solid #c6cbd1;\n}\n\n.markdown-body table tr:nth-child(2n) {\n  background-color: #f6f8fa;\n}\n\n.markdown-body img {\n  max-width: 100%;\n  box-sizing: content-box;\n  background-color: #fff;\n}\n\n.markdown-body img[align=right] {\n  padding-left: 20px;\n}\n\n.markdown-body img[align=left] {\n  padding-right: 20px;\n}\n\n.markdown-body code {\n  padding: 0;\n  padding-top: 0.2em;\n  padding-bottom: 0.2em;\n  margin: 0;\n  font-size: 85%;\n  background-color: rgba(27,31,35,0.05);\n  border-radius: 3px;\n}\n\n.markdown-body code::before,\n.markdown-body code::after {\n  letter-spacing: -0.2em;\n  content: \"\\00a0\";\n}\n\n.markdown-body pre {\n  word-wrap: normal;\n}\n\n.markdown-body pre>code {\n  padding: 0;\n  margin: 0;\n  font-size: 100%;\n  word-break: normal;\n  white-space: pre;\n  background: transparent;\n  border: 0;\n}\n\n.markdown-body .highlight {\n  margin-bottom: 16px;\n}\n\n.markdown-body .highlight pre {\n  margin-bottom: 0;\n  word-break: normal;\n}\n\n.markdown-body .highlight pre,\n.markdown-body pre {\n  padding: 16px;\n  overflow: auto;\n  font-size: 85%;\n  line-height: 1.45;\n  background-color: #f6f8fa;\n  border-radius: 3px;\n}\n\n.markdown-body pre code {\n  display: inline;\n  max-width: auto;\n  padding: 0;\n  margin: 0;\n  overflow: visible;\n  line-height: inherit;\n  word-wrap: normal;\n  background-color: transparent;\n  border: 0;\n}\n\n.markdown-body pre code::before,\n.markdown-body pre code::after {\n  content: normal;\n}\n\n.markdown-body .full-commit .btn-outline:not(:disabled):hover {\n  color: #005cc5;\n  border-color: #005cc5;\n}\n\n.markdown-body kbd {\n  display: inline-block;\n  padding: 3px 5px;\n  font: 11px \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  line-height: 10px;\n  color: #444d56;\n  vertical-align: middle;\n  background-color: #fafbfc;\n  border: solid 1px #d1d5da;\n  border-bottom-color: #c6cbd1;\n  border-radius: 3px;\n  box-shadow: inset 0 -1px 0 #c6cbd1;\n}\n\n.markdown-body :checked+.radio-label {\n  position: relative;\n  z-index: 1;\n  border-color: #0366d6;\n}\n\n.markdown-body .task-list-item {\n  list-style-type: none;\n}\n\n.markdown-body .task-list-item+.task-list-item {\n  margin-top: 3px;\n}\n\n.markdown-body .task-list-item input {\n  margin: 0 0.2em 0.25em -1.6em;\n  vertical-align: middle;\n}\n\n.markdown-body hr {\n  border-bottom-color: #eee;\n}\n\n/*Markdown Viewer*/\n.markdown-body summary:hover { cursor: pointer; }\n.markdown-body ul li p { margin: 0; }\n\n/*GitHub Dark*/\n\nbody {\n  background: #181818;\n}\n\n.markdown-body {\n  color: #c0c0c0 !important;\n  background: #181818 !important;\n  border-color: #484848 !important;\n}\n.markdown-body table { color: #c0c0c0 !important; }\n.markdown-body table th { border-color: #343434 !important; }\n.markdown-body table td { border-color: #343434 !important; }\n.markdown-body table tr {\n  background: #141414 !important;\n  border-color: #343434 !important;\n}\n.markdown-body table tr:nth-child(2n) { background: #181818 !important; }\n.markdown-body hr { background: #383838 !important; }\n.markdown-body h1,\n.markdown-body h2 { border-color: #343434 !important;  }\n.markdown-body h1, .markdown-body h2,\n.markdown-body h3, .markdown-body h4,\n.markdown-body .octicon-link { color: #e0e0e0 !important; }\n.markdown-body blockquote strong { color: #808080 !important; }\n.markdown-body blockquote { border-color: #343434 !important; }\n.markdown-body blockquote,\n.markdown-body blockquote code { color: #666 !important; }\n.markdown-body code, .markdown-body tt, .markdown-body pre,\n.markdown-body .highlight pre, body.blog pre {\n  border: 1px solid rgba(255,255,255,.1) !important;\n}\n.markdown-body code, .markdown-body tt { background: #202020 !important; }\n.markdown-body pre {\n  background: #141414 !important; color: #ccc !important;\n}\n.markdown-body pre code { background: none !important; border: 0 !important; }\n.markdown-body code[class*=\"language-\"] {\n  color: #c0c0c0 !important;\n  text-shadow: none !important;\n}\n.markdown-body code[class*=\"language-\"] .operator,\n.markdown-body code[class*=\"language-\"] .string {\n  background: none !important;\n}\n.markdown-body a[href*=\"/labels/\"],\n.markdown-body a:not([href*=\"/labels/\"]),\n.markdown-body blockquote a code { color: #4183C4 !important; }\n\n.markdown-body summary:hover { cursor: pointer; }\n"
  },
  {
    "path": "doc/themes/github.css",
    "content": "@font-face {\n  font-family: octicons-link;\n  src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');\n}\n\n.markdown-body {\n  -ms-text-size-adjust: 100%;\n  -webkit-text-size-adjust: 100%;\n  line-height: 1.5;\n  color: #24292e;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n  font-size: 16px;\n  line-height: 1.5;\n  word-wrap: break-word;\n}\n\n.markdown-body .pl-c {\n  color: #6a737d;\n}\n\n.markdown-body .pl-c1,\n.markdown-body .pl-s .pl-v {\n  color: #005cc5;\n}\n\n.markdown-body .pl-e,\n.markdown-body .pl-en {\n  color: #6f42c1;\n}\n\n.markdown-body .pl-smi,\n.markdown-body .pl-s .pl-s1 {\n  color: #24292e;\n}\n\n.markdown-body .pl-ent {\n  color: #22863a;\n}\n\n.markdown-body .pl-k {\n  color: #d73a49;\n}\n\n.markdown-body .pl-s,\n.markdown-body .pl-pds,\n.markdown-body .pl-s .pl-pse .pl-s1,\n.markdown-body .pl-sr,\n.markdown-body .pl-sr .pl-cce,\n.markdown-body .pl-sr .pl-sre,\n.markdown-body .pl-sr .pl-sra {\n  color: #032f62;\n}\n\n.markdown-body .pl-v,\n.markdown-body .pl-smw {\n  color: #e36209;\n}\n\n.markdown-body .pl-bu {\n  color: #b31d28;\n}\n\n.markdown-body .pl-ii {\n  color: #fafbfc;\n  background-color: #b31d28;\n}\n\n.markdown-body .pl-c2 {\n  color: #fafbfc;\n  background-color: #d73a49;\n}\n\n.markdown-body .pl-c2::before {\n  content: \"^M\";\n}\n\n.markdown-body .pl-sr .pl-cce {\n  font-weight: bold;\n  color: #22863a;\n}\n\n.markdown-body .pl-ml {\n  color: #735c0f;\n}\n\n.markdown-body .pl-mh,\n.markdown-body .pl-mh .pl-en,\n.markdown-body .pl-ms {\n  font-weight: bold;\n  color: #005cc5;\n}\n\n.markdown-body .pl-mi {\n  font-style: italic;\n  color: #24292e;\n}\n\n.markdown-body .pl-mb {\n  font-weight: bold;\n  color: #24292e;\n}\n\n.markdown-body .pl-md {\n  color: #b31d28;\n  background-color: #ffeef0;\n}\n\n.markdown-body .pl-mi1 {\n  color: #22863a;\n  background-color: #f0fff4;\n}\n\n.markdown-body .pl-mc {\n  color: #e36209;\n  background-color: #ffebda;\n}\n\n.markdown-body .pl-mi2 {\n  color: #f6f8fa;\n  background-color: #005cc5;\n}\n\n.markdown-body .pl-mdr {\n  font-weight: bold;\n  color: #6f42c1;\n}\n\n.markdown-body .pl-ba {\n  color: #586069;\n}\n\n.markdown-body .pl-sg {\n  color: #959da5;\n}\n\n.markdown-body .pl-corl {\n  text-decoration: underline;\n  color: #032f62;\n}\n\n.markdown-body .octicon {\n  display: inline-block;\n  vertical-align: text-top;\n  fill: currentColor;\n}\n\n.markdown-body a {\n  background-color: transparent;\n  -webkit-text-decoration-skip: objects;\n}\n\n.markdown-body a:active,\n.markdown-body a:hover {\n  outline-width: 0;\n}\n\n.markdown-body strong {\n  font-weight: inherit;\n}\n\n.markdown-body strong {\n  font-weight: bolder;\n}\n\n.markdown-body h1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n.markdown-body img {\n  border-style: none;\n}\n\n.markdown-body svg:not(:root) {\n  overflow: hidden;\n}\n\n.markdown-body code,\n.markdown-body kbd,\n.markdown-body pre {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\n\n.markdown-body hr {\n  box-sizing: content-box;\n  height: 0;\n  overflow: visible;\n}\n\n.markdown-body input {\n  font: inherit;\n  margin: 0;\n}\n\n.markdown-body input {\n  overflow: visible;\n}\n\n.markdown-body [type=\"checkbox\"] {\n  box-sizing: border-box;\n  padding: 0;\n}\n\n.markdown-body * {\n  box-sizing: border-box;\n}\n\n.markdown-body input {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\n\n.markdown-body a {\n  color: #0366d6;\n  text-decoration: none;\n}\n\n.markdown-body a:hover {\n  text-decoration: underline;\n}\n\n.markdown-body strong {\n  font-weight: 600;\n}\n\n.markdown-body hr {\n  height: 0;\n  margin: 15px 0;\n  overflow: hidden;\n  background: transparent;\n  border: 0;\n  border-bottom: 1px solid #dfe2e5;\n}\n\n.markdown-body hr::before {\n  display: table;\n  content: \"\";\n}\n\n.markdown-body hr::after {\n  display: table;\n  clear: both;\n  content: \"\";\n}\n\n.markdown-body table {\n  border-spacing: 0;\n  border-collapse: collapse;\n}\n\n.markdown-body td,\n.markdown-body th {\n  padding: 0;\n}\n\n.markdown-body h1,\n.markdown-body h2,\n.markdown-body h3,\n.markdown-body h4,\n.markdown-body h5,\n.markdown-body h6 {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body h1 {\n  font-size: 32px;\n  font-weight: 600;\n}\n\n.markdown-body h2 {\n  font-size: 24px;\n  font-weight: 600;\n}\n\n.markdown-body h3 {\n  font-size: 20px;\n  font-weight: 600;\n}\n\n.markdown-body h4 {\n  font-size: 16px;\n  font-weight: 600;\n}\n\n.markdown-body h5 {\n  font-size: 14px;\n  font-weight: 600;\n}\n\n.markdown-body h6 {\n  font-size: 12px;\n  font-weight: 600;\n}\n\n.markdown-body p {\n  margin-top: 0;\n  margin-bottom: 10px;\n}\n\n.markdown-body blockquote {\n  margin: 0;\n}\n\n.markdown-body ul,\n.markdown-body ol {\n  padding-left: 0;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body ol ol,\n.markdown-body ul ol {\n  list-style-type: lower-roman;\n}\n\n.markdown-body ul ul ol,\n.markdown-body ul ol ol,\n.markdown-body ol ul ol,\n.markdown-body ol ol ol {\n  list-style-type: lower-alpha;\n}\n\n.markdown-body dd {\n  margin-left: 0;\n}\n\n.markdown-body code {\n  font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 12px;\n}\n\n.markdown-body pre {\n  margin-top: 0;\n  margin-bottom: 0;\n  font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 12px;\n}\n\n.markdown-body .octicon {\n  vertical-align: text-bottom;\n}\n\n.markdown-body .pl-0 {\n  padding-left: 0 !important;\n}\n\n.markdown-body .pl-1 {\n  padding-left: 4px !important;\n}\n\n.markdown-body .pl-2 {\n  padding-left: 8px !important;\n}\n\n.markdown-body .pl-3 {\n  padding-left: 16px !important;\n}\n\n.markdown-body .pl-4 {\n  padding-left: 24px !important;\n}\n\n.markdown-body .pl-5 {\n  padding-left: 32px !important;\n}\n\n.markdown-body .pl-6 {\n  padding-left: 40px !important;\n}\n\n.markdown-body::before {\n  display: table;\n  content: \"\";\n}\n\n.markdown-body::after {\n  display: table;\n  clear: both;\n  content: \"\";\n}\n\n.markdown-body>*:first-child {\n  margin-top: 0 !important;\n}\n\n.markdown-body>*:last-child {\n  margin-bottom: 0 !important;\n}\n\n.markdown-body a:not([href]) {\n  color: inherit;\n  text-decoration: none;\n}\n\n.markdown-body .anchor {\n  float: left;\n  padding-right: 4px;\n  margin-left: -20px;\n  line-height: 1;\n}\n\n.markdown-body .anchor:focus {\n  outline: none;\n}\n\n.markdown-body p,\n.markdown-body blockquote,\n.markdown-body ul,\n.markdown-body ol,\n.markdown-body dl,\n.markdown-body table,\n.markdown-body pre {\n  margin-top: 0;\n  margin-bottom: 16px;\n}\n\n.markdown-body hr {\n  height: 0.25em;\n  padding: 0;\n  margin: 24px 0;\n  background-color: #e1e4e8;\n  border: 0;\n}\n\n.markdown-body blockquote {\n  padding: 0 1em;\n  color: #6a737d;\n  border-left: 0.25em solid #dfe2e5;\n}\n\n.markdown-body blockquote>:first-child {\n  margin-top: 0;\n}\n\n.markdown-body blockquote>:last-child {\n  margin-bottom: 0;\n}\n\n.markdown-body kbd {\n  display: inline-block;\n  padding: 3px 5px;\n  font-size: 11px;\n  line-height: 10px;\n  color: #444d56;\n  vertical-align: middle;\n  background-color: #fafbfc;\n  border: solid 1px #c6cbd1;\n  border-bottom-color: #959da5;\n  border-radius: 3px;\n  box-shadow: inset 0 -1px 0 #959da5;\n}\n\n.markdown-body h1,\n.markdown-body h2,\n.markdown-body h3,\n.markdown-body h4,\n.markdown-body h5,\n.markdown-body h6 {\n  margin-top: 24px;\n  margin-bottom: 16px;\n  font-weight: 600;\n  line-height: 1.25;\n}\n\n.markdown-body h1 .octicon-link,\n.markdown-body h2 .octicon-link,\n.markdown-body h3 .octicon-link,\n.markdown-body h4 .octicon-link,\n.markdown-body h5 .octicon-link,\n.markdown-body h6 .octicon-link {\n  color: #1b1f23;\n  vertical-align: middle;\n  visibility: hidden;\n}\n\n.markdown-body h1:hover .anchor,\n.markdown-body h2:hover .anchor,\n.markdown-body h3:hover .anchor,\n.markdown-body h4:hover .anchor,\n.markdown-body h5:hover .anchor,\n.markdown-body h6:hover .anchor {\n  text-decoration: none;\n}\n\n.markdown-body h1:hover .anchor .octicon-link,\n.markdown-body h2:hover .anchor .octicon-link,\n.markdown-body h3:hover .anchor .octicon-link,\n.markdown-body h4:hover .anchor .octicon-link,\n.markdown-body h5:hover .anchor .octicon-link,\n.markdown-body h6:hover .anchor .octicon-link {\n  visibility: visible;\n}\n\n.markdown-body h1 {\n  padding-bottom: 0.3em;\n  font-size: 2em;\n  border-bottom: 1px solid #eaecef;\n}\n\n.markdown-body h2 {\n  padding-bottom: 0.3em;\n  font-size: 1.5em;\n  border-bottom: 1px solid #eaecef;\n}\n\n.markdown-body h3 {\n  font-size: 1.25em;\n}\n\n.markdown-body h4 {\n  font-size: 1em;\n}\n\n.markdown-body h5 {\n  font-size: 0.875em;\n}\n\n.markdown-body h6 {\n  font-size: 0.85em;\n  color: #6a737d;\n}\n\n.markdown-body ul,\n.markdown-body ol {\n  padding-left: 2em;\n}\n\n.markdown-body ul ul,\n.markdown-body ul ol,\n.markdown-body ol ol,\n.markdown-body ol ul {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body li>p {\n  margin-top: 16px;\n}\n\n.markdown-body li+li {\n  margin-top: 0.25em;\n}\n\n.markdown-body dl {\n  padding: 0;\n}\n\n.markdown-body dl dt {\n  padding: 0;\n  margin-top: 16px;\n  font-size: 1em;\n  font-style: italic;\n  font-weight: 600;\n}\n\n.markdown-body dl dd {\n  padding: 0 16px;\n  margin-bottom: 16px;\n}\n\n.markdown-body table {\n  display: block;\n  width: 100%;\n  overflow: auto;\n}\n\n.markdown-body table th {\n  font-weight: 600;\n}\n\n.markdown-body table th,\n.markdown-body table td {\n  padding: 6px 13px;\n  border: 1px solid #dfe2e5;\n}\n\n.markdown-body table tr {\n  background-color: #fff;\n  border-top: 1px solid #c6cbd1;\n}\n\n.markdown-body table tr:nth-child(2n) {\n  background-color: #f6f8fa;\n}\n\n.markdown-body img {\n  max-width: 100%;\n  box-sizing: content-box;\n  background-color: #fff;\n}\n\n.markdown-body img[align=right] {\n  padding-left: 20px;\n}\n\n.markdown-body img[align=left] {\n  padding-right: 20px;\n}\n\n.markdown-body code {\n  padding: 0;\n  padding-top: 0.2em;\n  padding-bottom: 0.2em;\n  margin: 0;\n  font-size: 85%;\n  background-color: rgba(27,31,35,0.05);\n  border-radius: 3px;\n}\n\n.markdown-body code::before,\n.markdown-body code::after {\n  letter-spacing: -0.2em;\n  content: \"\\00a0\";\n}\n\n.markdown-body pre {\n  word-wrap: normal;\n}\n\n.markdown-body pre>code {\n  padding: 0;\n  margin: 0;\n  font-size: 100%;\n  word-break: normal;\n  white-space: pre;\n  background: transparent;\n  border: 0;\n}\n\n.markdown-body .highlight {\n  margin-bottom: 16px;\n}\n\n.markdown-body .highlight pre {\n  margin-bottom: 0;\n  word-break: normal;\n}\n\n.markdown-body .highlight pre,\n.markdown-body pre {\n  padding: 16px;\n  overflow: auto;\n  font-size: 85%;\n  line-height: 1.45;\n  background-color: #f6f8fa;\n  border-radius: 3px;\n}\n\n.markdown-body pre code {\n  display: inline;\n  max-width: auto;\n  padding: 0;\n  margin: 0;\n  overflow: visible;\n  line-height: inherit;\n  word-wrap: normal;\n  background-color: transparent;\n  border: 0;\n}\n\n.markdown-body pre code::before,\n.markdown-body pre code::after {\n  content: normal;\n}\n\n.markdown-body .full-commit .btn-outline:not(:disabled):hover {\n  color: #005cc5;\n  border-color: #005cc5;\n}\n\n.markdown-body kbd {\n  display: inline-block;\n  padding: 3px 5px;\n  font: 11px \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  line-height: 10px;\n  color: #444d56;\n  vertical-align: middle;\n  background-color: #fafbfc;\n  border: solid 1px #d1d5da;\n  border-bottom-color: #c6cbd1;\n  border-radius: 3px;\n  box-shadow: inset 0 -1px 0 #c6cbd1;\n}\n\n.markdown-body :checked+.radio-label {\n  position: relative;\n  z-index: 1;\n  border-color: #0366d6;\n}\n\n.markdown-body .task-list-item {\n  list-style-type: none;\n}\n\n.markdown-body .task-list-item+.task-list-item {\n  margin-top: 3px;\n}\n\n.markdown-body .task-list-item input {\n  margin: 0 0.2em 0.25em -1.6em;\n  vertical-align: middle;\n}\n\n.markdown-body hr {\n  border-bottom-color: #eee;\n}\n\n/*Markdown Viewer*/\n.markdown-body summary:hover { cursor: pointer; }\n.markdown-body ul li p { margin: 0; }\n"
  },
  {
    "path": "doc/themes/godspeed.css",
    "content": "/* Title: Godspeed */\n/* Author: Jocelyn Richard http://jocelynrichard.com/ */\n/* Description: A quirky, low-contrast theme. Works best with Brush Up: http://www.myfonts.com/fonts/pintassilgo/brush-up/ */\n\n/* ================================================ */\n/* 1. Reset */\n/* 2. Skeleton */\n/* 3. Media Queries */\n/* 4. Print Styles */\n/* 5. Godspeed Overrides */\n/* ================================================ */\n\n\n\n/* ================================================ */\n/* 1. Reset */\n/* ================================================ */\n\nhtml, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var,\nb, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {margin: 0; padding: 0; border: 0;} /* Edited from http://www.cssreset.com/scripts/eric-meyer-reset-css/ */\n\narticle, aside, details, figcaption, figure, footer, header, hgroup, nav, section, summary {display: block;} /* Semantic tags definition for IE 6/7/8/9 and Firefox 3 */\n\nhtml {-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;} /* Prevents iOS text size adjust after orientation change, without disabling user zoom */\n\n\n\n/* ================================================ */\n/* 2. Skeleton */\n/* ================================================ */\n\n/* ------------------------------------------------- */\n/* General */\n/* ------------------------------------------------- */\n\nhtml {\n  font-size: 14px;\n}\n\nbody {\n  font-family: 'Open Sans', sans-serif;\n  margin: 1.71rem 1.71rem  3rem 1.71rem ; /* Get margins even if the Markdown rendering app doesn't include any */\n  background-color: white;\n  color: #222;\n}\n\n#wrapper { /* #wrapper: ID added by Marked */\n  max-width: 42rem;\n  margin: 0 auto;\n  margin-left: auto !important;  /* Countering toc.css added by Marked */\n  padding: 1.71rem 0 !important; /* Countering toc.css added by Marked */\n}\n\n/* ------------------------------------------------- */\n/* Typography */\n/* ------------------------------------------------- */\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n    margin-bottom: 1.6rem;\n}\n\nh1,\nh2 {\n    margin-top: 3.2rem;\n}\n\nh1 {\n    font-size:   2.82rem; /* 42.3px @15px */\n    line-height: 3.2rem;  /* 48px @15px */\n}\n\nh2 {\n    font-size:   1.99rem; /* 29.9px @15px */\n    line-height: 2.4rem;  /* 36px @15px */\n}\n\nh3 {\n    font-size:   1.41rem; /* 21.2px @15px */\n    line-height: 2rem;    /* 30px @15px */\n}\n\nh4 {\n    font-size:   1rem;   /* 15px @15px */\n    line-height: 1.6rem; /* 24px @15px */\n}\n\nh5, h6 {\n  font-size:   0.8rem;\n    line-height: 1.2rem;\n    text-transform: uppercase;\n}\n\nh6 {\n  margin-left: 1.6rem;\n}\n\np,\nol,\nul,\nblockquote {\n    font-size:     1rem;\n    line-height:   1.6rem;\n    margin-bottom: 1.6rem;\n}\n\nul ul,\nul ol,\nol ul,\nol ol {\n  margin-left: 1.6rem;\n  margin-top:  1.6rem;\n}\n\n#generated-toc ul ul, /* #generated-toc: added by Marked for its table of contents */\n#generated-toc ul ol,\n#generated-toc ol ul,\n#generated-toc ol ol {\n  margin-top:     0;\n  margin-bottom:  0;\n  padding-top:    0;\n  padding-bottom: 0;\n}\n\nblockquote {\n  margin: 0 0 1.6rem 2.4rem;\n  padding-left: 0.8rem; /* Voire */\n  border-left: 4px solid rgba(0,0,0,0.08);\n  font-style: normal;\n}\n\nblockquote ul {\n  margin-left: 0.8rem; /* Pour ne pas que les hanging bullets mordent sur le blockquote  */\n}\n\nol li blockquote, /* So that blockquote work in lists */\nul li blockquote {\n  margin-left: 0;\n}\n\na:link {\n  text-decoration: none;\n  color: #165bd4;\n  border-bottom: 1px solid #ccc;\n}\n\na:visited {\n  color: #7697cf;\n  border-bottom: 1px solid #ccc;\n}\n\na:hover {\n  border-color: #165bd4;\n}\n\na:active {\n  background-color: #e6e6e6;\n}\n\n/* ------------------------------------------------- */\n/* Tables */\n/* ------------------------------------------------- */\n\ntable {\n  font-size: 0.85rem;\n  margin: 0 0 1.6rem 0;\n  border-collapse: collapse;\n  border: 1px solid #ccc;\n}\n\nth,\ntd {\n  padding: 0.5rem 0.75rem;\n  max-width: 20rem; /* Avoid dropping lines for nothing without having ridiculously wide tables */\n}\n\nth {\n  border-bottom: 2px solid #222;\n}\n\ntr {\n  border-bottom: 1px solid #ccc;\n}\n\ntbody tr:nth-child(odd) {\n  background-color: #f9f9f9;\n}\n\ntable code {\n  font-size: 85%;\n}\n\n/* ------------------------------------------------- */\n/* Misc */\n/* ------------------------------------------------- */\n\nimg {\n  max-width: 100%\n}\n\ncaption,\nfigcaption {\n  font-size:   0.85rem;\n    line-height: 1.6rem;\n  margin: 0 1.6rem;\n  text-align: left;\n}\n\nfigcaption {\n  margin-bottom: 1.6rem;\n}\n\nh1, /* White-space mentions in order to force wrapping */\nh2,\na:link {\n  white-space:      pre;      /* CSS 2.0 */\n  white-space:      pre-wrap; /* CSS 2.1 */\n  white-space:      pre-line; /* CSS 3.0 */\n  white-space:     -pre-wrap; /* Opera 4-6 */\n  white-space:   -o-pre-wrap; /* Opera 7 */\n  white-space: -moz-pre-wrap; /* Mozilla */\n  white-space:  -hp-pre-wrap; /* HP Printers */\n  word-wrap: break-word;      /* IE 5+ */\n}\n\ncode {\n  font-family: \"Menlo\", \"Courier New\", \"Courier\", monospace;\n  font-size: 85%;\n  color: #666;\n  background-color: rgba(0,0,0,0.08);\n  padding: 2px 4px;\n  border-radius: 2px;\n}\n\npre {\n  background-color: rgba(0,0,0,0.08);\n  border-radius: 8px;\n  padding: 0.4rem;\n  margin-bottom: 1.6rem;\n}\n\npre code { /* Counter the code mentions */\n  background-color: transparent;\n  padding: 0;\n}\n\nsup,\nsub,\na.footnote { /* Keep line-height from being affected by sub, cf https://gist.github.com/unruthless/413930 */\n   font-size: 75%;\n   height: 0;\n   line-height: 1;\n   position: relative;\n}\n\nsup,\na.footnote {\n    vertical-align:super;\n}\n\nsub {\n    vertical-align: sub;\n}\n\ndt {\n  font-weight: 600;\n}\n\ndd {\n  font-size: 1rem;\n  line-height: 1.6rem;\n  margin-bottom: 1.6rem;\n}\n\nhr {\n  clear: none;\n  height: 0.2rem;\n  border: none;\n  margin: 0 auto 1.4rem auto; /* 2.4rem auto 2.2rem auto; */\n  width: 100%;\n  color: #ccc;\n  background-color: #ccc;\n}\n\n::selection {\n  background-color: #f8dc77;\n}\n\n::-moz-selection {\n  background-color: #f8dc77;\n}\n\na:focus {\n  outline: 2px solid;\n  outline-color: #165bd4;\n}\n\n/* ------------------------------------------------- */\n/* Animations */\n/* ------------------------------------------------- */\n\na:hover {\n     -moz-transition: all 0.2s ease-in-out;\n  -webkit-transition: all 0.2s ease-in-out;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote {\n     -moz-transition: all 0.2s ease;\n  -webkit-transition: all 0.2s ease;\n}\n\n\n\n/* ================================================ */\n/* 3. Media Queries */\n/* ================================================ */\n\n/* Base styles are for smartphones; elements are then tweaked as the viewport grows. */\n\n/* ------------------------------------------------- */\n/* iPad and desktop */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 641px) {\n\n  html {\n    font-size: 15px;\n  }\n\n  body {\n    margin: 2.4rem 2.4rem 3.2rem 2.4rem;\n  }\n\n  h1 {\n      font-size:   3.57rem; /* 53.2px @15px */\n    line-height: 4rem;    /* 60px @15px */\n  }\n\n  h2 {\n    font-size:   2.24rem; /* 33.6px @15px */\n    line-height: 2.8rem;  /* 42px @15px */\n  }\n\n}\n\n/* ------------------------------------------------- */\n/* Widescreens */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 1441px) {\n\n  html {\n    font-size: 22px;\n  }\n\n}\n\n\n\n/* ================================================ */\n/* 4. Print Styles */\n/* ================================================ */\n\n/* Inconsistent and buggy across browsers */\n\n@media print {\n\n  * {\n    background: transparent !important;\n    color: #000 !important; /* Black text prints faster and browsers are inconsistent in color reproduction anyway: h5bp.com/s */\n  }\n\n  @page {\n    margin: 1cm; /* Added to any #wrapper margin*/\n  }\n\n  html {\n    font-size: 15px;\n  }\n\n  body {\n    margin: 1rem !important; /* Security margins for browser without @page support */\n  }\n\n  #wrapper {\n    max-width: none;\n  }\n\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6,\n  p {\n    orphans: 3;\n    widows: 3;\n    page-break-after: avoid;\n  }\n\n  ul,\n  ol {\n    list-style-position: inside !important;\n    padding-right: 0 !important;\n    margin-left: 0 !important;\n  }\n\n  ul ul,\n  ul ol,\n  ol ul,\n  ol ol,\n  ul p:not(:first-child),\n  ol p:not(:first-child) {\n    margin-left: 2rem !important;\n  }\n\n  a:link,\n  a:visited {\n    text-decoration: underline !important;\n    font-weight: normal !important;\n  }\n\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n\n  a[href^=\"javascript:\"]:after,\n  a[href^=\"#\"]:after {\n    content: \"\"; /* Do not show javascript and internal links */\n  }\n\n  a[href^=\"#\"] {\n    text-decoration: none !important;\n  }\n\n  th {\n    background-color: rgba(0,0,0,0.2) !important;\n    border-bottom: none !important;\n  }\n\n  tr {\n    page-break-inside: avoid;\n  }\n\n  tbody tr:nth-child(even) {\n    background-color: rgba(0,0,0,0.1) !important;\n  }\n\n  pre {\n    border: 1px solid rgba(0,0,0,0.2);\n    page-break-inside: avoid;\n  }\n\n  img {\n    max-width: 100% !important;\n    page-break-inside: avoid;\n  }\n\n  /* #generated-toc: added by Marked for its table of contents */\n\n  #wrapper #generated-toc ul, /* Table of contents printing in Marked */\n  #wrapper #generated-toc ol {\n    list-style-type: decimal;\n  }\n\n  #wrapper #generated-toc ul li,\n  #wrapper #generated-toc ol li {\n    margin: 1rem 0;\n  }\n\n}\n\n\n\n/* ================================================ */\n/* 5. Godspeed Overrides */\n/* ================================================ */\n\n/* ------------------------------------------------- */\n/* General */\n/* ------------------------------------------------- */\n\nbody {\n  font-family: 'Source Sans Pro', Avenir, sans-serif;\n  background-color: #3c3d46;\n  color: #7d7d7a;\n  margin-bottom: 2.4rem; /* Visual tweak */\n}\n\n/* ------------------------------------------------- */\n/* Typography */\n/* ------------------------------------------------- */\n\nh1 {\n  font-family: 'Brush Up Too', 'Source Sans Pro', Avenir, sans-serif;\n  color: #e6ceaa;\n}\n\nh2,\nh3 {\n  color: #b98552;\n  text-transform: uppercase;\n}\n\nh4,\nh5,\nh6 {\n  text-transform: uppercase;\n}\n\nblockquote {\n  border-color: rgba(0,0,0,0.1); /* Pour correspondre à l'opacité des bordures ajoutées au #wrapper */\n}\n\na:link {\n  color: #6190d2;\n  text-decoration: none;\n  border-bottom: 2px solid rgba(0,0,0,0.2);\n}\n\na:hover {\n  color: #6190d2;\n  text-decoration: none;\n  border-bottom: 1px solid #6190d2;\n}\n\n/* ------------------------------------------------- */\n/* Tables */\n/* ------------------------------------------------- */\n\ntable {\n  border: 1px solid #7d7d7a;\n  border-radius: 8px;\n}\n\nth {\n  color: #b98552;\n  border-bottom: 1px solid #7d7d7a;\n}\n\ntr {\n  border-bottom: 1px solid #7d7d7a;\n}\n\ntbody tr:nth-child(odd) {\n  background-color: rgba(60,75,94,0.5);\n}\n\n/* ------------------------------------------------- */\n/* Misc */\n/* ------------------------------------------------- */\n\ncode {\n  font-size: 75%; /* Matching better Source Sans  */\n  color: #3c3d46;\n  background-color: #7d7d7a;\n}\n\npre {\n  background-color: rgba(60,75,94,0.5);\n}\n\npre code {\n  color: #7d7d7a;\n}\n\nhr {\n  color: rgba(0,0,0,0.2);\n  background-color: rgba(0,0,0,0.2);\n}\n\n::selection {\n  background-color: rgba(0,0,0,0.2);\n}\n\n::-moz-selection {\n  background-color: rgba(0,0,0,0.2);\n}\n\na:focus {\n  outline: 2px dotted;\n  outline-color: #7d7d7a;\n}\n\n/* ------------------------------------------------- */\n/* iPad and desktop */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 641px) {\n\n  html {\n    font-size: 16px;\n  }\n\n  #wrapper {\n    padding: 0 2.4rem;\n    border: 1px solid rgba(0,0,0,0.2) !important; /* !important otherwise doesn't show up in Marked */\n    box-shadow: 0 0 0 6px rgba(0,0,0,0.1);\n    padding: 2.4rem !important;\n    border-radius: 4px;\n  }\n\n}\n\n/* ------------------------------------------------- */\n/* Widescreens */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 1441px) {\n\n  html {\n    font-size: 22px;\n  }\n\n}\n"
  },
  {
    "path": "doc/themes/markdown-alt.css",
    "content": "body {\n    line-height: 1.4em;\n    color: black;\n        padding:1em;\n        margin:auto;\n        max-width:42em;\n}\n\nli {\n    color: black;\n}\n\nh1,\nh2, \nh3, \nh4, \nh5, \nh6 {\n    border: 0 none !important;\n}\n\nh1 {    \n    margin-top: 0.5em;\n    margin-bottom: 0.5em;\n    border-bottom: 2px solid #000080 !important;\n}\n\nh2 {\n    margin-top: 1em;\n    margin-bottom: 0.5em;    \n    border-bottom: 2px solid #000080 !important;    \n}\n\npre {\n    background-color: #f8f8f8;    \n    border: 1px solid #2f6fab;\n    border-radius: 3px;\n    overflow: auto;\n    padding: 5px;\n}\n\npre code {\n    background-color: inherit;\n    border: none;    \n    padding: 0;\n}\n\ncode {\n    background-color: #ffffe0;\n    border: 1px solid orange;\n    border-radius: 3px;\n    padding: 0 0.2em;\n}\n\na {\n    text-decoration: underline;\t\n}\n\nul, ol {\n    padding-left: 30px;\n}\n\nli {\n    margin: 0.2em 0 0 0em; padding: 0px;\n}\n\nem {\n    color: #b05000;\n}\n\ntable.text th, table.text td {\n    vertical-align: top;\n    border-top: 1px solid #ccc;\n    padding:5px;\n}\n"
  },
  {
    "path": "doc/themes/markdown.css",
    "content": "html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }\n\nbody{\ncolor:#444;\nfont-family:Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman', serif;\nfont-size:12px;\nline-height:1.5em;\npadding:1em;\nmargin:auto;\nmax-width:42em;\nbackground:#fefefe;\n}\n\na{ color: #0645ad; text-decoration:none;}\na:visited{ color: #0b0080; }\na:hover{ color: #06e; }\na:active{ color:#faa700; }\na:focus{ outline: thin dotted; }\na:hover, a:active{ outline: 0; }\n\n::-moz-selection{background:rgba(255,255,0,0.3);color:#000}\n::selection{background:rgba(255,255,0,0.3);color:#000}\n\na::-moz-selection{background:rgba(255,255,0,0.3);color:#0645ad}\na::selection{background:rgba(255,255,0,0.3);color:#0645ad}\n\np{\nmargin:1em 0;\n}\n\nimg{\nmax-width:100%;\n}\n\nh1,h2,h3,h4,h5,h6{\nfont-weight:normal;\ncolor:#111;\nline-height:1em;\n}\nh4,h5,h6{ font-weight: bold; }\nh1{ font-size:2.5em; }\nh2{ font-size:2em; }\nh3{ font-size:1.5em; }\nh4{ font-size:1.2em; }\nh5{ font-size:1em; }\nh6{ font-size:0.9em; }\n\nblockquote{\ncolor:#666666;\nmargin:0;\npadding-left: 3em;\nborder-left: 0.5em #EEE solid;\n}\nhr { display: block; height: 2px; border: 0; border-top: 1px solid #aaa;border-bottom: 1px solid #eee; margin: 1em 0; padding: 0; }\npre, code, kbd, samp { color: #000; font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 0.98em; }\npre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }\n\nb, strong { font-weight: bold; }\n\ndfn { font-style: italic; }\n\nins { background: #ff9; color: #000; text-decoration: none; }\n\nmark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }\n\nsub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }\nsup { top: -0.5em; }\nsub { bottom: -0.25em; }\n\nul, ol { margin: 1em 0; padding: 0 0 0 2em; }\nli p:last-child { margin:0 }\ndd { margin: 0 0 0 2em; }\n\nimg { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }\n\ntable { border-collapse: collapse; border-spacing: 0; }\ntd { vertical-align: top; }\n\n@media only screen and (min-width: 480px) {\nbody{font-size:14px;}\n}\n\n@media only screen and (min-width: 768px) {\nbody{font-size:16px;}\n}\n\n@media print {\n  * { background: transparent !important; color: black !important; filter:none !important; -ms-filter: none !important; }\n  body{font-size:12pt; max-width:100%;}\n  a, a:visited { text-decoration: underline; }\n  hr { height: 1px; border:0; border-bottom:1px solid black; }\n  a[href]:after { content: \" (\" attr(href) \")\"; }\n  abbr[title]:after { content: \" (\" attr(title) \")\"; }\n  .ir a:after, a[href^=\"javascript:\"]:after, a[href^=\"#\"]:after { content: \"\"; }\n  pre, blockquote { border: 1px solid #999; padding-right: 1em; page-break-inside: avoid; }\n  tr, img { page-break-inside: avoid; }\n  img { max-width: 100% !important; }\n  @page :left { margin: 15mm 20mm 15mm 10mm; }\n  @page :right { margin: 15mm 10mm 15mm 20mm; }\n  p, h2, h3 { orphans: 3; widows: 3; }\n  h2, h3 { page-break-after: avoid; }\n}\n"
  },
  {
    "path": "doc/themes/markdown5.css",
    "content": "body{\n    margin: 0 auto;\n    background-color:white;\n\n/*\t--------- FONT FAMILY --------\n    following are some optional font families. Usually a family\n    is safer to choose than a specific font,\n    which may not be on the users computer\t\t*/\n    font-family:Georgia, Palatino, serif;\n\n/*\t-------------- COLOR OPTIONS ------------\n    following are additional color options for base font\n    you could uncomment another one to easily change the base color\n    or add one to a specific element style below         */\n    color: #333333; /* dark gray not black */\n\n    line-height: 1;\n    max-width: 800px;\n    padding: 30px;\n    font-size: 18px;\n}\n\n\np   {\n    line-height: 150%;\n    max-width: 960px;\n    font-weight: 400;\n     color: #333333\n}\n\n\nh1, h2, h3, h4 {\n    font-weight: 400;\n}\n\nh2, h3, h4, h5, p {\n    margin-bottom: 25px;\n    padding: 0;\n}\n\nh1 {\n    margin-bottom: 10px;\n    font-size:300%;\n    padding: 0px;\n    font-variant:small-caps;\n}\n\nh2 {\n    font-size:150%\n}\n\nh3 {\n    font-size:120%\n}\nh4 {\n    font-size:100%\n    font-variant:small-caps;\n\n}\nh5 {\n    font-size:80%\n    font-weight: 100;\n}\n\nh6 {\n    font-size:80%\n    font-weight: 100;\n    color:red;\n    font-variant:small-caps;\n}\na {\n    color: grey;\n    margin: 0;\n    padding: 0;\n    vertical-align: baseline;\n}\na:hover {\n    text-decoration: blink;\n    color: green;\n}\na:visited {\n    color: black;\n}\nul, ol {\n    padding: 0;\n    margin: 0px 0px 0px 50px;\n}\nul {\n    list-style-type: square;\n    list-style-position: inside;\n\n}\n\nli {\n     line-height:150%\n}\nli ul, li ul {\n    margin-left: 24px;\n}\n\npre {\n    padding: 0px 24px;\n    max-width: 800px;\n    white-space: pre-wrap;\n}\ncode {\n    font-family: Consolas, Monaco, Andale Mono, monospace;\n    line-height: 1.5;\n    font-size: 13px;\n}\naside {\n    display: block;\n    float: right;\n    width: 390px;\n}\nblockquote {\n    border-left:.5em solid #eee;\n    padding: 0 1em;\n    margin-left:0;\n    max-width: 476px;\n}\nblockquote  cite {\n    line-height:20px;\n    color:#bfbfbf;\n}\nblockquote cite:before {\n    content: '\\2014 \\00A0';\n}\n\nblockquote p {\n    color: #666;\n    max-width: 460px;\n}\nhr {\n    text-align: left;\n    margin: 0 auto 0 0;\n    color: #999;\n}\n\n"
  },
  {
    "path": "doc/themes/markdown6.css",
    "content": "/* Extracted and interpreted from adcstyle.css and frameset_styles.css */\n\n/* body */\nbody {\n    margin: 20px auto;\n    width: 800px;\n    background-color: #fff;\n    color: #000;\n    font: 13px \"Myriad Pro\", \"Lucida Grande\", Lucida, Verdana, sans-serif;\n}\n\n/* links */\na:link {\n    color: #00f;\n    text-decoration: none;\n}\n\na:visited {\n    color: #00a;\n    text-decoration: none;\n}\n\na:hover {\n    color: #f60;\n    text-decoration: underline;\n}\n    \na:active {\n    color: #f60;\n    text-decoration: underline;\n}\n\n\n/* html tags */\n\n/*  Work around IE/Win code size bug - courtesy Jesper, waffle.wootest.net  */\n\n* html code\t{\n    font-size: 101%;\n}\n\n* html pre {\n    font-size: 101%;\n}\n\n/* code */\n\npre, code {\n    font-size: 11px; font-family: monaco, courier, consolas, monospace;\n}\n\npre {\n    margin-top: 5px;\n    margin-bottom: 10px;\n    border: 1px solid #c7cfd5;\n    background: #f1f5f9;\n    margin: 20px 0;\n    padding: 8px;\n    text-align: left;\n}\n\nhr {\n    color: #919699;\n    size: 1;\n    width: 100%;\n    noshade: \"noshade\"\n}\n\n/* headers */\n\n\nh1, h2, h3, h4, h5, h6 {\n    font-family: \"Myriad Pro\", \"Lucida Grande\", Lucida, Verdana, sans-serif;\n    font-weight: bold;\n}\n\nh1\t{\n    margin-top: 1em;\n    margin-bottom: 25px;\n    color: #000;\n    font-weight: bold;\n    font-size: 30px;\n}\nh2\t{\n    margin-top: 2.5em;\n    font-size: 24px;\n    color: #000;\n    padding-bottom: 2px;\n    border-bottom: 1px solid #919699;\n}\nh3\t{\n    margin-top: 2em;\n    margin-bottom: .5em;\n    font-size: 17px;\n    color: #000;\n}\nh4\t{\n    margin-top: 2em;\n    margin-bottom: .5em;\n    font-size: 15px;\n    color: #000;\n}\nh5\t{\n    margin-top: 20px;\n    margin-bottom: .5em;\n    padding: 0;\n    font-size: 13px;\n    color: #000;\n}\n\nh6\t{\n    margin-top: 20px;\n    margin-bottom: .5em;\n    padding: 0;\n    font-size: 11px;\n    color: #000;\n}\n\np {\n    margin-top: 0px;\n    margin-bottom: 10px;\n}\n\n/* lists */\n\nul\t{\n    list-style: square outside;\n    margin: 0 0 0 30px;\n    padding: 0 0 12px 6px;\n}\n\nli\t{\n    margin-top: 7px;\n}\n            \nol {\n    list-style-type: decimal;\n    list-style-position: outside;\n    margin: 0 0 0 30px;\n    padding: 0 0 12px 6px;\n}\n    \nol ol {\n    list-style-type: lower-alpha;\n    list-style-position: outside;\n    margin: 7px 0 0 30px;\n    padding: 0 0 0 10px;\n    }\n\nul ul {\n    margin-left: 40px;\n    padding: 0 0 0 6px;\n}\n\nli>p { display: inline }\nli>p+p { display: block }\nli>a+p { display: block }\n\n\n/* table */\n\ntable {\n    width: 100%;\n    border-top: 1px solid #919699;\n    border-left: 1px solid #919699;\n    border-spacing: 0;\n}\n    \ntable th {\n    padding: 4px 8px 4px 8px;\n    background: #E2E2E2;\n    font-size: 12px;\n    border-bottom: 1px solid #919699;\n    border-right: 1px solid #919699;\n}\ntable th p {\n    font-weight: bold;\n    margin-bottom: 0px; \n}\n    \ntable td {\n    padding: 8px;\n    font-size: 12px;\n    vertical-align: top;\n    border-bottom: 1px solid #919699;\n    border-right: 1px solid #919699;\n}\ntable td p {\n    margin-bottom: 0px; \n}\ntable td p + p  {\n    margin-top: 5px; \n}\ntable td p + p + p {\n    margin-top: 5px; \n}\n\n/* forms */\n\nform {\n    margin: 0;\n}\n\nbutton {\n    margin: 3px 0 10px 0;\n}\ninput {\n    vertical-align: middle;\n    padding: 0;\n    margin: 0 0 5px 0;\n}\n\nselect {\n    vertical-align: middle;\n    padding: 0;\n    margin: 0 0 3px 0;\n}\n\ntextarea {\n    margin: 0 0 10px 0;\n    width: 100%;\n}"
  },
  {
    "path": "doc/themes/markdown7.css",
    "content": "body {\n   font-family: Helvetica, arial, sans-serif;\n   font-size: 14px;\n   line-height: 1.6;\n   padding-top: 10px;\n   padding-bottom: 10px;\n   background-color: white;\n   padding: 30px; }\n\nbody > *:first-child {\n   margin-top: 0 !important; }\nbody > *:last-child {\n   margin-bottom: 0 !important; }\n\na {\n   color: #4183C4; }\na.absent {\n   color: #cc0000; }\na.anchor {\n   display: block;\n   padding-left: 30px;\n   margin-left: -30px;\n   cursor: pointer;\n   position: absolute;\n   top: 0;\n   left: 0;\n   bottom: 0; }\n\nh1, h2, h3, h4, h5, h6 {\n   margin: 20px 0 10px;\n   padding: 0;\n   font-weight: bold;\n   -webkit-font-smoothing: antialiased;\n   cursor: text;\n   position: relative; }\n\nh1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {\n   text-decoration: none; }\n\nh1 tt, h1 code {\n   font-size: inherit; }\n\nh2 tt, h2 code {\n   font-size: inherit; }\n\nh3 tt, h3 code {\n   font-size: inherit; }\n\nh4 tt, h4 code {\n   font-size: inherit; }\n\nh5 tt, h5 code {\n   font-size: inherit; }\n\nh6 tt, h6 code {\n   font-size: inherit; }\n\nh1 {\n   font-size: 28px;\n   color: black; }\n\nh2 {\n   font-size: 24px;\n   border-bottom: 1px solid #cccccc;\n   color: black; }\n\nh3 {\n   font-size: 18px; }\n\nh4 {\n   font-size: 16px; }\n\nh5 {\n   font-size: 14px; }\n\nh6 {\n   color: #777777;\n   font-size: 14px; }\n\np, blockquote, ul, ol, dl, li, table, pre {\n   margin: 15px 0; }\n\nhr {\n   border: 0 none;\n   color: #cccccc;\n   height: 4px;\n   padding: 0;\n}\n\nbody > h2:first-child {\n   margin-top: 0;\n   padding-top: 0; }\nbody > h1:first-child {\n   margin-top: 0;\n   padding-top: 0; }\nbody > h1:first-child + h2 {\n   margin-top: 0;\n   padding-top: 0; }\nbody > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {\n   margin-top: 0;\n   padding-top: 0; }\n\na:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {\n   margin-top: 0;\n   padding-top: 0; }\n\nh1 p, h2 p, h3 p, h4 p, h5 p, h6 p {\n   margin-top: 0; }\n\nli p.first {\n   display: inline-block; }\nli {\n   margin: 0; }\nul, ol {\n   padding-left: 30px; }\n\nul :first-child, ol :first-child {\n   margin-top: 0; }\n\ndl {\n   padding: 0; }\ndl dt {\n   font-size: 14px;\n   font-weight: bold;\n   font-style: italic;\n   padding: 0;\n   margin: 15px 0 5px; }\ndl dt:first-child {\n   padding: 0; }\ndl dt > :first-child {\n   margin-top: 0; }\ndl dt > :last-child {\n   margin-bottom: 0; }\ndl dd {\n   margin: 0 0 15px;\n   padding: 0 15px; }\ndl dd > :first-child {\n   margin-top: 0; }\ndl dd > :last-child {\n   margin-bottom: 0; }\n\nblockquote {\n   border-left: 4px solid #dddddd;\n   padding: 0 15px;\n   color: #777777; }\nblockquote > :first-child {\n   margin-top: 0; }\nblockquote > :last-child {\n   margin-bottom: 0; }\n\ntable {\n   padding: 0;border-collapse: collapse; }\ntable tr {\n   border-top: 1px solid #cccccc;\n   background-color: white;\n   margin: 0;\n   padding: 0; }\ntable tr:nth-child(2n) {\n   background-color: #f8f8f8; }\ntable tr th {\n   font-weight: bold;\n   border: 1px solid #cccccc;\n   margin: 0;\n   padding: 6px 13px; }\ntable tr td {\n   border: 1px solid #cccccc;\n   margin: 0;\n   padding: 6px 13px; }\ntable tr th :first-child, table tr td :first-child {\n   margin-top: 0; }\ntable tr th :last-child, table tr td :last-child {\n   margin-bottom: 0; }\n\nimg {\n   max-width: 100%; }\n\nspan.frame {\n   display: block;\n   overflow: hidden; }\nspan.frame > span {\n   border: 1px solid #dddddd;\n   display: block;\n   float: left;\n   overflow: hidden;\n   margin: 13px 0 0;\n   padding: 7px;\n   width: auto; }\nspan.frame span img {\n   display: block;\n   float: left; }\nspan.frame span span {\n   clear: both;\n   color: #333333;\n   display: block;\n   padding: 5px 0 0; }\nspan.align-center {\n   display: block;\n   overflow: hidden;\n   clear: both; }\nspan.align-center > span {\n   display: block;\n   overflow: hidden;\n   margin: 13px auto 0;\n   text-align: center; }\nspan.align-center span img {\n   margin: 0 auto;\n   text-align: center; }\nspan.align-right {\n   display: block;\n   overflow: hidden;\n   clear: both; }\nspan.align-right > span {\n   display: block;\n   overflow: hidden;\n   margin: 13px 0 0;\n   text-align: right; }\nspan.align-right span img {\n   margin: 0;\n   text-align: right; }\nspan.float-left {\n   display: block;\n   margin-right: 13px;\n   overflow: hidden;\n   float: left; }\nspan.float-left span {\n   margin: 13px 0 0; }\nspan.float-right {\n   display: block;\n   margin-left: 13px;\n   overflow: hidden;\n   float: right; }\nspan.float-right > span {\n   display: block;\n   overflow: hidden;\n   margin: 13px auto 0;\n   text-align: right; }\n\ncode, tt {\n   margin: 0 2px;\n   padding: 0 5px;\n   white-space: nowrap;\n   border: 1px solid #eaeaea;\n   background-color: #f8f8f8;\n   border-radius: 3px; }\n\npre code {\n   margin: 0;\n   padding: 0;\n   white-space: pre;\n   border: none;\n   background: transparent; }\n\n.highlight pre {\n   background-color: #f8f8f8;\n   border: 1px solid #cccccc;\n   font-size: 13px;\n   line-height: 19px;\n   overflow: auto;\n   padding: 6px 10px;\n   border-radius: 3px; }\n\npre {\n   background-color: #f8f8f8;\n   border: 1px solid #cccccc;\n   font-size: 13px;\n   line-height: 19px;\n   overflow: auto;\n   padding: 6px 10px;\n   border-radius: 3px; }\npre code, pre tt {\n   background-color: transparent;\n   border: none; }\n\nsup {\n   font-size: 0.83em;\n   vertical-align: super;\n   line-height: 0;\n}\n* {\n     -webkit-print-color-adjust: exact;\n}\n@media screen and (min-width: 914px) {\n   body {\n      width: 854px;\n      margin:0 auto;\n   }\n}\n@media print {\n     table, pre {\n          page-break-inside: avoid;\n     }\n     pre {\n          word-wrap: break-word;\n     }\n}\n"
  },
  {
    "path": "doc/themes/markdown8.css",
    "content": "h1, h2, h3, h4, h5, h6, p, blockquote {\n   margin: 0;\n   padding: 0;\n}\nbody {\n   font-family: \"Helvetica Neue\", Helvetica, \"Hiragino Sans GB\", Arial, sans-serif;\n   font-size: 13px;\n   line-height: 18px;\n   color: #737373;\n   background-color: white;\n   margin: 10px 13px 10px 13px;\n}\ntable {\n   margin: 10px 0 15px 0;\n   border-collapse: collapse;\n}\ntd,th {\t\n   border: 1px solid #ddd;\n   padding: 3px 10px;\n}\nth {\n   padding: 5px 10px;\t\n}\n\na {\n   color: #0069d6;\n}\na:hover {\n   color: #0050a3;\n   text-decoration: none;\n}\na img {\n   border: none;\n}\np {\n   margin-bottom: 9px;\n}\n\nh1, h2, h3, h4, h5, h6 {\n   color: #404040;\n   line-height: 36px;\n}\nh1 {\n   margin-bottom: 18px;\n   font-size: 30px;\n}\nh2 {\n   font-size: 24px;\n}\nh3 {\n   font-size: 18px;\n}\nh4 {\n   font-size: 16px;\n}\nh5 {\n   font-size: 14px;\n}\nh6 {\n   font-size: 13px;\n}\nhr {\n   margin: 0 0 19px;\n   border: 0;\n   border-bottom: 1px solid #ccc;\n}\nblockquote {\n   padding: 13px 13px 21px 15px;\n   margin-bottom: 18px;\n   font-family:georgia,serif;\n   font-style: italic;\n}\nblockquote:before {\n   content:\"\\201C\";\n   font-size:40px;\n   margin-left:-10px;\n   font-family:georgia,serif;\n   color:#eee;\n}\nblockquote p {\n   font-size: 14px;\n   font-weight: 300;\n   line-height: 18px;\n   margin-bottom: 0;\n   font-style: italic;\n}\ncode, pre {\n   font-family: Monaco, Andale Mono, Courier New, monospace;\n}\ncode {\n   background-color: #fee9cc;\n   color: rgba(0, 0, 0, 0.75);\n   padding: 1px 3px;\n   font-size: 12px;\n   -webkit-border-radius: 3px;\n   -moz-border-radius: 3px;\n   border-radius: 3px;\n}\npre {\n   display: block;\n   padding: 14px;\n   margin: 0 0 18px;\n   line-height: 16px;\n   font-size: 11px;\n   border: 1px solid #d9d9d9;\n   white-space: pre-wrap;\n   word-wrap: break-word;\n}\npre code {\n   background-color: #fff;\n   color:#737373;\n   font-size: 11px;\n   padding: 0;\n}\nsup {\n   font-size: 0.83em;\n   vertical-align: super;\n   line-height: 0;\n}\n* {\n   -webkit-print-color-adjust: exact;\n}\n@media screen and (min-width: 914px) {\n   body {\n      width: 854px;\n      margin:10px auto;\n   }\n}\n@media print {\n   body,code,pre code,h1,h2,h3,h4,h5,h6 {\n      color: black;\n   }\n   table, pre {\n      page-break-inside: avoid;\n   }\n}\n"
  },
  {
    "path": "doc/themes/markdown9.css",
    "content": "h1, h2, h3, h4, h5, h6, p, blockquote {\n   margin: 0;\n   padding: 0;\n}\nbody {\n   font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n   font-size: 13px;\n   line-height: 18px;\n   color: #fff;\n   background-color: #110F14;\n   margin: 10px 13px 10px 13px;\n}\ntable {\n     margin: 10px 0 15px 0;\n     border-collapse: collapse;\n}\ntd,th {\t\n     border: 1px solid #ddd;\n     padding: 3px 10px;\n}\nth {\n     padding: 5px 10px;\t\n}\na {\n   color: #59acf3;\n}\na:hover {\n   color: #a7d8ff;\n   text-decoration: none;\n}\na img {\n   border: none;\n}\np {\n   margin-bottom: 9px;\n}\nh1, h2, h3, h4, h5, h6 {\n   color: #fff;\n   line-height: 36px;\n}\nh1 {\n   margin-bottom: 18px;\n   font-size: 30px;\n}\nh2 {\n   font-size: 24px;\n}\nh3 {\n   font-size: 18px;\n}\nh4 {\n   font-size: 16px;\n}\nh5 {\n   font-size: 14px;\n}\nh6 {\n   font-size: 13px;\n}\nhr {\n   margin: 0 0 19px;\n   border: 0;\n   border-bottom: 1px solid #ccc;\n}\nblockquote {\n   padding: 13px 13px 21px 15px;\n   margin-bottom: 18px;\n   font-family:georgia,serif;\n   font-style: italic;\n}\nblockquote:before {\n   content:\"\\201C\";\n   font-size:40px;\n   margin-left:-10px;\n   font-family:georgia,serif;\n   color:#eee;\n}\nblockquote p {\n   font-size: 14px;\n   font-weight: 300;\n   line-height: 18px;\n   margin-bottom: 0;\n   font-style: italic;\n}\n\ncode, pre {\n   font-family: Menlo, Monaco, Andale Mono, Courier New, monospace;\n}\n\ncode {\n   padding: 1px 3px;\n   font-size: 12px;\n   -webkit-border-radius: 3px;\n   -moz-border-radius: 3px;\n   border-radius: 3px;\n   background: #334;\n}\n\npre {\n   display: block;\n   padding: 14px;\n   margin: 0 0 18px;\n   line-height: 16px;\n   font-size: 11px;\n   border: 1px solid #334;\n   white-space: pre;\n   white-space: pre-wrap;\n   word-wrap: break-word;\n   background-color: #282a36;\n   border-radius: 6px;\n}\npre code {\n   font-size: 11px;\n   padding: 0;\n   background: transparent;\n}\nsup {\n   font-size: 0.83em;\n   vertical-align: super;\n   line-height: 0;\n}\n* {\n     -webkit-print-color-adjust: exact;\n}\n@media screen and (min-width: 914px) {\n   body {\n      width: 854px;\n      margin:10px auto;\n   }\n}\n@media print {\n     body,code,pre code,h1,h2,h3,h4,h5,h6 {\n          color: black;\n     }\n     table, pre {\n          page-break-inside: avoid;\n     }\n}\n"
  },
  {
    "path": "doc/themes/markedapp-byword.css",
    "content": "/*\n * This document has been created with Marked.app <http://markedapp.com>.\n * Copyright 2011 Brett Terpstra\n * ---------------------------------------------------------------------------\n * Please leave this notice in place, along with any additional credits below.\n *\n * Byword.css theme is based on Byword.app <http://bywordapp.com>\n * Authors: @brunodecarvalho, @jpedroso, @rcabaco\n * Copyright 2011 Metaclassy, Lda. <http://metaclassy.com>\n */\n\nhtml {\n  font-size: 62.5%; /* base font-size: 10px */\n}\n\nbody {\n    background-color: #f2f2f2;\n    color: #3c3c3c;\n\n    /* Change font size below */\n  font-size: 1.7em;\n  line-height: 1.4em;\n\n  /* Change font below */\n\n  /* Sans-serif fonts */\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n\n  /* Serif fonts */\n  /*\n  font-family: \"Cochin\", \"Baskerville\", \"Georgia\", serif;\n  -webkit-font-smoothing: subpixel-antialiased;\n  */\n\n  /* Monospaced fonts */\n  /*\n  font-family: \"Courier New\", Menlo, Monaco, mono;\n  -webkit-font-smoothing: antialiased;\n  */\n\n  margin: auto;\n  max-width: 42em;\n}\na {\n    color: #308bd8;\n    text-decoration:none;\n}\na:hover {\n    text-decoration: underline;\n}\n/* headings */\nh1, h2 {\n    line-height:1.2em;\n    margin-top:32px;\n    margin-bottom:12px;\n}\nh1:first-child {\n    margin-top:0;\n}\nh3, h4, h5, h6 {\n    margin-top:12px;\n    margin-bottom:0;\n}\nh5, h6 {\n    font-size:0.9em;\n    line-height:1.0em;\n}\n/* end of headings */\np {\n    margin:0 0 24px 0;\n}\np:last-child {\n    margin:0;\n}\n#wrapper hr {\n    width: 100%;\n    margin: 3em auto;\n    border: 0;\n    color: #eee;\n    background-color: #ccc;\n    height: 1px;\n    -webkit-box-shadow:0px 1px 0px rgba(255, 255, 255, 0.75);\n}\n/* lists */\nol {\n    list-style: outside decimal;\n}\nul {\n    list-style: outside disc;\n}\nol, ul {\n    padding-left:0;\n    margin-bottom:24px;\n}\nol li {\n    margin-left:28px;\n}\nul li {\n    margin-bottom:8px;\n    margin-left:16px;\n}\nol:last-child, ul:last-child {\n    margin:0;\n}\nli > ol, li > ul {\n    padding-left:12px;\n}\ndl {\n    margin-bottom:24px;\n}\ndl dt {\n    font-weight:bold;\n    margin-bottom:8px;\n}\ndl dd {\n    margin-left:0;\n    margin-bottom:12px;\n}\ndl dd:last-child, dl:last-child {\n    margin-bottom:0;\n}\n/* end of lists */\npre {\n    white-space: pre-wrap;\n    width: 96%;\n    margin-bottom: 24px;\n    overflow: hidden;\n    padding: 3px 10px;\n    -webkit-border-radius: 3px;\n    background-color: #eee;\n    border: 1px solid #ddd;\n}\ncode {\n    white-space: nowrap;\n    font-size: 1.1em;\n    padding: 2px;\n    -webkit-border-radius: 3px;\n    background-color: #eee;\n    border: 1px solid #ddd;\n}\npre code {\n    white-space: pre-wrap;\n    border: none;\n    padding: 0;\n    background-color: transparent;\n    -webkit-border-radius: 0;\n}\nblockquote {\n    margin-left: 0;\n    margin-right: 0;\n    width: 96%;\n    padding: 0 10px;\n    border-left: 3px solid #ddd;\n    color: #777;\n}\ntable {\n    margin-left: auto;\n    margin-right: auto;\n    margin-bottom: 24px;\n    border-bottom: 1px solid #ddd;\n    border-right: 1px solid #ddd;\n    border-spacing: 0;\n}\ntable th {\n    padding: 3px 10px;\n    background-color: #eee;\n    border-top: 1px solid #ddd;\n    border-left: 1px solid #ddd;\n}\ntable tr {\n}\ntable td {\n    padding: 3px 10px;\n    border-top: 1px solid #ddd;\n    border-left: 1px solid #ddd;\n}\ncaption {\n    font-size: 1.2em;\n    font-weight: bold;\n    margin-bottom: 5px;\n}\nfigure {\n    display: block;\n    text-align: center;\n}\n#wrapper img {\n    border: none;\n    display: block;\n    margin: 1em auto;\n    max-width: 100%;\n}\nfigcaption {\n    font-size: 0.8em;\n    font-style: italic;\n}\nmark {\n    background: #fefec0;\n    padding:1px 3px;\n}\n\n\n/* classes */\n\n.markdowncitation {\n}\n.footnote {\n    font-size: 0.8em;\n    vertical-align: super;\n}\n.footnotes ol {\n    font-weight: bold;\n}\n.footnotes ol li p {\n    font-weight: normal;\n}\n\n/* custom formatting classes */\n\n.shadow {\n    -webkit-box-shadow: 0 2px 4px #999;\n}\n\n.source {\n    text-align: center;\n    font-size: 0.8em;\n    color: #777;\n    margin: -40px;\n}\n\n@media screen {\n  .inverted, .inverted #wrapper {\n      background-color: #1a1a1a !important;\n      color: #bebebe !important;\n\n      /* SANS-SERIF */\n    font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif !important;\n    -webkit-font-smoothing: antialiased !important;\n\n    /* SERIF */\n    /*\n    font-family: \"Cochin\", \"Baskerville\", \"Georgia\", serif !important;\n    -webkit-font-smoothing: subpixel-antialiased !important;\n    */\n    /* MONO */\n    /*\n    font-family: \"Courier\", mono !important;\n    -webkit-font-smoothing: antialiased !important;\n    */\n  }\n  .inverted a {\n      color: #308bd8 !important;\n  }\n  .inverted hr {\n      color: #666 !important;\n      border: 0;\n      background-color: #666 !important;\n      -webkit-box-shadow: none !important;\n  }\n  .inverted pre {\n      background-color: #222 !important;\n      border-color: #3c3c3c !important;\n  }\n  .inverted code {\n      background-color: #222 !important;\n      border-color: #3c3c3c !important;\n  }\n  .inverted blockquote {\n      border-color: #333 !important;\n      color: #999 !important;\n  }\n  .inverted table {\n      border-color: #3c3c3c !important;\n  }\n  .inverted table th {\n      background-color: #222 !important;\n      border-color: #3c3c3c !important;\n  }\n  .inverted table td {\n      border-color: #3c3c3c !important;\n  }\n  .inverted mark {\n      background: #bc990b !important;\n      color:#000 !important;\n  }\n    .inverted .shadow { -webkit-box-shadow: 0 2px 4px #000 !important; }\n  #wrapper {\n        background: transparent;\n        margin: 40px;\n    }\n}\n\n/* Printing support */\n@media print {\n  body {\n    overflow: auto;\n  }\n    img, pre, blockquote, table, figure {\n        page-break-inside: avoid;\n    }\n    pre, code {\n    border: none !important;\n  }\n  #wrapper {\n        background: #fff;\n        position: relative;\n        text-indent: 0px;\n        padding: 10px;\n        font-size:85%;\n    }\n    .footnotes {\n        page-break-before: always;\n    }\n}\n"
  },
  {
    "path": "doc/themes/new-modern.css",
    "content": "/* Title: New Modern */\n/* Author: Jocelyn Richard http://jocelynrichard.com/ */\n/* Description: Baseline style, meant to be used on its own or to serve as development basis. */\n\n/* ================================================ */\n/* 1. Reset */\n/* 2. Skeleton */\n/* 3. Media Queries */\n/* 4. Print Styles */\n/* ================================================ */\n\n\n\n/* ================================================ */\n/* 1. Reset */\n/* ================================================ */\n\nhtml, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var,\nb, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {margin: 0; padding: 0; border: 0;} /* Edited from http://www.cssreset.com/scripts/eric-meyer-reset-css/ */\n\narticle, aside, details, figcaption, figure, footer, header, hgroup, nav, section, summary {display: block;} /* Semantic tags definition for IE 6/7/8/9 and Firefox 3 */\n\nhtml {-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;} /* Prevents iOS text size adjust after orientation change, without disabling user zoom */\n\n\n\n/* ================================================ */\n/* 2. Skeleton */\n/* ================================================ */\n\n/* ------------------------------------------------- */\n/* General */\n/* ------------------------------------------------- */\n\nhtml {\n  font-size: 14px;\n}\n\nbody {\n  font-family: 'Open Sans', sans-serif;\n  /*margin: 3.42rem 1.71rem !important;*/ /* Get margins even if the Markdown rendering app doesn't include any */\n  background-color: white;\n  color: #222;\n}\n\n#wrapper { /* #wrapper: ID added by Marked */\n  max-width: 42rem;\n  margin: 0 auto;\n  margin-left: auto !important;  /* Countering toc.css added by Marked */\n  padding: 1.71rem 0 !important; /* Countering toc.css added by Marked */\n}\n\n/* ------------------------------------------------- */\n/* Typography */\n/* ------------------------------------------------- */\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n    margin-bottom: 1.6rem;\n}\n\nh1,\nh2 {\n    margin-top: 3.2rem;\n}\n\nh1 {\n    font-size:   2.82rem; /* 42.3px @15px */\n    line-height: 3.2rem;  /* 48px @15px */\n}\n\nh2 {\n    font-size:   1.99rem; /* 29.9px @15px */\n    line-height: 2.4rem;  /* 36px @15px */\n}\n\nh3 {\n    font-size:   1.41rem; /* 21.2px @15px */\n    line-height: 2rem;    /* 30px @15px */\n}\n\nh4 {\n    font-size:   1rem;   /* 15px @15px */\n    line-height: 1.6rem; /* 24px @15px */\n}\n\nh5, h6 {\n  font-size:   0.8rem;\n    line-height: 1.2rem;\n    text-transform: uppercase;\n}\n\nh6 {\n  margin-left: 1.6rem;\n}\n\np,\nol,\nul,\nblockquote {\n    font-size:     1rem;\n    line-height:   1.6rem;\n    margin-bottom: 1.6rem;\n}\n\nul ul,\nul ol,\nol ul,\nol ol {\n  margin-left: 1.6rem;\n  margin-top:  1.6rem;\n}\n\n#generated-toc ul ul, /* #generated-toc: added by Marked for its table of contents */\n#generated-toc ul ol,\n#generated-toc ol ul,\n#generated-toc ol ol {\n  margin-top:     0;\n  margin-bottom:  0;\n  padding-top:    0;\n  padding-bottom: 0;\n}\n\nblockquote {\n  margin: 0 0 1.6rem 2.4rem;\n  padding-left: 0.8rem; /* Voire */\n  border-left: 4px solid rgba(0,0,0,0.08);\n  font-style: normal;\n}\n\nblockquote ul {\n  margin-left: 0.8rem; /* Pour ne pas que les hanging bullets mordent sur le blockquote  */\n}\n\nol li blockquote, /* So that blockquote work in lists */\nul li blockquote {\n  margin-left: 0;\n}\n\na:link {\n  text-decoration: none;\n  color: #165bd4;\n  border-bottom: 1px solid #ccc;\n}\n\na:visited {\n  color: #7697cf;\n  border-bottom: 1px solid #ccc;\n}\n\na:hover {\n  border-color: #165bd4;\n}\n\na:active {\n  background-color: #e6e6e6;\n}\n\n/* ------------------------------------------------- */\n/* Tables */\n/* ------------------------------------------------- */\n\ntable {\n  font-size: 0.85rem;\n  margin: 0 0 1.6rem 0;\n  border-collapse: collapse;\n  border: 1px solid #ccc;\n}\n\nth,\ntd {\n  padding: 0.5rem 0.75rem;\n  max-width: 20rem; /* Avoid dropping lines for nothing without having ridiculously wide tables */\n}\n\nth {\n  border-bottom: 2px solid #222;\n}\n\ntr {\n  border-bottom: 1px solid #ccc;\n}\n\ntbody tr:nth-child(odd) {\n  background-color: #f9f9f9;\n}\n\ntable code {\n  font-size: 85%;\n}\n\n/* ------------------------------------------------- */\n/* Misc */\n/* ------------------------------------------------- */\n\nimg {\n  max-width: 100%\n}\n\ncaption,\nfigcaption {\n  font-size:   0.85rem;\n    line-height: 1.6rem;\n  margin: 0 1.6rem;\n  text-align: left;\n}\n\nfigcaption {\n  margin-bottom: 1.6rem;\n}\n\nh1, /* White-space mentions in order to force wrapping */\nh2,\na:link,\npre {\n  white-space:      pre;      /* CSS 2.0 */\n  white-space:      pre-wrap; /* CSS 2.1 */\n  white-space:      pre-line; /* CSS 3.0 */\n  white-space:     -pre-wrap; /* Opera 4-6 */\n  white-space:   -o-pre-wrap; /* Opera 7 */\n  white-space: -moz-pre-wrap; /* Mozilla */\n  white-space:  -hp-pre-wrap; /* HP Printers */\n  word-wrap: break-word;      /* IE 5+ */\n}\n\ncode {\n  font-family: \"Menlo\", \"Courier New\", \"Courier\", monospace;\n  font-size: 85%;\n  color: #666;\n  background-color: rgba(0,0,0,0.08);\n  padding: 2px 4px;\n  border-radius: 2px;\n}\n\npre {\n  background-color: rgba(0,0,0,0.08);\n  border-radius: 8px;\n  padding: 0.4rem;\n  margin-bottom: 1.6rem;\n}\n\npre code { /* Counter the code mentions */\n  background-color: transparent;\n  padding: 0;\n}\n\nsup,\nsub,\na.footnote { /* Keep line-height from being affected by sub, cf https://gist.github.com/unruthless/413930 */\n   font-size: 75%;\n   height: 0;\n   line-height: 1;\n   position: relative;\n}\n\nsup,\na.footnote {\n    vertical-align:super;\n}\n\nsub {\n    vertical-align: sub;\n}\n\ndt {\n  font-weight: 600;\n}\n\ndd {\n  font-size: 1rem;\n  line-height: 1.6rem;\n  margin-bottom: 1.6rem;\n}\n\nhr {\n  clear: none;\n  height: 0.2rem;\n  border: none;\n  margin: 0 auto 1.4rem auto; /* 2.4rem auto 2.2rem auto; */\n  width: 100%;\n  color: #ccc;\n  background-color: #ccc;\n}\n\n::selection {\n  background-color: #f8dc77;\n}\n\n::-moz-selection {\n  background-color: #f8dc77;\n}\n\na:focus {\n  outline: 2px solid;\n  outline-color: #165bd4;\n}\n\n/* ------------------------------------------------- */\n/* Animations */\n/* ------------------------------------------------- */\n\na:hover {\n     -moz-transition: all 0.2s ease-in-out;\n  -webkit-transition: all 0.2s ease-in-out;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote {\n     -moz-transition: all 0.2s ease;\n  -webkit-transition: all 0.2s ease;\n}\n\n\n\n/* ================================================ */\n/* 3. Media Queries */\n/* ================================================ */\n\n/* Base styles are for smartphones; elements are then tweaked as the viewport grows. */\n\n/* ------------------------------------------------- */\n/* iPad and desktop */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 641px) {\n\n  html {\n    font-size: 15px;\n  }\n\n  body {\n    /*margin: 4.8em 2.4rem 3.2rem 2.4rem !important;*/\n  }\n\n  h1 {\n      font-size:   3.57rem; /* 53.2px @15px */\n    line-height: 4rem;    /* 60px @15px */\n  }\n\n  h2 {\n    font-size:   2.24rem; /* 33.6px @15px */\n    line-height: 2.8rem;  /* 42px @15px */\n  }\n\n}\n\n/* ------------------------------------------------- */\n/* Widescreens */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 1441px) {\n\n  html {\n    font-size: 22px;\n  }\n\n}\n\n\n\n/* ================================================ */\n/* 4. Print Styles */\n/* ================================================ */\n\n/* Inconsistent and buggy across browsers */\n\n@media print {\n\n  * {\n    background: transparent !important;\n    color: #000 !important; /* Black text prints faster and browsers are inconsistent in color reproduction anyway: h5bp.com/s */\n  }\n\n  @page {\n    margin: 1cm; /* Added to any #wrapper margin*/\n  }\n\n  html {\n    font-size: 15px;\n  }\n\n  body {\n    margin: 1rem !important; /* Security margins for browser without @page support */\n  }\n\n  #wrapper {\n    max-width: none;\n  }\n\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6,\n  p {\n    orphans: 3;\n    widows: 3;\n    page-break-after: avoid;\n  }\n\n  ul,\n  ol {\n    list-style-position: inside !important;\n    padding-right: 0 !important;\n    margin-left: 0 !important;\n  }\n\n  ul ul,\n  ul ol,\n  ol ul,\n  ol ol,\n  ul p:not(:first-child),\n  ol p:not(:first-child) {\n    margin-left: 2rem !important;\n  }\n\n  a:link,\n  a:visited {\n    text-decoration: underline !important;\n    font-weight: normal !important;\n  }\n\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n\n  a[href^=\"javascript:\"]:after,\n  a[href^=\"#\"]:after {\n    content: \"\"; /* Do not show javascript and internal links */\n  }\n\n  a[href^=\"#\"] {\n    text-decoration: none !important;\n  }\n\n  th {\n    background-color: rgba(0,0,0,0.2) !important;\n    border-bottom: none !important;\n  }\n\n  tr {\n    page-break-inside: avoid;\n  }\n\n  tbody tr:nth-child(even) {\n    background-color: rgba(0,0,0,0.1) !important;\n  }\n\n  pre {\n    border: 1px solid rgba(0,0,0,0.2);\n    page-break-inside: avoid;\n  }\n\n  img {\n    max-width: 100% !important;\n    page-break-inside: avoid;\n  }\n\n  /* #generated-toc: added by Marked for its table of contents */\n\n  #wrapper #generated-toc ul, /* Table of contents printing in Marked */\n  #wrapper #generated-toc ol {\n    list-style-type: decimal;\n  }\n\n  #wrapper #generated-toc ul li,\n  #wrapper #generated-toc ol li {\n    margin: 1rem 0;\n  }\n\n}\n"
  },
  {
    "path": "doc/themes/radar.css",
    "content": "\nbody {\n  margin: 0px;\n\n  font-family: 'PT Sans', Helvetica, 'Helvetica Neuve', Arial, Tahoma, sans-serif;\n  font-size: 17px;\n\n  color: #333;\n}\n\nh1, h2, h3, h4, h5, h6 {\n  color:#222;\n  margin:0 0 20px;\n}\n\np, ul, ol, table, pre, dl {\n  margin:0 0 20px;\n}\n\nh1, h2, h3 {\n  line-height:1.1;\n}\n\nh1 {\n  font-size:28px;\n}\n\nh2 {\n  color:#393939;\n}\n\nh3, h4, h5, h6 {\n  color:#494949;\n}\n\na {\n  color:#39c;\n  font-weight:400;\n  text-decoration:none;\n}\n\na small {\n  font-size:11px;\n  color:#777;\n  margin-top:-0.6em;\n  display:block;\n}\n\n.wrapper {\n  width:860px;\n  margin:0 auto;\n}\n\nblockquote {\n  border-left:1px solid #e5e5e5;\n  margin:0;\n  padding:0 0 0 20px;\n  font-style:italic;\n}\n\ncode, pre {\n  color:#333;\n  font-size:12px;\n}\n\npre {\n  padding:8px 15px;\n  background: #f8f8f8;\n  border-radius:5px;\n  border:1px solid #e5e5e5;\n  overflow-x: auto;\n}\n\ntable {\n  width:100%;\n  border-collapse:collapse;\n}\n\nth, td {\n  text-align:left;\n  padding:5px 10px;\n  border-bottom:1px solid #e5e5e5;\n}\n\ndt {\n  color:#444;\n  font-weight:700;\n}\n\nth {\n  color:#444;\n}\n\nimg {\n  max-width:100%;\n}\n\nheader {\n  width:270px;\n  float:left;\n  position:fixed;\n}\n\nheader ul {\n  list-style:none;\n  height:40px;\n\n  padding:0;\n\n  background: #eee;\n  background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);\n  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));\n  background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);\n  background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);\n  background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);\n  background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%);\n\n  border-radius:5px;\n  border:1px solid #d2d2d2;\n  box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;\n  width:270px;\n}\n\nheader li {\n  width:89px;\n  float:left;\n  border-right:1px solid #d2d2d2;\n  height:40px;\n}\n\nheader ul a {\n  line-height:1;\n  font-size:11px;\n  color:#999;\n  display:block;\n  text-align:center;\n  padding-top:6px;\n  height:40px;\n}\n\nstrong {\n  color:#222;\n  font-weight:700;\n}\n\nheader ul li + li {\n  width:88px;\n  border-left:1px solid #fff;\n}\n\nheader ul li + li + li {\n  border-right:none;\n  width:89px;\n}\n\nheader ul a strong {\n  font-size:14px;\n  display:block;\n  color:#222;\n}\n\nsection {\n  width:500px;\n  float:right;\n  padding-bottom:50px;\n}\n\nsmall {\n  font-size:11px;\n}\n\nhr {\n  border:0;\n  background:#e5e5e5;\n  height:1px;\n  margin:0 0 20px;\n}\n\nfooter {\n  width:270px;\n  float:left;\n  position:fixed;\n  bottom:50px;\n}\n\n@media print, screen and (max-width: 960px) {\n\n  div.wrapper {\n    width:auto;\n    margin:0;\n  }\n\n  header, section, footer {\n    float:none;\n    position:static;\n    width:auto;\n  }\n\n  header {\n    padding-right:320px;\n  }\n\n  section {\n    border:1px solid #e5e5e5;\n    border-width:1px 0;\n    padding:20px 0;\n    margin:0 0 20px;\n  }\n\n  header a small {\n    display:inline;\n  }\n\n  header ul {\n    position:absolute;\n    right:50px;\n    top:52px;\n  }\n}\n\n@media print, screen and (max-width: 720px) {\n  body {\n    word-wrap:break-word;\n  }\n\n  header {\n    padding:0;\n  }\n\n  header ul, header p.view {\n    position:static;\n  }\n\n  pre, code {\n    word-wrap:normal;\n  }\n}\n\n@media print, screen and (max-width: 480px) {\n  body {\n    padding:15px;\n  }\n\n  header ul {\n    display:none;\n  }\n}\n\n@media print {\n  body {\n    padding:0.4in;\n    font-size:12pt;\n    color:#444;\n  }\n}\n\n\n#wrapper {\n  padding: 1em;\n}\n\n.ca-menu {\n list-style: none;\n  padding: 0;\n  margin: 20px auto;\n}\n\n#toc {\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: auto;\n  width: 20%;\n  background-color: #fff;\n  padding: 20px;\n  position: fixed;\n  z-index: 1;\n  display: none;\n  height: 100%;\n}\n\n\n#toc::before {\n  content: \"\";\n  position: absolute;\n\n  top: 15%;\n  bottom: 15%;\n  left: -1px;\n  width: 1px;\n  background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(227,224,216,0)), color-stop(20%, #e3e0d8), color-stop(80%, #e3e0d8), color-stop(100%, rgba(227,224,216,0)));\n  background: -webkit-linear-gradient(top, rgba(227,224,216,0) 0%,#e3e0d8 20%,#e3e0d8 80%,rgba(227,224,216,0) 100%);\n  background: -moz-linear-gradient(top, rgba(227,224,216,0) 0%,#e3e0d8 20%,#e3e0d8 80%,rgba(227,224,216,0) 100%);\n  background: -o-linear-gradient(top, rgba(227,224,216,0) 0%,#e3e0d8 20%,#e3e0d8 80%,rgba(227,224,216,0) 100%);\n  background: linear-gradient(top, rgba(227,224,216,0) 0%,#e3e0d8 20%,#e3e0d8 80%,rgba(227,224,216,0) 100%);\n}\n\n#toc-inner {\n  display: table-cell;\n  vertical-align: middle;\n}\n\n.nav-list {\n  height: 50%;\n  margin: auto 0;\n}\n\ndiv.clear {\n  clear: both;\n}\n\nh1 { font-size: 2.5em; line-height: 1; }\nh2 { font-size: 2em;   line-height: 1; }\nh3 { font-size: 1.5em; line-height: 1; }\nh4 { font-size: 1.2em; line-height: 1.25; }\nh5 { font-size: 1em;   line-height: 1; font-weight: bold; }\nh6 { font-size: 1em;   line-height: 1; font-weight: bold; }\n\nh1, h2, h3, h4, h5, h6 { font-weight: normal; margin-top: 1em; margin-bottom: 0.5em; }\nh1, h2 { margin-bottom: 0.5em; }\n\n.post p {\n  max-width: 580px;\n}\n\nul.list, ol.list {\n  padding-left: 3.333em;\n  max-width: 580px;\n}\n\n.post h2 {\n  border-bottom: 1px solid #EDEDED;\n}\n\nh1:nth-child(1),\nh2:nth-child(1),\nh3:nth-child(1),\nh4:nth-child(1),\nh5:nth-child(1),\nh6:nth-child(1) {\n  margin-top: 0;\n}\n\n@media (min-width: 43.75em) {\n  #wrapper {\n    width: 650px;\n    padding: 20px 50px;\n  }\n}\n\n@media (min-width: 62em) {\n  #toc {\n    display: table;\n  }\n}\n"
  },
  {
    "path": "doc/themes/screen.css",
    "content": "html { font-size: 62.5%; }\nhtml, body { height: 100%; }\n\nbody {\n    font-family: Helvetica, Arial, sans-serif;\n    font-size: 150%;\n    line-height: 1.3;\n    color: #f6e6cc;\n    width: 700px;\n    margin: auto;\n    background: #27221a;\n    position: relative;\n    padding: 0 30px;\n}\n\np,ul,ol,dl,table,pre { margin-bottom: 1em; }\nul { margin-left: 20px; }\na { text-decoration: none; cursor: pointer; color: #ba832c; font-weight: bold; }\na:focus { outline: 1px dotted; }\na:visited {  }\na:hover, a:focus { color: #d3a459; text-decoration: none; }\na *, button * { cursor: pointer; }\nhr { display: none; }\nsmall { font-size: 90%; }\ninput, select, button, textarea, option { font-family: Arial, \"Lucida Grande\", \"Lucida Sans Unicode\", Arial, Verdana, sans-serif; font-size: 100%; }\nbutton, label, select, option, input[type=submit] { cursor: pointer; }\n.group:after { content: \".\"; display: block; height: 0; clear: both; visibility: hidden; } .group {display: inline-block;}\n/* Hides from IE-mac \\*/ * html .group {height: 1%;} .group {display: block;} /* End hide from IE-mac */\nsup { font-size: 80%; line-height: 1; vertical-align: super; }\nbutton::-moz-focus-inner { border: 0; padding: 1px; }\nspan.amp { font-family: Baskerville, \"Goudy Old Style\", \"Palatino\", \"Book Antiqua\", serif; font-weight: normal; font-style: italic; font-size: 1.2em; line-height: 0.8; }\n\nh1,h2,h3,h4,h5,h6 { \n    line-height: 1.1; \n    font-family: Baskerville, \"Goudy Old Style\", \"Palatino\", \"Book Antiqua\", serif;\n}\n\nh2 { font-size: 22pt; }\nh3 { font-size: 20pt; }\nh4 { font-size: 18pt; }\nh5 { font-size: 16pt; }\nh6 { font-size: 14pt; }\n\n::selection { background: #745626; }\n::-moz-selection { background: #745626; }\n\nh1 {\n    font-size: 420%;\n    margin: 0 0 0.1em;\n    font-family: Baskerville, \"Goudy Old Style\", \"Palatino\", \"Book Antiqua\", serif;\n}\n\nh1 a,\nh1 a:hover {\n    color: #d7af72;\n    font-weight: normal;\n    text-decoration: none;\n}\n\npre {\n    background: rgba(0,0,0,0.3);\n    color: #fff;\n    padding: 8px 10px;\n    border-radius: 0.4em;\n    -moz-border-radius: 0.4em;\n    -webkit-border-radius: 0.4em;\n    overflow-x: hidden;\n}\n\npre code {\n    font-size: 10pt;\n}\n\n.thumb { \n    float:left;\n    margin: 10px;\n}\n"
  },
  {
    "path": "doc/themes/solarized-dark.css",
    "content": "article,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\ncanvas,\nvideo {\n  display: inline-block;\n}\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n[hidden] {\n  display: none;\n}\nhtml {\n  font-family: sans-serif;\n  -webkit-text-size-adjust: 100%;\n  -ms-text-size-adjust: 100%;\n}\nbody {\n  margin: 0;\n}\na:focus {\n  outline: thin dotted;\n}\na:active,\na:hover {\n  outline: 0;\n}\nh1 {\n  font-size: 2em;\n}\nabbr[title] {\n  border-bottom: 1px dotted;\n}\nb,\nstrong {\n  font-weight: bold;\n}\ndfn {\n  font-style: italic;\n}\nmark {\n  background: #ff0;\n  color: #000;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, serif;\n  font-size: 1em;\n}\npre {\n  white-space: pre-wrap;\n  word-wrap: break-word;\n}\nq {\n  quotes: \"\\201C\" \"\\201D\" \"\\2018\" \"\\2019\";\n}\nsmall {\n  font-size: 80%;\n}\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\nsup {\n  top: -0.5em;\n}\nsub {\n  bottom: -0.25em;\n}\nimg {\n  border: 0;\n}\nsvg:not(:root) {\n  overflow: hidden;\n}\nfigure {\n  margin: 0;\n}\nfieldset {\n  border: 1px solid #c0c0c0;\n  margin: 0 2px;\n  padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n  border: 0;\n  padding: 0;\n}\nbutton,\ninput,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: 100%;\n  margin: 0;\n}\nbutton,\ninput {\n  line-height: normal;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button;\n  cursor: pointer;\n}\nbutton[disabled],\ninput[disabled] {\n  cursor: default;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  box-sizing: border-box;\n  padding: 0;\n}\ninput[type=\"search\"] {\n  -webkit-appearance: textfield;\n  -moz-box-sizing: content-box;\n  -webkit-box-sizing: content-box;\n  box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  border: 0;\n  padding: 0;\n}\ntextarea {\n  overflow: auto;\n  vertical-align: top;\n}\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n\nhtml {\n  font-family: 'PT Sans', sans-serif;\n}\npre,\ncode {\n  font-family: 'Inconsolata', sans-serif;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-family: 'PT Sans Narrow', sans-serif;\n  font-weight: 700;\n}\nhtml {\n  background-color: #002b36;\n  color: #839496;\n  margin: 1em;\n}\ncode {\n  /*background-color: #073642;*/\n  padding: 2px;\n}\na {\n  color: #b58900;\n}\na:visited {\n  color: #cb4b16;\n}\na:hover {\n  color: #cb4b16;\n}\nh1 {\n  color: #d33682;\n}\nh2,\nh3,\nh4,\nh5,\nh6 {\n  color: #859900;\n}\npre {\n  /*background-color: #002b36;*/\n  color: #839496;\n  border: 1pt solid #586e75;\n  padding: 1em;\n  box-shadow: 5pt 5pt 8pt #073642;\n}\npre code {\n  /*background-color: #002b36;*/\n}\nh1 {\n  font-size: 2.8em;\n}\nh2 {\n  font-size: 2.4em;\n}\nh3 {\n  font-size: 1.8em;\n}\nh4 {\n  font-size: 1.4em;\n}\nh5 {\n  font-size: 1.3em;\n}\nh6 {\n  font-size: 1.15em;\n}\n.tag {\n  /*background-color: #073642;*/\n  color: #d33682;\n  padding: 0 0.2em;\n}\n.todo,\n.next,\n.done {\n  color: #002b36;\n  background-color: #dc322f;\n  padding: 0 0.2em;\n}\n.tag {\n  -webkit-border-radius: 0.35em;\n  -moz-border-radius: 0.35em;\n  border-radius: 0.35em;\n}\n.TODO {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  background-color: #2aa198;\n}\n.NEXT {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  background-color: #268bd2;\n}\n.ACTIVE {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  background-color: #268bd2;\n}\n.DONE {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  background-color: #859900;\n}\n.WAITING {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  foreground-color: #cb4b16;\n}\n.HOLD {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  foreground-color: #d33682;\n}\n.NOTE {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  foreground-color: #d33682;\n}\n.CANCELLED {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  foreground-color: #859900;\n}\n"
  },
  {
    "path": "doc/themes/solarized-light.css",
    "content": "article,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\ncanvas,\nvideo {\n  display: inline-block;\n}\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n[hidden] {\n  display: none;\n}\nhtml {\n  font-family: sans-serif;\n  -webkit-text-size-adjust: 100%;\n  -ms-text-size-adjust: 100%;\n}\nbody {\n  margin: 0;\n}\na:focus {\n  outline: thin dotted;\n}\na:active,\na:hover {\n  outline: 0;\n}\nh1 {\n  font-size: 2em;\n}\nabbr[title] {\n  border-bottom: 1px dotted;\n}\nb,\nstrong {\n  font-weight: bold;\n}\ndfn {\n  font-style: italic;\n}\nmark {\n  background: #ff0;\n  color: #000;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, serif;\n  font-size: 1em;\n}\npre {\n  white-space: pre-wrap;\n  word-wrap: break-word;\n}\nq {\n  quotes: \"\\201C\" \"\\201D\" \"\\2018\" \"\\2019\";\n}\nsmall {\n  font-size: 80%;\n}\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\nsup {\n  top: -0.5em;\n}\nsub {\n  bottom: -0.25em;\n}\nimg {\n  border: 0;\n}\nsvg:not(:root) {\n  overflow: hidden;\n}\nfigure {\n  margin: 0;\n}\nfieldset {\n  border: 1px solid #c0c0c0;\n  margin: 0 2px;\n  padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n  border: 0;\n  padding: 0;\n}\nbutton,\ninput,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: 100%;\n  margin: 0;\n}\nbutton,\ninput {\n  line-height: normal;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button;\n  cursor: pointer;\n}\nbutton[disabled],\ninput[disabled] {\n  cursor: default;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  box-sizing: border-box;\n  padding: 0;\n}\ninput[type=\"search\"] {\n  -webkit-appearance: textfield;\n  -moz-box-sizing: content-box;\n  -webkit-box-sizing: content-box;\n  box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  border: 0;\n  padding: 0;\n}\ntextarea {\n  overflow: auto;\n  vertical-align: top;\n}\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n\nhtml {\n  font-family: 'PT Sans', sans-serif;\n}\npre,\ncode {\n  font-family: 'Inconsolata', sans-serif;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-family: 'PT Sans Narrow', sans-serif;\n  font-weight: 700;\n}\nhtml {\n  background-color: #fdf6e3;\n  color: #657b83;\n  margin: 1em;\n}\ncode {\n  background-color: #eee8d5;\n  padding: 2px;\n}\na {\n  color: #b58900;\n}\na:visited {\n  color: #cb4b16;\n}\na:hover {\n  color: #cb4b16;\n}\nh1 {\n  color: #d33682;\n}\nh2,\nh3,\nh4,\nh5,\nh6 {\n  color: #859900;\n}\npre {\n  background-color: #fdf6e3;\n  color: #657b83;\n  border: 1pt solid #93a1a1;\n  padding: 1em;\n  box-shadow: 5pt 5pt 8pt #eee8d5;\n}\npre code {\n  background-color: #fdf6e3;\n}\nh1 {\n  font-size: 2.8em;\n}\nh2 {\n  font-size: 2.4em;\n}\nh3 {\n  font-size: 1.8em;\n}\nh4 {\n  font-size: 1.4em;\n}\nh5 {\n  font-size: 1.3em;\n}\nh6 {\n  font-size: 1.15em;\n}\n.tag {\n  background-color: #eee8d5;\n  color: #d33682;\n  padding: 0 0.2em;\n}\n.todo,\n.next,\n.done {\n  color: #fdf6e3;\n  background-color: #dc322f;\n  padding: 0 0.2em;\n}\n.tag {\n  -webkit-border-radius: 0.35em;\n  -moz-border-radius: 0.35em;\n  border-radius: 0.35em;\n}\n.TODO {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  background-color: #2aa198;\n}\n.NEXT {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  background-color: #268bd2;\n}\n.ACTIVE {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  background-color: #268bd2;\n}\n.DONE {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  background-color: #859900;\n}\n.WAITING {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  foreground-color: #cb4b16;\n}\n.HOLD {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  foreground-color: #d33682;\n}\n.NOTE {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  foreground-color: #d33682;\n}\n.CANCELLED {\n  -webkit-border-radius: 0.2em;\n  -moz-border-radius: 0.2em;\n  border-radius: 0.2em;\n  foreground-color: #859900;\n}\n"
  },
  {
    "path": "doc/themes/torpedo.css",
    "content": "/* Title: Torpedo */\n/* Author: Jocelyn Richard http://jocelynrichard.com/ */\n/* Description: A muted color palette for long-form writing or reading, suited for technical documentation. Works best with Cinta: http://www.myfonts.com/fonts/tipo-pepel/cinta/ */\n\n/* ================================================ */\n/* 1. Reset */\n/* 2. Skeleton */\n/* 3. Media Queries */\n/* 4. Print Styles */\n/* 5. Torpedo Overrides */\n/* ================================================ */\n\n\n\n/* ================================================ */\n/* 1. Reset */\n/* ================================================ */\n\nhtml, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var,\nb, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {margin: 0; padding: 0; border: 0;} /* Edited from http://www.cssreset.com/scripts/eric-meyer-reset-css/ */\n\narticle, aside, details, figcaption, figure, footer, header, hgroup, nav, section, summary {display: block;} /* Semantic tags definition for IE 6/7/8/9 and Firefox 3 */\n\nhtml {-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;} /* Prevents iOS text size adjust after orientation change, without disabling user zoom */\n\n\n\n/* ================================================ */\n/* 2. Skeleton */\n/* ================================================ */\n\n/* ------------------------------------------------- */\n/* General */\n/* ------------------------------------------------- */\n\nhtml {\n  font-size: 14px;\n}\n\nbody {\n  font-family: 'Open Sans', sans-serif;\n  margin: 1.71rem 1.71rem  3rem 1.71rem ; /* Get margins even if the Markdown rendering app doesn't include any */\n  background-color: white;\n  color: #222;\n}\n\n#wrapper { /* #wrapper: ID added by Marked */\n  max-width: 42rem;\n  margin: 0 auto;\n  margin-left: auto !important;  /* Countering toc.css added by Marked */\n  padding: 1.71rem 0 !important; /* Countering toc.css added by Marked */\n}\n\n/* ------------------------------------------------- */\n/* Typography */\n/* ------------------------------------------------- */\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n    margin-bottom: 1.6rem;\n}\n\nh1,\nh2 {\n    margin-top: 3.2rem;\n}\n\nh1 {\n    font-size:   2.82rem; /* 42.3px @15px */\n    line-height: 3.2rem;  /* 48px @15px */\n}\n\nh2 {\n    font-size:   1.99rem; /* 29.9px @15px */\n    line-height: 2.4rem;  /* 36px @15px */\n}\n\nh3 {\n    font-size:   1.41rem; /* 21.2px @15px */\n    line-height: 2rem;    /* 30px @15px */\n}\n\nh4 {\n    font-size:   1rem;   /* 15px @15px */\n    line-height: 1.6rem; /* 24px @15px */\n}\n\nh5, h6 {\n  font-size:   0.8rem;\n    line-height: 1.2rem;\n    text-transform: uppercase;\n}\n\nh6 {\n  margin-left: 1.6rem;\n}\n\np,\nol,\nul,\nblockquote {\n    font-size:     1rem;\n    line-height:   1.6rem;\n    margin-bottom: 1.6rem;\n}\n\nul ul,\nul ol,\nol ul,\nol ol {\n  margin-left: 1.6rem;\n  margin-top:  1.6rem;\n}\n\n#generated-toc ul ul, /* #generated-toc: added by Marked for its table of contents */\n#generated-toc ul ol,\n#generated-toc ol ul,\n#generated-toc ol ol {\n  margin-top:     0;\n  margin-bottom:  0;\n  padding-top:    0;\n  padding-bottom: 0;\n}\n\nblockquote {\n  margin: 0 0 1.6rem 2.4rem;\n  padding-left: 0.8rem; /* Voire */\n  border-left: 4px solid rgba(0,0,0,0.08);\n  font-style: normal;\n}\n\nblockquote ul {\n  margin-left: 0.8rem; /* Pour ne pas que les hanging bullets mordent sur le blockquote  */\n}\n\nol li blockquote, /* So that blockquote work in lists */\nul li blockquote {\n  margin-left: 0;\n}\n\na:link {\n  text-decoration: none;\n  color: #165bd4;\n  border-bottom: 1px solid #ccc;\n}\n\na:visited {\n  color: #7697cf;\n  border-bottom: 1px solid #ccc;\n}\n\na:hover {\n  border-color: #165bd4;\n}\n\na:active {\n  background-color: #e6e6e6;\n}\n\n/* ------------------------------------------------- */\n/* Tables */\n/* ------------------------------------------------- */\n\ntable {\n  font-size: 0.85rem;\n  margin: 0 0 1.6rem 0;\n  border-collapse: collapse;\n  border: 1px solid #ccc;\n}\n\nth,\ntd {\n  padding: 0.5rem 0.75rem;\n  max-width: 20rem; /* Avoid dropping lines for nothing without having ridiculously wide tables */\n}\n\nth {\n  border-bottom: 2px solid #222;\n}\n\ntr {\n  border-bottom: 1px solid #ccc;\n}\n\ntbody tr:nth-child(odd) {\n  background-color: #f9f9f9;\n}\n\ntable code {\n  font-size: 85%;\n}\n\n/* ------------------------------------------------- */\n/* Misc */\n/* ------------------------------------------------- */\n\nimg {\n  max-width: 100%\n}\n\ncaption,\nfigcaption {\n  font-size:   0.85rem;\n    line-height: 1.6rem;\n  margin: 0 1.6rem;\n  text-align: left;\n}\n\nfigcaption {\n  margin-bottom: 1.6rem;\n}\n\nh1, /* White-space mentions in order to force wrapping */\nh2,\na:link,\npre {\n  white-space:      pre;      /* CSS 2.0 */\n  white-space:      pre-wrap; /* CSS 2.1 */\n  white-space:      pre-line; /* CSS 3.0 */\n  white-space:     -pre-wrap; /* Opera 4-6 */\n  white-space:   -o-pre-wrap; /* Opera 7 */\n  white-space: -moz-pre-wrap; /* Mozilla */\n  white-space:  -hp-pre-wrap; /* HP Printers */\n  word-wrap: break-word;      /* IE 5+ */\n}\n\ncode {\n  font-family: \"Menlo\", \"Courier New\", \"Courier\", monospace;\n  font-size: 85%;\n  color: #666;\n  background-color: rgba(0,0,0,0.08);\n  padding: 2px 4px;\n  border-radius: 2px;\n}\n\npre {\n  background-color: rgba(0,0,0,0.08);\n  border-radius: 8px;\n  padding: 0.4rem;\n  margin-bottom: 1.6rem;\n}\n\npre code { /* Counter the code mentions */\n  background-color: transparent;\n  padding: 0;\n}\n\nsup,\nsub,\na.footnote { /* Keep line-height from being affected by sub, cf https://gist.github.com/unruthless/413930 */\n   font-size: 75%;\n   height: 0;\n   line-height: 1;\n   position: relative;\n}\n\nsup,\na.footnote {\n    vertical-align:super;\n}\n\nsub {\n    vertical-align: sub;\n}\n\ndt {\n  font-weight: 600;\n}\n\ndd {\n  font-size: 1rem;\n  line-height: 1.6rem;\n  margin-bottom: 1.6rem;\n}\n\nhr {\n  clear: none;\n  height: 0.2rem;\n  border: none;\n  margin: 0 auto 1.4rem auto; /* 2.4rem auto 2.2rem auto; */\n  width: 100%;\n  color: #ccc;\n  background-color: #ccc;\n}\n\n::selection {\n  background-color: #f8dc77;\n}\n\n::-moz-selection {\n  background-color: #f8dc77;\n}\n\na:focus {\n  outline: 2px solid;\n  outline-color: #165bd4;\n}\n\n/* ------------------------------------------------- */\n/* Animations */\n/* ------------------------------------------------- */\n\na:hover {\n     -moz-transition: all 0.2s ease-in-out;\n  -webkit-transition: all 0.2s ease-in-out;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote {\n     -moz-transition: all 0.2s ease;\n  -webkit-transition: all 0.2s ease;\n}\n\n\n\n/* ================================================ */\n/* 3. Media Queries */\n/* ================================================ */\n\n/* Base styles are for smartphones; elements are then tweaked as the viewport grows. */\n\n/* ------------------------------------------------- */\n/* iPad and desktop */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 641px) {\n\n  html {\n    font-size: 15px;\n  }\n\n  body {\n    margin: 2.4rem 2.4rem 3.2rem 2.4rem;\n  }\n\n  h1 {\n      font-size:   3.57rem; /* 53.2px @15px */\n    line-height: 4rem;    /* 60px @15px */\n  }\n\n  h2 {\n    font-size:   2.24rem; /* 33.6px @15px */\n    line-height: 2.8rem;  /* 42px @15px */\n  }\n\n}\n\n/* ------------------------------------------------- */\n/* Widescreens */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 1441px) {\n\n  html {\n    font-size: 22px;\n  }\n\n}\n\n\n\n/* ================================================ */\n/* 4. Print Styles */\n/* ================================================ */\n\n/* Inconsistent and buggy across browsers */\n\n@media print {\n\n  * {\n    background: transparent !important;\n    color: #000 !important; /* Black text prints faster and browsers are inconsistent in color reproduction anyway: h5bp.com/s */\n  }\n\n  @page {\n    margin: 1cm; /* Added to any #wrapper margin*/\n  }\n\n  html {\n    font-size: 15px;\n  }\n\n  body {\n    margin: 1rem !important; /* Security margins for browser without @page support */\n  }\n\n  #wrapper {\n    max-width: none;\n  }\n\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6,\n  p {\n    orphans: 3;\n    widows: 3;\n    page-break-after: avoid;\n  }\n\n  ul,\n  ol {\n    list-style-position: inside !important;\n    padding-right: 0 !important;\n    margin-left: 0 !important;\n  }\n\n  ul ul,\n  ul ol,\n  ol ul,\n  ol ol,\n  ul p:not(:first-child),\n  ol p:not(:first-child) {\n    margin-left: 2rem !important;\n  }\n\n  a:link,\n  a:visited {\n    text-decoration: underline !important;\n    font-weight: normal !important;\n  }\n\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n\n  a[href^=\"javascript:\"]:after,\n  a[href^=\"#\"]:after {\n    content: \"\"; /* Do not show javascript and internal links */\n  }\n\n  a[href^=\"#\"] {\n    text-decoration: none !important;\n  }\n\n  th {\n    background-color: rgba(0,0,0,0.2) !important;\n    border-bottom: none !important;\n  }\n\n  tr {\n    page-break-inside: avoid;\n  }\n\n  tbody tr:nth-child(even) {\n    background-color: rgba(0,0,0,0.1) !important;\n  }\n\n  pre {\n    border: 1px solid rgba(0,0,0,0.2);\n    page-break-inside: avoid;\n  }\n\n  img {\n    max-width: 100% !important;\n    page-break-inside: avoid;\n  }\n\n  /* #generated-toc: added by Marked for its table of contents */\n\n  #wrapper #generated-toc ul, /* Table of contents printing in Marked */\n  #wrapper #generated-toc ol {\n    list-style-type: decimal;\n  }\n\n  #wrapper #generated-toc ul li,\n  #wrapper #generated-toc ol li {\n    margin: 1rem 0;\n  }\n\n}\n\n\n\n/* ================================================ */\n/* 5. Torpedo Overrides */\n/* ================================================ */\n\n/* ------------------------------------------------- */\n/* General */\n/* ------------------------------------------------- */\n\nhtml {\n  font-size: 16px;\n}\n\nbody {\n  font-family: 'Cinta', 'Source Sans Pro', Avenir, sans-serif;\n  background-color: #F9F9F9;\n  color: #636463;\n}\n\n#wrapper {\n  max-width: 40rem;\n}\n\n/* ------------------------------------------------- */\n/* Typography */\n/* ------------------------------------------------- */\n\nh1,\nh2 {\n  color: #febf60;\n}\n\nh3 {\n  color: #b46864;\n}\n\nh4,\nh5,\nh6 {\n  font-size: 1rem;\n  color: #b46864;\n  text-transform: none;\n}\n\nh5 {\n  margin-left: 1.6rem;\n}\n\nh6 {\n  margin-left: 3.2rem;\n}\n\np,\nol,\nul,\ndd,\nfigcaption {\n  font-weight: 300;\n}\n\nblockquote {\n  border-color: #efefef;\n}\n\na:link,\na:visited,\na:hover,\na:visited {\n  color: #636463;\n}\n\na:link {\n  font-weight: 600;\n  text-decoration: none;\n  border-bottom: none;\n  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMEAAAAJCAYAAACL+UhFAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAE9JREFUWAnt0wENACEQBLHn/SEGK1jCGAk2pufgmp2x9pmfIxAW+MO/e53AExCBIeQFRJCfAAAR2EBeQAT5CQAQgQ3kBUSQnwAAEdhAXuACbF4CJXG10MIAAAAASUVORK5CYII=);\n  background-repeat: repeat-x;\n  background-position: 0 0.7rem;\n}\n\na:visited { /* Doesn't work? */\n  background-position: 0 1rem;\n}\n\na:hover {\n  font-weight: 600;\n  text-decoration: none;\n  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMEAAAAJCAYAAACL+UhFAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAE9JREFUWAnt0wENACEQBLHn/SEGK1jCGAk2pufgmp2x9pmfIxAW+MO/e53AExCBIeQFRJCfAAAR2EBeQAT5CQAQgQ3kBUSQnwAAEdhAXuACbF4CJXG10MIAAAAASUVORK5CYII=);\n  background-repeat: repeat;\n  background-position-y: 0 0;\n}\n\n/* ------------------------------------------------- */\n/* Tables */\n/* ------------------------------------------------- */\n\ntable {\n  font-weight: 300;\n}\n\ntable,\nth,\ntr {\n  border: none;\n}\n\nth {\n  border-bottom: 1px solid #da9e1a;\n  background-color: #ffd18c;\n}\n\ntbody tr:nth-child(even) {\n  background-color: #efefef;\n}\n\n/* ------------------------------------------------- */\n/* Misc */\n/* ------------------------------------------------- */\n\ncode {\n  color: #5597e7;\n  padding: 0; /* PLus besoin car plus de risque de collision avec l'arrière-plan coloré */\n  background-color: transparent;\n}\n\npre {\n  background-color: transparent;\n}\n\nhr {\n  clear: none;\n  height: 1rem;\n  width: 14rem;\n  margin: 2.5rem auto;\n  border: none;\n  color: none;\n  background-color: transparent;\n  background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjEyNnB4IiBoZWlnaHQ9IjE2cHgiIHZpZXdCb3g9IjAgMCAxMjYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPHRpdGxlPmhyPC90aXRsZT4KICAgIDxkZXNjcmlwdGlvbj5DcmVhdGVkIHdpdGggU2tldGNoIChodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gpPC9kZXNjcmlwdGlvbj4KICAgIDxkZWZzPjwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPgogICAgICAgIDxwYXRoIGQ9Ik0xMywzIEwzLDEzIiBpZD0iTGluZSIgc3Ryb2tlPSIjRDRERUVCIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNMTMsMyBMMjMuMDQ5ODc1MywxMy4wNDk4NzUzIiBpZD0iTGluZSIgc3Ryb2tlPSIjRDRERUVCIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNMzMsMyBMMjMsMTMiIGlkPSJMaW5lIiBzdHJva2U9IiNENERFRUIiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPgogICAgICAgIDxwYXRoIGQ9Ik0zMywzIEw0My4wNDk4NzUzLDEzLjA0OTg3NTMiIGlkPSJMaW5lIiBzdHJva2U9IiNENERFRUIiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPgogICAgICAgIDxwYXRoIGQ9Ik01MywzIEw0MywxMyIgaWQ9IkxpbmUiIHN0cm9rZT0iI0Q0REVFQiIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgPHBhdGggZD0iTTUzLDMgTDYzLjA0OTg3NTMsMTMuMDQ5ODc1MyIgaWQ9IkxpbmUiIHN0cm9rZT0iI0Q0REVFQiIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgPHBhdGggZD0iTTczLDMgTDYzLDEzIiBpZD0iTGluZSIgc3Ryb2tlPSIjRDRERUVCIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNNzMsMyBMODMuMDQ5ODc1MywxMy4wNDk4NzUzIiBpZD0iTGluZSIgc3Ryb2tlPSIjRDRERUVCIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNOTMsMyBMODMsMTMiIGlkPSJMaW5lIiBzdHJva2U9IiNENERFRUIiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPgogICAgICAgIDxwYXRoIGQ9Ik05MywzIEwxMDMuMDQ5ODc1LDEzLjA0OTg3NTMiIGlkPSJMaW5lIiBzdHJva2U9IiNENERFRUIiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPgogICAgICAgIDxwYXRoIGQ9Ik0xMTMsMyBMMTAzLDEzIiBpZD0iTGluZSIgc3Ryb2tlPSIjRDRERUVCIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNMTEzLDMgTDEyMy4wNDk4NzUsMTMuMDQ5ODc1MyIgaWQ9IkxpbmUiIHN0cm9rZT0iI0Q0REVFQiIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICA8L2c+Cjwvc3ZnPg==);\n  background-repeat: no-repeat;\n  background-position-x: 50%;\n}\n\n::selection {\n  background-color: #D4DEEB;\n  color: #b46864;\n  }\n\n::-moz-selection {\n  background-color: #D4DEEB;\n  color: #b46864;\n}\n\n/* #generated-toc: added by Marked for its table of contents */\n\n#generated-toc ul li a {\n  font-weight: normal;\n}\n\n/* ------------------------------------------------- */\n/* iPad and desktop */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 641px) {\n\n  html {\n    font-size: 17px;\n  }\n\n}\n\n/* ------------------------------------------------- */\n/* Widescreens */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 1441px) {\n\n  html {\n    font-size: 22px;\n  }\n\n}\n"
  },
  {
    "path": "doc/themes/vostok.css",
    "content": "/* Title: Vostok */\n/* Author: Jocelyn Richard http://jocelynrichard.com/ */\n/* Description: Generous x-height and contrasted colors make for highly legible documents. Works best with the free PT fonts: http://www.paratype.com/public/ */\n\n/* ================================================ */\n/* 1. Reset */\n/* 2. Skeleton */\n/* 3. Media Queries */\n/* 4. Print Styles */\n/* 5. Vostok Overrides */\n/* ================================================ */\n\n\n\n/* ================================================ */\n/* 1. Reset */\n/* ================================================ */\n\nhtml, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var,\nb, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {margin: 0; padding: 0; border: 0;} /* Edited from http://www.cssreset.com/scripts/eric-meyer-reset-css/ */\n\narticle, aside, details, figcaption, figure, footer, header, hgroup, nav, section, summary {display: block;} /* Semantic tags definition for IE 6/7/8/9 and Firefox 3 */\n\nhtml {-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;} /* Prevents iOS text size adjust after orientation change, without disabling user zoom */\n\n\n\n/* ================================================ */\n/* 2. Skeleton */\n/* ================================================ */\n\n/* ------------------------------------------------- */\n/* General */\n/* ------------------------------------------------- */\n\nhtml {\n  font-size: 14px;\n}\n\nbody {\n  font-family: 'Open Sans', sans-serif;\n  margin: 1.71rem 1.71rem  3rem 1.71rem ; /* Get margins even if the Markdown rendering app doesn't include any */\n  background-color: white;\n  color: #222;\n}\n\n#wrapper { /* #wrapper: ID added by Marked */\n  max-width: 42rem;\n  margin: 0 auto;\n  margin-left: auto !important;  /* Countering toc.css added by Marked */\n  padding: 1.71rem 0 !important; /* Countering toc.css added by Marked */\n}\n\n/* ------------------------------------------------- */\n/* Typography */\n/* ------------------------------------------------- */\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n    margin-bottom: 1.6rem;\n}\n\nh1,\nh2 {\n    margin-top: 3.2rem;\n}\n\nh1 {\n    font-size:   2.82rem; /* 42.3px @15px */\n    line-height: 3.2rem;  /* 48px @15px */\n}\n\nh2 {\n    font-size:   1.99rem; /* 29.9px @15px */\n    line-height: 2.4rem;  /* 36px @15px */\n}\n\nh3 {\n    font-size:   1.41rem; /* 21.2px @15px */\n    line-height: 2rem;    /* 30px @15px */\n}\n\nh4 {\n    font-size:   1rem;   /* 15px @15px */\n    line-height: 1.6rem; /* 24px @15px */\n}\n\nh5, h6 {\n  font-size:   0.8rem;\n    line-height: 1.2rem;\n    text-transform: uppercase;\n}\n\nh6 {\n  margin-left: 1.6rem;\n}\n\np,\nol,\nul,\nblockquote {\n    font-size:     1rem;\n    line-height:   1.6rem;\n    margin-bottom: 1.6rem;\n}\n\nul ul,\nul ol,\nol ul,\nol ol {\n  margin-left: 1.6rem;\n  margin-top:  1.6rem;\n}\n\n#generated-toc ul ul, /* #generated-toc: added by Marked for its table of contents */\n#generated-toc ul ol,\n#generated-toc ol ul,\n#generated-toc ol ol {\n  margin-top:     0;\n  margin-bottom:  0;\n  padding-top:    0;\n  padding-bottom: 0;\n}\n\nblockquote {\n  margin: 0 0 1.6rem 2.4rem;\n  padding-left: 0.8rem; /* Voire */\n  border-left: 4px solid rgba(0,0,0,0.08);\n  font-style: normal;\n}\n\nblockquote ul {\n  margin-left: 0.8rem; /* Pour ne pas que les hanging bullets mordent sur le blockquote  */\n}\n\nol li blockquote, /* So that blockquote work in lists */\nul li blockquote {\n  margin-left: 0;\n}\n\na:link {\n  text-decoration: none;\n  color: #165bd4;\n  border-bottom: 1px solid #ccc;\n}\n\na:visited {\n  color: #7697cf;\n  border-bottom: 1px solid #ccc;\n}\n\na:hover {\n  border-color: #165bd4;\n}\n\na:active {\n  background-color: #e6e6e6;\n}\n\n/* ------------------------------------------------- */\n/* Tables */\n/* ------------------------------------------------- */\n\ntable {\n  font-size: 0.85rem;\n  margin: 0 0 1.6rem 0;\n  border-collapse: collapse;\n  border: 1px solid #ccc;\n}\n\nth,\ntd {\n  padding: 0.5rem 0.75rem;\n  max-width: 20rem; /* Avoid dropping lines for nothing without having ridiculously wide tables */\n}\n\nth {\n  border-bottom: 2px solid #222;\n}\n\ntr {\n  border-bottom: 1px solid #ccc;\n}\n\ntbody tr:nth-child(odd) {\n  background-color: #f9f9f9;\n}\n\ntable code {\n  font-size: 85%;\n}\n\n/* ------------------------------------------------- */\n/* Misc */\n/* ------------------------------------------------- */\n\nimg {\n  max-width: 100%\n}\n\ncaption,\nfigcaption {\n  font-size:   0.85rem;\n    line-height: 1.6rem;\n  margin: 0 1.6rem;\n  text-align: left;\n}\n\nfigcaption {\n  margin-bottom: 1.6rem;\n}\n\nh1, /* White-space mentions in order to force wrapping */\nh2,\na:link,\npre {\n  white-space:      pre;      /* CSS 2.0 */\n  white-space:      pre-wrap; /* CSS 2.1 */\n  white-space:      pre-line; /* CSS 3.0 */\n  white-space:     -pre-wrap; /* Opera 4-6 */\n  white-space:   -o-pre-wrap; /* Opera 7 */\n  white-space: -moz-pre-wrap; /* Mozilla */\n  white-space:  -hp-pre-wrap; /* HP Printers */\n  word-wrap: break-word;      /* IE 5+ */\n}\n\ncode {\n  font-family: \"Menlo\", \"Courier New\", \"Courier\", monospace;\n  font-size: 85%;\n  color: #666;\n  background-color: rgba(0,0,0,0.08);\n  padding: 2px 4px;\n  border-radius: 2px;\n}\n\npre {\n  background-color: rgba(0,0,0,0.08);\n  border-radius: 8px;\n  padding: 0.4rem;\n  margin-bottom: 1.6rem;\n}\n\npre code { /* Counter the code mentions */\n  background-color: transparent;\n  padding: 0;\n}\n\nsup,\nsub,\na.footnote { /* Keep line-height from being affected by sub, cf https://gist.github.com/unruthless/413930 */\n   font-size: 75%;\n   height: 0;\n   line-height: 1;\n   position: relative;\n}\n\nsup,\na.footnote {\n    vertical-align:super;\n}\n\nsub {\n    vertical-align: sub;\n}\n\ndt {\n  font-weight: 600;\n}\n\ndd {\n  font-size: 1rem;\n  line-height: 1.6rem;\n  margin-bottom: 1.6rem;\n}\n\nhr {\n  clear: none;\n  height: 0.2rem;\n  border: none;\n  margin: 0 auto 1.4rem auto; /* 2.4rem auto 2.2rem auto; */\n  width: 100%;\n  color: #ccc;\n  background-color: #ccc;\n}\n\n::selection {\n  background-color: #f8dc77;\n}\n\n::-moz-selection {\n  background-color: #f8dc77;\n}\n\na:focus {\n  outline: 2px solid;\n  outline-color: #165bd4;\n}\n\n/* ------------------------------------------------- */\n/* Animations */\n/* ------------------------------------------------- */\n\na:hover {\n     -moz-transition: all 0.2s ease-in-out;\n  -webkit-transition: all 0.2s ease-in-out;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote {\n     -moz-transition: all 0.2s ease;\n  -webkit-transition: all 0.2s ease;\n}\n\n\n\n/* ================================================ */\n/* 3. Media Queries */\n/* ================================================ */\n\n/* Base styles are for smartphones; elements are then tweaked as the viewport grows. */\n\n/* ------------------------------------------------- */\n/* iPad and desktop */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 641px) {\n\n  html {\n    font-size: 15px;\n  }\n\n  body {\n    margin: 2.4rem 2.4rem 3.2rem 2.4rem;\n  }\n\n  h1 {\n      font-size:   3.57rem; /* 53.2px @15px */\n    line-height: 4rem;    /* 60px @15px */\n  }\n\n  h2 {\n    font-size:   2.24rem; /* 33.6px @15px */\n    line-height: 2.8rem;  /* 42px @15px */\n  }\n\n}\n\n/* ------------------------------------------------- */\n/* Widescreens */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 1441px) {\n\n  html {\n    font-size: 22px;\n  }\n\n}\n\n\n\n/* ================================================ */\n/* 4. Print Styles */\n/* ================================================ */\n\n/* Inconsistent and buggy across browsers */\n\n@media print {\n\n  * {\n    background: transparent !important;\n    color: #000 !important; /* Black text prints faster and browsers are inconsistent in color reproduction anyway: h5bp.com/s */\n  }\n\n  @page {\n    margin: 1cm; /* Added to any #wrapper margin*/\n  }\n\n  html {\n    font-size: 15px;\n  }\n\n  body {\n    margin: 1rem !important; /* Security margins for browser without @page support */\n  }\n\n  #wrapper {\n    max-width: none;\n  }\n\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6,\n  p {\n    orphans: 3;\n    widows: 3;\n    page-break-after: avoid;\n  }\n\n  ul,\n  ol {\n    list-style-position: inside !important;\n    padding-right: 0 !important;\n    margin-left: 0 !important;\n  }\n\n  ul ul,\n  ul ol,\n  ol ul,\n  ol ol,\n  ul p:not(:first-child),\n  ol p:not(:first-child) {\n    margin-left: 2rem !important;\n  }\n\n  a:link,\n  a:visited {\n    text-decoration: underline !important;\n    font-weight: normal !important;\n  }\n\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n\n  a[href^=\"javascript:\"]:after,\n  a[href^=\"#\"]:after {\n    content: \"\"; /* Do not show javascript and internal links */\n  }\n\n  a[href^=\"#\"] {\n    text-decoration: none !important;\n  }\n\n  th {\n    background-color: rgba(0,0,0,0.2) !important;\n    border-bottom: none !important;\n  }\n\n  tr {\n    page-break-inside: avoid;\n  }\n\n  tbody tr:nth-child(even) {\n    background-color: rgba(0,0,0,0.1) !important;\n  }\n\n  pre {\n    border: 1px solid rgba(0,0,0,0.2);\n    page-break-inside: avoid;\n  }\n\n  img {\n    max-width: 100% !important;\n    page-break-inside: avoid;\n  }\n\n  /* #generated-toc: added by Marked for its table of contents */\n\n  #wrapper #generated-toc ul, /* Table of contents printing in Marked */\n  #wrapper #generated-toc ol {\n    list-style-type: decimal;\n  }\n\n  #wrapper #generated-toc ul li,\n  #wrapper #generated-toc ol li {\n    margin: 1rem 0;\n  }\n\n}\n\n\n\n/* ================================================ */\n/* 5. Vostok Overrides */\n/* ================================================ */\n\n/* ------------------------------------------------- */\n/* General */\n/* ------------------------------------------------- */\n\nhtml {\n  font-size: 15px;\n}\n\nbody {\n  font-family: \"pt serif\", Georgia, serif;\n  color: rgba(0,0,0,0.7);\n  text-shadow: 0 1px 0 white;\n  background-color: #ececec;\n}\n\n#wrapper {\n  max-width: 40rem;\n}\n\n/* ------------------------------------------------- */\n/* Typography */\n/* ------------------------------------------------- */\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-family: \"pt sans\", \"avenir next\", sans-serif;\n  font-weight: 700;\n  color: rgba(0,0,0,1);\n}\n\nh2,\nh3 {\n  font-family: \"pt sans narrow\", \"avenir next condensed\", sans-serif;\n}\n\nh5 {\n  color: rgba(0,0,0,0.7);\n}\n\nh6 {\n  color: rgba(0,0,0,0.7);\n}\n\nul {\n  list-style-type: none;\n}\n\nul > li:before {\n  content: \"\\2022\";\n  float: left;\n  margin-left: -1.2rem;\n  padding-right: 0.6rem; /* Empirically chosen to align horizontally with the position of standard bullet points */\n}\n\nul li,\nol li {\n  margin: 0;\n}\n\nul ul,\nul ol,\nol ul,\nol ol,\nul p:not(:first-child),\nol p:not(:first-child) {\n  margin-left: 1.6rem;\n}\n\nblockquote {\n  border-left: 2px solid rgba(0,0,0,0.5);\n}\n\na:link {\n  text-decoration: none;\n  border-bottom: none;\n  font-weight: 700;\n  color: rgba(25,107,240,1);\n}\n\na:visited {\n  color: #2c508a;\n}\n\na:hover {\n  color: #2DAB5F;\n}\n\na:focus {\n  outline: 0.125rem solid;\n}\n\n/* ------------------------------------------------- */\n/* Tables */\n/* ------------------------------------------------- */\n\nthead {\n  border: 1px solid #4C4C4C; /* In hex (= rgba(0,0,0,0.7)) otherwise the alpha gets the border of the tr below  */\n}\n\nth {\n  background-color: #4C4C4C; /* = rgba(0,0,0,0.7) */\n  border-bottom: 1px solid #4C4C4C; /* = rgba(0,0,0,0.7) ; mentionned otherwise the stroke is of the tr below */\n  color: white;\n  text-shadow: 0px -1px 0px black;\n}\n\ntbody tr:nth-child(even) {\n  background-color: rgba(255,255,255,0.5);\n}\n\ntbody tr:nth-child(odd) {\n  background-color: transparent;\n}\n\n/* ------------------------------------------------- */\n/* Misc */\n/* ------------------------------------------------- */\n\n\ntable,\ncaption,\nfigcaption {\n  font-family: \"pt sans\", \"avenir next\", sans-serif;\n}\n\ncode {\n  border: 1px solid rgba(255,255,255,0.7);\n  background-color: rgba(255,255,255,0.5);\n  border-radius: 2px;\n  color: #2DAB5F;\n  text-shadow: none;\n}\n\npre {\n  border: 1px solid rgba(255,255,255,0.7);\n  background-color: rgba(255,255,255,0.5);\n  border-radius: 2px;\n}\n\npre code {\n  color: #81A181;\n  border: none;\n}\n\nhr {\n  clear: none;\n  height: 2px; /* height: 0.125rem; */\n  border: none;\n  margin: 1.5rem auto;\n  width: 14rem;\n  color: rgba(0,0,0,0.5);\n  background-color: rgba(0,0,0,0.5);\n}\n\n::selection {\n  background-color: #fbfb48;\n}\n\n::-moz-selection {\n  background-color: #fbfb48;\n}\n\n/* #generated-toc: added by Marked for its table of contents */\n\n#generated-toc {\n  text-shadow: none;\n}\n\n#generated-toc ul { /* Compensate for the earlier custom bullet point */\n  margin: 0;\n  list-style-type: none;\n}\n\n#generated-toc ul li { /* Compensate for the earlier custom bullet point */\n  margin: 0;\n}\n\n#generated-toc ul > li:before { /* Compensate for the earlier custom bullet point */\n  content: none;\n  margin-left: 0;\n  padding-right: 0;\n}\n\n#generated-toc ul li a {\n  font-weight: normal;\n  display: inline;\n}\n\n#generated-toc ul li ul li ul li a {\n  text-transform: lowercase;\n\n}\n\n/* ------------------------------------------------- */\n/* iPad and desktop */\n/* ------------------------------------------------- */\n\n@media only screen and (min-width: 641px) {\n\n  html {\n    font-size: 17px;\n  }\n\n  h1 {\n    font-size: 2.81rem;\n  }\n\n  h2 {\n    font-size: 1.78rem;\n    line-height: 2.21rem;\n  }\n\n  ul,\n  ol {\n    margin-left: 0;\n  }\n\n}\n"
  },
  {
    "path": "doc.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package soar is a command-line tool for SQL optimizing and rewriting.\npackage soar\n"
  },
  {
    "path": "env/doc.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package env contain virtual database build, rehash, cleanup.\npackage env\n"
  },
  {
    "path": "env/env.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage env\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/XiaoMi/soar/ast\"\n\t\"github.com/XiaoMi/soar/common\"\n\t\"github.com/XiaoMi/soar/database\"\n\n\t\"github.com/dchest/uniuri\"\n\t\"vitess.io/vitess/go/vt/sqlparser\"\n)\n\n// VirtualEnv SQL优化评审 测试环境\n// DB使用的信息从配置文件中获取\ntype VirtualEnv struct {\n\t*database.Connector\n\n\t// 保存 DB 测试环境映射关系，防止 vEnv 环境冲突。\n\tDBRef   map[string]string // db -> optimizer_xxx\n\tHash2DB map[string]string // optimizer_xxx -> db\n\t// 保存 Table 创建关系，防止重复创建表\n\tTableMap map[string]map[string]string\n\t// 错误\n\tError error\n}\n\n// NewVirtualEnv 初始化一个新的测试环境\nfunc NewVirtualEnv(vEnv *database.Connector) *VirtualEnv {\n\treturn &VirtualEnv{\n\t\tConnector: vEnv,\n\t\tDBRef:     make(map[string]string),\n\t\tHash2DB:   make(map[string]string),\n\t\tTableMap:  make(map[string]map[string]string),\n\t}\n}\n\n// BuildEnv 测试环境初始化&连接线上环境检查\n// @output *VirtualEnv\t测试环境\n// @output *database.Connector 线上环境连接句柄\nfunc BuildEnv() (*VirtualEnv, *database.Connector) {\n\tconnTest, err := database.NewConnector(common.Config.TestDSN)\n\tcommon.LogIfError(err, \"\")\n\t// 生成测试环境\n\tvEnv := NewVirtualEnv(connTest)\n\n\t// 检查测试环境可用性，并记录数据库版本\n\tvEnvVersion, err := vEnv.Version()\n\tcommon.Config.TestDSN.Version = vEnvVersion\n\tif err != nil {\n\t\tcommon.Log.Warn(\"BuildEnv TestDSN: %s:********@%s/%s not available , Error: %s\",\n\t\t\tvEnv.User, vEnv.Addr, vEnv.Database, err.Error())\n\t\tcommon.Config.TestDSN.Disable = true\n\t}\n\n\t// 连接线上环境\n\t// 如果未配置线上环境线测试环境配置为线上环境\n\tif common.Config.OnlineDSN.User == \"\" {\n\t\tcommon.Log.Warn(\"BuildEnv AllowOnlineAsTest: OnlineDSN not config, use TestDSN： %s:********@%s/%s as OnlineDSN\",\n\t\t\tvEnv.User, vEnv.Addr, vEnv.Database)\n\t\tcommon.Config.OnlineDSN = common.Config.TestDSN\n\t}\n\tconnOnline, err := database.NewConnector(common.Config.OnlineDSN)\n\tcommon.LogIfError(err, \"\")\n\n\t// 检查线上环境可用性版本\n\trEnvVersion, err := connOnline.Version()\n\tcommon.Config.OnlineDSN.Version = rEnvVersion\n\tif err != nil {\n\t\tcommon.Log.Warn(\"BuildEnv OnlineDSN: %s:********@%s/%s not available , Error: %s\",\n\t\t\tconnOnline.User, connOnline.Addr, connOnline.Database, err.Error())\n\t\tcommon.Config.TestDSN.Disable = true\n\t}\n\n\t// 检查是否允许 Online 和 Test 一致，防止误操作\n\tif common.FormatDSN(common.Config.OnlineDSN) == common.FormatDSN(common.Config.TestDSN) &&\n\t\t!common.Config.AllowOnlineAsTest {\n\t\tcommon.Log.Warn(\"BuildEnv AllowOnlineAsTest: %s:********@%s/%s OnlineDSN can't config as TestDSN\",\n\t\t\tvEnv.User, vEnv.Addr, vEnv.Database)\n\t\tcommon.Config.TestDSN.Disable = true\n\t\tcommon.Config.OnlineDSN.Disable = true\n\t}\n\n\t// 是否禁用版本检测，禁用后，不再对比测试环境和线上环境的版本大小\n\tif !common.Config.DisableVersionCheck {\n\t\t// 判断测试环境与线上环境版本是否一致，要求测试环境版本不低于线上环境\n\t\tif vEnvVersion < rEnvVersion {\n\t\t\tcommon.Log.Warning(\"TestDSN MySQL version older than OnlineDSN(%d), TestDSN(%d) will not be used\", rEnvVersion, vEnvVersion)\n\t\t\tcommon.Config.TestDSN.Disable = true\n\t\t}\n\t}\n\n\treturn vEnv, connOnline\n}\n\n// RealDB 从测试环境中获取通过 hash 后的 DB\nfunc (vEnv *VirtualEnv) RealDB(hash string) string {\n\tif _, ok := vEnv.Hash2DB[hash]; ok {\n\t\treturn vEnv.Hash2DB[hash]\n\t}\n\t// hash may be real database name not hash\n\tif strings.HasPrefix(hash, \"optimizer_\") {\n\t\tcommon.Log.Warning(\"RealDB, Hash2DB missing hash map: %s\", hash)\n\t}\n\treturn hash\n}\n\n// DBHash 从测试环境中根据 DB 找到对应的 hash 值\nfunc (vEnv *VirtualEnv) DBHash(db string) string {\n\tif _, ok := vEnv.DBRef[db]; ok {\n\t\treturn vEnv.DBRef[db]\n\t}\n\treturn db\n}\n\n// CleanUp 环境清理\nfunc (vEnv *VirtualEnv) CleanUp() bool {\n\tif !common.Config.TestDSN.Disable && common.Config.DropTestTemporary {\n\t\tcommon.Log.Debug(\"CleanUp ...\")\n\t\tfor db := range vEnv.Hash2DB {\n\t\t\t_, err := vEnv.Query(fmt.Sprintf(\"drop database %s\", db))\n\t\t\tif err != nil {\n\t\t\t\tcommon.Log.Error(\"CleanUp failed Error: %s\", err)\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\t// cleanup hash map\n\t\tvEnv.DBRef = make(map[string]string)\n\t\tvEnv.Hash2DB = make(map[string]string)\n\t\tvEnv.TableMap = make(map[string]map[string]string)\n\n\t\tcommon.Log.Debug(\"CleanUp, done\")\n\t}\n\treturn true\n}\n\n// CleanupTestDatabase 清除一小时前的环境\nfunc (vEnv *VirtualEnv) CleanupTestDatabase() {\n\tcommon.Log.Debug(\"CleanupTestDatabase ...\")\n\tdbs, err := vEnv.Query(\"show databases like 'optimizer%%'\")\n\tif err != nil {\n\t\tcommon.Log.Error(\"CleanupTestDatabase failed Error:%s\", err.Error())\n\t\treturn\n\t}\n\n\t// TODO: 1 hour should be config-able\n\tminHour := 1\n\tfor dbs.Rows.Next() {\n\t\tvar testDatabase string\n\t\terr = dbs.Rows.Scan(&testDatabase)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\t// test temporary database format `optimizer_YYMMDDHHmmss_randomString(16)`\n\t\tif len(testDatabase) != 39 {\n\t\t\tcommon.Log.Debug(\"CleanupTestDatabase by pass %s\", testDatabase)\n\t\t\tcontinue\n\t\t}\n\t\ts := strings.Split(testDatabase, \"_\")\n\t\tpastTime, err := time.ParseInLocation(\"060102150405\", s[1], time.Local)\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"CleanupTestDatabase compute  pastTime Error: %s\", err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tsubHour := time.Since(pastTime).Hours()\n\t\tif subHour > float64(minHour) {\n\t\t\tif _, err := vEnv.Query(fmt.Sprintf(\"drop database %s\", testDatabase)); err != nil {\n\t\t\t\tcommon.Log.Error(\"CleanupTestDatabase failed Error: %s\", err.Error())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcommon.Log.Debug(\"CleanupTestDatabase drop database %s success\", testDatabase)\n\t\t\tcontinue\n\t\t}\n\t\tcommon.Log.Debug(\"CleanupTestDatabase by pass database %s, %.2f less than %d hours\", testDatabase, subHour, minHour)\n\t}\n\terr = dbs.Rows.Close()\n\tcommon.LogIfError(err, \"\")\n\tcommon.Log.Debug(\"CleanupTestDatabase done\")\n}\n\n// ChangeDB use db change dsn Database\nfunc ChangeDB(env *database.Connector, sql string) {\n\tstmt, err := sqlparser.Parse(sql)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tswitch stmt := stmt.(type) {\n\tcase *sqlparser.Use:\n\t\tif stmt.DBName.String() != \"\" {\n\t\t\tenv.Database = stmt.DBName.String()\n\t\t}\n\t}\n}\n\nfunc CurrentDB(sql, db string) string {\n\tstmt, err := sqlparser.Parse(sql)\n\tif err != nil {\n\t\treturn common.Config.TestDSN.Schema\n\t}\n\n\tswitch stmt := stmt.(type) {\n\tcase *sqlparser.Use:\n\t\tif stmt.DBName.String() != \"\" {\n\t\t\tdb = stmt.DBName.String()\n\t\t}\n\t}\n\tif db == \"\" {\n\t\tdb = common.Config.TestDSN.Schema\n\t}\n\treturn db\n}\n\n// BuildVirtualEnv rEnv 为 SQL 源环境，DB 使用的信息从接口获取\n// 注意：如果是 USE, DDL 等语句，执行完第一条就会返回，后面的 SQL 不会执行\nfunc (vEnv *VirtualEnv) BuildVirtualEnv(rEnv *database.Connector, SQLs ...string) bool {\n\tvar stmt sqlparser.Statement\n\tvar err error\n\n\t// 置空错误信息\n\tvEnv.Error = nil\n\t// 检测是否已经创建初始数据库，如果未创建则创建一个名称 hash 过的映射数据库\n\terr = vEnv.createDatabase(rEnv)\n\tcommon.LogIfWarn(err, \"\")\n\n\t// 测试环境检测\n\tif common.Config.TestDSN.Disable {\n\t\tcommon.Log.Info(\"BuildVirtualEnv TestDSN not config\")\n\t\treturn true\n\t}\n\n\t// 判断 rEnv 中是否指定了 DB\n\tif rEnv.Database == \"\" {\n\t\tcommon.Log.Error(\"BuildVirtualEnv no database specified, TestDSN init failed\")\n\t\treturn false\n\t}\n\n\t// 库表提取\n\tmeta := make(map[string]*common.DB)\n\tfor _, sql := range SQLs {\n\t\tcommon.Log.Debug(\"BuildVirtualEnv Database&TableName Mapping, SQL: %s\", sql)\n\t\tstmt, err = sqlparser.Parse(sql)\n\t\tif err != nil {\n\t\t\tcommon.Log.Error(\"BuildVirtualEnv Error : %v\", err)\n\t\t\treturn false\n\t\t}\n\n\t\t// 语句类型判断\n\t\tswitch stmt := stmt.(type) {\n\t\tcase *sqlparser.Use:\n\t\t\t// 如果是use语句，则更改基础环配置\n\t\t\tif _, ok := meta[stmt.DBName.String()]; !ok {\n\t\t\t\t// 如果USE了一个线上环境不存在的数据库，将创建该数据库\n\t\t\t\tmeta[stmt.DBName.String()] = common.NewDB(stmt.DBName.String())\n\t\t\t\trEnv.Database = stmt.DBName.String()\n\n\t\t\t\t// use DB 后检查 DB是否已经创建，如果没有创建则创建DB\n\t\t\t\terr = vEnv.createDatabase(rEnv)\n\t\t\t\tcommon.LogIfWarn(err, \"\")\n\t\t\t}\n\t\t\treturn true\n\t\tcase *sqlparser.DDL:\n\t\t\t// 如果是DDL，则先获取DDL对应的表结构，然后直接在测试环境接执行SQL\n\t\t\t// 为不影响其他SQL操作，复制一个Connector对象，将数据库切换到对应的DB上直接执行\n\t\t\tvEnv.Database = vEnv.DBRef[rEnv.Database]\n\n\t\t\t// 为了支持并发，需要将DB进行映射，但 db.table 这种形式无法保证 DB 的映射是正确的\n\t\t\t// TODO：暂不支持 create db.tableName (id int) 形式的建表语句\n\t\t\tif stmt.Table.Qualifier.String() != \"\" {\n\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv DDL Not support db.tb format\")\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tfor _, tb := range stmt.FromTables {\n\t\t\t\tif tb.Qualifier.String() != \"\" {\n\t\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv DDL Not support db.tb format\")\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, tb := range stmt.ToTables {\n\t\t\t\tif tb.Qualifier.String() != \"\" {\n\t\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv DDL Not support db.tb format\")\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 拉取表结构\n\t\t\ttable := stmt.Table.Name.String()\n\t\t\tif table != \"\" {\n\t\t\t\terr = vEnv.createTable(rEnv, table)\n\t\t\t\t// 这里如果报错可能有两种可能：\n\t\t\t\t// 1. SQL 是 Create 语句，线上环境并没有相关的库表结构\n\t\t\t\t// 2. 在测试环境中执行 SQL 报错\n\t\t\t\t// 如果是因为 Create 语句报错，后续会在测试环境中直接执行 create 语句，不会对程序有负面影响\n\t\t\t\t// 如果是因为执行 SQL 报错，那么其他地方执行 SQL 的时候也一定会报错\n\t\t\t\t// 所以这里不需要 `return false`，可以继续执行\n\t\t\t\tif err != nil {\n\t\t\t\t\tcommon.Log.Warning(\"BuildVirtualEnv Error : %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_, err = vEnv.Query(sql)\n\t\t\tif err != nil {\n\t\t\t\tswitch stmt.Action {\n\t\t\t\tcase \"create\", \"alter\":\n\t\t\t\t\t// 如果是创建或者修改语句，且报错信息为如重复建表、重复索引等信息，将错误反馈到上一次层输出建议\n\t\t\t\t\tvEnv.Error = err\n\t\t\t\tdefault:\n\t\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv DDL Execute Error : %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\n\t\tmeta := ast.GetMeta(stmt, nil)\n\n\t\t// 由于 DB 环境可能是变的，所以需要每一次都单独的提取库表结构，整体随着 rEnv 的变动而发生变化\n\t\tfor db, table := range meta {\n\t\t\tif db == \"\" {\n\t\t\t\tdb = rEnv.Database\n\t\t\t}\n\t\t\trEnv.Database = db\n\n\t\t\t// 创建数据库环境\n\t\t\tfor _, tb := range table.Table {\n\t\t\t\tif tb.TableName == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// 视图检查\n\t\t\t\tcommon.Log.Debug(\"BuildVirtualEnv Checking view -- %s.%s\", rEnv.Database, tb.TableName)\n\t\t\t\ttbStatus, err := rEnv.ShowTableStatus(tb.TableName)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv ShowTableStatus Error : %v\", err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\n\t\t\t\t// 如果是视图，解析语句\n\t\t\t\tif len(tbStatus.Rows) > 0 && string(tbStatus.Rows[0].Comment) == \"VIEW\" {\n\t\t\t\t\tvar viewDDL string\n\t\t\t\t\tviewDDL, err = rEnv.ShowCreateTable(tb.TableName)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv create view failed: %v\", err)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tstartIdx := strings.Index(viewDDL, \"AS\")\n\t\t\t\t\tif startIdx < 0 || viewDDL == \"\" {\n\t\t\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv '%s' got '%s', Index: %d\", tb.TableName, viewDDL, startIdx)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tviewDDL = viewDDL[startIdx+2:]\n\t\t\t\t\tif !vEnv.BuildVirtualEnv(rEnv, viewDDL) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\terr = vEnv.createTable(rEnv, tb.TableName)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcommon.Log.Error(\"BuildVirtualEnv %s.%s Error : %v\", rEnv.Database, tb.TableName, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (vEnv *VirtualEnv) createDatabase(rEnv *database.Connector) error {\n\t// 生成映射关系\n\tif _, ok := vEnv.DBRef[rEnv.Database]; ok {\n\t\tcommon.Log.Debug(\"createDatabase, Database `%s` has created, mapping from `%s`\", vEnv.DBRef[rEnv.Database], rEnv.Database)\n\t\treturn nil\n\t}\n\n\t// optimizer_YYMMDDHHmmss_xxxx\n\tdbHash := fmt.Sprintf(\"optimizer_%s_%s\", // Total 39 bytes\n\t\ttime.Now().Format(\"060102150405\"), // 12 Bytes 180102030405\n\t\tstrings.ToLower(uniuri.New()))     // 16 Bytes random string\n\tcommon.Log.Debug(\"createDatabase, mapping `%s` :`%s`-->`%s`\", rEnv.Database, rEnv.Database, dbHash)\n\tddl, err := rEnv.ShowCreateDatabase(rEnv.Database)\n\tif err != nil {\n\t\tcommon.Log.Warning(\"createDatabase, rEnv.ShowCreateDatabase Error : %v\", err)\n\t\tddl = fmt.Sprintf(\"create database `%s` character set %s\", rEnv.Database, rEnv.Charset)\n\t}\n\n\tddl = strings.Replace(ddl, rEnv.Database, dbHash, -1)\n\tif ddl == \"\" {\n\t\treturn fmt.Errorf(\"dbName: '%s' get create info error\", rEnv.Database)\n\t}\n\tres, err := vEnv.Query(ddl)\n\tif err != nil {\n\t\tcommon.Log.Warning(\"createDatabase, Error : %v\", err)\n\t\treturn err\n\t}\n\terr = res.Rows.Close()\n\tcommon.LogIfWarn(err, \"\")\n\n\t// 创建成功，添加映射记录\n\tvEnv.DBRef[rEnv.Database] = dbHash\n\tvEnv.Hash2DB[dbHash] = rEnv.Database\n\treturn nil\n}\n\n/*\n@input:\n\n\tdatabase.Connector 为一个线上环境数据库连接句柄的复制，因为在处理SQL时需要对上下文进行关联处理，\n\t所以存在修改DB连接参数（主要是数据库名称变更）的可能性，为了不影响整体上下文的环境，所以需要一个镜像句柄来做当前环境的操作。\n\n\tdbName, tbName: 需要在环境中操作的库表名称，\n\n@output:\n\n\treturn 执行过程中的错误\n\nNOTE:\n\n\t该函数会将线上环境中使用到的库表结构复制到测试环境中，为后续操作提供基础环境。\n\t传入的库表名称均来自于对AST的解析，库表名称的获取遵循以下原则：\n\t\t如果未在SQL中指定数据库名称，则数据库一定是配置文件（或命令行参数传入DSN）中指定的数据库\n\t\t如果一个SQL中存在多个数据库，则只能有一个数据库是没有在SQL中被显示指定的（即DSN中指定的数据库）\n\nTODO:\n\n\t在一些可能的情况下，由于数据库配置的不一致（如SQL_MODE不同）导致remote环境的库表无法正确的在测试环境进行同步，\n\tsoar 能够做出判断并进行 session 级别的修改，但是这一阶段可用性保证应该是由用户提供两个完全相同（或测试环境兼容线上环境）\n\t的数据库环境来实现的。\n*/\nfunc (vEnv *VirtualEnv) createTable(rEnv *database.Connector, tbName string) error {\n\t// 判断数据库是否已经创建\n\tif vEnv.DBRef[rEnv.Database] == \"\" {\n\t\t// 若没创建，则创建数据库\n\t\terr := vEnv.createDatabase(rEnv)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif vEnv.TableMap[rEnv.Database] == nil {\n\t\tvEnv.TableMap[rEnv.Database] = make(map[string]string)\n\t}\n\n\tif strings.ToLower(tbName) == \"dual\" {\n\t\tcommon.Log.Debug(\"createTable, %s no need create\", tbName)\n\t\treturn nil\n\t}\n\n\tif vEnv.TableMap[rEnv.Database][tbName] != \"\" {\n\t\tcommon.Log.Debug(\"createTable, `%s`.`%s` has created, mapping from `%s`.`%s`\", vEnv.DBRef[rEnv.Database], tbName, rEnv.Database, tbName)\n\t\treturn nil\n\t}\n\n\tcommon.Log.Debug(\"createTable, Database: %s, TableName: %s\", vEnv.DBRef[rEnv.Database], tbName)\n\n\t//  TODO：查看是否有外键关联（done），对外键的支持 (未解决循环依赖的问题)\n\n\t// 记录Table创建信息\n\tvEnv.TableMap[rEnv.Database][tbName] = tbName\n\n\t// 生成建表语句\n\tcommon.Log.Debug(\"createTable DSN(%s/%s): generate ddl\", rEnv.Addr, rEnv.Database)\n\tddl, err := rEnv.ShowCreateTable(tbName)\n\tif err != nil {\n\t\t// 有可能是用户新建表，因此线上环境查不到\n\t\tcommon.Log.Error(\"createTable, %s DDL Error : %v\", tbName, err)\n\t\treturn err\n\t}\n\n\t// 改变数据环境\n\tvEnv.Database = vEnv.DBRef[rEnv.Database]\n\tres, err := vEnv.Query(ddl)\n\tif err != nil {\n\t\t// 有可能是用户新建表，因此线上环境查不到\n\t\tcommon.Log.Error(\"createTable: %s Error : %v\", tbName, err)\n\t\treturn err\n\t}\n\terr = res.Rows.Close()\n\tcommon.LogIfWarn(err, \"\")\n\n\t// 泵取数据\n\tif common.Config.Sampling {\n\t\tcommon.Log.Debug(\"createTable, Start Sampling data from %s.%s to %s.%s ...\", rEnv.Database, tbName, vEnv.DBRef[rEnv.Database], tbName)\n\t\terr = vEnv.SamplingData(rEnv, tbName)\n\t}\n\treturn err\n}\n\n// GenTableColumns 为 Rewrite 提供的结构体初始化\nfunc (vEnv *VirtualEnv) GenTableColumns(meta common.Meta) common.TableColumns {\n\ttableColumns := make(common.TableColumns)\n\tfor dbName, db := range meta {\n\t\tfor _, tb := range db.Table {\n\t\t\t// 防止传入非预期值\n\t\t\tif tb == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttd, err := vEnv.Connector.ShowColumns(tb.TableName)\n\t\t\tif err != nil {\n\t\t\t\tcommon.Log.Warn(\"GenTableColumns, ShowColumns Error: \" + err.Error())\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// tableColumns 初始化\n\t\t\tif dbName == \"\" {\n\t\t\t\tdbName = vEnv.RealDB(vEnv.Connector.Database)\n\t\t\t}\n\n\t\t\tif _, ok := tableColumns[dbName]; !ok {\n\t\t\t\ttableColumns[dbName] = make(map[string][]*common.Column)\n\t\t\t}\n\n\t\t\tif _, ok := tableColumns[dbName][tb.TableName]; !ok {\n\t\t\t\ttableColumns[dbName][tb.TableName] = make([]*common.Column, 0)\n\t\t\t}\n\n\t\t\tif len(tb.Column) == 0 {\n\t\t\t\t// tb.column 为空说明 SQL 里这个表是用的*来查询\n\t\t\t\tif err != nil {\n\t\t\t\t\tcommon.Log.Error(\"ast.Rewrite ShowColumns, Error: %v\", err)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tfor _, colInfo := range td.DescValues {\n\t\t\t\t\ttableColumns[dbName][tb.TableName] = append(tableColumns[dbName][tb.TableName], &common.Column{\n\t\t\t\t\t\tName:       colInfo.Field,\n\t\t\t\t\t\tDB:         dbName,\n\t\t\t\t\t\tTable:      tb.TableName,\n\t\t\t\t\t\tDataType:   colInfo.Type,\n\t\t\t\t\t\tCharacter:  string(colInfo.Collation),\n\t\t\t\t\t\tKey:        colInfo.Key,\n\t\t\t\t\t\tDefault:    string(colInfo.Default),\n\t\t\t\t\t\tExtra:      colInfo.Extra,\n\t\t\t\t\t\tComment:    colInfo.Comment,\n\t\t\t\t\t\tPrivileges: colInfo.Privileges,\n\t\t\t\t\t\tNull:       colInfo.Null,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// tb.column如果不为空则需要把使用到的列填写进去\n\t\t\t\tvar columns []*common.Column\n\t\t\t\tfor _, col := range tb.Column {\n\t\t\t\t\tfor _, colInfo := range td.DescValues {\n\t\t\t\t\t\tif col.Name == colInfo.Field {\n\t\t\t\t\t\t\t// 根据获取的信息将列的信息补全\n\t\t\t\t\t\t\tcol.DB = dbName\n\t\t\t\t\t\t\tcol.Table = tb.TableName\n\t\t\t\t\t\t\tcol.DataType = colInfo.Type\n\t\t\t\t\t\t\tcol.Character = string(colInfo.Collation)\n\t\t\t\t\t\t\tcol.Key = colInfo.Key\n\t\t\t\t\t\t\tcol.Default = string(colInfo.Default)\n\t\t\t\t\t\t\tcol.Extra = colInfo.Extra\n\t\t\t\t\t\t\tcol.Comment = colInfo.Comment\n\t\t\t\t\t\t\tcol.Privileges = colInfo.Privileges\n\t\t\t\t\t\t\tcol.Null = colInfo.Null\n\n\t\t\t\t\t\t\tcolumns = append(columns, col)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttableColumns[dbName][tb.TableName] = columns\n\t\t\t}\n\t\t}\n\t}\n\treturn tableColumns\n}\n"
  },
  {
    "path": "env/env_test.go",
    "content": "/*\n * Copyright 2018 Xiaomi, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage env\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/XiaoMi/soar/common\"\n\t\"github.com/XiaoMi/soar/database\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/kr/pretty\"\n)\n\nvar update = flag.Bool(\"update\", false, \"update .golden files\")\nvar vEnv *VirtualEnv\nvar rEnv *database.Connector\n\nfunc TestMain(m *testing.M) {\n\t// 初始化 init\n\tif common.DevPath == \"\" {\n\t\t_, file, _, _ := runtime.Caller(0)\n\t\tcommon.DevPath, _ = filepath.Abs(filepath.Dir(filepath.Join(file, \"..\"+string(filepath.Separator))))\n\t}\n\tcommon.BaseDir = common.DevPath\n\terr := common.ParseConfig(\"\")\n\tcommon.LogIfError(err, \"init ParseConfig\")\n\tcommon.Log.Debug(\"env_test init\")\n\tvEnv, rEnv = BuildEnv()\n\tif _, err = vEnv.Version(); err != nil {\n\t\tfmt.Println(err.Error(), \", By pass all advisor test cases\")\n\t\tos.Exit(0)\n\t}\n\n\tif _, err := rEnv.Version(); err != nil {\n\t\tfmt.Println(err.Error(), \", By pass all advisor test cases\")\n\t\tos.Exit(0)\n\t}\n\n\t// 分割线\n\tflag.Parse()\n\tm.Run()\n\n\t// 环境清理\n\tvEnv.CleanUp()\n}\n\nfunc TestNewVirtualEnv(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\ttestSQL := []string{\n\t\t\"use sakila\",\n\t\t\"select frm syntaxError\",\n\t\t\"create table t(id int,c1 varchar(20),PRIMARY KEY (id));\",\n\t\t\"alter table t add index `idx_c1`(c1);\",\n\t\t\"select * from city where country_id = 44;\",\n\t\t\"select * from address where address2 is not null;\",\n\t\t\"select * from address where address2 is null;\",\n\t\t\"select * from address where address2 >= 44;\",\n\t\t\"select * from city where country_id between 44 and 107;\",\n\t\t\"select * from city where city like 'Ad%';\",\n\t\t\"select * from city where city = 'Aden' and country_id = 107;\",\n\t\t\"select * from city where country_id > 31 and city = 'Aden';\",\n\t\t\"select * from address where address_id > 8 and city_id < 400 and district = 'Nantou';\",\n\t\t\"select * from address where address_id > 8 and city_id < 400;\",\n\t\t\"select * from actor where last_update='2006-02-15 04:34:33' and last_name='CHASE' group by first_name;\",\n\t\t\"select * from address where last_update >='2014-09-25 22:33:47' group by district;\",\n\t\t\"select * from address group by address,district;\",\n\t\t\"select * from address where last_update='2014-09-25 22:30:27' group by district,(address_id+city_id);\",\n\t\t\"select * from customer where active=1 order by last_name limit 10;\",\n\t\t\"select * from customer order by last_name limit 10;\",\n\t\t\"select * from customer where address_id > 224 order by address_id limit 10;\",\n\t\t\"select * from customer where address_id < 224 order by address_id limit 10;\",\n\t\t\"select * from customer where active=1 order by last_name;\",\n\t\t\"select * from customer where address_id > 224 order by address_id;\",\n\t\t\"select * from customer where address_id in (224,510) order by last_name;\",\n\t\t\"select city from city where country_id = 44;\",\n\t\t\"select city,city_id from city where country_id = 44 and last_update='2006-02-15 04:45:25';\",\n\t\t\"select city from city where country_id > 44 and last_update > '2006-02-15 04:45:25';\",\n\t\t\"select * from city where country_id=1 and city='Kabul' order by last_update;\",\n\t\t\"select * from city where country_id>1 and city='Kabul' order by last_update;\",\n\t\t\"select * from city where city_id>251 order by last_update; \",\n\t\t\"select * from city i inner join country o on i.country_id=o.country_id;\",\n\t\t\"select * from city i left join country o on i.city_id=o.country_id;\",\n\t\t\"select * from city i right join country o on i.city_id=o.country_id;\",\n\t\t\"select * from city i left join country o on i.city_id=o.country_id where o.country_id is null;\",\n\t\t\"select * from city i right join country o on i.city_id=o.country_id where i.city_id is null;\",\n\t\t\"select * from city i left join country o on i.city_id=o.country_id union select * from city i right join country o on i.city_id=o.country_id;\",\n\t\t\"select * from city i left join country o on i.city_id=o.country_id where o.country_id is null union select * from city i right join country o on i.city_id=o.country_id where i.city_id is null;\",\n\t\t\"select first_name,last_name,email from customer natural left join address;\",\n\t\t\"select first_name,last_name,email from customer natural left join address;\",\n\t\t\"select first_name,last_name,email from customer natural right join address;\",\n\t\t\"select first_name,last_name,email from customer STRAIGHT_JOIN address on customer.address_id=address.address_id;\",\n\t\t\"select ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc;\",\n\t}\n\n\terr := common.GoldenDiff(func() {\n\t\tfor _, sql := range testSQL {\n\t\t\tvEnv.BuildVirtualEnv(rEnv, sql)\n\t\t\tswitch err := vEnv.Error.(type) {\n\t\t\tcase nil:\n\t\t\t\tpretty.Println(sql, \"OK\")\n\t\t\tcase error:\n\t\t\t\t// unexpected EOF\n\t\t\t\t// 测试环境无法访问，或者被Disable的时候会进入这个分支\n\t\t\t\tpretty.Println(sql, err)\n\t\t\tcase *mysql.MySQLError:\n\t\t\t\tif err.Number != 1061 {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}\n\t}, t.Name(), update)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestCleanupTestDatabase(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\tif common.Config.TestDSN.Disable {\n\t\tcommon.Log.Warn(\"common.Config.TestDSN.Disable=true, by pass TestCleanupTestDatabase\")\n\t\treturn\n\t}\n\tvEnv.Query(\"drop database if exists optimizer_060102150405_xxxxxxxxxxxxxxxx\")\n\t_, err := vEnv.Query(\"create database optimizer_060102150405_xxxxxxxxxxxxxxxx\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tvEnv.CleanupTestDatabase()\n\t_, err = vEnv.Query(\"show create database optimizer_060102150405_xxxxxxxxxxxxxxxx\")\n\tif err == nil {\n\t\tt.Error(\"optimizer_060102150405_xxxxxxxxxxxxxxxx exist, should be dropped\")\n\t}\n\n\tvEnv.Query(\"drop database if exists optimizer_060102150405\")\n\t_, err = vEnv.Query(\"create database optimizer_060102150405\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tvEnv.CleanupTestDatabase()\n\t_, err = vEnv.Query(\"drop database optimizer_060102150405\")\n\tif err != nil {\n\t\tt.Error(\"optimizer_060102150405 not exist, should not be dropped\")\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestGenTableColumns(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\n\tpretty.Println(common.Config.TestDSN.Disable)\n\tif common.Config.TestDSN.Disable {\n\t\tcommon.Log.Warn(\"common.Config.TestDSN.Disable=true, by pass TestGenTableColumns\")\n\t\treturn\n\t}\n\n\t// 只能对sakila数据库进行测试\n\tif rEnv.Database == \"sakila\" {\n\t\ttestSQL := []string{\n\t\t\t\"select * from city where country_id = 44;\",\n\t\t\t\"select country_id from city where country_id = 44;\",\n\t\t\t\"select country_id from city where country_id > 44;\",\n\t\t}\n\n\t\tmetaList := []common.Meta{\n\t\t\t{\n\t\t\t\t\"\": &common.DB{\n\t\t\t\t\tTable: map[string]*common.Table{\n\t\t\t\t\t\t\"city\": common.NewTable(\"city\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"sakila\": &common.DB{\n\t\t\t\t\tTable: map[string]*common.Table{\n\t\t\t\t\t\t\"city\": common.NewTable(\"city\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"sakila\": &common.DB{\n\t\t\t\t\tTable: map[string]*common.Table{\n\t\t\t\t\t\t\"city\": {\n\t\t\t\t\t\t\tTableName: \"city\",\n\t\t\t\t\t\t\tColumn: map[string]*common.Column{\n\t\t\t\t\t\t\t\t\"country_id\": {\n\t\t\t\t\t\t\t\t\tName: \"country_id\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor i, sql := range testSQL {\n\t\t\tvEnv.BuildVirtualEnv(rEnv, sql)\n\t\t\ttFlag := false\n\t\t\tcolumns := vEnv.GenTableColumns(metaList[i])\n\t\t\tif _, ok := columns[\"sakila\"]; ok {\n\t\t\t\tif _, okk := columns[\"sakila\"][\"city\"]; okk {\n\t\t\t\t\tif length := len(columns[\"sakila\"][\"city\"]); length >= 1 {\n\t\t\t\t\t\ttFlag = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !tFlag {\n\t\t\t\tt.Errorf(\"columns: \\n%s\", pretty.Sprint(columns))\n\t\t\t}\n\t\t}\n\t}\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestCreateTable(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgSamplingCondition := common.Config.SamplingCondition\n\tcommon.Config.SamplingCondition = \"LIMIT 1\"\n\n\torgREnvDatabase := rEnv.Database\n\trEnv.Database = \"sakila\"\n\ttables := []string{\n\t\t\"actor\",\n\t\t\"address\",\n\t\t\"category\",\n\t\t\"city\",\n\t\t\"country\",\n\t\t\"customer\",\n\t\t\"film\",\n\t\t\"film_actor\",\n\t\t\"film_category\",\n\t\t\"film_text\",\n\t\t\"inventory\",\n\t\t\"language\",\n\t\t\"payment\",\n\t\t\"rental\",\n\t\t\"staff\",\n\t\t\"store\",\n\t\t\"staff_list\",\n\t\t\"customer_list\",\n\t\t\"actor_info\",\n\t\t\"sales_by_film_category\",\n\t\t\"sales_by_store\",\n\t\t\"nicer_but_slower_film_list\",\n\t\t\"film_list\",\n\t}\n\tfor _, table := range tables {\n\t\terr := vEnv.createTable(rEnv, table)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\tcommon.Config.SamplingCondition = orgSamplingCondition\n\trEnv.Database = orgREnvDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n\nfunc TestCreateDatabase(t *testing.T) {\n\tcommon.Log.Debug(\"Entering function: %s\", common.GetFunctionName())\n\torgREnvDatabase := rEnv.Database\n\trEnv.Database = \"sakila\"\n\terr := vEnv.createDatabase(rEnv)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif vEnv.DBHash(\"sakila\") == \"sakila\" {\n\t\tt.Errorf(\"database: sakila rehashed failed!\")\n\t}\n\n\tif vEnv.DBHash(\"not_exist_db\") != \"not_exist_db\" {\n\t\tt.Errorf(\"database: not_exist_db rehashed!\")\n\t}\n\trEnv.Database = orgREnvDatabase\n\tcommon.Log.Debug(\"Exiting function: %s\", common.GetFunctionName())\n}\n"
  },
  {
    "path": "env/testdata/TestNewVirtualEnv.golden",
    "content": "use sakila OK\nselect frm syntaxError OK\ncreate table t(id int,c1 varchar(20),PRIMARY KEY (id)); OK\nalter table t add index `idx_c1`(c1); OK\nselect * from city where country_id = 44; OK\nselect * from address where address2 is not null; OK\nselect * from address where address2 is null; OK\nselect * from address where address2 >= 44; OK\nselect * from city where country_id between 44 and 107; OK\nselect * from city where city like 'Ad%'; OK\nselect * from city where city = 'Aden' and country_id = 107; OK\nselect * from city where country_id > 31 and city = 'Aden'; OK\nselect * from address where address_id > 8 and city_id < 400 and district = 'Nantou'; OK\nselect * from address where address_id > 8 and city_id < 400; OK\nselect * from actor where last_update='2006-02-15 04:34:33' and last_name='CHASE' group by first_name; OK\nselect * from address where last_update >='2014-09-25 22:33:47' group by district; OK\nselect * from address group by address,district; OK\nselect * from address where last_update='2014-09-25 22:30:27' group by district,(address_id+city_id); OK\nselect * from customer where active=1 order by last_name limit 10; OK\nselect * from customer order by last_name limit 10; OK\nselect * from customer where address_id > 224 order by address_id limit 10; OK\nselect * from customer where address_id < 224 order by address_id limit 10; OK\nselect * from customer where active=1 order by last_name; OK\nselect * from customer where address_id > 224 order by address_id; OK\nselect * from customer where address_id in (224,510) order by last_name; OK\nselect city from city where country_id = 44; OK\nselect city,city_id from city where country_id = 44 and last_update='2006-02-15 04:45:25'; OK\nselect city from city where country_id > 44 and last_update > '2006-02-15 04:45:25'; OK\nselect * from city where country_id=1 and city='Kabul' order by last_update; OK\nselect * from city where country_id>1 and city='Kabul' order by last_update; OK\nselect * from city where city_id>251 order by last_update;  OK\nselect * from city i inner join country o on i.country_id=o.country_id; OK\nselect * from city i left join country o on i.city_id=o.country_id; OK\nselect * from city i right join country o on i.city_id=o.country_id; OK\nselect * from city i left join country o on i.city_id=o.country_id where o.country_id is null; OK\nselect * from city i right join country o on i.city_id=o.country_id where i.city_id is null; OK\nselect * from city i left join country o on i.city_id=o.country_id union select * from city i right join country o on i.city_id=o.country_id; OK\nselect * from city i left join country o on i.city_id=o.country_id where o.country_id is null union select * from city i right join country o on i.city_id=o.country_id where i.city_id is null; OK\nselect first_name,last_name,email from customer natural left join address; OK\nselect first_name,last_name,email from customer natural left join address; OK\nselect first_name,last_name,email from customer natural right join address; OK\nselect first_name,last_name,email from customer STRAIGHT_JOIN address on customer.address_id=address.address_id; OK\nselect ID,name from (select address from customer_list where SID=1 order by phone limit 50,10) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc; OK\n"
  },
  {
    "path": "etc/soar.blacklist",
    "content": "# 这是一个黑名单例子\n## 不评审常见的SET， SHOW， SELECT CONST等完美请求\n^set.*\n^show.*\n^select \\?$\n^\\/\\*.*\\*\\/$\n^drop.*\n^lock.*\n^unlock.*\n"
  },
  {
    "path": "etc/soar.yaml",
    "content": "# 这是一个配置文件例子\nonline-dsn:\n    addr:     127.0.0.1:3306\n    schema:   sakila\n    user:     root\n    password: \"1tIsB1g3rt\"\n    disable:  false\n\ntest-dsn:\n    addr:     127.0.0.1:3306\n    schema:   sakila\n    user:     root\n    password: \"1tIsB1g3rt\"\n    disable:  false\n\n# 高危，仅测试时使用，线上环境禁止配置为 true\nallow-online-as-test: true\ndisable-version-check: false\ncolumn-not-allow-type:\n- json\n- text\n- boolean\n\nlog-level:  7\nlog-output: soar.log\nsampling: true\n"
  },
  {
    "path": "genver.sh",
    "content": "#!/bin/bash\n\n## Generate Repository Version\ntag=\"$(git describe --tags --always)\"\nversion=\"$(git log --date=iso --pretty=format:\"%cd\" -1) ${tag}\"\nif [ \"X${version}\" == \"X\" ]; then\n    version=\"not a git repo\"\n    tag=\"not a git repo\"\nfi\n\ngit_dirty=$(git diff --no-ext-diff 2>/dev/null | wc -l)\n\ncompile=\"$(date +\"%F %T %z\") by $(go version)\"\n\nbranch=$(git rev-parse --abbrev-ref HEAD)\n\ndev_path=$(\n    cd \"$(dirname \"$0\")\" || exit\n    pwd\n)\n\ncat <<EOF | gofmt >common/version.go\npackage common\n\n// -version输出信息\nconst (\n    Version = \"${version}\"\n    Compile = \"${compile}\"\n    Branch  = \"${branch}\"\n    GitDirty= ${git_dirty}\n    DevPath = \"${dev_path}\"\n)\nEOF\n\nXIAOMI=$(git ls-remote --get-url | grep XiaoMi)\nif [ \"x${XIAOMI}\" != \"x\" ]; then\n    echo \"${tag}\" | awk -F '-' '{print $1}' > VERSION\nfi\n"
  },
  {
    "path": "go.mod",
    "content": "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.com/Azure/go-autorest/autorest v0.10.0 // indirect\n\tgithub.com/CorgiMan/json2 v0.0.0-20150213135156-e72957aba209\n\tgithub.com/aquarapid/vaultlib v0.5.1 // indirect\n\tgithub.com/astaxie/beego v1.12.3\n\tgithub.com/buger/jsonparser v1.1.1 // indirect\n\tgithub.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect\n\tgithub.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432 // indirect\n\tgithub.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5\n\tgithub.com/gedex/inflector v0.0.0-20170307190818-16278e9db813\n\tgithub.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab // indirect\n\tgithub.com/go-sql-driver/mysql v1.6.0\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/gorilla/handlers v1.5.1 // indirect\n\tgithub.com/gorilla/mux v1.8.0 // indirect\n\tgithub.com/hashicorp/consul/api v1.5.0 // indirect\n\tgithub.com/hashicorp/go-immutable-radix v1.1.0 // indirect\n\tgithub.com/hashicorp/go-sockaddr v1.0.2 // indirect\n\tgithub.com/hashicorp/go-uuid v1.0.2 // indirect\n\tgithub.com/hashicorp/serf v0.9.2 // indirect\n\tgithub.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect\n\tgithub.com/jeremywohl/flatten v0.0.0-20190921043622-d936035e55cf // indirect\n\tgithub.com/klauspost/pgzip v1.2.4 // indirect\n\tgithub.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect\n\tgithub.com/kr/pretty v0.2.1\n\tgithub.com/looplab/fsm v0.2.0 // indirect\n\tgithub.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8 // indirect\n\tgithub.com/martini-contrib/gzip v0.0.0-20151124214156-6c035326b43f // indirect\n\tgithub.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 // indirect\n\tgithub.com/mitchellh/go-ps v1.0.0 // indirect\n\tgithub.com/mitchellh/go-testing-interface v1.14.0 // indirect\n\tgithub.com/mitchellh/mapstructure v1.2.3 // indirect\n\tgithub.com/montanaflynn/stats v0.6.3 // indirect\n\tgithub.com/olekukonko/tablewriter v0.0.5-0.20200416053754-163badb3bac6 // indirect\n\tgithub.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect\n\tgithub.com/patrickmn/go-cache v2.1.0+incompatible // indirect\n\tgithub.com/percona/go-mysql v0.0.0-20210427141028-73d29c6da78c\n\tgithub.com/pingcap/log v0.0.0-20210317133921-96f4fcab92a4 // indirect\n\tgithub.com/pingcap/parser v0.0.0-20210525032559-c37778aff307\n\tgithub.com/pingcap/pd/v4 v4.0.0-beta.1.0.20200305072537-61d9f9cc35d3 // indirect\n\tgithub.com/pingcap/tidb v1.1.0-beta.0.20210601085537-5d7c852770eb\n\tgithub.com/pingcap/tipb v0.0.0-20210601083426-79a378b6d1c4 // indirect\n\tgithub.com/planetscale/pargzip v0.0.0-20201116224723-90c7fc03ea8a // indirect\n\tgithub.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect\n\tgithub.com/russross/blackfriday v1.6.0\n\tgithub.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca\n\tgithub.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e // indirect\n\tgithub.com/shirou/gopsutil v3.21.2+incompatible // indirect\n\tgithub.com/sirupsen/logrus v1.8.1 // indirect\n\tgithub.com/sjmudd/stopwatch v0.0.0-20170613150411-f380bf8a9be1 // indirect\n\tgithub.com/spf13/cobra v1.1.1 // indirect\n\tgithub.com/spyzhov/ajson v0.4.2 // indirect\n\tgithub.com/tidwall/gjson v1.12.1\n\tgo.uber.org/multierr v1.7.0 // indirect\n\tgo.uber.org/zap v1.17.0 // indirect\n\tgolang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect\n\tgolang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b // indirect\n\tgolang.org/x/text v0.3.6 // indirect\n\tgopkg.in/gcfg.v1 v1.2.3 // indirect\n\tgopkg.in/warnings.v0 v0.1.2 // indirect\n\tgopkg.in/yaml.v2 v2.4.0\n\tvitess.io/vitess v0.0.0-20200325000816-eda961851d63\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=\ngithub.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=\ngithub.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=\ngithub.com/Azure/azure-storage-blob-go v0.10.0/go.mod h1:ep1edmW+kNQx4UfWM9heESNmQdijykocJ0YOxmMX8SE=\ngithub.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=\ngithub.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=\ngithub.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=\ngithub.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=\ngithub.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=\ngithub.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=\ngithub.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=\ngithub.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=\ngithub.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=\ngithub.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=\ngithub.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=\ngithub.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=\ngithub.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=\ngithub.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=\ngithub.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=\ngithub.com/CorgiMan/json2 v0.0.0-20150213135156-e72957aba209 h1:rRZPHlNlREFwuEtpYikMNZPs4l5g6zic54l2XDAK4ws=\ngithub.com/CorgiMan/json2 v0.0.0-20150213135156-e72957aba209/go.mod h1:VwmVPvMIZlx23Q7F1umyYmkhNDqf6WQKfMUhGEdVcLA=\ngithub.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=\ngithub.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=\ngithub.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=\ngithub.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=\ngithub.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4=\ngithub.com/Jeffail/gabs v1.1.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=\ngithub.com/Jeffail/gabs/v2 v2.5.1/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI=\ngithub.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=\ngithub.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=\ngithub.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=\ngithub.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=\ngithub.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=\ngithub.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=\ngithub.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=\ngithub.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=\ngithub.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/SAP/go-hdb v0.12.0/go.mod h1:etBT+FAi1t5k3K3tf5vQTnosgYmhDkRi8jEnQqCnxF0=\ngithub.com/SermoDigital/jose v0.0.0-20180104203859-803625baeddc/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA=\ngithub.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=\ngithub.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=\ngithub.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=\ngithub.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=\ngithub.com/VividCortex/mysqlerr v0.0.0-20200629151747-c28746d985dd/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0=\ngithub.com/Xeoncross/go-aesctr-with-hmac v0.0.0-20200623134604-12b17a7ff502/go.mod h1:pmnBM9bxWSiHvC/gSWunUIyDvGn33EkP2CUjxFKtTTM=\ngithub.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=\ngithub.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=\ngithub.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=\ngithub.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=\ngithub.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=\ngithub.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=\ngithub.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=\ngithub.com/apache/thrift v0.0.0-20181112125854-24918abba929/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=\ngithub.com/apache/thrift v0.13.1-0.20201008052519-daf620915714/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=\ngithub.com/appleboy/gin-jwt/v2 v2.6.3/go.mod h1:MfPYA4ogzvOcVkRwAxT7quHOtQmVKDpTwxyUrC2DNw0=\ngithub.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=\ngithub.com/aquarapid/vaultlib v0.5.1/go.mod h1:yT7AlEXtuabkxylOc/+Ulyp18tff1+QjgNLTnFWTlOs=\ngithub.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=\ngithub.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=\ngithub.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=\ngithub.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=\ngithub.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=\ngithub.com/aws/aws-sdk-go v1.28.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=\ngithub.com/aws/aws-sdk-go v1.30.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=\ngithub.com/aws/aws-sdk-go v1.35.3/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=\ngithub.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=\ngithub.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=\ngithub.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=\ngithub.com/blacktear23/go-proxyprotocol v0.0.0-20180807104634-af7a81e8dd0d/go.mod h1:VKt7CNAQxpFpSDz3sXyj9hY/GbVsQCr0sB3w59nE7lU=\ngithub.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=\ngithub.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=\ngithub.com/bombsimon/wsl v1.2.5/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM=\ngithub.com/bombsimon/wsl v1.2.8/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM=\ngithub.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=\ngithub.com/buger/jsonparser v0.0.0-20200322175846-f7e751efca13/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=\ngithub.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=\ngithub.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=\ngithub.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5/go.mod h1:jtAfVaU/2cu1+wdSRPWE2c1N2qeAA3K4RH9pYgqwets=\ngithub.com/carlmjohnson/flagext v0.21.0/go.mod h1:Eenv0epIUAr4NuedNmkzI8WmBmjIxZC239XcKxYS2ac=\ngithub.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=\ngithub.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=\ngithub.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=\ngithub.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20171208011716-f6d7a1f6fbf3/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=\ngithub.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=\ngithub.com/cockroachdb/cmux v0.0.0-20170110192607-30d10be49292/go.mod h1:qRiX68mZX1lGBkTWyp3CLcenw9I94W2dLeRvMzcn9N4=\ngithub.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=\ngithub.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=\ngithub.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=\ngithub.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=\ngithub.com/colinmarc/hdfs/v2 v2.1.1/go.mod h1:M3x+k8UKKmxtFu++uAZ0OtDU8jR3jnaZIAc6yK4Ue0c=\ngithub.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=\ngithub.com/coocood/bbloom v0.0.0-20190830030839-58deb6228d64/go.mod h1:F86k/6c7aDUdwSUevnLpHS/3Q9hzYCE99jGk2xsHnt0=\ngithub.com/coocood/freecache v1.1.1/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY=\ngithub.com/coocood/rtutil v0.0.0-20190304133409-c84515f646f2/go.mod h1:7qG7YFnOALvsx6tKTNmQot8d7cGFXM9TidzvRFLWYwM=\ngithub.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=\ngithub.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=\ngithub.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI=\ngithub.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=\ngithub.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=\ngithub.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=\ngithub.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=\ngithub.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=\ngithub.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432/go.mod h1:xwIwAxMvYnVrGJPe2FKx5prTrnAjGOD8zvDOnxnrrkM=\ngithub.com/cznic/golex v0.0.0-20181122101858-9c343928389c/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=\ngithub.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso=\ngithub.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=\ngithub.com/cznic/parser v0.0.0-20160622100904-31edd927e5b1/go.mod h1:2B43mz36vGZNZEwkWi8ayRSSUXLfjL8OkbzwW4NcPMM=\ngithub.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=\ngithub.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=\ngithub.com/cznic/y v0.0.0-20170802143616-045f81c6662a/go.mod h1:1rk5VM7oSnA4vjp+hrLQ3HWHa+Y4yPCa3/CsJrcNnvs=\ngithub.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=\ngithub.com/danjacques/gofslock v0.0.0-20191023191349-0a45f885bc37 h1:X6mKGhCFOxrKeeHAjv/3UvT6e5RRxW6wRdlqlV6/H4w=\ngithub.com/danjacques/gofslock v0.0.0-20191023191349-0a45f885bc37/go.mod h1:DC3JtzuG7kxMvJ6dZmf2ymjNyoXwgtklr7FN+Um2B0U=\ngithub.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU=\ngithub.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=\ngithub.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=\ngithub.com/denisenkom/go-mssqldb v0.0.0-20180620032804-94c9c97e8c9f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=\ngithub.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=\ngithub.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=\ngithub.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=\ngithub.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=\ngithub.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=\ngithub.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=\ngithub.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo=\ngithub.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=\ngithub.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=\ngithub.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=\ngithub.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=\ngithub.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=\ngithub.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/envoyproxy/go-control-plane v0.0.0-20180919002855-2137d9196328/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=\ngithub.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=\ngithub.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=\ngithub.com/fatih/structs v0.0.0-20180123065059-ebf56d35bba7/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=\ngithub.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=\ngithub.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=\ngithub.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 h1:Uc+IZ7gYqAf/rSGFplbWBSHaGolEQlNLgMgSE3ccnIQ=\ngithub.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9yhSRvsmYyZsshflcR6ePWYLql6UU1amW13IM=\ngithub.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=\ngithub.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=\ngithub.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=\ngithub.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=\ngithub.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=\ngithub.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=\ngithub.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=\ngithub.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=\ngithub.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=\ngithub.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=\ngithub.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=\ngithub.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=\ngithub.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=\ngithub.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=\ngithub.com/go-critic/go-critic v0.4.0/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g=\ngithub.com/go-echarts/go-echarts v1.0.0/go.mod h1:qbmyAb/Rl1f2w7wKba1D4LoNq4U164yO4/wedFbcWyo=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=\ngithub.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=\ngithub.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=\ngithub.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=\ngithub.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=\ngithub.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=\ngithub.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=\ngithub.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=\ngithub.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=\ngithub.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=\ngithub.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=\ngithub.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=\ngithub.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=\ngithub.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=\ngithub.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=\ngithub.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=\ngithub.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=\ngithub.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=\ngithub.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=\ngithub.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=\ngithub.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=\ngithub.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=\ngithub.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=\ngithub.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=\ngithub.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=\ngithub.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=\ngithub.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=\ngithub.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=\ngithub.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=\ngithub.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=\ngithub.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=\ngithub.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=\ngithub.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=\ngithub.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=\ngithub.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=\ngithub.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=\ngithub.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=\ngithub.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=\ngithub.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=\ngithub.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=\ngithub.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=\ngithub.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=\ngithub.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=\ngithub.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=\ngithub.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=\ngithub.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=\ngithub.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=\ngithub.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=\ngithub.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=\ngithub.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=\ngithub.com/go-playground/overalls v0.0.0-20180201144345-22ec1a223b7c/go.mod h1:UqxAgEOt89sCiXlrc/ycnx00LVvUO/eS8tMUkWX4R7w=\ngithub.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=\ngithub.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=\ngithub.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=\ngithub.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=\ngithub.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=\ngithub.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=\ngithub.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=\ngithub.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=\ngithub.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=\ngithub.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=\ngithub.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=\ngithub.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=\ngithub.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=\ngithub.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=\ngithub.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=\ngithub.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=\ngithub.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=\ngithub.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=\ngithub.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/goccy/go-graphviz v0.0.5/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk=\ngithub.com/gocql/gocql v0.0.0-20180617115710-e06f8c1bcd78/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=\ngithub.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=\ngithub.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=\ngithub.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v0.0.0-20180717141946-636bf0302bc9/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v0.0.0-20180814211427-aa810b61a9c7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=\ngithub.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=\ngithub.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=\ngithub.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=\ngithub.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=\ngithub.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=\ngithub.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=\ngithub.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=\ngithub.com/golangci/golangci-lint v1.21.0/go.mod h1:phxpHK52q7SE+5KpPnti4oZTdFCEsn/tKN+nFvCKXfk=\ngithub.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=\ngithub.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=\ngithub.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=\ngithub.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=\ngithub.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=\ngithub.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=\ngithub.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=\ngithub.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=\ngithub.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190930153522-6ce02741cba3/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200407044318-7d83b28da2e9/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=\ngithub.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=\ngithub.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=\ngithub.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=\ngithub.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=\ngithub.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=\ngithub.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=\ngithub.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=\ngithub.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69/go.mod h1:YLEMZOtU+AZ7dhN9T/IpGhXVGly2bvkJQ+zxj3WeVQo=\ngithub.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=\ngithub.com/hashicorp/consul v1.4.5/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=\ngithub.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=\ngithub.com/hashicorp/consul/api v1.5.0/go.mod h1:LqwrLNW876eYSuUOo4ZLHBcdKc038txr/IMfbLPATa4=\ngithub.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=\ngithub.com/hashicorp/consul/sdk v0.5.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-bexpr v0.1.0/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU=\ngithub.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4=\ngithub.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-hclog v0.0.0-20180402200405-69ff559dc25f/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=\ngithub.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71/go.mod h1:kbfItVoBJwCfKXDXN4YoAXjxcFVZ7MRrJzyTX6H4giE=\ngithub.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=\ngithub.com/hashicorp/go-plugin v0.0.0-20180331002553-e8d22c780116/go.mod h1:JSqWYsict+jzcj0+xElxyrBQRPNoiWQuddnxArJ7XHQ=\ngithub.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=\ngithub.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=\ngithub.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=\ngithub.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=\ngithub.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v0.0.0-20170202080759-03c5bf6be031/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts=\ngithub.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=\ngithub.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=\ngithub.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=\ngithub.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=\ngithub.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=\ngithub.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q=\ngithub.com/hashicorp/raft v1.0.1-0.20190409200437-d9fe23f7d472/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI=\ngithub.com/hashicorp/raft-boltdb v0.0.0-20150201200839-d1e82c1ec3f1/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk=\ngithub.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=\ngithub.com/hashicorp/serf v0.8.5/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k=\ngithub.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU=\ngithub.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=\ngithub.com/hashicorp/vault v0.10.3/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0=\ngithub.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190318174639-195e0e9d07f1/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0=\ngithub.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo=\ngithub.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=\ngithub.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/hypnoglow/gormzap v0.3.0/go.mod h1:5Wom8B7Jl2oK0Im9hs6KQ+Kl92w4Y7gKCrj66rhyvw0=\ngithub.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=\ngithub.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=\ngithub.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=\ngithub.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee/go.mod h1:N0t2vlmpe8nyZB5ouIbJQPDSR+mH6oe7xHB9VZHSUzM=\ngithub.com/jeremywohl/flatten v0.0.0-20190921043622-d936035e55cf/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=\ngithub.com/joho/sqltocsv v0.0.0-20210208114054-cb2c3a95fb99/go.mod h1:mAVCUAYtW9NG31eB30umMSLKcDt6mCUWSjoSn5qBh0k=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=\ngithub.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=\ngithub.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=\ngithub.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/crc32 v1.2.0/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=\ngithub.com/klauspost/pgzip v1.2.0/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=\ngithub.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/krishicks/yaml-patch v0.0.10/go.mod h1:Sm5TchwZS6sm7RJoyg87tzxm2ZcKzdRE4Q7TjNhPrME=\ngithub.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=\ngithub.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=\ngithub.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=\ngithub.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=\ngithub.com/looplab/fsm v0.2.0/go.mod h1:p+IElwgCnAByqr2DWMuNbPjgMwqcHvTRZZn3dvKEke0=\ngithub.com/lyft/protoc-gen-validate v0.0.0-20180911180927-64fcb82c878e/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=\ngithub.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=\ngithub.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8/go.mod h1:ahTFgV/NtzY/CALneRrC67m1dis5arHTQDfyIhKk69E=\ngithub.com/martini-contrib/gzip v0.0.0-20151124214156-6c035326b43f/go.mod h1:jhUB0rZB2TPWqy0yGugKRRictO591eSO7If7O4MfCaA=\ngithub.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI=\ngithub.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=\ngithub.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=\ngithub.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=\ngithub.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=\ngithub.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=\ngithub.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=\ngithub.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=\ngithub.com/mgechev/revive v1.0.2/go.mod h1:rb0dQy1LVAxW9SWy5R3LPUjevzUbUS316U5MFySA2lo=\ngithub.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=\ngithub.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1/go.mod h1:vuvdOZLJuf5HmJAJrKV64MmozrSsk+or0PB5dzdfspg=\ngithub.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=\ngithub.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=\ngithub.com/mitchellh/copystructure v0.0.0-20160804032330-cdac8253d00f/go.mod h1:eOsF2yLPlBBJPvD+nhl5QMTBSOBbOph6N7j/IDUw7PY=\ngithub.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=\ngithub.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=\ngithub.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=\ngithub.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=\ngithub.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=\ngithub.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=\ngithub.com/montanaflynn/stats v0.0.0-20180911141734-db72e6cae808/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=\ngithub.com/montanaflynn/stats v0.5.0/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=\ngithub.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=\ngithub.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=\ngithub.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\ngithub.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=\ngithub.com/ncw/directio v1.0.4/go.mod h1:CKGdcN7StAaqjT7Qack3lAXeX4pjnyc46YeqZH1yWVY=\ngithub.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=\ngithub.com/ngaut/pools v0.0.0-20180318154953-b7bc8c42aac7/go.mod h1:iWMfgwqYW+e8n5lC/jjNEhwcjbRDpl5NT7n2h+4UNcI=\ngithub.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef/go.mod h1:7WjlapSfwQyo6LNmIvEWzsW1hbBQfpUO4JWnuQRmva8=\ngithub.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=\ngithub.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=\ngithub.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=\ngithub.com/oklog/run v0.0.0-20180308005104-6934b124db28/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60=\ngithub.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=\ngithub.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=\ngithub.com/olekukonko/tablewriter v0.0.5-0.20200416053754-163badb3bac6/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=\ngithub.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=\ngithub.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=\ngithub.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc=\ngithub.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=\ngithub.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=\ngithub.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=\ngithub.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M=\ngithub.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=\ngithub.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=\ngithub.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=\ngithub.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=\ngithub.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=\ngithub.com/percona/go-mysql v0.0.0-20210427141028-73d29c6da78c h1:1SZ7nS+kSaO63IpaKspf/gf8602QcgP2eXNPMNOIc0M=\ngithub.com/percona/go-mysql v0.0.0-20210427141028-73d29c6da78c/go.mod h1:/SGLf9OMxlnK6jq4mkFiImBcJXXk5jwD+lDrwDaGXcw=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=\ngithub.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=\ngithub.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=\ngithub.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=\ngithub.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d/go.mod h1:lXfE4PvvTW5xOjO6Mba8zDPyw8M93B6AQ7frTGnMlA8=\ngithub.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=\ngithub.com/pingcap-incubator/tidb-dashboard v0.0.0-20200302022638-35a6e979dca9/go.mod h1:YUceA4BHY/MTtp63yZLTYP22waFSwMNo9lXq2FDtzVw=\ngithub.com/pingcap/badger v1.5.1-0.20200908111422-2e78ee155d19/go.mod h1:LyrqUOHZrUDf9oGi1yoz1+qw9ckSIhQb5eMa1acOLNQ=\ngithub.com/pingcap/br v5.1.0-alpha.0.20210526054934-d5f5f9df24f5+incompatible/go.mod h1:ymVmo50lQydxib0tmK5hHk4oteB7hZ0IMCArunwy3UQ=\ngithub.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ=\ngithub.com/pingcap/check v0.0.0-20191107115940-caf2b9e6ccf4/go.mod h1:PYMCGwN0JHjoqGr3HrZoD+b8Tgx8bKnArhSq8YVzUMc=\ngithub.com/pingcap/check v0.0.0-20191216031241-8a5a85928f12/go.mod h1:PYMCGwN0JHjoqGr3HrZoD+b8Tgx8bKnArhSq8YVzUMc=\ngithub.com/pingcap/check v0.0.0-20200212061837-5e12011dc712/go.mod h1:PYMCGwN0JHjoqGr3HrZoD+b8Tgx8bKnArhSq8YVzUMc=\ngithub.com/pingcap/errcode v0.0.0-20180921232412-a1a7271709d9/go.mod h1:4b2X8xSqxIroj/IZ9MX/VGZhAwc11wB9wRIzHvz6SeM=\ngithub.com/pingcap/errcode v0.3.0/go.mod h1:4b2X8xSqxIroj/IZ9MX/VGZhAwc11wB9wRIzHvz6SeM=\ngithub.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=\ngithub.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=\ngithub.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=\ngithub.com/pingcap/errors v0.11.5-0.20190809092503-95897b64e011 h1:58naV4XMEqm0hl9LcYo6cZoGBGiLtefMQMF/vo3XLgQ=\ngithub.com/pingcap/errors v0.11.5-0.20190809092503-95897b64e011/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=\ngithub.com/pingcap/errors v0.11.5-0.20200917111840-a15ef68f753d/go.mod h1:g4vx//d6VakjJ0mk7iLBlKA8LFavV/sAVINT/1PFxeQ=\ngithub.com/pingcap/errors v0.11.5-0.20201029093017-5a7df2af2ac7 h1:wQKuKP2HUtej2gSvx1cZmY4DENUH6tlOxRkfvPT8EBU=\ngithub.com/pingcap/errors v0.11.5-0.20201029093017-5a7df2af2ac7/go.mod h1:G7x87le1poQzLB/TqvTJI2ILrSgobnq4Ut7luOwvfvI=\ngithub.com/pingcap/errors v0.11.5-0.20201126102027-b0a155152ca3 h1:LllgC9eGfqzkfubMgjKIDyZYaa609nNWAyNZtpy2B3M=\ngithub.com/pingcap/errors v0.11.5-0.20201126102027-b0a155152ca3/go.mod h1:G7x87le1poQzLB/TqvTJI2ILrSgobnq4Ut7luOwvfvI=\ngithub.com/pingcap/failpoint v0.0.0-20191029060244-12f4ac2fd11d/go.mod h1:DNS3Qg7bEDhU6EXNHF+XSv/PGznQaMJ5FWvctpm6pQI=\ngithub.com/pingcap/failpoint v0.0.0-20200210140405-f8f9fb234798/go.mod h1:DNS3Qg7bEDhU6EXNHF+XSv/PGznQaMJ5FWvctpm6pQI=\ngithub.com/pingcap/failpoint v0.0.0-20200702092429-9f69995143ce/go.mod h1:w4PEZ5y16LeofeeGwdgZB4ddv9bLyDuIX+ljstgKZyk=\ngithub.com/pingcap/failpoint v0.0.0-20210316064728-7acb0f0a3dfd h1:I8IeI8MNiZVKnwuXhcIIzz6pREcOSbq18Q31KYIzFVM=\ngithub.com/pingcap/failpoint v0.0.0-20210316064728-7acb0f0a3dfd/go.mod h1:IVF+ijPSMZVtx2oIqxAg7ur6EyixtTYfOHwpfmlhqI4=\ngithub.com/pingcap/fn v0.0.0-20191016082858-07623b84a47d/go.mod h1:fMRU1BA1y+r89AxUoaAar4JjrhUkVDt0o0Np6V8XbDQ=\ngithub.com/pingcap/fn v0.0.0-20200306044125-d5540d389059/go.mod h1:fMRU1BA1y+r89AxUoaAar4JjrhUkVDt0o0Np6V8XbDQ=\ngithub.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989/go.mod h1:O17XtbryoCJhkKGbT62+L2OlrniwqiGLSqrmdHCMzZw=\ngithub.com/pingcap/kvproto v0.0.0-20191211054548-3c6b38ea5107/go.mod h1:WWLmULLO7l8IOcQG+t+ItJ3fEcrL5FxF0Wu+HrMy26w=\ngithub.com/pingcap/kvproto v0.0.0-20200214064158-62d31900d88e/go.mod h1:IOdRDPLyda8GX2hE/jO7gqaCV/PNFh8BZQCQZXfIOqI=\ngithub.com/pingcap/kvproto v0.0.0-20200221034943-a2aa1d1e20a8/go.mod h1:IOdRDPLyda8GX2hE/jO7gqaCV/PNFh8BZQCQZXfIOqI=\ngithub.com/pingcap/kvproto v0.0.0-20200228095611-2cf9a243b8d5/go.mod h1:IOdRDPLyda8GX2hE/jO7gqaCV/PNFh8BZQCQZXfIOqI=\ngithub.com/pingcap/kvproto v0.0.0-20200411081810-b85805c9476c/go.mod h1:IOdRDPLyda8GX2hE/jO7gqaCV/PNFh8BZQCQZXfIOqI=\ngithub.com/pingcap/kvproto v0.0.0-20210219064844-c1844a4775d6/go.mod h1:IOdRDPLyda8GX2hE/jO7gqaCV/PNFh8BZQCQZXfIOqI=\ngithub.com/pingcap/kvproto v0.0.0-20210507054410-a8152f8a876c h1:cy87vgUJT0U4JuxC7R14PuwBrabI9fDawYhyKTbjOBQ=\ngithub.com/pingcap/kvproto v0.0.0-20210507054410-a8152f8a876c/go.mod h1:IOdRDPLyda8GX2hE/jO7gqaCV/PNFh8BZQCQZXfIOqI=\ngithub.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=\ngithub.com/pingcap/log v0.0.0-20200117041106-d28c14d3b1cd h1:CV3VsP3Z02MVtdpTMfEgRJ4T9NGgGTxdHpJerent7rM=\ngithub.com/pingcap/log v0.0.0-20200117041106-d28c14d3b1cd/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=\ngithub.com/pingcap/log v0.0.0-20200511115504-543df19646ad h1:SveG82rmu/GFxYanffxsSF503SiQV+2JLnWEiGiF+Tc=\ngithub.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=\ngithub.com/pingcap/log v0.0.0-20201112100606-8f1e84a3abc8/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=\ngithub.com/pingcap/log v0.0.0-20210317133921-96f4fcab92a4 h1:ERrF0fTuIOnwfGbt71Ji3DKbOEaP189tjym50u8gpC8=\ngithub.com/pingcap/log v0.0.0-20210317133921-96f4fcab92a4/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=\ngithub.com/pingcap/parser v0.0.0-20200317021010-cd90cc2a7d87/go.mod h1:9v0Edh8IbgjGYW2ArJr19E+bvL8zKahsFp+ixWeId+4=\ngithub.com/pingcap/parser v0.0.0-20200325032611-b7d1e7e1b93d h1:rTWwJg7aC5ct8QVTcFZoRLiJ2dtjFOUFRlIdK5481bk=\ngithub.com/pingcap/parser v0.0.0-20200325032611-b7d1e7e1b93d/go.mod h1:9v0Edh8IbgjGYW2ArJr19E+bvL8zKahsFp+ixWeId+4=\ngithub.com/pingcap/parser v0.0.0-20210525032559-c37778aff307 h1:v7SipssMu4X1tVQOe3PIVE73keJNHCFXe4Cza5uNDZ8=\ngithub.com/pingcap/parser v0.0.0-20210525032559-c37778aff307/go.mod h1:xZC8I7bug4GJ5KtHhgAikjTfU4kBv1Sbo3Pf1MZ6lVw=\ngithub.com/pingcap/parser v3.1.2+incompatible h1:ZAtv2VBZitECpaHshSIp1bkBhEqJYerw7nO/HYsn8MM=\ngithub.com/pingcap/parser v3.1.2+incompatible/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA=\ngithub.com/pingcap/pd/v4 v4.0.0-beta.1.0.20200305072537-61d9f9cc35d3/go.mod h1:25GfNw6+Jcr9kca5rtmTb4gKCJ4jOpow2zV2S9Dgafs=\ngithub.com/pingcap/sysutil v0.0.0-20200206130906-2bfa6dc40bcd/go.mod h1:EB/852NMQ+aRKioCpToQ94Wl7fktV+FNnxf3CX/TTXI=\ngithub.com/pingcap/sysutil v0.0.0-20200302022240-21c8c70d0ab1/go.mod h1:EB/852NMQ+aRKioCpToQ94Wl7fktV+FNnxf3CX/TTXI=\ngithub.com/pingcap/sysutil v0.0.0-20200309085538-962fd285f3bb/go.mod h1:EB/852NMQ+aRKioCpToQ94Wl7fktV+FNnxf3CX/TTXI=\ngithub.com/pingcap/sysutil v0.0.0-20210315073920-cc0985d983a3/go.mod h1:tckvA041UWP+NqYzrJ3fMgC/Hw9wnmQ/tUkp/JaHly8=\ngithub.com/pingcap/tidb v1.1.0-beta h1:d/Yw09RQoxh1IK/QNFm9H4ssfI9FqSlBCVowEfbBCmk=\ngithub.com/pingcap/tidb v1.1.0-beta.0.20200325081839-4a7d477399f4 h1:Ej+7Wu3RXhS5wvL5PA8e+iJbdATqc1Cjy+dfU0Rgxj4=\ngithub.com/pingcap/tidb v1.1.0-beta.0.20200325081839-4a7d477399f4/go.mod h1:4CGOiKZSaOU/Da3QYMtp0c3uBE2SxpcLOpESXmeQhcs=\ngithub.com/pingcap/tidb v1.1.0-beta.0.20210601085537-5d7c852770eb h1:tSZP1zie7y4arLuGFUaYQIGhTERUxCdLKd4SFujEqOQ=\ngithub.com/pingcap/tidb v1.1.0-beta.0.20210601085537-5d7c852770eb/go.mod h1:wDXJsUfKc+xXIuFBFY2vopJpfi1zl5EsNwbgU5k+iAQ=\ngithub.com/pingcap/tidb v2.0.11+incompatible h1:Shz+ry1DzQNsPk1QAejnM+5tgjbwZuzPnIER5aCjQ6c=\ngithub.com/pingcap/tidb-dashboard v0.0.0-20210312062513-eef5d6404638/go.mod h1:OzFN8H0EDMMqeulPhPMw2i2JaiZWOKFQ7zdRPhENNgo=\ngithub.com/pingcap/tidb-tools v4.0.0-beta.1.0.20200306084441-875bd09aa3d5+incompatible/go.mod h1:XGdcy9+yqlDSEMTpOXnwf3hiTeqrV6MN/u1se9N8yIM=\ngithub.com/pingcap/tidb-tools v4.0.9-0.20201127090955-2707c97b3853+incompatible/go.mod h1:XGdcy9+yqlDSEMTpOXnwf3hiTeqrV6MN/u1se9N8yIM=\ngithub.com/pingcap/tipb v0.0.0-20190428032612-535e1abaa330/go.mod h1:RtkHW8WbcNxj8lsbzjaILci01CtYnYbIkQhjyZWrWVI=\ngithub.com/pingcap/tipb v0.0.0-20200212061130-c4d518eb1d60/go.mod h1:RtkHW8WbcNxj8lsbzjaILci01CtYnYbIkQhjyZWrWVI=\ngithub.com/pingcap/tipb v0.0.0-20210425040103-dc47a87b52aa h1:lPZ5NmNel7RKlX4D9sU045y04knCMEaBTUrPAxb45os=\ngithub.com/pingcap/tipb v0.0.0-20210425040103-dc47a87b52aa/go.mod h1:nsEhnMokcn7MRqd2J60yxpn/ac3ZH8A6GOJ9NslabUo=\ngithub.com/pingcap/tipb v0.0.0-20210525032549-b80be13ddf6c/go.mod h1:nsEhnMokcn7MRqd2J60yxpn/ac3ZH8A6GOJ9NslabUo=\ngithub.com/pingcap/tipb v0.0.0-20210601083426-79a378b6d1c4 h1:n47+OwdI/uxKenfBT8Y2/be11MwbeLKNLdzOWnxNQKg=\ngithub.com/pingcap/tipb v0.0.0-20210601083426-79a378b6d1c4/go.mod h1:nsEhnMokcn7MRqd2J60yxpn/ac3ZH8A6GOJ9NslabUo=\ngithub.com/pires/go-proxyproto v0.0.0-20191211124218-517ecdf5bb2b/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/planetscale/pargzip v0.0.0-20201116224723-90c7fc03ea8a/go.mod h1:GJFUzQuXIoB2Kjn1ZfDhJr/42D5nWOqRcIQVgCxTuIE=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=\ngithub.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=\ngithub.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=\ngithub.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=\ngithub.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=\ngithub.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=\ngithub.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=\ngithub.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=\ngithub.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=\ngithub.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=\ngithub.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=\ngithub.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=\ngithub.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 h1:HQagqIiBmr8YXawX/le3+O26N+vPPC1PtjaF3mwnook=\ngithub.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=\ngithub.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=\ngithub.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=\ngithub.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=\ngithub.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=\ngithub.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=\ngithub.com/satori/go.uuid v0.0.0-20160713180306-0aa62d5ddceb/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=\ngithub.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=\ngithub.com/securego/gosec v0.0.0-20191217083152-cb4f343eaff1/go.mod h1:sM2KJ/O9PKom+0jAmXpblJ8PWrLbGAk6F2Lzwj4H6wg=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=\ngithub.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=\ngithub.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=\ngithub.com/shirou/gopsutil v2.19.10+incompatible h1:lA4Pi29JEVIQIgATSeftHSY0rMGI9CLrl2ZvDLiahto=\ngithub.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=\ngithub.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A=\ngithub.com/shirou/gopsutil v3.21.2+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=\ngithub.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=\ngithub.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=\ngithub.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=\ngithub.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=\ngithub.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=\ngithub.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sjmudd/stopwatch v0.0.0-20170613150411-f380bf8a9be1/go.mod h1:Pgf1sZ2KrHK8vdRTV5UHGp80LT7HMUKuNAiKC402abY=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=\ngithub.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=\ngithub.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=\ngithub.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=\ngithub.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=\ngithub.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=\ngithub.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=\ngithub.com/spyzhov/ajson v0.4.2/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=\ngithub.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=\ngithub.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=\ngithub.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=\ngithub.com/swaggo/http-swagger v0.0.0-20200103000832-0e9263c4b516/go.mod h1:O1lAbCgAAX/KZ80LM/OXwtWFI/5TvZlwxSg8Cq08PV0=\ngithub.com/swaggo/http-swagger v0.0.0-20200308142732-58ac5e232fba/go.mod h1:O1lAbCgAAX/KZ80LM/OXwtWFI/5TvZlwxSg8Cq08PV0=\ngithub.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=\ngithub.com/swaggo/swag v1.6.3/go.mod h1:wcc83tB4Mb2aNiL/HP4MFeQdpHUrca+Rp/DRNgWAUio=\ngithub.com/swaggo/swag v1.6.5/go.mod h1:Y7ZLSS0d0DdxhWGVhQdu+Bu1QhaF5k0RD7FKdiAykeY=\ngithub.com/swaggo/swag v1.6.6-0.20200529100950-7c765ddd0476/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=\ngithub.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=\ngithub.com/syndtr/goleveldb v0.0.0-20180815032940-ae2bd5eed72d/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=\ngithub.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=\ngithub.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=\ngithub.com/tchap/go-patricia v0.0.0-20160729071656-dd168db6051b/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=\ngithub.com/tebeka/selenium v0.9.9/go.mod h1:5Fr8+pUvU6B1OiPfkdCKdXZyr5znvVkxuPd0NOdZCQc=\ngithub.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8=\ngithub.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=\ngithub.com/tiancaiamao/appdash v0.0.0-20181126055449-889f96f722a2/go.mod h1:2PfKggNGDuadAa0LElHrByyrz4JPZ9fFx6Gs7nx7ZZU=\ngithub.com/tidwall/gjson v1.3.5/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=\ngithub.com/tidwall/gjson v1.7.5 h1:zmAN/xmX7OtpAkv4Ovfso60r/BiCi5IErCDYGNJu+uc=\ngithub.com/tidwall/gjson v1.7.5/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=\ngithub.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=\ngithub.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=\ngithub.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=\ngithub.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=\ngithub.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=\ngithub.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=\ngithub.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tikv/pd v1.1.0-beta.0.20210323121136-78679e5e209d h1:K0XnvsnT6ofLDuM8Rt3PuFQO4p8bNraeHYstspD316g=\ngithub.com/tikv/pd v1.1.0-beta.0.20210323121136-78679e5e209d/go.mod h1:Jw9KG11C/23Rr7DW4XWQ7H5xOgGZo6DFL1OKAF4+Igw=\ngithub.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=\ngithub.com/tinylib/msgp v1.1.1/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=\ngithub.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=\ngithub.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=\ngithub.com/twmb/murmur3 v1.1.3/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=\ngithub.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=\ngithub.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=\ngithub.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=\ngithub.com/uber/jaeger-client-go v2.16.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=\ngithub.com/uber/jaeger-client-go v2.22.1+incompatible h1:NHcubEkVbahf9t3p75TOCR83gdUHXjRJvjoBh1yACsM=\ngithub.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=\ngithub.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=\ngithub.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=\ngithub.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ=\ngithub.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=\ngithub.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=\ngithub.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=\ngithub.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=\ngithub.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=\ngithub.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=\ngithub.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=\ngithub.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=\ngithub.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=\ngithub.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=\ngithub.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=\ngithub.com/unrolled/render v0.0.0-20171102162132-65450fb6b2d3/go.mod h1:tu82oB5W2ykJRVioYsB+IQKcft7ryBr7w12qMBUPyXg=\ngithub.com/unrolled/render v1.0.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=\ngithub.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=\ngithub.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=\ngithub.com/urfave/negroni v0.3.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=\ngithub.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=\ngithub.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=\ngithub.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=\ngithub.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=\ngithub.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=\ngithub.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=\ngithub.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=\ngithub.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI=\ngithub.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=\ngithub.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=\ngithub.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xitongsys/parquet-go v1.5.1/go.mod h1:xUxwM8ELydxh4edHGegYq1pA8NnMKDx0K/GyB0o2bww=\ngithub.com/xitongsys/parquet-go v1.5.5-0.20201110004701-b09c49d6d457/go.mod h1:pheqtXeHQFzxJk45lRQ0UIGIivKnLXvialZSFWs81A8=\ngithub.com/xitongsys/parquet-go-source v0.0.0-20190524061010-2b72cbee77d5/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA=\ngithub.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0/go.mod h1:HYhIKsdns7xz80OgkbgJYrtQY7FjHWHKH6cvN7+czGE=\ngithub.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=\ngithub.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=\ngithub.com/z-division/go-zookeeper v0.0.0-20190128072838-6d7457066b9b/go.mod h1:JNALoWa+nCXR8SmgLluHcBNVJgyejzpKPZk9pX2yXXE=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=\ngo.etcd.io/etcd v0.5.0-alpha.5.0.20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=\ngo.etcd.io/etcd v0.5.0-alpha.5.0.20200824191128-ae9734ed278b h1:3kC4J3eQF6p1UEfQTkC67eEeb3rTk+shQqdX6tFyq9Q=\ngo.etcd.io/etcd v0.5.0-alpha.5.0.20200824191128-ae9734ed278b/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=\ngo.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=\ngo.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=\ngo.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/automaxprocs v1.2.0/go.mod h1:YfO3fm683kQpzETxlTGZhGIVmXAhaw3gxeBADbpZtnU=\ngo.uber.org/dig v1.8.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw=\ngo.uber.org/fx v1.10.0/go.mod h1:vLRicqpG/qQEzno4SYU86iCwfT95EZza+Eba0ItuxqY=\ngo.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=\ngo.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=\ngo.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=\ngo.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=\ngo.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=\ngo.uber.org/zap v1.14.0 h1:/pduUoebOeeJzTDFuoMgC6nRkiasr1sBCIEorly7m4o=\ngo.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=\ngo.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=\ngo.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=\ngo.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=\ngo.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=\ngo.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=\ngolang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b h1:qh4f65QIVFjq9eBURLEYWqaEXmOyqdUyiBSgaXWccWk=\ngolang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191030062658-86caa796c7ab/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191107010934-f79515f33823/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191114200427-caa0b0f7d508/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191217033636-bbbf87ae2631/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191219041853-979b82bfef62/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200301222351-066e0c02454c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200527183253-8e7acdbce89d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20201125231158-b5590deeca9b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=\ngonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=\ngonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20190926190326-7ee9db18f195/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63 h1:YzfoEYWbODU5Fbt37+h7X16BWQbad7Q4S6gclTKFXM8=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/grpc v0.0.0-20180607172857-7a6a684ca69e/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s=\ngoogle.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=\ngoogle.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngopkg.in/DataDog/dd-trace-go.v1 v1.17.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=\ngopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=\ngopkg.in/alecthomas/gometalinter.v2 v2.0.12/go.mod h1:NDRytsqEZyolNuAgTzJkZMkSQM7FIKyzVzGhjB/qfYo=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA=\ngopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=\ngopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=\ngopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=\ngopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=\ngopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=\ngopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=\ngopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=\ngopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=\ngopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=\ngopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=\ngopkg.in/ldap.v2 v2.5.0/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=\ngopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=\ngopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=\ngopkg.in/ory-am/dockertest.v3 v3.3.4/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.2.0/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=\nk8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=\nk8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY=\nk8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=\nk8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY=\nk8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ=\nk8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=\nk8s.io/component-base v0.17.3/go.mod h1:GeQf4BrgelWm64PXkIXiPh/XS0hnO42d9gx9BtbZRp8=\nk8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=\nk8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=\nk8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=\nk8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=\nk8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=\nmodernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=\nmodernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=\nmodernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=\nmodernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=\nmodernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=\nmvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=\nmvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=\nmvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=\nmvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2/go.mod h1:rCqoQrfAmpTX/h2APczwM7UymU/uvaOluiVPIYCSY/k=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=\nsigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=\nsigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=\nsourcegraph.com/sourcegraph/appdash v0.0.0-20180531100431-4c381bd170b4/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=\nsourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=\nsourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=\nsourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=\nsourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4=\nvitess.io/vitess v0.0.0-20200325000816-eda961851d63 h1:kPgA1yZNgrc/AjBZ2k+103rN7/4pFmK9/GmvtegII2k=\nvitess.io/vitess v0.0.0-20200325000816-eda961851d63/go.mod h1:b+HPfM4HAckoHm+x/E2VWnRfLmq/ZRveGiCU1yNU3EE=\nvitess.io/vitess v0.9.0 h1:AiBFrhzTCqXcDutjPMp7FtkX2s3dXQBg252rGho6rn8=\nvitess.io/vitess v0.9.0/go.mod h1:0eX/mIMRc3Kl62gXTORUC/ozkV57fZ8gw7eSXmVIrz8=\nvitess.io/vitess/examples/are-you-alive v0.0.0-20200302220708-6b7695375ce9/go.mod h1:3Lnc7tMP/PtC7X4Yxu2tcqYbXAX4aGLDKEDsaQwPDp4=\n"
  },
  {
    "path": "retool-install.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n# This script generates tools.json\n# It helps record what releases/branches are being used\nwhich retool >/dev/null || go get -u github.com/twitchtv/retool\n\n# This tool can run other checks in a standardized way\nretool add gopkg.in/alecthomas/gometalinter.v2 v2.0.11\n\n# check spelling\n# misspell works with gometalinter\nretool add github.com/client9/misspell/cmd/misspell v0.3.4\n# goword adds additional capability to check comments\nretool add github.com/chzchzchz/goword a9744cb52b033fe5c269df48eeef2c954526cd79\n\n# checks correctness\nretool add github.com/gordonklaus/ineffassign 7bae11eba15a3285c75e388f77eb6357a2d73ee2\nretool add honnef.co/go/tools/cmd/megacheck master\nretool add github.com/dnephin/govet 4a96d43e39d340b63daa8bc5576985aa599885f6\n\n# slow checks\nretool add github.com/kisielk/errcheck v1.1.0\nretool add github.com/securego/gosec/cmd/gosec 1.0.0\n\n# linter\nretool add github.com/mgechev/revive 7773f47324c2bf1c8f7a5500aff2b6c01d3ed73b\nretool add github.com/golangci/golangci-lint/cmd/golangci-lint v1.10\n"
  },
  {
    "path": "revive.toml",
    "content": "ignoreGeneratedHeader = false\nseverity = \"error\"\nconfidence = 0.8\nerrorCode = 0\nwarningCode = 0\n\n[rule.blank-imports]\n[rule.context-as-argument]\n[rule.dot-imports]\n[rule.error-return]\n[rule.error-strings]\n[rule.error-naming]\n[rule.exported]\n[rule.if-return]\n[rule.var-naming]\n[rule.package-comments]\n[rule.range]\n[rule.receiver-naming]\n[rule.indent-error-flow]\n[rule.superfluous-else]\n[rule.modifies-parameter]\n\n# This can be checked by other tools like megacheck\n#[rule.unreachable-code]\n\n\n# Currently this makes too much noise, but should add it in\n# and perhaps ignore it in a few files\n#[rule.confusing-naming]\n#  severity = \"warning\"\n#[rule.confusing-results]\n#  severity = \"warning\"\n#[rule.unused-parameter]\n#  severity = \"warning\"\n#[rule.deep-exit]\n#  severity = \"warning\"\n#[rule.flag-parameter]\n#  severity = \"warning\"\n\n\n\n# Adding these will slow down the linter\n# They are already provided by megacheck\n#[rule.unexported-return]\n#[rule.time-naming]\n#[rule.errorf]\n\n# Adding these will slow down the linter\n# Not sure if they are already provided by megacheck\n#[rule.var-declaration]\n#[rule.context-keys-type]\n"
  },
  {
    "path": "test/env.bats",
    "content": "#!/usr/bin/env bats\n\nload test_helper\n\n@test \"Simple Query Optimizer\" {\n  ${SOAR_BIN_ENV} -query \"select * from film where length > 120\" | sed \"s/ [0-9.]*%/ n%/g\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"$output\"\n  [ $status -eq 0 ]\n}\n\n@test \"Run all test cases\" {\n  ${SOAR_BIN} -list-test-sqls | ${SOAR_BIN_ENV} | sed \"s/ [0-9.]*%/ n%/g\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"$output\"\n  [ $status -eq 0 ]\n}\n\n@test \"Check dial timeout\" {\n  run timeout 1 ${SOAR_BIN} -test-dsn \"1.1.1.1\" -check-config\n  echo \"$output\"\n  [ $status -eq 124 ]\n}\n\n# 12. 带数据库连接时黑名单功能是否正常\n# soar 的日志和黑名单的相对路径都相对于 soar 的二进制文件路径说的\n@test \"Check Soar With Mysql Connect Blacklist\" {\n  run ${SOAR_BIN_ENV} -blacklist ../etc/soar.blacklist -query \"show processlist;\"\n  [ $status -eq 0 ]\n  [ -z ${output} ]\n}\n"
  },
  {
    "path": "test/fixture/test_Check_Max_Join_Table_Count_Default.golden",
    "content": "# 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",
    "content": "# 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\n* **Item:**  JOI.005\n\n* **Severity:**  L2\n\n* **Content:**  太多的 JOIN 是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询，并减少 JOIN 的数量。\n\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_Delimiter.golden",
    "content": "# Query: 8093354EDF76BFDA\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  b  \nFROM  \n  c\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n# Query: DD1A4C78C3479C1E\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  a  \nFROM  \n  b\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_Max_Column_Count.golden",
    "content": "# Query: 7EC60923DD614DF2\n\n★ ☆ ☆ ☆ ☆ 30分\n\n```sql\ncreate  table  a  (a  int, b  int, c  int, d  int)\n```\n\n## 建议为表添加注释\n\n* **Item:**  CLA.011\n\n* **Severity:**  L1\n\n* **Content:**  为表添加注释能够使得表的意义更明确，从而为日后的维护带来极大的便利。\n\n## 请为列添加默认值\n\n* **Item:**  COL.004\n\n* **Severity:**  L1\n\n* **Content:**  请为列添加默认值，如果是 ALTER 操作，请不要忘记将原字段的默认值写上。字段无默认值，当表较大时无法在线变更表结构。\n\n## 列未添加注释\n\n* **Item:**  COL.005\n\n* **Severity:**  L1\n\n* **Content:**  建议对表中每个列添加注释，来明确每个列在表中的含义及作用。\n\n## 表中包含有太多的列\n\n* **Item:**  COL.006\n\n* **Severity:**  L3\n\n* **Content:**  表中包含有太多的列\n\n## 未指定主键或主键非 int 或 bigint\n\n* **Item:**  KEY.007\n\n* **Severity:**  L4\n\n* **Content:**  未指定主键或主键非 int 或 bigint，建议将主键设置为 int unsigned 或 bigint unsigned。\n\n## 请为表选择合适的存储引擎\n\n* **Item:**  TBL.002\n\n* **Severity:**  L4\n\n* **Content:**  建表或修改表的存储引擎时建议使用推荐的存储引擎，如：innodb\n\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Fingerprint.golden",
    "content": "select * from film where length = ?\nselect * from film where length is null\nselect * from film having title = ?\nselect * from sakila.film where length >= ?\nselect * from sakila.film where length >= ?\nselect * from film where length between ? and ?\nselect * from film where title like ?\nselect * from film where title is not null\nselect * from film where length = ? and title = ?\nselect * from film where length > ? and title = ?\nselect * from film where length > ? and language_id < ? and title = ?\nselect * from film where length > ? and language_id < ?\nselect release_year, sum(length) from film where length = ? and language_id = ? group by release_year\nselect release_year, sum(length) from film where length >= ? group by release_year\nselect release_year, language_id, sum(length) from film group by release_year, language_id\nselect release_year, sum(length) from film where length = ? group by language_id)\nselect release_year, sum(film_id) from film group by release_year\nselect * from address group by address,district\nselect title from film where abs(language_id) = ? group by title\nselect language_id from film where length = ? group by release_year order by language_id\nselect release_year from film where length = ? group by release_year order by release_year\nselect * from film where length = ? order by release_year, language_id desc\nselect release_year from film where length = ? group by release_year order by release_year limit ?\nselect * from film where length = ? order by release_year limit ?\nselect * from film order by release_year limit ?\nselect film_id from film order by release_year limit ?\nselect * from film where length > ? order by length limit ?\nselect * from film where length < ? order by length limit ?\nselect * from customer where address_id in(?+) order by last_name\nselect * from film where release_year = ? and length != ? order by title\nselect title from film where release_year = ?\nselect title, replacement_cost from film where language_id = ? and length = ?\nselect title from film where language_id > ? and length > ?\nselect * from film where length = ? and title = ? order by release_year\nselect * from film where length > ? and title = ? order by release_year\nselect * from film where length > ? order by release_year\nselect * from city a inner join country b on a.country_id=b.country_id\nselect * from city a left join country b on a.country_id=b.country_id\nselect * from city a right join country b on a.country_id=b.country_id\nselect * from city a left join country b on a.country_id=b.country_id where b.last_update is null\nselect * from city a right join country b on a.country_id=b.country_id where a.last_update is null\nselect * from city a left join country b on a.country_id=b.country_id union select * from city a right join country b on a.country_id=b.country_id\nselect * from city a right join country b on a.country_id=b.country_id where a.last_update is null union select * from city a left join country b on a.country_id=b.country_id where b.last_update is null\nselect country_id, last_update from city natural join country\nselect country_id, last_update from city natural left join country\nselect country_id, last_update from city natural right join country\nselect a.country_id, a.last_update from city a straight_join country b on a.country_id=b.country_id\nselect a.address, a.postal_code from sakila.address a where a.city_id in(?+)\nselect city from( select city_id from city where city = ? order by last_update desc limit ?, ?) i join city on (i.city_id = city.city_id) join country on (country.country_id = city.country_id) order by city desc\ndelete city, country from city inner join country using (country_id) where city.city_id = ?\ndelete city from city left join country on city.country_id = country.country_id where country.country is null\ndelete a1, a2 from city as a1 inner join country as a2 where a1.country_id=a2.country_id\ndelete from a1, a2 using city as a1 inner join country as a2 where a1.country_id=a2.country_id\ndelete from film where length > ?\nupdate city inner join country using(country_id) set city.city = ?, city.last_update = ?, country.country = ? where city.city_id=?\nupdate city inner join country on city.country_id = country.country_id inner join address on city.city_id = address.city_id set city.city = ?, city.last_update = ?, country.country = ? where city.city_id=?\nupdate city, country set city.city = ?, city.last_update = ?, country.country = ? where city.country_id = country.country_id and city.city_id=?\nupdate film set length = ? where language_id = ?\ninsert into city (country_id) select country_id from country\ninsert into city (country_id) values(?+)\ninsert into city (country_id) values(?+)\ninsert into city (country_id) select ? from dual\nreplace into city (country_id) select country_id from country\nreplace into city (country_id) values(?+)\nreplace into city (country_id) values(?+)\nreplace into city (country_id) select ? from dual\nselect film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from ( select film_id from film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film\nselect * from film where language_id = (select language_id from language limit ?)\nselect * from city i left join country o on i.city_id=o.country_id union select * from city i right join country o on i.city_id=o.country_id\nselect * from (select * from actor where last_update=? and last_name=?) t where last_update=? and last_name=? group by first_name\nselect * from city i left join country o on i.city_id=o.country_id union select * from city i right join country o on i.city_id=o.country_id\nselect * from city i left join country o on i.city_id=o.country_id where o.country_id is null union select * from city i right join country o on i.city_id=o.country_id where i.city_id is null\nselect first_name,last_name,email from customer straight_join address on customer.address_id=address.address_id\nselect id,name from (select address from customer_list where sid=? order by phone limit ?,?) a join customer_list l on (a.address=l.address) join city c on (c.city=l.city) order by phone desc\nselect * from film where date(last_update)=?\nselect last_update from film group by date(last_update)\nselect last_update from film order by date(last_update)\nselect description from film where description in(?+) group by description\nalter table address add index idx_city_id(city_id)\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`)\nalter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`)\nselect date_format(t.last_update, ?), count(distinct (t.city)) from city t where t.last_update > ? and t.city like ? and t.city = ? group by date_format(t.last_update, ?) order by date_format(t.last_update, ?)\ncreate table hello.t (id int unsigned)\nselect * from tb where data >= ?\nalter table tb alter column id drop default\nselect maxid, minid from (select max(film_id) maxid, min(film_id) minid from film where last_update > ?) as d\nselect maxid, minid from (select max(film_id) maxid, min(film_id) minid from film) as d\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Alwaystrue_.golden",
    "content": "select count(col) from tbl where (a = 'b')\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Countstar_.golden",
    "content": "select count(*) from tbl group by 1\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Delimiter_.golden",
    "content": "use sakila;\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Distinctstar_.golden",
    "content": "SELECT * FROM film\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Dml2select_.golden",
    "content": "select * from film where length > 100\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Dmlorderby_.golden",
    "content": "delete from tbl where col1 = 1\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Having_.golden",
    "content": "select state, COUNT(*) from Drivers where state in ('GA', 'TX') group by state order by state asc\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Mergealter_.golden",
    "content": "ALTER TABLE `address` add index idx_city_id(city_id) ;\nALTER TABLE `inventory` add index `idx_store_film` (`store_id`,`film_id`), add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`) ;\nALTER TABLE `tb` alter column id drop default ;\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1\nDELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id\nDELETE FROM film WHERE length > 100\nINSERT INTO city (country_id) SELECT 10 FROM DUAL\nINSERT INTO city (country_id) SELECT country_id FROM country\nINSERT INTO city (country_id) VALUES (1),(2),(3)\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL\nREPLACE INTO city (country_id) SELECT country_id FROM country\nREPLACE INTO city (country_id) VALUES (1),(2),(3)\nSELECT\tDATE_FORMAT(t.last_update, '%Y-%m-%d'),\tCOUNT(DISTINCT (t.city))\tFROM city t WHERE t.last_update > '2018-10-22 00:00:00'\tAND t.city LIKE '%Chrome%'\tAND t.city = 'eip' GROUP BY DATE_FORMAT(t.last_update, '%Y-%m-%d') ORDER BY DATE_FORMAT(t.last_update, '%Y-%m-%d')\nSELECT * FROM address GROUP BY address,district\nSELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id\nSELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL\nSELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id\nSELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null\nSELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name\nSELECT * FROM film HAVING title = 'abc'\nSELECT * FROM film ORDER BY release_year LIMIT 10\nSELECT * FROM film WHERE date(last_update)='2006-02-15'\nSELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1)\nSELECT * FROM film WHERE length > 100 and language_id < 10\nSELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz'\nSELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL'\nSELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year\nSELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year\nSELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10\nSELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10\nSELECT * FROM film WHERE length > 100 ORDER BY release_year\nSELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL'\nSELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC\nSELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10\nSELECT * FROM film WHERE length = 86\nSELECT * FROM film WHERE length BETWEEN 60 AND 84\nSELECT * FROM film WHERE length IS NULL\nSELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title\nSELECT * FROM film WHERE title IS NOT NULL\nSELECT * FROM film WHERE title LIKE 'AIR%'\nSELECT * FROM sakila.film WHERE length >= 60\nSELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name\nselect * from tb where data >= ''\nSELECT a.address, a.postal_code FROM sakila.address a WHERE a.city_id IN  (SELECT c.city_id FROM sakila.city c)\nSELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I.city_id = city.city_id) JOIN country ON (country.country_id = city.country_id) ORDER BY city DESC\nSELECT country_id, last_update FROM city NATURAL JOIN country\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country\nSELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM  film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film\nSELECT film_id FROM film ORDER BY release_year LIMIT 10\nSELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id\nSELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc\nSELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id\nSELECT last_update FROM film GROUP BY date(last_update)\nSELECT last_update FROM film order by date(last_update)\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film) as d\nselect maxId, minId from (select max(film_id) maxId, min(film_id) minId from film where last_update > '2016-03-27 02:01:01') as d\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year\nSELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10\nSELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id\nSELECT release_year, sum(film_id) FROM film GROUP BY release_year\nSELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year\nSELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year\nSELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id)\nSELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title\nSELECT title FROM film WHERE language_id > 5 AND length > 70\nSELECT title FROM film WHERE release_year = 1995\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70\nUPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10\nUPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10\nUPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10\nUPDATE film SET length = 10 WHERE language_id = 20\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Or2in_.golden",
    "content": "select country_id from city where (col2 in (1, 2)) or col1 in (1, 3)\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Orderbynull_.golden",
    "content": "select sum(col1) from tbl group by col order by null\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Rmparenthesis_.golden",
    "content": "select col from a where col = 1\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Standard_.golden",
    "content": "select sum(col1) from tbl group by 1\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Star2columns_.golden",
    "content": "select film_id, title, description, release_year, language_id, original_language_id, rental_duration, rental_rate, length, replacement_cost, rating, special_features, last_update from film where length > 120\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Truncate_.golden",
    "content": "truncate table tbl\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_Rewrite_Unionall_.golden",
    "content": "select country_id from city union all select country_id from country\n"
  },
  {
    "path": "test/fixture/test_Check_Soar_SQL_pretty_And_Compress_.golden",
    "content": "SELECT * FROM film WHERE LENGTH = 86;\nSELECT * FROM film WHERE LENGTH IS NULL;\nSELECT * FROM film HAVING title = 'abc';\nSELECT * FROM sakila. film WHERE LENGTH >= 60;\nSELECT * FROM sakila. film WHERE LENGTH >= '60';\nSELECT * FROM film WHERE LENGTH BETWEEN 60 AND 84;\nSELECT * FROM film WHERE title LIKE 'AIR%';\nSELECT * FROM film WHERE title IS NOT NULL;\nSELECT * FROM film WHERE LENGTH = 114 AND title = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE LENGTH > 100 AND title = 'ALABAMA DEVIL';\nSELECT * FROM film WHERE LENGTH > 100 AND language_id < 10 AND title = 'xyz';\nSELECT * FROM film WHERE LENGTH > 100 AND language_id < 10;\nSELECT release_year, SUM( LENGTH) FROM film WHERE LENGTH = 123 AND language_id = 1 GROUP BY release_year;\nSELECT release_year, SUM( LENGTH) FROM film WHERE LENGTH >= 123 GROUP BY release_year;\nSELECT release_year, language_id, SUM( LENGTH) FROM film GROUP BY release_year, language_id;\nSELECT release_year, SUM( LENGTH) FROM film WHERE LENGTH = 123 GROUP BY release_year, (LENGTH+ language_id);\nSELECT release_year, SUM( film_id) FROM film GROUP BY release_year;\nSELECT * FROM address GROUP BY address, district;\nSELECT title FROM film WHERE ABS( language_id) = 3 GROUP BY title;\nSELECT language_id FROM film WHERE LENGTH = 123 GROUP BY release_year ORDER BY language_id;\nSELECT release_year FROM film WHERE LENGTH = 123 GROUP BY release_year ORDER BY release_year;\nSELECT * FROM film WHERE LENGTH = 123 ORDER BY release_year ASC, language_id DESC;\nSELECT release_year FROM film WHERE LENGTH = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;\nSELECT * FROM film WHERE LENGTH = 123 ORDER BY release_year LIMIT 10;\nSELECT * FROM film ORDER BY release_year LIMIT 10;\nSELECT film_id FROM film ORDER BY release_year LIMIT 10;\nSELECT * FROM film WHERE LENGTH > 100 ORDER BY LENGTH LIMIT 10;\nSELECT * FROM film WHERE LENGTH < 100 ORDER BY LENGTH LIMIT 10;\nSELECT * FROM customer WHERE address_id in (224, 510) ORDER BY last_name;\nSELECT * FROM film WHERE release_year = 2016 AND LENGTH != 1 ORDER BY title;\nSELECT title FROM film WHERE release_year = 1995;\nSELECT title, replacement_cost FROM film WHERE language_id = 5 AND LENGTH = 70;\nSELECT title FROM film WHERE language_id > 5 AND LENGTH > 70;\nSELECT * FROM film WHERE LENGTH = 100 AND title = 'xyz' ORDER BY release_year;\nSELECT * FROM film WHERE LENGTH > 100 AND title = 'xyz' ORDER BY release_year;\nSELECT * FROM film WHERE LENGTH > 100 ORDER BY release_year;\nSELECT * FROM city a INNER JOIN country b ON a. country_id= b. country_id;\nSELECT * FROM city a LEFT JOIN country b ON a. country_id= b. country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a. country_id= b. country_id;\nSELECT * FROM city a LEFT JOIN country b ON a. country_id= b. country_id WHERE b. last_update IS NULL;\nSELECT * FROM city a RIGHT JOIN country b ON a. country_id= b. country_id WHERE a. last_update IS NULL;\nSELECT * FROM city a LEFT JOIN country b ON a. country_id= b. country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a. country_id= b. country_id;\nSELECT * FROM city a RIGHT JOIN country b ON a. country_id= b. country_id WHERE a. last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a. country_id= b. country_id WHERE b. last_update IS NULL;\nSELECT country_id, last_update FROM city NATURAL JOIN country;\nSELECT country_id, last_update FROM city NATURAL LEFT JOIN country;\nSELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;\nSELECT a. country_id, a. last_update FROM city a STRAIGHT_JOIN country b ON a. country_id= b. country_id;\nSELECT a. address, a. postal_code FROM sakila. address a WHERE a. city_id IN ( SELECT c. city_id FROM sakila. city c);\nSELECT city FROM( SELECT city_id FROM city WHERE city = \"A Corua (La Corua)\" ORDER BY last_update DESC LIMIT 50, 10) I JOIN city ON (I. city_id = city. city_id) JOIN country ON (country. country_id = city. country_id) ORDER BY city DESC;\nDELETE city, country FROM city INNER JOIN country using (country_id) WHERE city. city_id = 1;\nDELETE city FROM city LEFT JOIN country ON city. country_id = country. country_id WHERE country. country IS NULL;\nDELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1. country_id= a2. country_id;\nDELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1. country_id= a2. country_id;\nDELETE FROM film WHERE LENGTH > 100;\nUPDATE city INNER JOIN country USING( country_id) SET city. city = 'Abha', city. last_update = '2006-02-15 04:45:25', country. country = 'Afghanistan' WHERE city. city_id= 10;\nUPDATE city INNER JOIN country ON city. country_id = country. country_id INNER JOIN address ON city. city_id = address. city_id SET city. city = 'Abha', city. last_update = '2006-02-15 04:45:25', country. country = 'Afghanistan' WHERE city. city_id= 10;\nUPDATE city, country SET city. city = 'Abha', city. last_update = '2006-02-15 04:45:25', country. country = 'Afghanistan' WHERE city. country_id = country. country_id AND city. city_id= 10;\nUPDATE film SET LENGTH = 10 WHERE language_id = 20;\nINSERT INTO city (country_id) SELECT country_id FROM country;\nINSERT INTO city (country_id) VALUES (1), (2), (3);\nINSERT INTO city (country_id) VALUES (10);\nINSERT INTO city (country_id) SELECT 10 FROM DUAL;\nREPLACE INTO city (country_id) SELECT country_id FROM country;\nREPLACE INTO city (country_id) VALUES (1), (2), (3);\nREPLACE INTO city (country_id) VALUES (10);\nREPLACE INTO city (country_id) SELECT 10 FROM DUAL;\nSELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;\nSELECT * FROM film WHERE language_id = ( SELECT language_id FROM language LIMIT 1);\nSELECT * FROM city i LEFT JOIN country o ON i. city_id= o. country_id UNION SELECT * FROM city i RIGHT JOIN country o ON i. city_id= o. country_id;\nSELECT * FROM ( SELECT * FROM actor WHERE last_update= '2006-02-15 04:34:33' AND last_name= 'CHASE' ) t WHERE last_update= '2006-02-15 04:34:33' AND last_name= 'CHASE' GROUP BY first_name;\nSELECT * FROM city i LEFT JOIN country o ON i. city_id= o. country_id UNION SELECT * FROM city i RIGHT JOIN country o ON i. city_id= o. country_id;\nSELECT * FROM city i LEFT JOIN country o ON i. city_id= o. country_id WHERE o. country_id is null UNION SELECT * FROM city i RIGHT JOIN country o ON i. city_id= o. country_id WHERE i. city_id is null;\nSELECT first_name, last_name, email FROM customer STRAIGHT_JOIN address ON customer. address_id= address. address_id;\nSELECT ID, name FROM ( SELECT address FROM customer_list WHERE SID= 1 ORDER BY phone LIMIT 50, 10) a JOIN customer_list l ON (a. address= l. address) JOIN city c ON (c. city= l. city) ORDER BY phone desc;\nSELECT * FROM film WHERE DATE( last_update) = '2006-02-15';\nSELECT last_update FROM film GROUP BY DATE( last_update);\nSELECT last_update FROM film ORDER BY DATE( last_update);\nSELECT description FROM film WHERE description IN( 'NEWS', 'asd' ) GROUP BY description;\nALTER TABLE address ADD index idx_city_id( city_id);\nALTER TABLE inventory ADD index `idx_store_film` ( `store_id`, `film_id`);\nALTER TABLE inventory ADD index `idx_store_film` ( `store_id`, `film_id`), ADD index `idx_store_film` ( `store_id`, `film_id`), ADD index `idx_store_film` ( `store_id`, `film_id`);\nSELECT DATE_FORMAT( t. last_update, '%Y-%m-%d' ), COUNT( DISTINCT ( t. city)) FROM city t WHERE t. last_update > '2018-10-22 00:00:00' AND t. city LIKE '%Chrome%' AND t. city = 'eip' GROUP BY DATE_FORMAT( t. last_update, '%Y-%m-%d' ) ORDER BY DATE_FORMAT( t. last_update, '%Y-%m-%d' );\ncreate table hello. t (id int unsigned);\nSELECT * FROM tb WHERE data >= '';\nALTER TABLE tb alter column id DROP DEFAULT;\nSELECT maxId, minId FROM ( SELECT MAX( film_id) maxId, MIN( film_id) minId FROM film WHERE last_update > '2016-03-27 02:01:01' ) as d;\nSELECT maxId, minId FROM ( SELECT MAX( film_id) maxId, MIN( film_id) minId FROM film) as d;\n"
  },
  {
    "path": "test/fixture/test_Check_get_tables_from_SQL.golden",
    "content": "{\n  \"084DA3E3EE38DD85\": [\n    \"`unknown`.`tb`\"\n  ],\n  \"08CFE41C7D20AAC8\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"0BE2D79E2F1E7CB0\": [\n    \"`unknown`.`film`\"\n  ],\n  \"0D0DABACEDFF5765\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"105C870D5DFB6710\": [\n    \"`unknown`.`film`\"\n  ],\n  \"11EC7AAACC97DC0F\": [\n    \"`unknown`.`city`\"\n  ],\n  \"12FF1DAA3D425FA9\": [\n    \"`unknown`.`film`\"\n  ],\n  \"16C2B14E7DAA9906\": [\n    \"`unknown`.`film`\",\n    \"`unknown`.`language`\"\n  ],\n  \"16CB4628D2597D40\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"17D5BCF21DC2364C\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"18A2AD1395A58EAE\": [\n    \"`unknown`.`film`\"\n  ],\n  \"1A53649C43122975\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"1E2CF4145EE706A5\": [\n    \"`unknown`.`film`\"\n  ],\n  \"1E56C6CCEA2131CC\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"1E8B70E30062FD13\": [\n    \"`unknown`.`address`\",\n    \"`unknown`.`customer`\"\n  ],\n  \"1ED2B7ECBA4215E1\": [\n    \"`unknown`.`film`\"\n  ],\n  \"23D176AEA2947002\": [\n    \"`unknown`.`film`\"\n  ],\n  \"255BAC03F56CDBC7\": [\n    \"`unknown`.`address`\"\n  ],\n  \"291F95B7DCB74C21\": [\n    \"`unknown`.`tb`\"\n  ],\n  \"2BA1217F6C8CF0AB\": [\n    \"`unknown`.`address`\"\n  ],\n  \"2EAACFD7030EA528\": [\n    \"`unknown`.`film`\"\n  ],\n  \"2F7439623B712317\": [\n    \"`unknown`.`city`\"\n  ],\n  \"3656B13CC4F888E2\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"3FF20E28EC9CBEF9\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"466F1AC2F5851149\": [\n    \"`unknown`.`city`\"\n  ],\n  \"47044E1FE1A965A5\": [\n    \"`unknown`.`film`\"\n  ],\n  \"485D56FC88BBBDB9\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"4A39009B402BAD9B\": [\n    \"`unknown`.`film`\"\n  ],\n  \"4E73AA068370E6A8\": [\n    \"`unknown`.`film`\"\n  ],\n  \"4ECCA9568BE69E68\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"50F2AB4243CE2071\": [\n    \"`sakila`.`address`\",\n    \"`sakila`.`city`\"\n  ],\n  \"584CCEC8069B6947\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"5C547F08EADBB131\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"5CE2F187DBF2A710\": [\n    \"`unknown`.`film`\"\n  ],\n  \"60F234BA33AAC132\": [\n    \"`unknown`.`film`\"\n  ],\n  \"626571EAE84E2C8A\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"68E48001ECD53152\": [\n    \"`unknown`.`film`\"\n  ],\n  \"6A0F035BD4E01018\": [\n    \"`unknown`.`film`\"\n  ],\n  \"6E9B96CA3F0E6BDA\": [\n    \"`unknown`.`film`\"\n  ],\n  \"707FE669669FA075\": [\n    \"`unknown`.`film`\"\n  ],\n  \"73DDF6E6D9E40384\": [\n    \"`unknown`.`film`\"\n  ],\n  \"7598A4EDE6CFA6BE\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"7F02E23D44A38A6D\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"863A85207E4F410D\": [\n    \"`unknown`.`film`\"\n  ],\n  \"868317D1973FD1B0\": [\n    \"`unknown`.`film`\"\n  ],\n  \"8A106444D14B9880\": [\n    \"`unknown`.`film`\"\n  ],\n  \"965D5AC955824512\": [\n    \"`unknown`.`film`\"\n  ],\n  \"9BB74D074BA0727C\": [\n    \"`unknown`.`inventory`\"\n  ],\n  \"A0C5E62C724A121A\": [\n    \"`sakila`.`film`\"\n  ],\n  \"A314542EEE8571EE\": [\n    \"`unknown`.`customer`\"\n  ],\n  \"A3FAB6027484B88B\": [\n    \"`unknown`.`film`\"\n  ],\n  \"A4911095C201896F\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"A7973BDD268F926E\": [\n    \"`unknown`.`city`\"\n  ],\n  \"AF0C1EB58B23D2FA\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"AFEEBF10A8D74E32\": [\n    \"`unknown`.`film`\"\n  ],\n  \"B0BA5A7079EA16B3\": [\n    \"`unknown`.`film`\"\n  ],\n  \"B13E0ACEAF8F3119\": [\n    \"`unknown`.`film`\"\n  ],\n  \"B3C502B4AA344196\": [\n    \"`unknown`.`film`\"\n  ],\n  \"B48292EDB9D0E010\": [\n    \"`unknown`.`film`\"\n  ],\n  \"B862978586C6338B\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"B9336971FF3D3792\": [\n    \"`unknown`.`film`\"\n  ],\n  \"BA7111449E4F1122\": [\n    \"`unknown`.`film`\"\n  ],\n  \"C11ECE7AE5F80CE5\": [\n    \"`hello`.`t`\"\n  ],\n  \"C15BDF2C73B5B7ED\": [\n    \"`unknown`.`address`\",\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"C315BC4EE0F4E523\": [\n    \"`unknown`.`inventory`\"\n  ],\n  \"C3FAEDA6AD6D762B\": [\n    \"`unknown`.`film`\"\n  ],\n  \"C4A212A42400411D\": [\n    \"`unknown`.`film`\"\n  ],\n  \"C95B5C028C8FFF95\": [\n    \"`unknown`.`city`\"\n  ],\n  \"CB42080E9F35AB07\": [\n    \"`unknown`.`film`\"\n  ],\n  \"DF59FD602E4AA368\": [\n    \"`unknown`.`film`\"\n  ],\n  \"DF916439ABD07664\": [\n    \"`unknown`.`film`\"\n  ],\n  \"E3DDA1A929236E72\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"E48A20D0413512DA\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`customer_list`\"\n  ],\n  \"E75234155B5E2E14\": [\n    \"`unknown`.`film`\"\n  ],\n  \"E84CBAAC2E12BDEA\": [\n    \"`unknown`.`film`\"\n  ],\n  \"E969B9297DA79BA6\": [\n    \"`unknown`.`film`\"\n  ],\n  \"EA50643B01E139A8\": [\n    \"`unknown`.`actor`\"\n  ],\n  \"F16FD63381EF8299\": [\n    \"`unknown`.`film`\"\n  ],\n  \"F5D30BCAC1E206A1\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"F6DBEAA606D800FC\": [\n    \"`unknown`.`film`\"\n  ],\n  \"F8314ABD1CBF2FF1\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"FCD1ABF36F8CDAD7\": [\n    \"`unknown`.`city`\",\n    \"`unknown`.`country`\"\n  ],\n  \"FE409EB794EE91CF\": [\n    \"`unknown`.`film`\"\n  ]\n}\n{\n  \"084DA3E3EE38DD85\": [\n    \"`sakila`.`tb`\"\n  ],\n  \"08CFE41C7D20AAC8\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"0BE2D79E2F1E7CB0\": [\n    \"`sakila`.`film`\"\n  ],\n  \"0D0DABACEDFF5765\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"105C870D5DFB6710\": [\n    \"`sakila`.`film`\"\n  ],\n  \"11EC7AAACC97DC0F\": [\n    \"`sakila`.`city`\"\n  ],\n  \"12FF1DAA3D425FA9\": [\n    \"`sakila`.`film`\"\n  ],\n  \"16C2B14E7DAA9906\": [\n    \"`sakila`.`film`\",\n    \"`sakila`.`language`\"\n  ],\n  \"16CB4628D2597D40\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"17D5BCF21DC2364C\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"18A2AD1395A58EAE\": [\n    \"`sakila`.`film`\"\n  ],\n  \"1A53649C43122975\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"1E2CF4145EE706A5\": [\n    \"`sakila`.`film`\"\n  ],\n  \"1E56C6CCEA2131CC\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"1E8B70E30062FD13\": [\n    \"`sakila`.`address`\",\n    \"`sakila`.`customer`\"\n  ],\n  \"1ED2B7ECBA4215E1\": [\n    \"`sakila`.`film`\"\n  ],\n  \"23D176AEA2947002\": [\n    \"`sakila`.`film`\"\n  ],\n  \"255BAC03F56CDBC7\": [\n    \"`sakila`.`address`\"\n  ],\n  \"291F95B7DCB74C21\": [\n    \"`sakila`.`tb`\"\n  ],\n  \"2BA1217F6C8CF0AB\": [\n    \"`sakila`.`address`\"\n  ],\n  \"2EAACFD7030EA528\": [\n    \"`sakila`.`film`\"\n  ],\n  \"2F7439623B712317\": [\n    \"`sakila`.`city`\"\n  ],\n  \"3656B13CC4F888E2\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"3FF20E28EC9CBEF9\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"466F1AC2F5851149\": [\n    \"`sakila`.`city`\"\n  ],\n  \"47044E1FE1A965A5\": [\n    \"`sakila`.`film`\"\n  ],\n  \"485D56FC88BBBDB9\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"4A39009B402BAD9B\": [\n    \"`sakila`.`film`\"\n  ],\n  \"4E73AA068370E6A8\": [\n    \"`sakila`.`film`\"\n  ],\n  \"4ECCA9568BE69E68\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"50F2AB4243CE2071\": [\n    \"`sakila`.`address`\",\n    \"`sakila`.`city`\"\n  ],\n  \"584CCEC8069B6947\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"5C547F08EADBB131\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"5CE2F187DBF2A710\": [\n    \"`sakila`.`film`\"\n  ],\n  \"60F234BA33AAC132\": [\n    \"`sakila`.`film`\"\n  ],\n  \"626571EAE84E2C8A\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"68E48001ECD53152\": [\n    \"`sakila`.`film`\"\n  ],\n  \"6A0F035BD4E01018\": [\n    \"`sakila`.`film`\"\n  ],\n  \"6E9B96CA3F0E6BDA\": [\n    \"`sakila`.`film`\"\n  ],\n  \"707FE669669FA075\": [\n    \"`sakila`.`film`\"\n  ],\n  \"73DDF6E6D9E40384\": [\n    \"`sakila`.`film`\"\n  ],\n  \"7598A4EDE6CFA6BE\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"7F02E23D44A38A6D\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"863A85207E4F410D\": [\n    \"`sakila`.`film`\"\n  ],\n  \"868317D1973FD1B0\": [\n    \"`sakila`.`film`\"\n  ],\n  \"8A106444D14B9880\": [\n    \"`sakila`.`film`\"\n  ],\n  \"965D5AC955824512\": [\n    \"`sakila`.`film`\"\n  ],\n  \"9BB74D074BA0727C\": [\n    \"`sakila`.`inventory`\"\n  ],\n  \"A0C5E62C724A121A\": [\n    \"`sakila`.`film`\"\n  ],\n  \"A314542EEE8571EE\": [\n    \"`sakila`.`customer`\"\n  ],\n  \"A3FAB6027484B88B\": [\n    \"`sakila`.`film`\"\n  ],\n  \"A4911095C201896F\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"A7973BDD268F926E\": [\n    \"`sakila`.`city`\"\n  ],\n  \"AF0C1EB58B23D2FA\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"AFEEBF10A8D74E32\": [\n    \"`sakila`.`film`\"\n  ],\n  \"B0BA5A7079EA16B3\": [\n    \"`sakila`.`film`\"\n  ],\n  \"B13E0ACEAF8F3119\": [\n    \"`sakila`.`film`\"\n  ],\n  \"B3C502B4AA344196\": [\n    \"`sakila`.`film`\"\n  ],\n  \"B48292EDB9D0E010\": [\n    \"`sakila`.`film`\"\n  ],\n  \"B862978586C6338B\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"B9336971FF3D3792\": [\n    \"`sakila`.`film`\"\n  ],\n  \"BA7111449E4F1122\": [\n    \"`sakila`.`film`\"\n  ],\n  \"C11ECE7AE5F80CE5\": [\n    \"`hello`.`t`\"\n  ],\n  \"C15BDF2C73B5B7ED\": [\n    \"`sakila`.`address`\",\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"C315BC4EE0F4E523\": [\n    \"`sakila`.`inventory`\"\n  ],\n  \"C3FAEDA6AD6D762B\": [\n    \"`sakila`.`film`\"\n  ],\n  \"C4A212A42400411D\": [\n    \"`sakila`.`film`\"\n  ],\n  \"C95B5C028C8FFF95\": [\n    \"`sakila`.`city`\"\n  ],\n  \"CB42080E9F35AB07\": [\n    \"`sakila`.`film`\"\n  ],\n  \"DF59FD602E4AA368\": [\n    \"`sakila`.`film`\"\n  ],\n  \"DF916439ABD07664\": [\n    \"`sakila`.`film`\"\n  ],\n  \"E3DDA1A929236E72\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"E48A20D0413512DA\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`customer_list`\"\n  ],\n  \"E75234155B5E2E14\": [\n    \"`sakila`.`film`\"\n  ],\n  \"E84CBAAC2E12BDEA\": [\n    \"`sakila`.`film`\"\n  ],\n  \"E969B9297DA79BA6\": [\n    \"`sakila`.`film`\"\n  ],\n  \"EA50643B01E139A8\": [\n    \"`sakila`.`actor`\"\n  ],\n  \"F16FD63381EF8299\": [\n    \"`sakila`.`film`\"\n  ],\n  \"F5D30BCAC1E206A1\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"F6DBEAA606D800FC\": [\n    \"`sakila`.`film`\"\n  ],\n  \"F8314ABD1CBF2FF1\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"FCD1ABF36F8CDAD7\": [\n    \"`sakila`.`city`\",\n    \"`sakila`.`country`\"\n  ],\n  \"FE409EB794EE91CF\": [\n    \"`sakila`.`film`\"\n  ]\n}\n"
  },
  {
    "path": "test/fixture/test_Check_soar_for_pipe_input.golden",
    "content": "# Query: C3FAEDA6AD6D762B\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 86\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E969B9297DA79BA6\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  IS  NULL\n```\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 8A106444D14B9880\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nHAVING  \n  title  = 'abc'\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 HAVING 子句\n\n* **Item:**  CLA.013\n\n* **Severity:**  L3\n\n* **Content:**  将查询的 HAVING 子句改写为 WHERE 中的查询条件，可以在查询处理期间使用索引。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: A0C5E62C724A121A\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  sakila. film  \nWHERE  \n  LENGTH  >= 60\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 868317D1973FD1B0\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  BETWEEN  60  \n  AND  84\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 707FE669669FA075\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  LIKE  'AIR%'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: DF916439ABD07664\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  IS  NOT  NULL\n```\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: B9336971FF3D3792\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 114  \n  AND  title  = 'ALABAMA DEVIL'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 68E48001ECD53152\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'ALABAMA DEVIL'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 12FF1DAA3D425FA9\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10  \n  AND  title  = 'xyz'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E84CBAAC2E12BDEA\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 6A0F035BD4E01018\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \n  AND  language_id  = 1  \nGROUP BY  \n  release_year\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 23D176AEA2947002\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  >= 123  \nGROUP BY  \n  release_year\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 73DDF6E6D9E40384\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\n\nSELECT  \n  release_year, language_id, SUM( LENGTH) \nFROM  \n  film  \nGROUP BY  \n  release_year, language_id\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: B3C502B4AA344196\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year, (LENGTH+ language_id)\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 47044E1FE1A965A5\n\n★ ★ ★ ☆ ☆ 70分\n\n```sql\n\nSELECT  \n  release_year, SUM( film_id) \nFROM  \n  film  \nGROUP BY  \n  release_year\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n# Query: 2BA1217F6C8CF0AB\n\n★ ★ ☆ ☆ ☆ 45分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  address  \nGROUP BY  \n  address, district\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n# Query: 863A85207E4F410D\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  ABS( language_id) = 3  \nGROUP BY  \n  title\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 避免在 WHERE 条件中使用函数或其他运算符\n\n* **Item:**  FUN.001\n\n* **Severity:**  L2\n\n* **Content:**  虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\n\n# Query: DF59FD602E4AA368\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  language_id  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  language_id\n```\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n# Query: F6DBEAA606D800FC\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year\n```\n\n## OK\n\n# Query: 6E9B96CA3F0E6BDA\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  ASC, language_id  DESC\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## ORDER BY 多个列但排序方向不同时可能无法使用索引\n\n* **Item:**  KEY.008\n\n* **Severity:**  L4\n\n* **Content:**  在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\n\n# Query: 2EAACFD7030EA528\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n## OK\n\n# Query: 5CE2F187DBF2A710\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E75234155B5E2E14\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: AFEEBF10A8D74E32\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  film_id  \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n# Query: 965D5AC955824512\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 1E2CF4145EE706A5\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  < 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: A314542EEE8571EE\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  customer  \nWHERE  \n  address_id  in  (224, 510) \nORDER BY  \n  last_name\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 0BE2D79E2F1E7CB0\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  release_year  = 2016  \n  AND  LENGTH  != 1  \nORDER BY  \n  title\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## '!=' 运算符是非标准的\n\n* **Item:**  STA.001\n\n* **Severity:**  L0\n\n* **Content:**  \"<>\"才是标准SQL中的不等于运算符。\n\n# Query: 4E73AA068370E6A8\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  release_year  = 1995\n```\n\n## OK\n\n# Query: BA7111449E4F1122\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  title, replacement_cost  \nFROM  \n  film  \nWHERE  \n  language_id  = 5  \n  AND  LENGTH  = 70\n```\n\n## OK\n\n# Query: B13E0ACEAF8F3119\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  language_id  > 5  \n  AND  LENGTH  > 70\n```\n\n## OK\n\n# Query: A3FAB6027484B88B\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: CB42080E9F35AB07\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: C4A212A42400411D\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  release_year\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 4ECCA9568BE69E68\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  INNER JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 485D56FC88BBBDB9\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 0D0DABACEDFF5765\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 1E56C6CCEA2131CC\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: F5D30BCAC1E206A1\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 17D5BCF21DC2364C\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: A4911095C201896F\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: 3FF20E28EC9CBEF9\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  JOIN  country\n```\n\n## OK\n\n# Query: 5C547F08EADBB131\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  LEFT JOIN  country\n```\n\n## OK\n\n# Query: AF0C1EB58B23D2FA\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  RIGHT JOIN  country\n```\n\n## OK\n\n# Query: 626571EAE84E2C8A\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  a. country_id, a. last_update  \nFROM  \n  city  a  STRAIGHT_JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n# Query: 50F2AB4243CE2071\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  a. address, a. postal_code  \nFROM  \n  sakila. address  a  \nWHERE  \n  a. city_id  IN  (\nSELECT  \n  c. city_id  \nFROM  \n  sakila. city  c)\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 584CCEC8069B6947\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  city  \nFROM( \nSELECT  \n  city_id  \nFROM  \n  city  \nWHERE  \n  city  = \"A Corua (La Corua)\" \nORDER BY  \n  last_update  DESC  \nLIMIT  \n  50, 10) I  \n  JOIN  city  ON  (I. city_id  = city. city_id) \n  JOIN  country  ON  (country. country_id  = city. country_id) \nORDER BY  \n  city  DESC\n```\n\n## 同一张表被连接两次\n\n* **Item:**  JOI.002\n\n* **Severity:**  L4\n\n* **Content:**  相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 7F02E23D44A38A6D\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\nDELETE  city, country  \nFROM  \n  city  \n  INNER JOIN  country  using  (country_id) \nWHERE  \n  city. city_id  = 1\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: F8314ABD1CBF2FF1\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\nDELETE  city  \nFROM  \n  city  \n  LEFT JOIN  country  ON  city. country_id  = country. country_id  \nWHERE  \n  country. country  IS  NULL\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: 1A53649C43122975\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\nDELETE  a1, a2  \nFROM  \n  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: B862978586C6338B\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nDELETE FROM  \n  a1, a2  USING  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: F16FD63381EF8299\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nDELETE FROM  \n  film  \nWHERE  \n  LENGTH  > 100\n```\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: 08CFE41C7D20AAC8\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nUPDATE  \n  city  \n  INNER JOIN  country  USING( country_id) \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n# Query: C15BDF2C73B5B7ED\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nUPDATE  \n  city  \n  INNER JOIN  country  ON  city. country_id  = country. country_id  \n  INNER JOIN  address  ON  city. city_id  = address. city_id  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n# Query: FCD1ABF36F8CDAD7\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nUPDATE  \n  city, country  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. country_id  = country. country_id  \n  AND  city. city_id= 10\n```\n\n## OK\n\n# Query: FE409EB794EE91CF\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nUPDATE  \n  film  \nSET  \n  LENGTH  = 10  \nWHERE  \n  language_id  = 20\n```\n\n## OK\n\n# Query: 3656B13CC4F888E2\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\nINSERT  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 2F7439623B712317\n\n★ ★ ★ ★ ★ 100分\n\n```sql\nINSERT  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3)\n```\n\n## OK\n\n# Query: 11EC7AAACC97DC0F\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\nINSERT  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL\n```\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: E3DDA1A929236E72\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\nREPLACE  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 466F1AC2F5851149\n\n★ ★ ★ ★ ★ 100分\n\n```sql\nREPLACE  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3)\n```\n\n## OK\n\n# Query: A7973BDD268F926E\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\nREPLACE  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL\n```\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 105C870D5DFB6710\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\n\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 执行计划中嵌套连接深度过深\n\n* **Item:**  SUB.004\n\n* **Severity:**  L3\n\n* **Content:**  MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。\n\n# Query: 16C2B14E7DAA9906\n\n★ ☆ ☆ ☆ ☆ 35分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  language_id  = (\nSELECT  \n  language_id  \nFROM  \n  language  \nLIMIT  \n  1)\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 未使用 ORDER BY 的 LIMIT 查询\n\n* **Item:**  RES.002\n\n* **Severity:**  L4\n\n* **Content:**  没有 ORDER BY 的 LIMIT 会导致非确定性的结果，这取决于查询执行计划。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 16CB4628D2597D40\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: EA50643B01E139A8\n\n★ ★ ☆ ☆ ☆ 45分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  (\nSELECT  \n  * \nFROM  \n  actor  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE'\n) t  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE' \nGROUP BY  \n  first_name\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 7598A4EDE6CFA6BE\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  o. country_id  is  null  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  i. city_id  is  null\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: 1E8B70E30062FD13\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  first_name, last_name, email  \nFROM  \n  customer  STRAIGHT_JOIN  address  ON  customer. address_id= address. address_id\n```\n\n## OK\n\n# Query: E48A20D0413512DA\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  ID, name  \nFROM  \n  (\nSELECT  \n  address  \nFROM  \n  customer_list  \nWHERE  \n  SID= 1  \nORDER BY  \n  phone  \nLIMIT  \n  50, 10) a  \n  JOIN  customer_list  l  ON  (a. address= l. address) \n  JOIN  city  c  ON  (c. city= l. city) \nORDER BY  \n  phone  desc\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 同一张表被连接两次\n\n* **Item:**  JOI.002\n\n* **Severity:**  L4\n\n* **Content:**  相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: B0BA5A7079EA16B3\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  DATE( last_update) = '2006-02-15'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 避免在 WHERE 条件中使用函数或其他运算符\n\n* **Item:**  FUN.001\n\n* **Severity:**  L2\n\n* **Content:**  虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\n\n# Query: 18A2AD1395A58EAE\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  last_update  \nFROM  \n  film  \nGROUP BY  \n  DATE( last_update)\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n# Query: 60F234BA33AAC132\n\n★ ★ ★ ☆ ☆ 70分\n\n```sql\n\nSELECT  \n  last_update  \nFROM  \n  film  \nORDER BY  \n  DATE( last_update)\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## ORDER BY 的条件为表达式\n\n* **Item:**  CLA.009\n\n* **Severity:**  L2\n\n* **Content:**  当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n# Query: 1ED2B7ECBA4215E1\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  description  \nFROM  \n  film  \nWHERE  \n  description  IN( 'NEWS', \n  'asd'\n) \nGROUP BY  \n  description\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n# Query: 255BAC03F56CDBC7\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  address  \nADD  \n  index  idx_city_id( city_id)\n```\n\n## OK\n\n# Query: C315BC4EE0F4E523\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`)\n```\n\n## 提醒：请将索引属性顺序与查询对齐\n\n* **Item:**  KEY.004\n\n* **Severity:**  L0\n\n* **Content:**  如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\n\n# Query: 9BB74D074BA0727C\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`), \n      ADD  \n      index  `idx_store_film` (\n        `store_id`, `film_id`), \n              ADD  \n          index  `idx_store_film` (\n            `store_id`, `film_id`)\n```\n\n## 提醒：请将索引属性顺序与查询对齐\n\n* **Item:**  KEY.004\n\n* **Severity:**  L0\n\n* **Content:**  如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\n\n# Query: C95B5C028C8FFF95\n\n★ ★ ☆ ☆ ☆ 40分\n\n```sql\n\nSELECT  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n), \nCOUNT( DISTINCT  (\n  t. city)) \n  FROM  \n    city  t  \n  WHERE  \n    t. last_update  > '2018-10-22 00:00:00' \n    AND  t. city  LIKE  '%Chrome%' \n    AND  t. city  = 'eip' \n  GROUP BY  \n    DATE_FORMAT( t. last_update, '%Y-%m-%d'\n) \nORDER BY  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n)\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用前项通配符查找\n\n* **Item:**  ARG.001\n\n* **Severity:**  L4\n\n* **Content:**  例如 \"％foo\"，查询参数有一个前项通配符的情况无法使用已有索引。\n\n## ORDER BY 的条件为表达式\n\n* **Item:**  CLA.009\n\n* **Severity:**  L2\n\n* **Content:**  当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## ORDER BY 多个列但排序方向不同时可能无法使用索引\n\n* **Item:**  KEY.008\n\n* **Severity:**  L4\n\n* **Content:**  在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\n\n# Query: C11ECE7AE5F80CE5\n\n★ ★ ☆ ☆ ☆ 45分\n\n```sql\ncreate  table  hello. t  (id  int  unsigned)\n```\n\n## 建议为表添加注释\n\n* **Item:**  CLA.011\n\n* **Severity:**  L1\n\n* **Content:**  为表添加注释能够使得表的意义更明确，从而为日后的维护带来极大的便利。\n\n## 请为列添加默认值\n\n* **Item:**  COL.004\n\n* **Severity:**  L1\n\n* **Content:**  请为列添加默认值，如果是 ALTER 操作，请不要忘记将原字段的默认值写上。字段无默认值，当表较大时无法在线变更表结构。\n\n## 列未添加注释\n\n* **Item:**  COL.005\n\n* **Severity:**  L1\n\n* **Content:**  建议对表中每个列添加注释，来明确每个列在表中的含义及作用。\n\n## 未指定主键或主键非 int 或 bigint\n\n* **Item:**  KEY.007\n\n* **Severity:**  L4\n\n* **Content:**  未指定主键或主键非 int 或 bigint，建议将主键设置为 int unsigned 或 bigint unsigned。\n\n## 请为表选择合适的存储引擎\n\n* **Item:**  TBL.002\n\n* **Severity:**  L4\n\n* **Content:**  建表或修改表的存储引擎时建议使用推荐的存储引擎，如：innodb\n\n# Query: 291F95B7DCB74C21\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  tb  \nWHERE  \n  data  >= ''\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 084DA3E3EE38DD85\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  tb  alter  column  id  \nDROP  \n  DEFAULT\n```\n\n## OK\n\n# Query: B48292EDB9D0E010\n\n★ ★ ☆ ☆ ☆ 50分\n\n```sql\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film  \nWHERE  \n  last_update  > '2016-03-27 02:01:01'\n) as  d\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n## 不建议在子查询中使用函数\n\n* **Item:**  SUB.006\n\n* **Severity:**  L2\n\n* **Content:**  MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\n\n# Query: 4A39009B402BAD9B\n\n★ ★ ☆ ☆ ☆ 50分\n\n```sql\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film) as  d\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n## 不建议在子查询中使用函数\n\n* **Item:**  SUB.006\n\n* **Severity:**  L2\n\n* **Content:**  MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\n\n"
  },
  {
    "path": "test/fixture/test_Check_soar_query_for_input_file.golden",
    "content": "# Query: C3FAEDA6AD6D762B\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 86\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E969B9297DA79BA6\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  IS  NULL\n```\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 8A106444D14B9880\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nHAVING  \n  title  = 'abc'\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 HAVING 子句\n\n* **Item:**  CLA.013\n\n* **Severity:**  L3\n\n* **Content:**  将查询的 HAVING 子句改写为 WHERE 中的查询条件，可以在查询处理期间使用索引。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: A0C5E62C724A121A\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  sakila. film  \nWHERE  \n  LENGTH  >= 60\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 868317D1973FD1B0\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  BETWEEN  60  \n  AND  84\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 707FE669669FA075\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  LIKE  'AIR%'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: DF916439ABD07664\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  IS  NOT  NULL\n```\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: B9336971FF3D3792\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 114  \n  AND  title  = 'ALABAMA DEVIL'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 68E48001ECD53152\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'ALABAMA DEVIL'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 12FF1DAA3D425FA9\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10  \n  AND  title  = 'xyz'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E84CBAAC2E12BDEA\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 6A0F035BD4E01018\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \n  AND  language_id  = 1  \nGROUP BY  \n  release_year\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 23D176AEA2947002\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  >= 123  \nGROUP BY  \n  release_year\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 73DDF6E6D9E40384\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\n\nSELECT  \n  release_year, language_id, SUM( LENGTH) \nFROM  \n  film  \nGROUP BY  \n  release_year, language_id\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: B3C502B4AA344196\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year, (LENGTH+ language_id)\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 47044E1FE1A965A5\n\n★ ★ ★ ☆ ☆ 70分\n\n```sql\n\nSELECT  \n  release_year, SUM( film_id) \nFROM  \n  film  \nGROUP BY  \n  release_year\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n# Query: 2BA1217F6C8CF0AB\n\n★ ★ ☆ ☆ ☆ 45分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  address  \nGROUP BY  \n  address, district\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n# Query: 863A85207E4F410D\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  ABS( language_id) = 3  \nGROUP BY  \n  title\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 避免在 WHERE 条件中使用函数或其他运算符\n\n* **Item:**  FUN.001\n\n* **Severity:**  L2\n\n* **Content:**  虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\n\n# Query: DF59FD602E4AA368\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  language_id  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  language_id\n```\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n# Query: F6DBEAA606D800FC\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year\n```\n\n## OK\n\n# Query: 6E9B96CA3F0E6BDA\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  ASC, language_id  DESC\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## ORDER BY 多个列但排序方向不同时可能无法使用索引\n\n* **Item:**  KEY.008\n\n* **Severity:**  L4\n\n* **Content:**  在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\n\n# Query: 2EAACFD7030EA528\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n## OK\n\n# Query: 5CE2F187DBF2A710\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E75234155B5E2E14\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: AFEEBF10A8D74E32\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  film_id  \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n# Query: 965D5AC955824512\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 1E2CF4145EE706A5\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  < 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: A314542EEE8571EE\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  customer  \nWHERE  \n  address_id  in  (224, 510) \nORDER BY  \n  last_name\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 0BE2D79E2F1E7CB0\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  release_year  = 2016  \n  AND  LENGTH  != 1  \nORDER BY  \n  title\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## '!=' 运算符是非标准的\n\n* **Item:**  STA.001\n\n* **Severity:**  L0\n\n* **Content:**  \"<>\"才是标准SQL中的不等于运算符。\n\n# Query: 4E73AA068370E6A8\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  release_year  = 1995\n```\n\n## OK\n\n# Query: BA7111449E4F1122\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  title, replacement_cost  \nFROM  \n  film  \nWHERE  \n  language_id  = 5  \n  AND  LENGTH  = 70\n```\n\n## OK\n\n# Query: B13E0ACEAF8F3119\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  language_id  > 5  \n  AND  LENGTH  > 70\n```\n\n## OK\n\n# Query: A3FAB6027484B88B\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: CB42080E9F35AB07\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: C4A212A42400411D\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  release_year\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 4ECCA9568BE69E68\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  INNER JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 485D56FC88BBBDB9\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 0D0DABACEDFF5765\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 1E56C6CCEA2131CC\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: F5D30BCAC1E206A1\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 17D5BCF21DC2364C\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: A4911095C201896F\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: 3FF20E28EC9CBEF9\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  JOIN  country\n```\n\n## OK\n\n# Query: 5C547F08EADBB131\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  LEFT JOIN  country\n```\n\n## OK\n\n# Query: AF0C1EB58B23D2FA\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  RIGHT JOIN  country\n```\n\n## OK\n\n# Query: 626571EAE84E2C8A\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  a. country_id, a. last_update  \nFROM  \n  city  a  STRAIGHT_JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n# Query: 50F2AB4243CE2071\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  a. address, a. postal_code  \nFROM  \n  sakila. address  a  \nWHERE  \n  a. city_id  IN  (\nSELECT  \n  c. city_id  \nFROM  \n  sakila. city  c)\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 584CCEC8069B6947\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  city  \nFROM( \nSELECT  \n  city_id  \nFROM  \n  city  \nWHERE  \n  city  = \"A Corua (La Corua)\" \nORDER BY  \n  last_update  DESC  \nLIMIT  \n  50, 10) I  \n  JOIN  city  ON  (I. city_id  = city. city_id) \n  JOIN  country  ON  (country. country_id  = city. country_id) \nORDER BY  \n  city  DESC\n```\n\n## 同一张表被连接两次\n\n* **Item:**  JOI.002\n\n* **Severity:**  L4\n\n* **Content:**  相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 7F02E23D44A38A6D\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\nDELETE  city, country  \nFROM  \n  city  \n  INNER JOIN  country  using  (country_id) \nWHERE  \n  city. city_id  = 1\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: F8314ABD1CBF2FF1\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\nDELETE  city  \nFROM  \n  city  \n  LEFT JOIN  country  ON  city. country_id  = country. country_id  \nWHERE  \n  country. country  IS  NULL\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: 1A53649C43122975\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\nDELETE  a1, a2  \nFROM  \n  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: B862978586C6338B\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nDELETE FROM  \n  a1, a2  USING  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: F16FD63381EF8299\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nDELETE FROM  \n  film  \nWHERE  \n  LENGTH  > 100\n```\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: 08CFE41C7D20AAC8\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nUPDATE  \n  city  \n  INNER JOIN  country  USING( country_id) \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n# Query: C15BDF2C73B5B7ED\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nUPDATE  \n  city  \n  INNER JOIN  country  ON  city. country_id  = country. country_id  \n  INNER JOIN  address  ON  city. city_id  = address. city_id  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10\n```\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n# Query: FCD1ABF36F8CDAD7\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nUPDATE  \n  city, country  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. country_id  = country. country_id  \n  AND  city. city_id= 10\n```\n\n## OK\n\n# Query: FE409EB794EE91CF\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nUPDATE  \n  film  \nSET  \n  LENGTH  = 10  \nWHERE  \n  language_id  = 20\n```\n\n## OK\n\n# Query: 3656B13CC4F888E2\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\nINSERT  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 2F7439623B712317\n\n★ ★ ★ ★ ★ 100分\n\n```sql\nINSERT  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3)\n```\n\n## OK\n\n# Query: 11EC7AAACC97DC0F\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\nINSERT  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL\n```\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: E3DDA1A929236E72\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\nREPLACE  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 466F1AC2F5851149\n\n★ ★ ★ ★ ★ 100分\n\n```sql\nREPLACE  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3)\n```\n\n## OK\n\n# Query: A7973BDD268F926E\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\nREPLACE  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL\n```\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 105C870D5DFB6710\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\n\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 执行计划中嵌套连接深度过深\n\n* **Item:**  SUB.004\n\n* **Severity:**  L3\n\n* **Content:**  MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。\n\n# Query: 16C2B14E7DAA9906\n\n★ ☆ ☆ ☆ ☆ 35分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  language_id  = (\nSELECT  \n  language_id  \nFROM  \n  language  \nLIMIT  \n  1)\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 未使用 ORDER BY 的 LIMIT 查询\n\n* **Item:**  RES.002\n\n* **Severity:**  L4\n\n* **Content:**  没有 ORDER BY 的 LIMIT 会导致非确定性的结果，这取决于查询执行计划。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 16CB4628D2597D40\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: EA50643B01E139A8\n\n★ ★ ☆ ☆ ☆ 45分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  (\nSELECT  \n  * \nFROM  \n  actor  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE'\n) t  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE' \nGROUP BY  \n  first_name\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 7598A4EDE6CFA6BE\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  o. country_id  is  null  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  i. city_id  is  null\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: 1E8B70E30062FD13\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  first_name, last_name, email  \nFROM  \n  customer  STRAIGHT_JOIN  address  ON  customer. address_id= address. address_id\n```\n\n## OK\n\n# Query: E48A20D0413512DA\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  ID, name  \nFROM  \n  (\nSELECT  \n  address  \nFROM  \n  customer_list  \nWHERE  \n  SID= 1  \nORDER BY  \n  phone  \nLIMIT  \n  50, 10) a  \n  JOIN  customer_list  l  ON  (a. address= l. address) \n  JOIN  city  c  ON  (c. city= l. city) \nORDER BY  \n  phone  desc\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 同一张表被连接两次\n\n* **Item:**  JOI.002\n\n* **Severity:**  L4\n\n* **Content:**  相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: B0BA5A7079EA16B3\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  DATE( last_update) = '2006-02-15'\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 避免在 WHERE 条件中使用函数或其他运算符\n\n* **Item:**  FUN.001\n\n* **Severity:**  L2\n\n* **Content:**  虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\n\n# Query: 18A2AD1395A58EAE\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  last_update  \nFROM  \n  film  \nGROUP BY  \n  DATE( last_update)\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n# Query: 60F234BA33AAC132\n\n★ ★ ★ ☆ ☆ 70分\n\n```sql\n\nSELECT  \n  last_update  \nFROM  \n  film  \nORDER BY  \n  DATE( last_update)\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## ORDER BY 的条件为表达式\n\n* **Item:**  CLA.009\n\n* **Severity:**  L2\n\n* **Content:**  当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n# Query: 1ED2B7ECBA4215E1\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  description  \nFROM  \n  film  \nWHERE  \n  description  IN( 'NEWS', \n  'asd'\n) \nGROUP BY  \n  description\n```\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n# Query: 255BAC03F56CDBC7\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  address  \nADD  \n  index  idx_city_id( city_id)\n```\n\n## OK\n\n# Query: C315BC4EE0F4E523\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`)\n```\n\n## 提醒：请将索引属性顺序与查询对齐\n\n* **Item:**  KEY.004\n\n* **Severity:**  L0\n\n* **Content:**  如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\n\n# Query: 9BB74D074BA0727C\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`), \n      ADD  \n      index  `idx_store_film` (\n        `store_id`, `film_id`), \n              ADD  \n          index  `idx_store_film` (\n            `store_id`, `film_id`)\n```\n\n## 提醒：请将索引属性顺序与查询对齐\n\n* **Item:**  KEY.004\n\n* **Severity:**  L0\n\n* **Content:**  如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\n\n# Query: C95B5C028C8FFF95\n\n★ ★ ☆ ☆ ☆ 40分\n\n```sql\n\nSELECT  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n), \nCOUNT( DISTINCT  (\n  t. city)) \n  FROM  \n    city  t  \n  WHERE  \n    t. last_update  > '2018-10-22 00:00:00' \n    AND  t. city  LIKE  '%Chrome%' \n    AND  t. city  = 'eip' \n  GROUP BY  \n    DATE_FORMAT( t. last_update, '%Y-%m-%d'\n) \nORDER BY  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n)\n```\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用前项通配符查找\n\n* **Item:**  ARG.001\n\n* **Severity:**  L4\n\n* **Content:**  例如 \"％foo\"，查询参数有一个前项通配符的情况无法使用已有索引。\n\n## ORDER BY 的条件为表达式\n\n* **Item:**  CLA.009\n\n* **Severity:**  L2\n\n* **Content:**  当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## ORDER BY 多个列但排序方向不同时可能无法使用索引\n\n* **Item:**  KEY.008\n\n* **Severity:**  L4\n\n* **Content:**  在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\n\n# Query: C11ECE7AE5F80CE5\n\n★ ★ ☆ ☆ ☆ 45分\n\n```sql\ncreate  table  hello. t  (id  int  unsigned)\n```\n\n## 建议为表添加注释\n\n* **Item:**  CLA.011\n\n* **Severity:**  L1\n\n* **Content:**  为表添加注释能够使得表的意义更明确，从而为日后的维护带来极大的便利。\n\n## 请为列添加默认值\n\n* **Item:**  COL.004\n\n* **Severity:**  L1\n\n* **Content:**  请为列添加默认值，如果是 ALTER 操作，请不要忘记将原字段的默认值写上。字段无默认值，当表较大时无法在线变更表结构。\n\n## 列未添加注释\n\n* **Item:**  COL.005\n\n* **Severity:**  L1\n\n* **Content:**  建议对表中每个列添加注释，来明确每个列在表中的含义及作用。\n\n## 未指定主键或主键非 int 或 bigint\n\n* **Item:**  KEY.007\n\n* **Severity:**  L4\n\n* **Content:**  未指定主键或主键非 int 或 bigint，建议将主键设置为 int unsigned 或 bigint unsigned。\n\n## 请为表选择合适的存储引擎\n\n* **Item:**  TBL.002\n\n* **Severity:**  L4\n\n* **Content:**  建表或修改表的存储引擎时建议使用推荐的存储引擎，如：innodb\n\n# Query: 291F95B7DCB74C21\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  tb  \nWHERE  \n  data  >= ''\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 084DA3E3EE38DD85\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  tb  alter  column  id  \nDROP  \n  DEFAULT\n```\n\n## OK\n\n# Query: B48292EDB9D0E010\n\n★ ★ ☆ ☆ ☆ 50分\n\n```sql\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film  \nWHERE  \n  last_update  > '2016-03-27 02:01:01'\n) as  d\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n## 不建议在子查询中使用函数\n\n* **Item:**  SUB.006\n\n* **Severity:**  L2\n\n* **Content:**  MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\n\n# Query: 4A39009B402BAD9B\n\n★ ★ ☆ ☆ ☆ 50分\n\n```sql\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film) as  d\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n## 不建议在子查询中使用函数\n\n* **Item:**  SUB.006\n\n* **Severity:**  L2\n\n* **Content:**  MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\n\n"
  },
  {
    "path": "test/fixture/test_Check_soar_report_for_html.golden",
    "content": "<head>\n<meta http-equiv=Content-Type content=\"text/html;charset=utf-8\">\n<title>soar report check</title>\n<script>!function(e,E){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=E():\"function\"==typeof define&&define.amd?define([],E):\"object\"==typeof exports?exports.sqlFormatter=E():e.sqlFormatter=E()}(this,function(){return function(e){function E(n){if(t[n])return t[n].exports;var r=t[n]={exports:{},id:n,loaded:!1};return e[n].call(r.exports,r,r.exports,E),r.loaded=!0,r.exports}var t={};return E.m=e,E.c=t,E.p=\"\",E(0)}([function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(18),T=n(r),R=t(19),o=n(R),N=t(20),A=n(N),I=t(21),O=n(I);E[\"default\"]={format:function(e,E){switch(E=E||{},E.language){case\"db2\":return new T[\"default\"](E).format(e);case\"n1ql\":return new o[\"default\"](E).format(e);case\"pl/sql\":return new A[\"default\"](E).format(e);case\"sql\":case void 0:return new O[\"default\"](E).format(e);default:throw Error(\"Unsupported SQL dialect: \"+E.language)}}},e.exports=E[\"default\"]},function(e,E){\"use strict\";E.__esModule=!0,E[\"default\"]=function(e,E){if(!(e instanceof E))throw new TypeError(\"Cannot call a class as a function\")}},function(e,E,t){var n=t(39),r=\"object\"==typeof self&&self&&self.Object===Object&&self,T=n||r||Function(\"return this\")();e.exports=T},function(e,E,t){function n(e,E){var t=T(e,E);return r(t)?t:void 0}var r=t(33),T=t(41);e.exports=n},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(66),o=n(R),N=t(7),A=n(N),I=t(15),O=n(I),i=t(16),S=n(i),u=t(17),L=n(u),C=function(){function e(E,t){(0,T[\"default\"])(this,e),this.cfg=E||{},this.indentation=new O[\"default\"](this.cfg.indent),this.inlineBlock=new S[\"default\"],this.params=new L[\"default\"](this.cfg.params),this.tokenizer=t,this.previousReservedWord={}}return e.prototype.format=function(e){var E=this.tokenizer.tokenize(e),t=this.getFormattedQueryFromTokens(E);return t.trim()},e.prototype.getFormattedQueryFromTokens=function(e){var E=this,t=\"\";return e.forEach(function(n,r){n.type!==A[\"default\"].WHITESPACE&&(n.type===A[\"default\"].LINE_COMMENT?t=E.formatLineComment(n,t):n.type===A[\"default\"].BLOCK_COMMENT?t=E.formatBlockComment(n,t):n.type===A[\"default\"].RESERVED_TOPLEVEL?(t=E.formatToplevelReservedWord(n,t),E.previousReservedWord=n):n.type===A[\"default\"].RESERVED_NEWLINE?(t=E.formatNewlineReservedWord(n,t),E.previousReservedWord=n):n.type===A[\"default\"].RESERVED?(t=E.formatWithSpaces(n,t),E.previousReservedWord=n):t=n.type===A[\"default\"].OPEN_PAREN?E.formatOpeningParentheses(e,r,t):n.type===A[\"default\"].CLOSE_PAREN?E.formatClosingParentheses(n,t):n.type===A[\"default\"].PLACEHOLDER?E.formatPlaceholder(n,t):\",\"===n.value?E.formatComma(n,t):\":\"===n.value?E.formatWithSpaceAfter(n,t):\".\"===n.value||\";\"===n.value?E.formatWithoutSpaces(n,t):E.formatWithSpaces(n,t))}),t},e.prototype.formatLineComment=function(e,E){return this.addNewline(E+e.value)},e.prototype.formatBlockComment=function(e,E){return this.addNewline(this.addNewline(E)+this.indentComment(e.value))},e.prototype.indentComment=function(e){return e.replace(/\\n/g,\"\\n\"+this.indentation.getIndent())},e.prototype.formatToplevelReservedWord=function(e,E){return this.indentation.decreaseTopLevel(),E=this.addNewline(E),this.indentation.increaseToplevel(),E+=this.equalizeWhitespace(e.value),this.addNewline(E)},e.prototype.formatNewlineReservedWord=function(e,E){return this.addNewline(E)+this.equalizeWhitespace(e.value)+\" \"},e.prototype.equalizeWhitespace=function(e){return e.replace(/\\s+/g,\" \")},e.prototype.formatOpeningParentheses=function(e,E,t){var n=e[E-1];return n&&n.type!==A[\"default\"].WHITESPACE&&n.type!==A[\"default\"].OPEN_PAREN&&(t=(0,o[\"default\"])(t)),t+=e[E].value,this.inlineBlock.beginIfPossible(e,E),this.inlineBlock.isActive()||(this.indentation.increaseBlockLevel(),t=this.addNewline(t)),t},e.prototype.formatClosingParentheses=function(e,E){return this.inlineBlock.isActive()?(this.inlineBlock.end(),this.formatWithSpaceAfter(e,E)):(this.indentation.decreaseBlockLevel(),this.formatWithSpaces(e,this.addNewline(E)))},e.prototype.formatPlaceholder=function(e,E){return E+this.params.get(e)+\" \"},e.prototype.formatComma=function(e,E){return E=(0,o[\"default\"])(E)+e.value+\" \",this.inlineBlock.isActive()?E:/^LIMIT$/i.test(this.previousReservedWord.value)?E:this.addNewline(E)},e.prototype.formatWithSpaceAfter=function(e,E){return(0,o[\"default\"])(E)+e.value+\" \"},e.prototype.formatWithoutSpaces=function(e,E){return(0,o[\"default\"])(E)+e.value},e.prototype.formatWithSpaces=function(e,E){return E+e.value+\" \"},e.prototype.addNewline=function(e){return(0,o[\"default\"])(e)+\"\\n\"+this.indentation.getIndent()},e}();E[\"default\"]=C,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(58),o=n(R),N=t(53),A=n(N),I=t(7),O=n(I),i=function(){function e(E){(0,T[\"default\"])(this,e),this.WHITESPACE_REGEX=/^(\\s+)/,this.NUMBER_REGEX=/^((-\\s*)?[0-9]+(\\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)\\b/,this.OPERATOR_REGEX=/^(!=|<>|==|<=|>=|!<|!>|\\|\\||::|->>|->|~~\\*|~~|!~~\\*|!~~|~\\*|!~\\*|!~|.)/,this.BLOCK_COMMENT_REGEX=/^(\\/\\*[^]*?(?:\\*\\/|$))/,this.LINE_COMMENT_REGEX=this.createLineCommentRegex(E.lineCommentTypes),this.RESERVED_TOPLEVEL_REGEX=this.createReservedWordRegex(E.reservedToplevelWords),this.RESERVED_NEWLINE_REGEX=this.createReservedWordRegex(E.reservedNewlineWords),this.RESERVED_PLAIN_REGEX=this.createReservedWordRegex(E.reservedWords),this.WORD_REGEX=this.createWordRegex(E.specialWordChars),this.STRING_REGEX=this.createStringRegex(E.stringTypes),this.OPEN_PAREN_REGEX=this.createParenRegex(E.openParens),this.CLOSE_PAREN_REGEX=this.createParenRegex(E.closeParens),this.INDEXED_PLACEHOLDER_REGEX=this.createPlaceholderRegex(E.indexedPlaceholderTypes,\"[0-9]*\"),this.IDENT_NAMED_PLACEHOLDER_REGEX=this.createPlaceholderRegex(E.namedPlaceholderTypes,\"[a-zA-Z0-9._$]+\"),this.STRING_NAMED_PLACEHOLDER_REGEX=this.createPlaceholderRegex(E.namedPlaceholderTypes,this.createStringPattern(E.stringTypes))}return e.prototype.createLineCommentRegex=function(e){return RegExp(\"^((?:\"+e.map(function(e){return(0,A[\"default\"])(e)}).join(\"|\")+\").*?(?:\\n|$))\")},e.prototype.createReservedWordRegex=function(e){var E=e.join(\"|\").replace(/ /g,\"\\\\s+\");return RegExp(\"^(\"+E+\")\\\\b\",\"i\")},e.prototype.createWordRegex=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return RegExp(\"^([\\\\w\"+e.join(\"\")+\"]+)\")},e.prototype.createStringRegex=function(e){return RegExp(\"^(\"+this.createStringPattern(e)+\")\")},e.prototype.createStringPattern=function(e){var E={\"``\":\"((`[^`]*($|`))+)\",\"[]\":\"((\\\\[[^\\\\]]*($|\\\\]))(\\\\][^\\\\]]*($|\\\\]))*)\",'\"\"':'((\"[^\"\\\\\\\\]*(?:\\\\\\\\.[^\"\\\\\\\\]*)*(\"|$))+)',\"''\":\"(('[^'\\\\\\\\]*(?:\\\\\\\\.[^'\\\\\\\\]*)*('|$))+)\",\"N''\":\"((N'[^N'\\\\\\\\]*(?:\\\\\\\\.[^N'\\\\\\\\]*)*('|$))+)\"};return e.map(function(e){return E[e]}).join(\"|\")},e.prototype.createParenRegex=function(e){var E=this;return RegExp(\"^(\"+e.map(function(e){return E.escapeParen(e)}).join(\"|\")+\")\",\"i\")},e.prototype.escapeParen=function(e){return 1===e.length?(0,A[\"default\"])(e):\"\\\\b\"+e+\"\\\\b\"},e.prototype.createPlaceholderRegex=function(e,E){if((0,o[\"default\"])(e))return!1;var t=e.map(A[\"default\"]).join(\"|\");return RegExp(\"^((?:\"+t+\")(?:\"+E+\"))\")},e.prototype.tokenize=function(e){for(var E=[],t=void 0;e.length;)t=this.getNextToken(e,t),e=e.substring(t.value.length),E.push(t);return E},e.prototype.getNextToken=function(e,E){return this.getWhitespaceToken(e)||this.getCommentToken(e)||this.getStringToken(e)||this.getOpenParenToken(e)||this.getCloseParenToken(e)||this.getPlaceholderToken(e)||this.getNumberToken(e)||this.getReservedWordToken(e,E)||this.getWordToken(e)||this.getOperatorToken(e)},e.prototype.getWhitespaceToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].WHITESPACE,regex:this.WHITESPACE_REGEX})},e.prototype.getCommentToken=function(e){return this.getLineCommentToken(e)||this.getBlockCommentToken(e)},e.prototype.getLineCommentToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].LINE_COMMENT,regex:this.LINE_COMMENT_REGEX})},e.prototype.getBlockCommentToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].BLOCK_COMMENT,regex:this.BLOCK_COMMENT_REGEX})},e.prototype.getStringToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].STRING,regex:this.STRING_REGEX})},e.prototype.getOpenParenToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].OPEN_PAREN,regex:this.OPEN_PAREN_REGEX})},e.prototype.getCloseParenToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].CLOSE_PAREN,regex:this.CLOSE_PAREN_REGEX})},e.prototype.getPlaceholderToken=function(e){return this.getIdentNamedPlaceholderToken(e)||this.getStringNamedPlaceholderToken(e)||this.getIndexedPlaceholderToken(e)},e.prototype.getIdentNamedPlaceholderToken=function(e){return this.getPlaceholderTokenWithKey({input:e,regex:this.IDENT_NAMED_PLACEHOLDER_REGEX,parseKey:function(e){return e.slice(1)}})},e.prototype.getStringNamedPlaceholderToken=function(e){var E=this;return this.getPlaceholderTokenWithKey({input:e,regex:this.STRING_NAMED_PLACEHOLDER_REGEX,parseKey:function(e){return E.getEscapedPlaceholderKey({key:e.slice(2,-1),quoteChar:e.slice(-1)})}})},e.prototype.getIndexedPlaceholderToken=function(e){return this.getPlaceholderTokenWithKey({input:e,regex:this.INDEXED_PLACEHOLDER_REGEX,parseKey:function(e){return e.slice(1)}})},e.prototype.getPlaceholderTokenWithKey=function(e){var E=e.input,t=e.regex,n=e.parseKey,r=this.getTokenOnFirstMatch({input:E,regex:t,type:O[\"default\"].PLACEHOLDER});return r&&(r.key=n(r.value)),r},e.prototype.getEscapedPlaceholderKey=function(e){var E=e.key,t=e.quoteChar;return E.replace(RegExp((0,A[\"default\"])(\"\\\\\")+t,\"g\"),t)},e.prototype.getNumberToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].NUMBER,regex:this.NUMBER_REGEX})},e.prototype.getOperatorToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].OPERATOR,regex:this.OPERATOR_REGEX})},e.prototype.getReservedWordToken=function(e,E){if(!E||!E.value||\".\"!==E.value)return this.getToplevelReservedToken(e)||this.getNewlineReservedToken(e)||this.getPlainReservedToken(e)},e.prototype.getToplevelReservedToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].RESERVED_TOPLEVEL,regex:this.RESERVED_TOPLEVEL_REGEX})},e.prototype.getNewlineReservedToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].RESERVED_NEWLINE,regex:this.RESERVED_NEWLINE_REGEX})},e.prototype.getPlainReservedToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].RESERVED,regex:this.RESERVED_PLAIN_REGEX})},e.prototype.getWordToken=function(e){return this.getTokenOnFirstMatch({input:e,type:O[\"default\"].WORD,regex:this.WORD_REGEX})},e.prototype.getTokenOnFirstMatch=function(e){var E=e.input,t=e.type,n=e.regex,r=E.match(n);if(r)return{type:t,value:r[1]}},e}();E[\"default\"]=i,e.exports=E[\"default\"]},function(e,E){function t(e){var E=typeof e;return null!=e&&(\"object\"==E||\"function\"==E)}e.exports=t},function(e,E){\"use strict\";E.__esModule=!0,E[\"default\"]={WHITESPACE:\"whitespace\",WORD:\"word\",STRING:\"string\",RESERVED:\"reserved\",RESERVED_TOPLEVEL:\"reserved-toplevel\",RESERVED_NEWLINE:\"reserved-newline\",OPERATOR:\"operator\",OPEN_PAREN:\"open-paren\",CLOSE_PAREN:\"close-paren\",LINE_COMMENT:\"line-comment\",BLOCK_COMMENT:\"block-comment\",NUMBER:\"number\",PLACEHOLDER:\"placeholder\"},e.exports=E[\"default\"]},function(e,E,t){function n(e){return null!=e&&T(e.length)&&!r(e)}var r=t(12),T=t(59);e.exports=n},function(e,E,t){function n(e){return null==e?\"\":r(e)}var r=t(10);e.exports=n},function(e,E,t){function n(e){if(\"string\"==typeof e)return e;if(T(e))return N?N.call(e):\"\";var E=e+\"\";return\"0\"==E&&1/e==-R?\"-0\":E}var r=t(26),T=t(14),R=1/0,o=r?r.prototype:void 0,N=o?o.toString:void 0;e.exports=n},function(e,E){function t(e){if(null!=e){try{return r.call(e)}catch(E){}try{return e+\"\"}catch(E){}}return\"\"}var n=Function.prototype,r=n.toString;e.exports=t},function(e,E,t){function n(e){var E=r(e)?N.call(e):\"\";return E==T||E==R}var r=t(6),T=\"[object Function]\",R=\"[object GeneratorFunction]\",o=Object.prototype,N=o.toString;e.exports=n},function(e,E){function t(e){return null!=e&&\"object\"==typeof e}e.exports=t},function(e,E,t){function n(e){return\"symbol\"==typeof e||r(e)&&o.call(e)==T}var r=t(13),T=\"[object Symbol]\",R=Object.prototype,o=R.toString;e.exports=n},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(61),o=n(R),N=t(60),A=n(N),I=\"top-level\",O=\"block-level\",i=function(){function e(E){(0,T[\"default\"])(this,e),this.indent=E||\"  \",this.indentTypes=[]}return e.prototype.getIndent=function(){return(0,o[\"default\"])(this.indent,this.indentTypes.length)},e.prototype.increaseToplevel=function(){this.indentTypes.push(I)},e.prototype.increaseBlockLevel=function(){this.indentTypes.push(O)},e.prototype.decreaseTopLevel=function(){(0,A[\"default\"])(this.indentTypes)===I&&this.indentTypes.pop()},e.prototype.decreaseBlockLevel=function(){for(;this.indentTypes.length>0;){var e=this.indentTypes.pop();if(e!==I)break}},e}();E[\"default\"]=i,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(7),o=n(R),N=50,A=function(){function e(){(0,T[\"default\"])(this,e),this.level=0}return e.prototype.beginIfPossible=function(e,E){0===this.level&&this.isInlineBlock(e,E)?this.level=1:this.level>0?this.level++:this.level=0},e.prototype.end=function(){this.level--},e.prototype.isActive=function(){return this.level>0},e.prototype.isInlineBlock=function(e,E){for(var t=0,n=0,r=E;e.length>r;r++){var T=e[r];if(t+=T.value.length,t>N)return!1;if(T.type===o[\"default\"].OPEN_PAREN)n++;else if(T.type===o[\"default\"].CLOSE_PAREN&&(n--,0===n))return!0;if(this.isForbiddenToken(T))return!1}return!1},e.prototype.isForbiddenToken=function(e){var E=e.type,t=e.value;return E===o[\"default\"].RESERVED_TOPLEVEL||E===o[\"default\"].RESERVED_NEWLINE||E===o[\"default\"].COMMENT||E===o[\"default\"].BLOCK_COMMENT||\";\"===t},e}();E[\"default\"]=A,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=function(){function e(E){(0,T[\"default\"])(this,e),this.params=E,this.index=0}return e.prototype.get=function(e){var E=e.key,t=e.value;return this.params?E?this.params[E]:this.params[this.index++]:t},e}();E[\"default\"]=R,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(4),o=n(R),N=t(5),A=n(N),I=[\"ABS\",\"ACTIVATE\",\"ALIAS\",\"ALL\",\"ALLOCATE\",\"ALLOW\",\"ALTER\",\"ANY\",\"ARE\",\"ARRAY\",\"AS\",\"ASC\",\"ASENSITIVE\",\"ASSOCIATE\",\"ASUTIME\",\"ASYMMETRIC\",\"AT\",\"ATOMIC\",\"ATTRIBUTES\",\"AUDIT\",\"AUTHORIZATION\",\"AUX\",\"AUXILIARY\",\"AVG\",\"BEFORE\",\"BEGIN\",\"BETWEEN\",\"BIGINT\",\"BINARY\",\"BLOB\",\"BOOLEAN\",\"BOTH\",\"BUFFERPOOL\",\"BY\",\"CACHE\",\"CALL\",\"CALLED\",\"CAPTURE\",\"CARDINALITY\",\"CASCADED\",\"CASE\",\"CAST\",\"CCSID\",\"CEIL\",\"CEILING\",\"CHAR\",\"CHARACTER\",\"CHARACTER_LENGTH\",\"CHAR_LENGTH\",\"CHECK\",\"CLOB\",\"CLONE\",\"CLOSE\",\"CLUSTER\",\"COALESCE\",\"COLLATE\",\"COLLECT\",\"COLLECTION\",\"COLLID\",\"COLUMN\",\"COMMENT\",\"COMMIT\",\"CONCAT\",\"CONDITION\",\"CONNECT\",\"CONNECTION\",\"CONSTRAINT\",\"CONTAINS\",\"CONTINUE\",\"CONVERT\",\"CORR\",\"CORRESPONDING\",\"COUNT\",\"COUNT_BIG\",\"COVAR_POP\",\"COVAR_SAMP\",\"CREATE\",\"CROSS\",\"CUBE\",\"CUME_DIST\",\"CURRENT\",\"CURRENT_DATE\",\"CURRENT_DEFAULT_TRANSFORM_GROUP\",\"CURRENT_LC_CTYPE\",\"CURRENT_PATH\",\"CURRENT_ROLE\",\"CURRENT_SCHEMA\",\"CURRENT_SERVER\",\"CURRENT_TIME\",\"CURRENT_TIMESTAMP\",\"CURRENT_TIMEZONE\",\"CURRENT_TRANSFORM_GROUP_FOR_TYPE\",\"CURRENT_USER\",\"CURSOR\",\"CYCLE\",\"DATA\",\"DATABASE\",\"DATAPARTITIONNAME\",\"DATAPARTITIONNUM\",\"DATE\",\"DAY\",\"DAYS\",\"DB2GENERAL\",\"DB2GENRL\",\"DB2SQL\",\"DBINFO\",\"DBPARTITIONNAME\",\"DBPARTITIONNUM\",\"DEALLOCATE\",\"DEC\",\"DECIMAL\",\"DECLARE\",\"DEFAULT\",\"DEFAULTS\",\"DEFINITION\",\"DELETE\",\"DENSERANK\",\"DENSE_RANK\",\"DEREF\",\"DESCRIBE\",\"DESCRIPTOR\",\"DETERMINISTIC\",\"DIAGNOSTICS\",\"DISABLE\",\"DISALLOW\",\"DISCONNECT\",\"DISTINCT\",\"DO\",\"DOCUMENT\",\"DOUBLE\",\"DROP\",\"DSSIZE\",\"DYNAMIC\",\"EACH\",\"EDITPROC\",\"ELEMENT\",\"ELSE\",\"ELSEIF\",\"ENABLE\",\"ENCODING\",\"ENCRYPTION\",\"END\",\"END-EXEC\",\"ENDING\",\"ERASE\",\"ESCAPE\",\"EVERY\",\"EXCEPTION\",\"EXCLUDING\",\"EXCLUSIVE\",\"EXEC\",\"EXECUTE\",\"EXISTS\",\"EXIT\",\"EXP\",\"EXPLAIN\",\"EXTENDED\",\"EXTERNAL\",\"EXTRACT\",\"FALSE\",\"FENCED\",\"FETCH\",\"FIELDPROC\",\"FILE\",\"FILTER\",\"FINAL\",\"FIRST\",\"FLOAT\",\"FLOOR\",\"FOR\",\"FOREIGN\",\"FREE\",\"FULL\",\"FUNCTION\",\"FUSION\",\"GENERAL\",\"GENERATED\",\"GET\",\"GLOBAL\",\"GOTO\",\"GRANT\",\"GRAPHIC\",\"GROUP\",\"GROUPING\",\"HANDLER\",\"HASH\",\"HASHED_VALUE\",\"HINT\",\"HOLD\",\"HOUR\",\"HOURS\",\"IDENTITY\",\"IF\",\"IMMEDIATE\",\"IN\",\"INCLUDING\",\"INCLUSIVE\",\"INCREMENT\",\"INDEX\",\"INDICATOR\",\"INDICATORS\",\"INF\",\"INFINITY\",\"INHERIT\",\"INNER\",\"INOUT\",\"INSENSITIVE\",\"INSERT\",\"INT\",\"INTEGER\",\"INTEGRITY\",\"INTERSECTION\",\"INTERVAL\",\"INTO\",\"IS\",\"ISOBID\",\"ISOLATION\",\"ITERATE\",\"JAR\",\"JAVA\",\"KEEP\",\"KEY\",\"LABEL\",\"LANGUAGE\",\"LARGE\",\"LATERAL\",\"LC_CTYPE\",\"LEADING\",\"LEAVE\",\"LEFT\",\"LIKE\",\"LINKTYPE\",\"LN\",\"LOCAL\",\"LOCALDATE\",\"LOCALE\",\"LOCALTIME\",\"LOCALTIMESTAMP\",\"LOCATOR\",\"LOCATORS\",\"LOCK\",\"LOCKMAX\",\"LOCKSIZE\",\"LONG\",\"LOOP\",\"LOWER\",\"MAINTAINED\",\"MATCH\",\"MATERIALIZED\",\"MAX\",\"MAXVALUE\",\"MEMBER\",\"MERGE\",\"METHOD\",\"MICROSECOND\",\"MICROSECONDS\",\"MIN\",\"MINUTE\",\"MINUTES\",\"MINVALUE\",\"MOD\",\"MODE\",\"MODIFIES\",\"MODULE\",\"MONTH\",\"MONTHS\",\"MULTISET\",\"NAN\",\"NATIONAL\",\"NATURAL\",\"NCHAR\",\"NCLOB\",\"NEW\",\"NEW_TABLE\",\"NEXTVAL\",\"NO\",\"NOCACHE\",\"NOCYCLE\",\"NODENAME\",\"NODENUMBER\",\"NOMAXVALUE\",\"NOMINVALUE\",\"NONE\",\"NOORDER\",\"NORMALIZE\",\"NORMALIZED\",\"NOT\",\"NULL\",\"NULLIF\",\"NULLS\",\"NUMERIC\",\"NUMPARTS\",\"OBID\",\"OCTET_LENGTH\",\"OF\",\"OFFSET\",\"OLD\",\"OLD_TABLE\",\"ON\",\"ONLY\",\"OPEN\",\"OPTIMIZATION\",\"OPTIMIZE\",\"OPTION\",\"ORDER\",\"OUT\",\"OUTER\",\"OVER\",\"OVERLAPS\",\"OVERLAY\",\"OVERRIDING\",\"PACKAGE\",\"PADDED\",\"PAGESIZE\",\"PARAMETER\",\"PART\",\"PARTITION\",\"PARTITIONED\",\"PARTITIONING\",\"PARTITIONS\",\"PASSWORD\",\"PATH\",\"PERCENTILE_CONT\",\"PERCENTILE_DISC\",\"PERCENT_RANK\",\"PIECESIZE\",\"PLAN\",\"POSITION\",\"POWER\",\"PRECISION\",\"PREPARE\",\"PREVVAL\",\"PRIMARY\",\"PRIQTY\",\"PRIVILEGES\",\"PROCEDURE\",\"PROGRAM\",\"PSID\",\"PUBLIC\",\"QUERY\",\"QUERYNO\",\"RANGE\",\"RANK\",\"READ\",\"READS\",\"REAL\",\"RECOVERY\",\"RECURSIVE\",\"REF\",\"REFERENCES\",\"REFERENCING\",\"REFRESH\",\"REGR_AVGX\",\"REGR_AVGY\",\"REGR_COUNT\",\"REGR_INTERCEPT\",\"REGR_R2\",\"REGR_SLOPE\",\"REGR_SXX\",\"REGR_SXY\",\"REGR_SYY\",\"RELEASE\",\"RENAME\",\"REPEAT\",\"RESET\",\"RESIGNAL\",\"RESTART\",\"RESTRICT\",\"RESULT\",\"RESULT_SET_LOCATOR\",\"RETURN\",\"RETURNS\",\"REVOKE\",\"RIGHT\",\"ROLE\",\"ROLLBACK\",\"ROLLUP\",\"ROUND_CEILING\",\"ROUND_DOWN\",\"ROUND_FLOOR\",\"ROUND_HALF_DOWN\",\"ROUND_HALF_EVEN\",\"ROUND_HALF_UP\",\"ROUND_UP\",\"ROUTINE\",\"ROW\",\"ROWNUMBER\",\"ROWS\",\"ROWSET\",\"ROW_NUMBER\",\"RRN\",\"RUN\",\"SAVEPOINT\",\"SCHEMA\",\"SCOPE\",\"SCRATCHPAD\",\"SCROLL\",\"SEARCH\",\"SECOND\",\"SECONDS\",\"SECQTY\",\"SECURITY\",\"SENSITIVE\",\"SEQUENCE\",\"SESSION\",\"SESSION_USER\",\"SIGNAL\",\"SIMILAR\",\"SIMPLE\",\"SMALLINT\",\"SNAN\",\"SOME\",\"SOURCE\",\"SPECIFIC\",\"SPECIFICTYPE\",\"SQL\",\"SQLEXCEPTION\",\"SQLID\",\"SQLSTATE\",\"SQLWARNING\",\"SQRT\",\"STACKED\",\"STANDARD\",\"START\",\"STARTING\",\"STATEMENT\",\"STATIC\",\"STATMENT\",\"STAY\",\"STDDEV_POP\",\"STDDEV_SAMP\",\"STOGROUP\",\"STORES\",\"STYLE\",\"SUBMULTISET\",\"SUBSTRING\",\"SUM\",\"SUMMARY\",\"SYMMETRIC\",\"SYNONYM\",\"SYSFUN\",\"SYSIBM\",\"SYSPROC\",\"SYSTEM\",\"SYSTEM_USER\",\"TABLE\",\"TABLESAMPLE\",\"TABLESPACE\",\"THEN\",\"TIME\",\"TIMESTAMP\",\"TIMEZONE_HOUR\",\"TIMEZONE_MINUTE\",\"TO\",\"TRAILING\",\"TRANSACTION\",\"TRANSLATE\",\"TRANSLATION\",\"TREAT\",\"TRIGGER\",\"TRIM\",\"TRUE\",\"TRUNCATE\",\"TYPE\",\"UESCAPE\",\"UNDO\",\"UNIQUE\",\"UNKNOWN\",\"UNNEST\",\"UNTIL\",\"UPPER\",\"USAGE\",\"USER\",\"USING\",\"VALIDPROC\",\"VALUE\",\"VARCHAR\",\"VARIABLE\",\"VARIANT\",\"VARYING\",\"VAR_POP\",\"VAR_SAMP\",\"VCAT\",\"VERSION\",\"VIEW\",\"VOLATILE\",\"VOLUMES\",\"WHEN\",\"WHENEVER\",\"WHILE\",\"WIDTH_BUCKET\",\"WINDOW\",\"WITH\",\"WITHIN\",\"WITHOUT\",\"WLM\",\"WRITE\",\"XMLELEMENT\",\"XMLEXISTS\",\"XMLNAMESPACES\",\"YEAR\",\"YEARS\"],O=[\"ADD\",\"AFTER\",\"ALTER COLUMN\",\"ALTER TABLE\",\"DELETE FROM\",\"EXCEPT\",\"FETCH FIRST\",\"FROM\",\"GROUP BY\",\"GO\",\"HAVING\",\"INSERT INTO\",\"INTERSECT\",\"LIMIT\",\"ORDER BY\",\"SELECT\",\"SET CURRENT SCHEMA\",\"SET SCHEMA\",\"SET\",\"UNION ALL\",\"UPDATE\",\"VALUES\",\"WHERE\"],i=[\"AND\",\"CROSS JOIN\",\"INNER JOIN\",\"JOIN\",\"LEFT JOIN\",\"LEFT OUTER JOIN\",\"OR\",\"OUTER JOIN\",\"RIGHT JOIN\",\"RIGHT OUTER JOIN\"],S=void 0,u=function(){function e(E){(0,T[\"default\"])(this,e),this.cfg=E}return e.prototype.format=function(e){return S||(S=new A[\"default\"]({reservedWords:I,reservedToplevelWords:O,reservedNewlineWords:i,stringTypes:['\"\"',\"''\",\"``\",\"[]\"],openParens:[\"(\"],closeParens:[\")\"],indexedPlaceholderTypes:[\"?\"],namedPlaceholderTypes:[\":\"],lineCommentTypes:[\"--\"],specialWordChars:[\"#\",\"@\"]})),new o[\"default\"](this.cfg,S).format(e)},e}();E[\"default\"]=u,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(4),o=n(R),N=t(5),A=n(N),I=[\"ALL\",\"ALTER\",\"ANALYZE\",\"AND\",\"ANY\",\"ARRAY\",\"AS\",\"ASC\",\"BEGIN\",\"BETWEEN\",\"BINARY\",\"BOOLEAN\",\"BREAK\",\"BUCKET\",\"BUILD\",\"BY\",\"CALL\",\"CASE\",\"CAST\",\"CLUSTER\",\"COLLATE\",\"COLLECTION\",\"COMMIT\",\"CONNECT\",\"CONTINUE\",\"CORRELATE\",\"COVER\",\"CREATE\",\"DATABASE\",\"DATASET\",\"DATASTORE\",\"DECLARE\",\"DECREMENT\",\"DELETE\",\"DERIVED\",\"DESC\",\"DESCRIBE\",\"DISTINCT\",\"DO\",\"DROP\",\"EACH\",\"ELEMENT\",\"ELSE\",\"END\",\"EVERY\",\"EXCEPT\",\"EXCLUDE\",\"EXECUTE\",\"EXISTS\",\"EXPLAIN\",\"FALSE\",\"FETCH\",\"FIRST\",\"FLATTEN\",\"FOR\",\"FORCE\",\"FROM\",\"FUNCTION\",\"GRANT\",\"GROUP\",\"GSI\",\"HAVING\",\"IF\",\"IGNORE\",\"ILIKE\",\"IN\",\"INCLUDE\",\"INCREMENT\",\"INDEX\",\"INFER\",\"INLINE\",\"INNER\",\"INSERT\",\"INTERSECT\",\"INTO\",\"IS\",\"JOIN\",\"KEY\",\"KEYS\",\"KEYSPACE\",\"KNOWN\",\"LAST\",\"LEFT\",\"LET\",\"LETTING\",\"LIKE\",\"LIMIT\",\"LSM\",\"MAP\",\"MAPPING\",\"MATCHED\",\"MATERIALIZED\",\"MERGE\",\"MINUS\",\"MISSING\",\"NAMESPACE\",\"NEST\",\"NOT\",\"NULL\",\"NUMBER\",\"OBJECT\",\"OFFSET\",\"ON\",\"OPTION\",\"OR\",\"ORDER\",\"OUTER\",\"OVER\",\"PARSE\",\"PARTITION\",\"PASSWORD\",\"PATH\",\"POOL\",\"PREPARE\",\"PRIMARY\",\"PRIVATE\",\"PRIVILEGE\",\"PROCEDURE\",\"PUBLIC\",\"RAW\",\"REALM\",\"REDUCE\",\"RENAME\",\"RETURN\",\"RETURNING\",\"REVOKE\",\"RIGHT\",\"ROLE\",\"ROLLBACK\",\"SATISFIES\",\"SCHEMA\",\"SELECT\",\"SELF\",\"SEMI\",\"SET\",\"SHOW\",\"SOME\",\"START\",\"STATISTICS\",\"STRING\",\"SYSTEM\",\"THEN\",\"TO\",\"TRANSACTION\",\"TRIGGER\",\"TRUE\",\"TRUNCATE\",\"UNDER\",\"UNION\",\"UNIQUE\",\"UNKNOWN\",\"UNNEST\",\"UNSET\",\"UPDATE\",\"UPSERT\",\"USE\",\"USER\",\"USING\",\"VALIDATE\",\"VALUE\",\"VALUED\",\"VALUES\",\"VIA\",\"VIEW\",\"WHEN\",\"WHERE\",\"WHILE\",\"WITH\",\"WITHIN\",\"WORK\",\"XOR\"],O=[\"DELETE FROM\",\"EXCEPT ALL\",\"EXCEPT\",\"EXPLAIN DELETE FROM\",\"EXPLAIN UPDATE\",\"EXPLAIN UPSERT\",\"FROM\",\"GROUP BY\",\"HAVING\",\"INFER\",\"INSERT INTO\",\"INTERSECT ALL\",\"INTERSECT\",\"LET\",\"LIMIT\",\"MERGE\",\"NEST\",\"ORDER BY\",\"PREPARE\",\"SELECT\",\"SET CURRENT SCHEMA\",\"SET SCHEMA\",\"SET\",\"UNION ALL\",\"UNION\",\"UNNEST\",\"UPDATE\",\"UPSERT\",\"USE KEYS\",\"VALUES\",\"WHERE\"],i=[\"AND\",\"INNER JOIN\",\"JOIN\",\"LEFT JOIN\",\"LEFT OUTER JOIN\",\"OR\",\"OUTER JOIN\",\"RIGHT JOIN\",\"RIGHT OUTER JOIN\",\"XOR\"],S=void 0,u=function(){function e(E){(0,T[\"default\"])(this,e),this.cfg=E}return e.prototype.format=function(e){return S||(S=new A[\"default\"]({reservedWords:I,reservedToplevelWords:O,reservedNewlineWords:i,stringTypes:['\"\"',\"''\",\"``\"],openParens:[\"(\",\"[\",\"{\"],closeParens:[\")\",\"]\",\"}\"],namedPlaceholderTypes:[\"$\"],lineCommentTypes:[\"#\",\"--\"]})),new o[\"default\"](this.cfg,S).format(e)},e}();E[\"default\"]=u,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(4),o=n(R),N=t(5),A=n(N),I=[\"A\",\"ACCESSIBLE\",\"AGENT\",\"AGGREGATE\",\"ALL\",\"ALTER\",\"ANY\",\"ARRAY\",\"AS\",\"ASC\",\"AT\",\"ATTRIBUTE\",\"AUTHID\",\"AVG\",\"BETWEEN\",\"BFILE_BASE\",\"BINARY_INTEGER\",\"BINARY\",\"BLOB_BASE\",\"BLOCK\",\"BODY\",\"BOOLEAN\",\"BOTH\",\"BOUND\",\"BULK\",\"BY\",\"BYTE\",\"C\",\"CALL\",\"CALLING\",\"CASCADE\",\"CASE\",\"CHAR_BASE\",\"CHAR\",\"CHARACTER\",\"CHARSET\",\"CHARSETFORM\",\"CHARSETID\",\"CHECK\",\"CLOB_BASE\",\"CLONE\",\"CLOSE\",\"CLUSTER\",\"CLUSTERS\",\"COALESCE\",\"COLAUTH\",\"COLLECT\",\"COLUMNS\",\"COMMENT\",\"COMMIT\",\"COMMITTED\",\"COMPILED\",\"COMPRESS\",\"CONNECT\",\"CONSTANT\",\"CONSTRUCTOR\",\"CONTEXT\",\"CONTINUE\",\"CONVERT\",\"COUNT\",\"CRASH\",\"CREATE\",\"CREDENTIAL\",\"CURRENT\",\"CURRVAL\",\"CURSOR\",\"CUSTOMDATUM\",\"DANGLING\",\"DATA\",\"DATE_BASE\",\"DATE\",\"DAY\",\"DECIMAL\",\"DEFAULT\",\"DEFINE\",\"DELETE\",\"DESC\",\"DETERMINISTIC\",\"DIRECTORY\",\"DISTINCT\",\"DO\",\"DOUBLE\",\"DROP\",\"DURATION\",\"ELEMENT\",\"ELSIF\",\"EMPTY\",\"ESCAPE\",\"EXCEPTIONS\",\"EXCLUSIVE\",\"EXECUTE\",\"EXISTS\",\"EXIT\",\"EXTENDS\",\"EXTERNAL\",\"EXTRACT\",\"FALSE\",\"FETCH\",\"FINAL\",\"FIRST\",\"FIXED\",\"FLOAT\",\"FOR\",\"FORALL\",\"FORCE\",\"FROM\",\"FUNCTION\",\"GENERAL\",\"GOTO\",\"GRANT\",\"GROUP\",\"HASH\",\"HEAP\",\"HIDDEN\",\"HOUR\",\"IDENTIFIED\",\"IF\",\"IMMEDIATE\",\"IN\",\"INCLUDING\",\"INDEX\",\"INDEXES\",\"INDICATOR\",\"INDICES\",\"INFINITE\",\"INSTANTIABLE\",\"INT\",\"INTEGER\",\"INTERFACE\",\"INTERVAL\",\"INTO\",\"INVALIDATE\",\"IS\",\"ISOLATION\",\"JAVA\",\"LANGUAGE\",\"LARGE\",\"LEADING\",\"LENGTH\",\"LEVEL\",\"LIBRARY\",\"LIKE\",\"LIKE2\",\"LIKE4\",\"LIKEC\",\"LIMITED\",\"LOCAL\",\"LOCK\",\"LONG\",\"MAP\",\"MAX\",\"MAXLEN\",\"MEMBER\",\"MERGE\",\"MIN\",\"MINUS\",\"MINUTE\",\"MLSLABEL\",\"MOD\",\"MODE\",\"MONTH\",\"MULTISET\",\"NAME\",\"NAN\",\"NATIONAL\",\"NATIVE\",\"NATURAL\",\"NATURALN\",\"NCHAR\",\"NEW\",\"NEXTVAL\",\"NOCOMPRESS\",\"NOCOPY\",\"NOT\",\"NOWAIT\",\"NULL\",\"NULLIF\",\"NUMBER_BASE\",\"NUMBER\",\"OBJECT\",\"OCICOLL\",\"OCIDATE\",\"OCIDATETIME\",\"OCIDURATION\",\"OCIINTERVAL\",\"OCILOBLOCATOR\",\"OCINUMBER\",\"OCIRAW\",\"OCIREF\",\"OCIREFCURSOR\",\"OCIROWID\",\"OCISTRING\",\"OCITYPE\",\"OF\",\"OLD\",\"ON\",\"ONLY\",\"OPAQUE\",\"OPEN\",\"OPERATOR\",\"OPTION\",\"ORACLE\",\"ORADATA\",\"ORDER\",\"ORGANIZATION\",\"ORLANY\",\"ORLVARY\",\"OTHERS\",\"OUT\",\"OVERLAPS\",\"OVERRIDING\",\"PACKAGE\",\"PARALLEL_ENABLE\",\"PARAMETER\",\"PARAMETERS\",\"PARENT\",\"PARTITION\",\"PASCAL\",\"PCTFREE\",\"PIPE\",\"PIPELINED\",\"PLS_INTEGER\",\"PLUGGABLE\",\"POSITIVE\",\"POSITIVEN\",\"PRAGMA\",\"PRECISION\",\"PRIOR\",\"PRIVATE\",\"PROCEDURE\",\"PUBLIC\",\"RAISE\",\"RANGE\",\"RAW\",\"READ\",\"REAL\",\"RECORD\",\"REF\",\"REFERENCE\",\"RELEASE\",\"RELIES_ON\",\"REM\",\"REMAINDER\",\"RENAME\",\"RESOURCE\",\"RESULT_CACHE\",\"RESULT\",\"RETURN\",\"RETURNING\",\"REVERSE\",\"REVOKE\",\"ROLLBACK\",\"ROW\",\"ROWID\",\"ROWNUM\",\"ROWTYPE\",\"SAMPLE\",\"SAVE\",\"SAVEPOINT\",\"SB1\",\"SB2\",\"SB4\",\"SECOND\",\"SEGMENT\",\"SELF\",\"SEPARATE\",\"SEQUENCE\",\"SERIALIZABLE\",\"SHARE\",\"SHORT\",\"SIZE_T\",\"SIZE\",\"SMALLINT\",\"SOME\",\"SPACE\",\"SPARSE\",\"SQL\",\"SQLCODE\",\"SQLDATA\",\"SQLERRM\",\"SQLNAME\",\"SQLSTATE\",\"STANDARD\",\"START\",\"STATIC\",\"STDDEV\",\"STORED\",\"STRING\",\"STRUCT\",\"STYLE\",\"SUBMULTISET\",\"SUBPARTITION\",\"SUBSTITUTABLE\",\"SUBTYPE\",\"SUCCESSFUL\",\"SUM\",\"SYNONYM\",\"SYSDATE\",\"TABAUTH\",\"TABLE\",\"TDO\",\"THE\",\"THEN\",\"TIME\",\"TIMESTAMP\",\"TIMEZONE_ABBR\",\"TIMEZONE_HOUR\",\"TIMEZONE_MINUTE\",\"TIMEZONE_REGION\",\"TO\",\"TRAILING\",\"TRANSACTION\",\"TRANSACTIONAL\",\"TRIGGER\",\"TRUE\",\"TRUSTED\",\"TYPE\",\"UB1\",\"UB2\",\"UB4\",\"UID\",\"UNDER\",\"UNIQUE\",\"UNPLUG\",\"UNSIGNED\",\"UNTRUSTED\",\"USE\",\"USER\",\"USING\",\"VALIDATE\",\"VALIST\",\"VALUE\",\"VARCHAR\",\"VARCHAR2\",\"VARIABLE\",\"VARIANCE\",\"VARRAY\",\"VARYING\",\"VIEW\",\"VIEWS\",\"VOID\",\"WHENEVER\",\"WHILE\",\"WITH\",\"WORK\",\"WRAPPED\",\"WRITE\",\"YEAR\",\"ZONE\"],O=[\"ADD\",\"ALTER COLUMN\",\"ALTER TABLE\",\"BEGIN\",\"CONNECT BY\",\"DECLARE\",\"DELETE FROM\",\"DELETE\",\"END\",\"EXCEPT\",\"EXCEPTION\",\"FETCH FIRST\",\"FROM\",\"GROUP BY\",\"HAVING\",\"INSERT INTO\",\"INSERT\",\"INTERSECT\",\"LIMIT\",\"LOOP\",\"MODIFY\",\"ORDER BY\",\"SELECT\",\"SET CURRENT SCHEMA\",\"SET SCHEMA\",\"SET\",\"START WITH\",\"UNION ALL\",\"UNION\",\"UPDATE\",\"VALUES\",\"WHERE\"],i=[\"AND\",\"CROSS APPLY\",\"CROSS JOIN\",\"ELSE\",\"END\",\"INNER JOIN\",\"JOIN\",\"LEFT JOIN\",\"LEFT OUTER JOIN\",\"OR\",\"OUTER APPLY\",\"OUTER JOIN\",\"RIGHT JOIN\",\"RIGHT OUTER JOIN\",\"WHEN\",\"XOR\"],S=void 0,u=function(){function e(E){(0,T[\"default\"])(this,e),this.cfg=E}return e.prototype.format=function(e){return S||(S=new A[\"default\"]({reservedWords:I,reservedToplevelWords:O,reservedNewlineWords:i,stringTypes:['\"\"',\"N''\",\"''\",\"``\"],openParens:[\"(\",\"CASE\"],closeParens:[\")\",\"END\"],indexedPlaceholderTypes:[\"?\"],namedPlaceholderTypes:[\":\"],lineCommentTypes:[\"--\"],specialWordChars:[\"_\",\"$\",\"#\",\".\",\"@\"]})),new o[\"default\"](this.cfg,S).format(e)},e}();E[\"default\"]=u,e.exports=E[\"default\"]},function(e,E,t){\"use strict\";function n(e){return e&&e.__esModule?e:{\"default\":e}}E.__esModule=!0;var r=t(1),T=n(r),R=t(4),o=n(R),N=t(5),A=n(N),I=[\"ACCESSIBLE\",\"ACTION\",\"AGAINST\",\"AGGREGATE\",\"ALGORITHM\",\"ALL\",\"ALTER\",\"ANALYSE\",\"ANALYZE\",\"AS\",\"ASC\",\"AUTOCOMMIT\",\"AUTO_INCREMENT\",\"BACKUP\",\"BEGIN\",\"BETWEEN\",\"BINLOG\",\"BOTH\",\"CASCADE\",\"CASE\",\"CHANGE\",\"CHANGED\",\"CHARACTER SET\",\"CHARSET\",\"CHECK\",\"CHECKSUM\",\"COLLATE\",\"COLLATION\",\"COLUMN\",\"COLUMNS\",\"COMMENT\",\"COMMIT\",\"COMMITTED\",\"COMPRESSED\",\"CONCURRENT\",\"CONSTRAINT\",\"CONTAINS\",\"CONVERT\",\"CREATE\",\"CROSS\",\"CURRENT_TIMESTAMP\",\"DATABASE\",\"DATABASES\",\"DAY\",\"DAY_HOUR\",\"DAY_MINUTE\",\"DAY_SECOND\",\"DEFAULT\",\"DEFINER\",\"DELAYED\",\"DELETE\",\"DESC\",\"DESCRIBE\",\"DETERMINISTIC\",\"DISTINCT\",\"DISTINCTROW\",\"DIV\",\"DO\",\"DROP\",\"DUMPFILE\",\"DUPLICATE\",\"DYNAMIC\",\"ELSE\",\"ENCLOSED\",\"END\",\"ENGINE\",\"ENGINES\",\"ENGINE_TYPE\",\"ESCAPE\",\"ESCAPED\",\"EVENTS\",\"EXEC\",\"EXECUTE\",\"EXISTS\",\"EXPLAIN\",\"EXTENDED\",\"FAST\",\"FETCH\",\"FIELDS\",\"FILE\",\"FIRST\",\"FIXED\",\"FLUSH\",\"FOR\",\"FORCE\",\"FOREIGN\",\"FULL\",\"FULLTEXT\",\"FUNCTION\",\"GLOBAL\",\"GRANT\",\"GRANTS\",\"GROUP_CONCAT\",\"HEAP\",\"HIGH_PRIORITY\",\"HOSTS\",\"HOUR\",\"HOUR_MINUTE\",\"HOUR_SECOND\",\"IDENTIFIED\",\"IF\",\"IFNULL\",\"IGNORE\",\"IN\",\"INDEX\",\"INDEXES\",\"INFILE\",\"INSERT\",\"INSERT_ID\",\"INSERT_METHOD\",\"INTERVAL\",\"INTO\",\"INVOKER\",\"IS\",\"ISOLATION\",\"KEY\",\"KEYS\",\"KILL\",\"LAST_INSERT_ID\",\"LEADING\",\"LEVEL\",\"LIKE\",\"LINEAR\",\"LINES\",\"LOAD\",\"LOCAL\",\"LOCK\",\"LOCKS\",\"LOGS\",\"LOW_PRIORITY\",\"MARIA\",\"MASTER\",\"MASTER_CONNECT_RETRY\",\"MASTER_HOST\",\"MASTER_LOG_FILE\",\"MATCH\",\"MAX_CONNECTIONS_PER_HOUR\",\"MAX_QUERIES_PER_HOUR\",\"MAX_ROWS\",\"MAX_UPDATES_PER_HOUR\",\"MAX_USER_CONNECTIONS\",\"MEDIUM\",\"MERGE\",\"MINUTE\",\"MINUTE_SECOND\",\"MIN_ROWS\",\"MODE\",\"MODIFY\",\"MONTH\",\"MRG_MYISAM\",\"MYISAM\",\"NAMES\",\"NATURAL\",\"NOT\",\"NOW()\",\"NULL\",\"OFFSET\",\"ON DELETE\",\"ON UPDATE\",\"ON\",\"ONLY\",\"OPEN\",\"OPTIMIZE\",\"OPTION\",\"OPTIONALLY\",\"OUTFILE\",\"PACK_KEYS\",\"PAGE\",\"PARTIAL\",\"PARTITION\",\"PARTITIONS\",\"PASSWORD\",\"PRIMARY\",\"PRIVILEGES\",\"PROCEDURE\",\"PROCESS\",\"PROCESSLIST\",\"PURGE\",\"QUICK\",\"RAID0\",\"RAID_CHUNKS\",\"RAID_CHUNKSIZE\",\"RAID_TYPE\",\"RANGE\",\"READ\",\"READ_ONLY\",\"READ_WRITE\",\"REFERENCES\",\"REGEXP\",\"RELOAD\",\"RENAME\",\"REPAIR\",\"REPEATABLE\",\"REPLACE\",\"REPLICATION\",\"RESET\",\"RESTORE\",\"RESTRICT\",\"RETURN\",\"RETURNS\",\"REVOKE\",\"RLIKE\",\"ROLLBACK\",\"ROW\",\"ROWS\",\"ROW_FORMAT\",\"SECOND\",\"SECURITY\",\"SEPARATOR\",\"SERIALIZABLE\",\"SESSION\",\"SHARE\",\"SHOW\",\"SHUTDOWN\",\"SLAVE\",\"SONAME\",\"SOUNDS\",\"SQL\",\"SQL_AUTO_IS_NULL\",\"SQL_BIG_RESULT\",\"SQL_BIG_SELECTS\",\"SQL_BIG_TABLES\",\"SQL_BUFFER_RESULT\",\"SQL_CACHE\",\"SQL_CALC_FOUND_ROWS\",\"SQL_LOG_BIN\",\"SQL_LOG_OFF\",\"SQL_LOG_UPDATE\",\"SQL_LOW_PRIORITY_UPDATES\",\"SQL_MAX_JOIN_SIZE\",\"SQL_NO_CACHE\",\"SQL_QUOTE_SHOW_CREATE\",\"SQL_SAFE_UPDATES\",\"SQL_SELECT_LIMIT\",\"SQL_SLAVE_SKIP_COUNTER\",\"SQL_SMALL_RESULT\",\"SQL_WARNINGS\",\"START\",\"STARTING\",\"STATUS\",\"STOP\",\"STORAGE\",\"STRAIGHT_JOIN\",\"STRING\",\"STRIPED\",\"SUPER\",\"TABLE\",\"TABLES\",\"TEMPORARY\",\"TERMINATED\",\"THEN\",\"TO\",\"TRAILING\",\"TRANSACTIONAL\",\"TRUE\",\"TRUNCATE\",\"TYPE\",\"TYPES\",\"UNCOMMITTED\",\"UNIQUE\",\"UNLOCK\",\"UNSIGNED\",\"USAGE\",\"USE\",\"USING\",\"VARIABLES\",\"VIEW\",\"WHEN\",\"WITH\",\"WORK\",\"WRITE\",\"YEAR_MONTH\"],O=[\"ADD\",\"AFTER\",\"ALTER COLUMN\",\"ALTER TABLE\",\"DELETE FROM\",\"EXCEPT\",\"FETCH FIRST\",\"FROM\",\"GROUP BY\",\"GO\",\"HAVING\",\"INSERT INTO\",\"INSERT\",\"INTERSECT\",\"LIMIT\",\"MODIFY\",\"ORDER BY\",\"SELECT\",\"SET CURRENT SCHEMA\",\"SET SCHEMA\",\"SET\",\"UNION ALL\",\"UNION\",\"UPDATE\",\"VALUES\",\"WHERE\"],i=[\"AND\",\"CROSS APPLY\",\"CROSS JOIN\",\"ELSE\",\"INNER JOIN\",\"JOIN\",\"LEFT JOIN\",\"LEFT OUTER JOIN\",\"OR\",\"OUTER APPLY\",\"OUTER JOIN\",\"RIGHT JOIN\",\"RIGHT OUTER JOIN\",\"WHEN\",\"XOR\"],S=void 0,u=function(){function e(E){(0,T[\"default\"])(this,e),this.cfg=E}return e.prototype.format=function(e){return S||(S=new A[\"default\"]({reservedWords:I,reservedToplevelWords:O,reservedNewlineWords:i,stringTypes:['\"\"',\"N''\",\"''\",\"``\",\"[]\"],openParens:[\"(\",\"CASE\"],closeParens:[\")\",\"END\"],indexedPlaceholderTypes:[\"?\"],namedPlaceholderTypes:[\"@\",\":\"],lineCommentTypes:[\"#\",\"--\"]})),new o[\"default\"](this.cfg,S).format(e)},e}();E[\"default\"]=u,e.exports=E[\"default\"]},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"DataView\");e.exports=T},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"Map\");e.exports=T},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"Promise\");e.exports=T},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"Set\");e.exports=T},function(e,E,t){var n=t(2),r=n.Symbol;e.exports=r},function(e,E,t){var n=t(3),r=t(2),T=n(r,\"WeakMap\");e.exports=T},function(e,E){function t(e){return e.split(\"\")}e.exports=t},function(e,E){function t(e,E,t,n){for(var r=e.length,T=t+(n?1:-1);n?T--:++T<r;)if(E(e[T],T,e))return T;\nreturn-1}e.exports=t},function(e,E){function t(e){return r.call(e)}var n=Object.prototype,r=n.toString;e.exports=t},function(e,E,t){function n(e,E,t){return E===E?R(e,E,t):r(e,T,t)}var r=t(29),T=t(32),R=t(49);e.exports=n},function(e,E){function t(e){return e!==e}e.exports=t},function(e,E,t){function n(e){if(!R(e)||T(e))return!1;var E=r(e)?u:A;return E.test(o(e))}var r=t(12),T=t(45),R=t(6),o=t(11),N=/[\\\\^$.*+?()[\\]{}|]/g,A=/^\\[object .+?Constructor\\]$/,I=Function.prototype,O=Object.prototype,i=I.toString,S=O.hasOwnProperty,u=RegExp(\"^\"+i.call(S).replace(N,\"\\\\$&\").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g,\"$1.*?\")+\"$\");e.exports=n},function(e,E){function t(e,E){var t=\"\";if(!e||1>E||E>n)return t;do E%2&&(t+=e),E=r(E/2),E&&(e+=e);while(E);return t}var n=9007199254740991,r=Math.floor;e.exports=t},function(e,E){function t(e,E,t){var n=-1,r=e.length;0>E&&(E=-E>r?0:r+E),t=t>r?r:t,0>t&&(t+=r),r=E>t?0:t-E>>>0,E>>>=0;for(var T=Array(r);++n<r;)T[n]=e[n+E];return T}e.exports=t},function(e,E,t){function n(e,E,t){var n=e.length;return t=void 0===t?n:t,E||n>t?r(e,E,t):e}var r=t(35);e.exports=n},function(e,E,t){function n(e,E){for(var t=e.length;t--&&r(E,e[t],0)>-1;);return t}var r=t(31);e.exports=n},function(e,E,t){var n=t(2),r=n[\"__core-js_shared__\"];e.exports=r},function(e,E){(function(E){var t=\"object\"==typeof E&&E&&E.Object===Object&&E;e.exports=t}).call(E,function(){return this}())},function(e,E,t){var n=t(22),r=t(23),T=t(24),R=t(25),o=t(27),N=t(30),A=t(11),I=\"[object Map]\",O=\"[object Object]\",i=\"[object Promise]\",S=\"[object Set]\",u=\"[object WeakMap]\",L=\"[object DataView]\",C=Object.prototype,s=C.toString,a=A(n),f=A(r),c=A(T),p=A(R),l=A(o),D=N;(n&&D(new n(new ArrayBuffer(1)))!=L||r&&D(new r)!=I||T&&D(T.resolve())!=i||R&&D(new R)!=S||o&&D(new o)!=u)&&(D=function(e){var E=s.call(e),t=E==O?e.constructor:void 0,n=t?A(t):void 0;if(n)switch(n){case a:return L;case f:return I;case c:return i;case p:return S;case l:return u}return E}),e.exports=D},function(e,E){function t(e,E){return null==e?void 0:e[E]}e.exports=t},function(e,E){function t(e){return N.test(e)}var n=\"\\\\ud800-\\\\udfff\",r=\"\\\\u0300-\\\\u036f\\\\ufe20-\\\\ufe23\",T=\"\\\\u20d0-\\\\u20f0\",R=\"\\\\ufe0e\\\\ufe0f\",o=\"\\\\u200d\",N=RegExp(\"[\"+o+n+r+T+R+\"]\");e.exports=t},function(e,E){function t(e,E){return E=null==E?n:E,!!E&&(\"number\"==typeof e||r.test(e))&&e>-1&&e%1==0&&E>e}var n=9007199254740991,r=/^(?:0|[1-9]\\d*)$/;e.exports=t},function(e,E,t){function n(e,E,t){if(!o(t))return!1;var n=typeof E;return!!(\"number\"==n?T(t)&&R(E,t.length):\"string\"==n&&E in t)&&r(t[E],e)}var r=t(52),T=t(8),R=t(43),o=t(6);e.exports=n},function(e,E,t){function n(e){return!!T&&T in e}var r=t(38),T=function(){var e=/[^.]+$/.exec(r&&r.keys&&r.keys.IE_PROTO||\"\");return e?\"Symbol(src)_1.\"+e:\"\"}();e.exports=n},function(e,E){function t(e){var E=e&&e.constructor,t=\"function\"==typeof E&&E.prototype||n;return e===t}var n=Object.prototype;e.exports=t},function(e,E,t){var n=t(48),r=n(Object.keys,Object);e.exports=r},function(e,E){function t(e,E){return function(t){return e(E(t))}}e.exports=t},function(e,E){function t(e,E,t){for(var n=t-1,r=e.length;++n<r;)if(e[n]===E)return n;return-1}e.exports=t},function(e,E,t){function n(e){return T(e)?R(e):r(e)}var r=t(28),T=t(42),R=t(51);e.exports=n},function(e,E){function t(e){return e.match(c)||[]}var n=\"\\\\ud800-\\\\udfff\",r=\"\\\\u0300-\\\\u036f\\\\ufe20-\\\\ufe23\",T=\"\\\\u20d0-\\\\u20f0\",R=\"\\\\ufe0e\\\\ufe0f\",o=\"[\"+n+\"]\",N=\"[\"+r+T+\"]\",A=\"\\\\ud83c[\\\\udffb-\\\\udfff]\",I=\"(?:\"+N+\"|\"+A+\")\",O=\"[^\"+n+\"]\",i=\"(?:\\\\ud83c[\\\\udde6-\\\\uddff]){2}\",S=\"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\",u=\"\\\\u200d\",L=I+\"?\",C=\"[\"+R+\"]?\",s=\"(?:\"+u+\"(?:\"+[O,i,S].join(\"|\")+\")\"+C+L+\")*\",a=C+L+s,f=\"(?:\"+[O+N+\"?\",N,i,S,o].join(\"|\")+\")\",c=RegExp(A+\"(?=\"+A+\")|\"+f+a,\"g\");e.exports=t},function(e,E){function t(e,E){return e===E||e!==e&&E!==E}e.exports=t},function(e,E,t){function n(e){return e=r(e),e&&R.test(e)?e.replace(T,\"\\\\$&\"):e}var r=t(9),T=/[\\\\^$.*+?()[\\]{}|]/g,R=RegExp(T.source);e.exports=n},function(e,E,t){function n(e){return r(e)&&o.call(e,\"callee\")&&(!A.call(e,\"callee\")||N.call(e)==T)}var r=t(56),T=\"[object Arguments]\",R=Object.prototype,o=R.hasOwnProperty,N=R.toString,A=R.propertyIsEnumerable;e.exports=n},function(e,E){var t=Array.isArray;e.exports=t},function(e,E,t){function n(e){return T(e)&&r(e)}var r=t(8),T=t(13);e.exports=n},function(e,E,t){(function(e){var n=t(2),r=t(62),T=\"object\"==typeof E&&E&&!E.nodeType&&E,R=T&&\"object\"==typeof e&&e&&!e.nodeType&&e,o=R&&R.exports===T,N=o?n.Buffer:void 0,A=N?N.isBuffer:void 0,I=A||r;e.exports=I}).call(E,t(67)(e))},function(e,E,t){function n(e){if(o(e)&&(R(e)||\"string\"==typeof e||\"function\"==typeof e.splice||N(e)||T(e)))return!e.length;var E=r(e);if(E==O||E==i)return!e.size;if(A(e))return!I(e).length;for(var t in e)if(u.call(e,t))return!1;return!0}var r=t(40),T=t(54),R=t(55),o=t(8),N=t(57),A=t(46),I=t(47),O=\"[object Map]\",i=\"[object Set]\",S=Object.prototype,u=S.hasOwnProperty;e.exports=n},function(e,E){function t(e){return\"number\"==typeof e&&e>-1&&e%1==0&&n>=e}var n=9007199254740991;e.exports=t},function(e,E){function t(e){var E=e?e.length:0;return E?e[E-1]:void 0}e.exports=t},function(e,E,t){function n(e,E,t){return E=(t?T(e,E,t):void 0===E)?1:R(E),r(o(e),E)}var r=t(34),T=t(44),R=t(64),o=t(9);e.exports=n},function(e,E){function t(){return!1}e.exports=t},function(e,E,t){function n(e){if(!e)return 0===e?e:0;if(e=r(e),e===T||e===-T){var E=0>e?-1:1;return E*R}return e===e?e:0}var r=t(65),T=1/0,R=1.7976931348623157e308;e.exports=n},function(e,E,t){function n(e){var E=r(e),t=E%1;return E===E?t?E-t:E:0}var r=t(63);e.exports=n},function(e,E,t){function n(e){if(\"number\"==typeof e)return e;if(T(e))return R;if(r(e)){var E=\"function\"==typeof e.valueOf?e.valueOf():e;e=r(E)?E+\"\":E}if(\"string\"!=typeof e)return 0===e?e:+e;e=e.replace(o,\"\");var t=A.test(e);return t||I.test(e)?O(e.slice(2),t?2:8):N.test(e)?R:+e}var r=t(6),T=t(14),R=NaN,o=/^\\s+|\\s+$/g,N=/^[-+]0x[0-9a-f]+$/i,A=/^0b[01]+$/i,I=/^0o[0-7]+$/i,O=parseInt;e.exports=n},function(e,E,t){function n(e,E,t){if(e=N(e),e&&(t||void 0===E))return e.replace(A,\"\");if(!e||!(E=r(E)))return e;var n=o(e),I=R(n,o(E))+1;return T(n,0,I).join(\"\")}var r=t(10),T=t(36),R=t(37),o=t(50),N=t(9),A=/\\s+$/;e.exports=n},function(e,E){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children=[],e.webpackPolyfill=1),e}}])});\n\n\t\tfunction escape2Html(str) {\n    \t    var arrEntities = {'lt': '<', 'gt': '>', 'nbsp': '', 'amp': '&', 'quot': '\"'};\n    \t    return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, function (all, t) {\n    \t        return arrEntities[t];\n    \t    });\n    \t}\n\t\n    \tfunction load() {\n    \t    let codeList = document.getElementsByClassName('language-sql');\n\t\n    \t    for (let i = 0 ;i<codeList.length;i++) {\n    \t        codeList[i].innerHTML = window.sqlFormatter.format(escape2Html(codeList[i].innerHTML))\n    \t    }\n    \t};\n</script>\n<style id=\"soar_md\">\n\na:link,a:visited{text-decoration:none}h3,h4{margin-top:2em}h5,h6{margin-top:20px}h3,h4,h5,h6{margin-bottom:.5em;color:#000}body,h1,h2,h3,h4,h5,h6{color:#000}ol,ul{margin:0 0 0 30px;padding:0 0 12px 6px}ol,ol ol{list-style-position:outside}table td p,table th p{margin-bottom:0}input,select{vertical-align:middle;padding:0}h5,h6,input,select{padding:0}hr,table,textarea{width:100%}body{margin:20px auto;width:800px;background-color:#fff;font:13px \"Myriad Pro\",\"Lucida Grande\",Lucida,Verdana,sans-serif}h1,table th p{font-weight:700}a:link{color:#00f}a:visited{color:#00a}a:active,a:hover{color:#f60;text-decoration:underline}* html code,* html pre{font-size:101%}code,pre{font-size:11px;font-family:monaco,courier,consolas,monospace}pre{border:1px solid #c7cfd5;background:#f1f5f9;margin:20px 0;padding:8px;text-align:left}hr{color:#919699;size:1;noshade:\"noshade\"}h1,h2,h3,h4,h5,h6{font-family:\"Myriad Pro\",\"Lucida Grande\",Lucida,Verdana,sans-serif;font-weight:700}h1{margin-top:1em;margin-bottom:25px;font-size:30px}h2{margin-top:2.5em;font-size:24px;padding-bottom:2px;border-bottom:1px solid #919699}h3{font-size:17px}h4{font-size:15px}h5{font-size:13px}h6{font-size:11px}table td,table th{font-size:12px;border-bottom:1px solid #919699;border-right:1px solid #919699}p{margin-top:0;margin-bottom:10px}ul{list-style:square}li{margin-top:7px}ol{list-style-type:decimal}ol ol{list-style-type:lower-alpha;margin:7px 0 0 30px;padding:0 0 0 10px}ul ul{margin-left:40px;padding:0 0 0 6px}li>p{display:inline}li>a+p,li>p+p{display:block}table{border-top:1px solid #919699;border-left:1px solid #919699;border-spacing:0}table th{padding:4px 8px;background:#E2E2E2}table td{padding:8px;vertical-align:top}table td p+p,table td p+p+p{margin-top:5px}form{margin:0}button{margin:3px 0 10px}input{margin:0 0 5px}select{margin:0 0 3px}textarea{margin:0 0 10px}\n\n</style>\n</head>\n<body onload=load()>\n\n<h1>Query: 687D590364E29465</h1>\n\n<p>★ ★ ★ ☆ ☆ 75分</p>\n\n<pre><code class=\"language-sql\">select * from film\n</code></pre>\n\n<h2>最外层 SELECT 未指定 WHERE 条件</h2>\n\n<ul>\n<li><p><strong>Item:</strong>  CLA.001</p></li>\n\n<li><p><strong>Severity:</strong>  L4</p></li>\n\n<li><p><strong>Content:</strong>  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。</p></li>\n</ul>\n\n<h2>不建议使用 SELECT * 类型查询</h2>\n\n<ul>\n<li><p><strong>Item:</strong>  COL.001</p></li>\n\n<li><p><strong>Severity:</strong>  L1</p></li>\n\n<li><p><strong>Content:</strong>  当表结构变更时，使用 * 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。</p></li>\n</ul>\n\n"
  },
  {
    "path": "test/fixture/test_Check_soar_report_for_json.golden",
    "content": "[\n {\n  \"ID\": \"687D590364E29465\",\n  \"Fingerprint\": \"select * from film\",\n  \"Score\": 75,\n  \"Sample\": \"select * from film\",\n  \"Explain\": null,\n  \"HeuristicRules\": [\n    {\n      \"Item\": \"CLA.001\",\n      \"Severity\": \"L4\",\n      \"Summary\": \"最外层 SELECT 未指定 WHERE 条件\",\n      \"Content\": \"SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\",\n      \"Case\": \"select id from tbl\",\n      \"Position\": 0\n    },\n    {\n      \"Item\": \"COL.001\",\n      \"Severity\": \"L1\",\n      \"Summary\": \"不建议使用 SELECT * 类型查询\",\n      \"Content\": \"当表结构变更时，使用 * 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\",\n      \"Case\": \"select * from tbl where id=1\",\n      \"Position\": 0\n    }\n  ],\n  \"IndexRules\": null,\n  \"Tables\": [\n    \"`information_schema`.`film`\"\n  ]\n} \n]\n"
  },
  {
    "path": "test/fixture/test_Check_soar_report_for_markdown.golden",
    "content": "# Query: 687D590364E29465\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film\n```\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n"
  },
  {
    "path": "test/fixture/test_Check_the_default_config_of_the_changes.golden",
    "content": "online-dsn:\n  user: online-test\n  password: '********'\n  net: tcp\n  addr: 192.168.12.200:3307\n  schema: information_schema\n  charset: utf8mb4\n  collation: utf8mb4_general_ci\n  loc: GMT\n  tls: dsfsfdsf\n  server-public-key: AAAAB3NzaC1yc2EAAAADAQABAAABAQC0JFhoEgrl5/51UHlIIlfWwhiJLR/EEeF8enGnY0PnAldLQ8STdWD8Um2BUtVjgE9COl1X3mN4vMvnSm8A6aPn66enHn0hKzwh1GvcuZNTPgeaZyGKWG0kcvbziUjAPsxxvRvvoaUspSkRYAP/9vpq3SImJKuIHCPfjnGMurKV1n7W/QfpmNjUEwYOswDjL1Ik6Jy6Lrzf8T0hQEy+dYoV4zNM0HcROCXFuu1LyG+WTch3FW660BecNT8+c4sVTHuUMXgGot8OUtwgfXrt5ZL5as7cuyKiWsLVrtrtvL3T0ZHlV8qxQ3DT1gqjSw6jBOzyDOx3jwthAbdsWjyK4Oqp\n  max-allowed-packet: 419437\n  params:\n    charset: utf8mb4\n  timeout: 60s\n  read-timeout: 70s\n  write-timeout: 80s\n  allow-native-passwords: false\n  allow-old-passwords: true\n  disable: false\ntest-dsn:\n  user: test-user\n  password: '********'\n  net: tcp\n  addr: 192.168.12.34:3309\n  schema: information_schema\n  charset: utf8mb4\n  collation: utf8mb4_general_ci\n  loc: GMT\n  tls: aabbbaa\n  server-public-key: this is a tset serverpublic\n  max-allowed-packet: 4194309\n  params:\n    charset: utf8mb4\n  timeout: 50s\n  read-timeout: 40s\n  write-timeout: 30s\n  allow-native-passwords: false\n  allow-old-passwords: true\n  disable: false\nallow-online-as-test: true\ndisable-version-check: false\ndrop-test-temporary: false\ncleanup-test-database: true\nonly-syntax-check: true\nsampling-statistic-target: 110\nsampling: true\nsampling-condition: aaa\nprofiling: true\ntrace: true\nexplain: false\ndelimiter: ;\nlog-level: 3\nlog-output: /dev/null\nreport-type: html\nreport-css: sdfs\nreport-javascript: sdfsd\nreport-title: SQL优化分析报告-test\nmarkdown-extensions: 92\nmarkdown-html-flags: 10\nignore-rules:\n- COL.012\nrewrite-rules:\n- delimiter\n- orderbynull\n- groupbyconst\n- dmlorderby\n- having\n- star2columns\n- insertcolumns\n- distinctstar\nblacklist: /tmp/blacklist\nmax-join-table-count: 12\nmax-group-by-cols-count: 15\nmax-distinct-count: 7\nmax-index-cols-count: 2\nmax-text-cols-count: 3\nmax-total-rows: 9999991\nmax-query-cost: 9992\nspaghetti-query-length: 2041\nallow-drop-index: true\nmax-in-count: 101\nmax-index-bytes-percolumn: 762\nmax-index-bytes: 3073\nallow-charsets:\n- utf8\n- utf8mb4\nallow-collates: []\nallow-engines:\n- innodb\n- tokudb\nmax-index-count: 12\nmax-column-count: 41\nmax-value-count: 102\nindex-prefix: idx_\nunique-key-prefix: uk_\nmax-subquery-depth: 6\nmax-varchar-length: 1022\ncolumn-not-allow-type:\n- boolean\nmin-cardinality: 2\nexplain-sql-report-type: pretty\nexplain-type: extended\nexplain-format: traditional\nexplain-warn-select-type:\n- \"\"\nexplain-warn-access-type:\n- ALL\nexplain-max-keys: 31\nexplain-min-keys: 10\nexplain-max-rows: 10002\nexplain-warn-extra:\n- Using temporary\n- Using filesort\nexplain-max-filtered: 120\nexplain-warn-scalability:\n- O(log(n))\nshow-warnings: true\nshow-last-query-cost: true\nquery: \"\"\nlist-heuristic-rules: true\nlist-rewrite-rules: true\nlist-test-sqls: true\nlist-report-types: true\nverbose: true\ndry-run: false\nmax-pretty-sql-length: 1022\n"
  },
  {
    "path": "test/fixture/test_Run_all_test_cases.golden",
    "content": "# Query: C3FAEDA6AD6D762B\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 86\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E969B9297DA79BA6\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  IS  NULL\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 8A106444D14B9880\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nHAVING  \n  title  = 'abc'\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 HAVING 子句\n\n* **Item:**  CLA.013\n\n* **Severity:**  L3\n\n* **Content:**  将查询的 HAVING 子句改写为 WHERE 中的查询条件，可以在查询处理期间使用索引。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: A0C5E62C724A121A\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  sakila. film  \nWHERE  \n  LENGTH  >= 60\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 868317D1973FD1B0\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  BETWEEN  60  \n  AND  84\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 707FE669669FA075\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  LIKE  'AIR%'\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | range | idx\\_title | idx\\_title | 767 | NULL | 2 | ☠️ **100.00%** | O(log n)+ | Using index condition |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.\n\n#### Extra信息解读\n\n* **Using index condition**: 在5.6版本后加入的新特性（Index Condition Pushdown）。Using index condition 会先条件过滤索引，过滤完索引后找到所有符合索引条件的数据行，随后用 WHERE 子句中的其他条件去过滤这些数据行。\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: DF916439ABD07664\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  title  IS  NOT  NULL\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: B9336971FF3D3792\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 114  \n  AND  title  = 'ALABAMA DEVIL'\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ref | idx\\_title | idx\\_title | 767 | const | 1 | n% | O(log n) | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列title添加索引，散粒度为: n%; 为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_title\\_length\\` (\\`title\\`,\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 68E48001ECD53152\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'ALABAMA DEVIL'\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ref | idx\\_title | idx\\_title | 767 | const | 1 | n% | O(log n) | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列title添加索引，散粒度为: n%; 为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_title\\_length\\` (\\`title\\`,\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 12FF1DAA3D425FA9\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10  \n  AND  title  = 'xyz'\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ref | idx\\_title,<br>idx\\_fk\\_language\\_id | idx\\_title | 767 | const | 1 | n% | O(log n) | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列title添加索引，散粒度为: n%; 为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_title\\_length\\` (\\`title\\`,\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E84CBAAC2E12BDEA\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  language_id  < 10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | idx\\_fk\\_language\\_id | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 6A0F035BD4E01018\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \n  AND  language_id  = 1  \nGROUP BY  \n  release_year\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | idx\\_fk\\_language\\_id | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; 为列language\\_id添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\_language\\_id\\_release\\_year\\` (\\`length\\`,\\`language\\_id\\`,\\`release\\_year\\`) ;\n\n\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 23D176AEA2947002\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  >= 123  \nGROUP BY  \n  release_year\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\_release\\_year\\` (\\`length\\`,\\`release\\_year\\`) ;\n\n\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 73DDF6E6D9E40384\n\n★ ★ ☆ ☆ ☆ 55分\n\n```sql\n\nSELECT  \n  release_year, language_id, SUM( LENGTH) \nFROM  \n  film  \nGROUP BY  \n  release_year, language_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列release\\_year添加索引，散粒度为: n%; 为列language\\_id添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_release\\_year\\_language\\_id\\` (\\`release\\_year\\`,\\`language\\_id\\`) ;\n\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: B3C502B4AA344196\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\n\nSELECT  \n  release_year, SUM( LENGTH) \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year, (LENGTH+ language_id)\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## 使用 SUM(COL) 时需注意 NPE 问题\n\n* **Item:**  FUN.006\n\n* **Severity:**  L1\n\n* **Content:**  当某一列的值全是 NULL 时，COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL，因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl\n\n# Query: 47044E1FE1A965A5\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  release_year, SUM( film_id) \nFROM  \n  film  \nGROUP BY  \n  release_year\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_release\\_year\\` (\\`release\\_year\\`) ;\n\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n# Query: 2BA1217F6C8CF0AB\n\n★ ☆ ☆ ☆ ☆ 35分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  address  \nGROUP BY  \n  address, district\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *address* | NULL | ALL | NULL | NULL | NULL | NULL | 603 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的address表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列address添加索引，散粒度为: n%; 为列district添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`address\\` add index \\`idx\\_address\\_district\\` (\\`address\\`,\\`district\\`) ;\n\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n# Query: 863A85207E4F410D\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  ABS( language_id) = 3  \nGROUP BY  \n  title\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | index | idx\\_title | idx\\_title | 767 | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 避免在 WHERE 条件中使用函数或其他运算符\n\n* **Item:**  FUN.001\n\n* **Severity:**  L2\n\n* **Content:**  虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\n\n# Query: DF59FD602E4AA368\n\n★ ★ ★ ☆ ☆ 70分\n\n```sql\n\nSELECT  \n  language_id  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  language_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using temporary; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\_release\\_year\\` (\\`length\\`,\\`release\\_year\\`) ;\n\n\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n# Query: F6DBEAA606D800FC\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using temporary; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\_release\\_year\\` (\\`length\\`,\\`release\\_year\\`) ;\n\n\n\n# Query: 6E9B96CA3F0E6BDA\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  ASC, language_id  DESC\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## ORDER BY 多个列但排序方向不同时可能无法使用索引\n\n* **Item:**  KEY.008\n\n* **Severity:**  L4\n\n* **Content:**  在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\n\n# Query: 2EAACFD7030EA528\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  release_year  \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nGROUP BY  \n  release_year  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using temporary; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\_release\\_year\\` (\\`length\\`,\\`release\\_year\\`) ;\n\n\n\n# Query: 5CE2F187DBF2A710\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 123  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\_release\\_year\\` (\\`length\\`,\\`release\\_year\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: E75234155B5E2E14\n\n★ ★ ★ ☆ ☆ 75分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: AFEEBF10A8D74E32\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  film_id  \nFROM  \n  film  \nORDER BY  \n  release_year  \nLIMIT  \n  10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n# Query: 965D5AC955824512\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 1E2CF4145EE706A5\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  < 100  \nORDER BY  \n  LENGTH  \nLIMIT  \n  10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: A314542EEE8571EE\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  customer  \nWHERE  \n  address_id  in  (224, 510) \nORDER BY  \n  last_name\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *customer* | NULL | range | idx\\_fk\\_address\\_id | idx\\_fk\\_address\\_id | 2 | NULL | 2 | ☠️ **100.00%** | O(log n)+ | Using index condition; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.\n\n#### Extra信息解读\n\n* **Using index condition**: 在5.6版本后加入的新特性（Index Condition Pushdown）。Using index condition 会先条件过滤索引，过滤完索引后找到所有符合索引条件的数据行，随后用 WHERE 子句中的其他条件去过滤这些数据行。\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的customer表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列address\\_id添加索引，散粒度为: n%; 为列last\\_name添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`customer\\` add index \\`idx\\_address\\_id\\_last\\_name\\` (\\`address\\_id\\`,\\`last\\_name\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 0BE2D79E2F1E7CB0\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  release_year  = 2016  \n  AND  LENGTH  != 1  \nORDER BY  \n  title\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列release\\_year添加索引，散粒度为: n%; 为列length添加索引，散粒度为: n%; 为列title添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_release\\_year\\_length\\_title\\` (\\`release\\_year\\`,\\`length\\`,\\`title\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## '!=' 运算符是非标准的\n\n* **Item:**  STA.001\n\n* **Severity:**  L0\n\n* **Content:**  \"<>\"才是标准SQL中的不等于运算符。\n\n# Query: 4E73AA068370E6A8\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  release_year  = 1995\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_release\\_year\\` (\\`release\\_year\\`) ;\n\n\n\n# Query: BA7111449E4F1122\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  title, replacement_cost  \nFROM  \n  film  \nWHERE  \n  language_id  = 5  \n  AND  LENGTH  = 70\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ref | idx\\_fk\\_language\\_id | idx\\_fk\\_language\\_id | 1 | const | 1 | n% | O(log n) | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; 为列language\\_id添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\_language\\_id\\` (\\`length\\`,\\`language\\_id\\`) ;\n\n\n\n# Query: B13E0ACEAF8F3119\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nSELECT  \n  title  \nFROM  \n  film  \nWHERE  \n  language_id  > 5  \n  AND  LENGTH  > 70\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | range | idx\\_fk\\_language\\_id | idx\\_fk\\_language\\_id | 1 | NULL | 1 | n% | O(log n)+ | Using index condition; Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.\n\n#### Extra信息解读\n\n* **Using index condition**: 在5.6版本后加入的新特性（Index Condition Pushdown）。Using index condition 会先条件过滤索引，过滤完索引后找到所有符合索引条件的数据行，随后用 WHERE 子句中的其他条件去过滤这些数据行。\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n# Query: A3FAB6027484B88B\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  = 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ref | idx\\_title | idx\\_title | 767 | const | 1 | n% | O(log n) | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列title添加索引，散粒度为: n%; 为列length添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_title\\_length\\_release\\_year\\` (\\`title\\`,\\`length\\`,\\`release\\_year\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: CB42080E9F35AB07\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \n  AND  title  = 'xyz' \nORDER BY  \n  release_year\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ref | idx\\_title | idx\\_title | 767 | const | 1 | n% | O(log n) | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列title添加索引，散粒度为: n%; 为列length添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_title\\_length\\_release\\_year\\` (\\`title\\`,\\`length\\`,\\`release\\_year\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: C4A212A42400411D\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 100  \nORDER BY  \n  release_year\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; 为列release\\_year添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\_release\\_year\\` (\\`length\\`,\\`release\\_year\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 4ECCA9568BE69E68\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  INNER JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *b* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *a* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.b.country\\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 485D56FC88BBBDB9\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *b* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 0D0DABACEDFF5765\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *a* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.b.country\\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 1E56C6CCEA2131CC\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *b* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\\_id | 1 | n% | O(log n) | Using where; Not exists |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行.\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的country表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列last\\_update添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`country\\` add index \\`idx\\_last\\_update\\` (\\`last\\_update\\`) ;\n\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: F5D30BCAC1E206A1\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *a* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.b.country\\_id | 5 | n% | O(log n) | Using where; Not exists |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行.\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的city表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列last\\_update添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`city\\` add index \\`idx\\_last\\_update\\` (\\`last\\_update\\`) ;\n\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断\n\n* **Item:**  ARG.006\n\n* **Severity:**  L1\n\n* **Content:**  使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描，如：select id from t where num is null;可以在num上设置默认值0，确保表中 num 列没有 NULL 值，然后这样查询： select id from t where num=0;\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 17D5BCF21DC2364C\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | PRIMARY | *b* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n| 2  | UNION | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 2  | UNION | *a* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.b.country\\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |\n| 2  | UNION | *a* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.b.country\\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **PRIMARY**: 最外层的select.\n\n* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集.\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: A4911095C201896F\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  a  \n  RIGHT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  a. last_update  IS  NULL  \nUNION  \nSELECT  \n  * \nFROM  \n  city  a  \n  LEFT JOIN  country  b  ON  a. country_id= b. country_id  \nWHERE  \n  b. last_update  IS  NULL\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | PRIMARY | *a* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.b.country\\_id | 5 | n% | O(log n) | Using where; Not exists |\n| 2  | UNION | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 2  | UNION | *b* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\\_id | 1 | n% | O(log n) | Using where; Not exists |\n| 2  | UNION | *b* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\\_id | 1 | n% | O(log n) | Using where; Not exists |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **PRIMARY**: 最外层的select.\n\n* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集.\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行.\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的city表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列last\\_update添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`city\\` add index \\`idx\\_last\\_update\\` (\\`last\\_update\\`) ;\n\n\n\n##  为sakila库的country表添加索引\n\n* **Item:**  IDX.002\n\n* **Severity:**  L2\n\n* **Content:**  为列last\\_update添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`country\\` add index \\`idx\\_last\\_update\\` (\\`last\\_update\\`) ;\n\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: 3FF20E28EC9CBEF9\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  JOIN  country\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *country* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *city* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.country.country\\_id | 5 | n% | O(log n) | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n# Query: 5C547F08EADBB131\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  LEFT JOIN  country\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *country* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\\_id | 1 | ☠️ **100.00%** | O(log n) | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n# Query: AF0C1EB58B23D2FA\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  country_id, last_update  \nFROM  \n  city  NATURAL  \n  RIGHT JOIN  country\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *country* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *city* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.country.country\\_id | 5 | ☠️ **100.00%** | O(log n) | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n# Query: 626571EAE84E2C8A\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  a. country_id, a. last_update  \nFROM  \n  city  a  STRAIGHT_JOIN  country  b  ON  a. country_id= b. country_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *a* | NULL | ALL | idx\\_fk\\_country\\_id | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *b* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\\_id | 1 | ☠️ **100.00%** | O(log n) | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n# Query: 50F2AB4243CE2071\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  a. address, a. postal_code  \nFROM  \n  sakila. address  a  \nWHERE  \n  a. city_id  IN  (\nSELECT  \n  c. city_id  \nFROM  \n  sakila. city  c)\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *c* | NULL | index | PRIMARY | idx\\_fk\\_country\\_id | 2 | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |\n| 1  | SIMPLE | *a* | NULL | ref | idx\\_fk\\_city\\_id | idx\\_fk\\_city\\_id | 2 | sakila.c.city\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 584CCEC8069B6947\n\n★ ★ ☆ ☆ ☆ 50分\n\n```sql\n\nSELECT  \n  city  \nFROM( \nSELECT  \n  city_id  \nFROM  \n  city  \nWHERE  \n  city  = \"A Corua (La Corua)\" \nORDER BY  \n  last_update  DESC  \nLIMIT  \n  50, 10) I  \n  JOIN  city  ON  (I. city_id  = city. city_id) \n  JOIN  country  ON  (country. country_id  = city. country_id) \nORDER BY  \n  city  DESC\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *<derived2>* | NULL | ALL | NULL | NULL | NULL | NULL | 60 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary; Using filesort |\n| 1  | PRIMARY | *city* | NULL | eq\\_ref | PRIMARY,<br>idx\\_fk\\_country\\_id | PRIMARY | 2 | I.city\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n| 1  | PRIMARY | *country* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\\_id | 1 | ☠️ **100.00%** | O(log n) | Using index |\n| 2  | DERIVED | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | n% | ☠️ **O(n)** | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **DERIVED**: 用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里.\n\n* **PRIMARY**: 最外层的select.\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的city表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列city添加索引，散粒度为: n%; 为列last\\_update添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`city\\` add index \\`idx\\_city\\_last\\_update\\` (\\`city\\`,\\`last\\_update\\`) ;\n\n\n\n## 同一张表被连接两次\n\n* **Item:**  JOI.002\n\n* **Severity:**  L4\n\n* **Content:**  相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 7F02E23D44A38A6D\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\nDELETE  city, country  \nFROM  \n  city  \n  INNER JOIN  country  using  (country_id) \nWHERE  \n  city. city_id  = 1\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | DELETE | *city* | NULL | const | PRIMARY,<br>idx\\_fk\\_country\\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |\n| 1  | DELETE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.\n\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: F8314ABD1CBF2FF1\n\n★ ★ ★ ☆ ☆ 70分\n\n```sql\nDELETE  city  \nFROM  \n  city  \n  LEFT JOIN  country  ON  city. country_id  = country. country_id  \nWHERE  \n  country. country  IS  NULL\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | DELETE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *country* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\\_id | 1 | n% | O(log n) | Using where; Not exists |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行.\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的country表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列country添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`country\\` add index \\`idx\\_country\\` (\\`country\\`) ;\n\n\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: 1A53649C43122975\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\nDELETE  a1, a2  \nFROM  \n  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | DELETE | *a2* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | DELETE | *a1* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.a2.country\\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: B862978586C6338B\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nDELETE FROM  \n  a1, a2  USING  city  AS  a1  \n  INNER JOIN  country  AS  a2  \nWHERE  \n  a1. country_id= a2. country_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | DELETE | *a2* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | DELETE | *a1* | NULL | ref | idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.a2.country\\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: F16FD63381EF8299\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nDELETE FROM  \n  film  \nWHERE  \n  LENGTH  > 100\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | DELETE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 使用DELETE/DROP/TRUNCATE等操作时注意备份\n\n* **Item:**  SEC.003\n\n* **Severity:**  L0\n\n* **Content:**  在执行高危操作之前对数据进行备份是十分有必要的。\n\n# Query: 08CFE41C7D20AAC8\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nUPDATE  \n  city  \n  INNER JOIN  country  USING( country_id) \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | UPDATE | *city* | NULL | const | PRIMARY,<br>idx\\_fk\\_country\\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |\n| 1  | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.\n\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n# Query: C15BDF2C73B5B7ED\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nUPDATE  \n  city  \n  INNER JOIN  country  ON  city. country_id  = country. country_id  \n  INNER JOIN  address  ON  city. city_id  = address. city_id  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. city_id= 10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | UPDATE | *city* | NULL | const | PRIMARY,<br>idx\\_fk\\_country\\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |\n| 1  | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |\n| 1  | SIMPLE | *address* | NULL | ref | idx\\_fk\\_city\\_id | idx\\_fk\\_city\\_id | 2 | const | 1 | ☠️ **100.00%** | O(log n) | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n\n## 不建议使用联表删除或更新\n\n* **Item:**  JOI.007\n\n* **Severity:**  L4\n\n* **Content:**  当需要同时删除或更新多张表时建议使用简单语句，一条 SQL 只删除或更新一张表，尽量不要将多张表的操作在同一条语句。\n\n# Query: FCD1ABF36F8CDAD7\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nUPDATE  \n  city, country  \nSET  \n  city. city  = 'Abha', \n  city. last_update  = '2006-02-15 04:45:25', \n  country. country  = 'Afghanistan' \nWHERE  \n  city. country_id  = country. country_id  \n  AND  city. city_id= 10\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | UPDATE | *city* | NULL | const | PRIMARY,<br>idx\\_fk\\_country\\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |\n| 1  | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.\n\n\n# Query: FE409EB794EE91CF\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nUPDATE  \n  film  \nSET  \n  LENGTH  = 10  \nWHERE  \n  language_id  = 20\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | UPDATE | *film* | NULL | range | idx\\_fk\\_language\\_id | idx\\_fk\\_language\\_id | 1 | const | 1 | ☠️ **100.00%** | O(log n)+ | Using where |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n# Query: 3656B13CC4F888E2\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\nINSERT  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *country* | NULL | index | NULL | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 2F7439623B712317\n\n★ ★ ★ ★ ★ 100分\n\n```sql\nINSERT  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3)\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n# Query: 11EC7AAACC97DC0F\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\nINSERT  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: E3DDA1A929236E72\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\nREPLACE  INTO  city  (country_id) \nSELECT  \n  country_id  \nFROM  \n  country\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *country* | NULL | index | NULL | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 466F1AC2F5851149\n\n★ ★ ★ ★ ★ 100分\n\n```sql\nREPLACE  INTO  city  (country_id) \nVALUES  \n  (1), \n  (2), \n  (3)\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n# Query: A7973BDD268F926E\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\nREPLACE  INTO  city  (country_id) \nSELECT  \n  10  \nFROM  \n  DUAL\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |\n\n\n\n### Explain信息解读\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n* **Item:**  LCK.001\n\n* **Severity:**  L3\n\n* **Content:**  INSERT INTO xx SELECT 加锁粒度较大请谨慎\n\n# Query: 105C870D5DFB6710\n\n★ ★ ★ ☆ ☆ 65分\n\n```sql\n\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  (\nSELECT  \n  film_id  \nFROM  \n  film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n) film\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | index | NULL | idx\\_fk\\_language\\_id | 1 | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 执行计划中嵌套连接深度过深\n\n* **Item:**  SUB.004\n\n* **Severity:**  L3\n\n* **Content:**  MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。\n\n# Query: 16C2B14E7DAA9906\n\n★ ☆ ☆ ☆ ☆ 35分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  language_id  = (\nSELECT  \n  language_id  \nFROM  \n  language  \nLIMIT  \n  1)\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *film* | NULL | ALL | idx\\_fk\\_language\\_id | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where |\n| 2  | SUBQUERY | *language* | NULL | index | NULL | PRIMARY | 1 | NULL | 6 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **PRIMARY**: 最外层的select.\n\n* **SUBQUERY**: 子查询中的第一个SELECT查询, 不依赖于外部查询的结果集.\n\n#### Type信息解读\n\n* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 未使用 ORDER BY 的 LIMIT 查询\n\n* **Item:**  RES.002\n\n* **Severity:**  L4\n\n* **Content:**  没有 ORDER BY 的 LIMIT 会导致非确定性的结果，这取决于查询执行计划。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 16CB4628D2597D40\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *i* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | PRIMARY | *o* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.i.city\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n| 2  | UNION | *o* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 2  | UNION | *i* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n| 2  | UNION | *i* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **PRIMARY**: 最外层的select.\n\n* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集.\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: EA50643B01E139A8\n\n★ ☆ ☆ ☆ ☆ 35分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  (\nSELECT  \n  * \nFROM  \n  actor  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE'\n) t  \nWHERE  \n  last_update= '2006-02-15 04:34:33' \n  AND  last_name= 'CHASE' \nGROUP BY  \n  first_name\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *actor* | NULL | ref | idx\\_actor\\_last\\_name | idx\\_actor\\_last\\_name | 137 | const | 2 | n% | O(log n) | Using where; Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的actor表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列last\\_name添加索引，散粒度为: n%; 为列last\\_update添加索引，散粒度为: n%; 为列first\\_name添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`actor\\` add index \\`idx\\_last\\_name\\_last\\_update\\_first\\_name\\` (\\`last\\_name\\`,\\`last\\_update\\`,\\`first\\_name\\`) ;\n\n\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 非确定性的 GROUP BY\n\n* **Item:**  RES.001\n\n* **Severity:**  L4\n\n* **Content:**  SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中，因此这些值的结果将是非确定性的。如：select a, b, c from tbl where foo=\"bar\" group by a，该 SQL 返回的结果就是不确定的。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: 7598A4EDE6CFA6BE\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  city  i  \n  LEFT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  o. country_id  is  null  \nUNION  \nSELECT  \n  * \nFROM  \n  city  i  \n  RIGHT JOIN  country  o  ON  i. city_id= o. country_id  \nWHERE  \n  i. city_id  is  null\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *i* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | PRIMARY | *o* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.i.city\\_id | 1 | ☠️ **100.00%** | O(log n) | Using where; Not exists |\n| 2  | UNION | *o* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 2  | UNION | *i* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\\_id | 1 | ☠️ **100.00%** | O(log n) | Using where; Not exists |\n| 2  | UNION | *i* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\\_id | 1 | ☠️ **100.00%** | O(log n) | Using where; Not exists |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **PRIMARY**: 最外层的select.\n\n* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集.\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行.\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 如果您不在乎重复的话，建议使用 UNION ALL 替代 UNION\n\n* **Item:**  SUB.002\n\n* **Severity:**  L2\n\n* **Content:**  与去除重复的UNION不同，UNION ALL允许重复元组。如果您不关心重复元组，那么使用UNION ALL将是一个更快的选项。\n\n# Query: 1E8B70E30062FD13\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nSELECT  \n  first_name, last_name, email  \nFROM  \n  customer  STRAIGHT_JOIN  address  ON  customer. address_id= address. address_id\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *customer* | NULL | ALL | idx\\_fk\\_address\\_id | NULL | NULL | NULL | 599 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |\n| 1  | SIMPLE | *address* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.customer.address\\_id | 1 | ☠️ **100.00%** | O(log n) | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n\n# Query: E48A20D0413512DA\n\n★ ★ ☆ ☆ ☆ 50分\n\n```sql\n\nSELECT  \n  ID, name  \nFROM  \n  (\nSELECT  \n  address  \nFROM  \n  customer_list  \nWHERE  \n  SID= 1  \nORDER BY  \n  phone  \nLIMIT  \n  50, 10) a  \n  JOIN  customer_list  l  ON  (a. address= l. address) \n  JOIN  city  c  ON  (c. city= l. city) \nORDER BY  \n  phone  desc\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *country* | NULL | index | PRIMARY | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index; Using temporary; Using filesort |\n| 1  | PRIMARY | *city* | NULL | ref | PRIMARY,<br>idx\\_fk\\_country\\_id | idx\\_fk\\_country\\_id | 2 | sakila.country.country\\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |\n| 1  | PRIMARY | *c* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | n% | ☠️ **O(n)** | Using where; Using join buffer (hash join) |\n| 1  | PRIMARY | *a* | NULL | ref | PRIMARY,<br>idx\\_fk\\_city\\_id | idx\\_fk\\_city\\_id | 2 | sakila.city.city\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n| 1  | PRIMARY | *cu* | NULL | ref | idx\\_fk\\_address\\_id | idx\\_fk\\_address\\_id | 2 | sakila.a.address\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n| 1  | PRIMARY | *<derived2>* | NULL | ref | <auto\\_key0> | <auto\\_key0> | 152 | sakila.a.address | 6 | ☠️ **100.00%** | O(log n) | Using index |\n| 2  | DERIVED | *a* | NULL | ALL | PRIMARY,<br>idx\\_fk\\_city\\_id | NULL | NULL | NULL | 603 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |\n| 2  | DERIVED | *cu* | NULL | ref | idx\\_fk\\_store\\_id,<br>idx\\_fk\\_address\\_id | idx\\_fk\\_address\\_id | 2 | sakila.a.address\\_id | 1 | n% | O(log n) | Using where |\n| 2  | DERIVED | *city* | NULL | eq\\_ref | PRIMARY,<br>idx\\_fk\\_country\\_id | PRIMARY | 2 | sakila.a.city\\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |\n| 2  | DERIVED | *country* | NULL | eq\\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\\_id | 1 | ☠️ **100.00%** | O(log n) | Using index |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **DERIVED**: 用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里.\n\n* **PRIMARY**: 最外层的select.\n\n#### Type信息解读\n\n* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.\n\n* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.\n\n* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.\n\n* **Using join buffer**: 从已有连接中找被读入缓存的数据, 并且通过缓存来完成与当前表的连接.\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的city表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列city添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`city\\` add index \\`idx\\_city\\` (\\`city\\`) ;\n\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 同一张表被连接两次\n\n* **Item:**  JOI.002\n\n* **Severity:**  L4\n\n* **Content:**  相同的表在 FROM 子句中至少出现两次，可以简化为对该表的单次访问。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n# Query: B0BA5A7079EA16B3\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  DATE( last_update) = '2006-02-15'\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n## 避免在 WHERE 条件中使用函数或其他运算符\n\n* **Item:**  FUN.001\n\n* **Severity:**  L2\n\n* **Content:**  虽然在 SQL 中使用函数可以简化很多复杂的查询，但使用了函数的查询无法利用表中已经建立的索引，该查询将会是全表扫描，性能较差。通常建议将列名写在比较运算符左侧，将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号，这会对阅读产生比较大的困扰。\n\n# Query: 18A2AD1395A58EAE\n\n★ ★ ★ ☆ ☆ 60分\n\n```sql\n\nSELECT  \n  last_update  \nFROM  \n  film  \nGROUP BY  \n  DATE( last_update)\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n# Query: 60F234BA33AAC132\n\n★ ★ ★ ☆ ☆ 70分\n\n```sql\n\nSELECT  \n  last_update  \nFROM  \n  film  \nORDER BY  \n  DATE( last_update)\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## ORDER BY 的条件为表达式\n\n* **Item:**  CLA.009\n\n* **Severity:**  L2\n\n* **Content:**  当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n# Query: 1ED2B7ECBA4215E1\n\n★ ★ ★ ★ ☆ 80分\n\n```sql\n\nSELECT  \n  description  \nFROM  \n  film  \nWHERE  \n  description  IN( 'NEWS', \n  'asd'\n) \nGROUP BY  \n  description\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where; Using temporary |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列description添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_description\\` (\\`description\\`(191)) ;\n\n\n\n## 请为 GROUP BY 显示添加 ORDER BY 条件\n\n* **Item:**  CLA.008\n\n* **Severity:**  L2\n\n* **Content:**  默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生，如果不需要排序建议添加 'ORDER BY NULL'。\n\n# Query: 255BAC03F56CDBC7\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  address  \nADD  \n  index  idx_city_id( city_id)\n```\n\n## OK\n\n# Query: C315BC4EE0F4E523\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`)\n```\n\n## 提醒：请将索引属性顺序与查询对齐\n\n* **Item:**  KEY.004\n\n* **Severity:**  L0\n\n* **Content:**  如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\n\n# Query: 9BB74D074BA0727C\n\n★ ★ ★ ★ ☆ 90分\n\n```sql\n\nALTER TABLE  \n  inventory  \nADD  \n  index  `idx_store_film` (\n    `store_id`, `film_id`), \n      ADD  \n      index  `idx_store_film` (\n        `store_id`, `film_id`), \n              ADD  \n          index  `idx_store_film` (\n            `store_id`, `film_id`)\n```\n\n##  索引名称已存在\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  Duplicate key name 'idx\\_store\\_film'\n\n* **Case:** alter table inventory add index \\`idx\\_store\\_film\\` (\\`store\\_id\\`,\\`film\\_id\\`),add index \\`idx\\_store\\_film\\` (\\`store\\_id\\`,\\`film\\_id\\`),add index \\`idx\\_store\\_film\\` (\\`store\\_id\\`,\\`film\\_id\\`)\n\n\n## 提醒：请将索引属性顺序与查询对齐\n\n* **Item:**  KEY.004\n\n* **Severity:**  L0\n\n* **Content:**  如果为列创建复合索引，请确保查询属性与索引属性的顺序相同，以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐，那么DBMS可能无法在查询处理期间使用索引。\n\n# Query: C95B5C028C8FFF95\n\n★ ☆ ☆ ☆ ☆ 30分\n\n```sql\n\nSELECT  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n), \nCOUNT( DISTINCT  (\n  t. city)) \n  FROM  \n    city  t  \n  WHERE  \n    t. last_update  > '2018-10-22 00:00:00' \n    AND  t. city  LIKE  '%Chrome%' \n    AND  t. city  = 'eip' \n  GROUP BY  \n    DATE_FORMAT( t. last_update, '%Y-%m-%d'\n) \nORDER BY  \n  DATE_FORMAT( t. last_update, '%Y-%m-%d'\n)\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *t* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | n% | ☠️ **O(n)** | Using where; Using filesort |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.\n\n\n##  为sakila库的city表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列city添加索引，散粒度为: n%; 为列last\\_update添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`city\\` add index \\`idx\\_city\\_last\\_update\\` (\\`city\\`,\\`last\\_update\\`) ;\n\n\n\n## 建议使用 AS 关键字显示声明一个别名\n\n* **Item:**  ALI.001\n\n* **Severity:**  L0\n\n* **Content:**  在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。\n\n## 不建议使用前项通配符查找\n\n* **Item:**  ARG.001\n\n* **Severity:**  L4\n\n* **Content:**  例如 \"％foo\"，查询参数有一个前项通配符的情况无法使用已有索引。\n\n## ORDER BY 的条件为表达式\n\n* **Item:**  CLA.009\n\n* **Severity:**  L2\n\n* **Content:**  当 ORDER BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## GROUP BY 的条件为表达式\n\n* **Item:**  CLA.010\n\n* **Severity:**  L2\n\n* **Content:**  当 GROUP BY 条件为表达式或函数时会使用到临时表，如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。\n\n## ORDER BY 多个列但排序方向不同时可能无法使用索引\n\n* **Item:**  KEY.008\n\n* **Severity:**  L4\n\n* **Content:**  在 MySQL 8.0 之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。\n\n# Query: C11ECE7AE5F80CE5\n\n★ ★ ☆ ☆ ☆ 45分\n\n```sql\ncreate  table  hello. t  (id  int  unsigned)\n```\n\n## 建议为表添加注释\n\n* **Item:**  CLA.011\n\n* **Severity:**  L1\n\n* **Content:**  为表添加注释能够使得表的意义更明确，从而为日后的维护带来极大的便利。\n\n## 请为列添加默认值\n\n* **Item:**  COL.004\n\n* **Severity:**  L1\n\n* **Content:**  请为列添加默认值，如果是 ALTER 操作，请不要忘记将原字段的默认值写上。字段无默认值，当表较大时无法在线变更表结构。\n\n## 列未添加注释\n\n* **Item:**  COL.005\n\n* **Severity:**  L1\n\n* **Content:**  建议对表中每个列添加注释，来明确每个列在表中的含义及作用。\n\n## 未指定主键或主键非 int 或 bigint\n\n* **Item:**  KEY.007\n\n* **Severity:**  L4\n\n* **Content:**  未指定主键或主键非 int 或 bigint，建议将主键设置为 int unsigned 或 bigint unsigned。\n\n## 请为表选择合适的存储引擎\n\n* **Item:**  TBL.002\n\n* **Severity:**  L4\n\n* **Content:**  建表或修改表的存储引擎时建议使用推荐的存储引擎，如：innodb\n\n# Query: 291F95B7DCB74C21\n\n★ ★ ★ ★ ☆ 95分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  tb  \nWHERE  \n  data  >= ''\n```\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n# Query: 084DA3E3EE38DD85\n\n★ ★ ★ ★ ★ 100分\n\n```sql\n\nALTER TABLE  \n  tb  alter  column  id  \nDROP  \n  DEFAULT\n```\n\n# Query: B48292EDB9D0E010\n\n★ ★ ☆ ☆ ☆ 40分\n\n```sql\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film  \nWHERE  \n  last_update  > '2016-03-27 02:01:01'\n) as  d\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *<derived2>* | NULL | system | NULL | NULL | NULL | NULL | 1 | ☠️ **100.00%** | O(1) | NULL |\n| 2  | DERIVED | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **DERIVED**: 用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里.\n\n* **PRIMARY**: 最外层的select.\n\n#### Type信息解读\n\n* **system**: 这是const连接类型的一种特例, 该表仅有一行数据(=系统表).\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列last\\_update添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_last\\_update\\` (\\`last\\_update\\`) ;\n\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n## 不建议在子查询中使用函数\n\n* **Item:**  SUB.006\n\n* **Severity:**  L2\n\n* **Content:**  MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\n\n# Query: 4A39009B402BAD9B\n\n★ ★ ☆ ☆ ☆ 50分\n\n```sql\n\nSELECT  \n  maxId, minId  \nFROM  \n  (\nSELECT  \n  MAX( film_id) maxId, MIN( film_id) minId  \nFROM  \n  film) as  d\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | PRIMARY | *<derived2>* | NULL | system | NULL | NULL | NULL | NULL | 1 | ☠️ **100.00%** | O(1) | NULL |\n| 2  | DERIVED | *NULL* | NULL | NULL | NULL | NULL | NULL | NULL | 0 | n% | NULL | Select tables optimized away |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **DERIVED**: 用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里.\n\n* **PRIMARY**: 最外层的select.\n\n#### Type信息解读\n\n* **system**: 这是const连接类型的一种特例, 该表仅有一行数据(=系统表).\n\n#### Extra信息解读\n\n* **Select tables optimized away**: 仅通过使用索引，优化器可能仅从聚合函数结果中返回一行。如：在没有 GROUP BY 子句的情况下，基于索引优化 MIN/MAX 操作，或者对于 MyISAM 存储引擎优化 COUNT(*) 操作，不必等到执行阶段再进行计算，查询执行计划生成的阶段即完成优化。\n\n\n## 最外层 SELECT 未指定 WHERE 条件\n\n* **Item:**  CLA.001\n\n* **Severity:**  L4\n\n* **Content:**  SELECT 语句没有 WHERE 子句，可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\\*) 类型的请求如果不要求精度，建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。\n\n## MySQL 对子查询的优化效果不佳\n\n* **Item:**  SUB.001\n\n* **Severity:**  L4\n\n* **Content:**  MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。\n\n## 不建议在子查询中使用函数\n\n* **Item:**  SUB.006\n\n* **Severity:**  L2\n\n* **Content:**  MySQL将外部查询中的每一行作为依赖子查询执行子查询，如果在子查询中使用函数，即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。\n\n"
  },
  {
    "path": "test/fixture/test_Run_default_printconfig_cases.golden",
    "content": "online-dsn:\n  user: \"\"\n  password: '********'\n  net: tcp\n  addr: 127.0.0.1:3306\n  schema: information_schema\n  charset: utf8\n  collation: utf8mb4_general_ci\n  loc: UTC\n  tls: \"\"\n  server-public-key: \"\"\n  max-allowed-packet: 4194304\n  params:\n    charset: utf8\n  timeout: 3s\n  read-timeout: 0s\n  write-timeout: 0s\n  allow-native-passwords: true\n  allow-old-passwords: false\n  disable: false\ntest-dsn:\n  user: \"\"\n  password: '********'\n  net: tcp\n  addr: 127.0.0.1:3306\n  schema: information_schema\n  charset: utf8\n  collation: utf8mb4_general_ci\n  loc: UTC\n  tls: \"\"\n  server-public-key: \"\"\n  max-allowed-packet: 4194304\n  params:\n    charset: utf8\n  timeout: 3s\n  read-timeout: 0s\n  write-timeout: 0s\n  allow-native-passwords: true\n  allow-old-passwords: false\n  disable: false\nallow-online-as-test: false\ndisable-version-check: false\ndrop-test-temporary: true\ncleanup-test-database: false\nonly-syntax-check: false\nsampling-statistic-target: 100\nsampling: false\nsampling-condition: \"\"\nprofiling: false\ntrace: false\nexplain: true\ndelimiter: ;\nlog-level: 3\nlog-output: /dev/null\nreport-type: markdown\nreport-css: \"\"\nreport-javascript: \"\"\nreport-title: SQL优化分析报告\nmarkdown-extensions: 94\nmarkdown-html-flags: 0\nignore-rules:\n- COL.011\nrewrite-rules:\n- delimiter\n- orderbynull\n- groupbyconst\n- dmlorderby\n- having\n- star2columns\n- insertcolumns\n- distinctstar\nblacklist: \"\"\nmax-join-table-count: 5\nmax-group-by-cols-count: 5\nmax-distinct-count: 5\nmax-index-cols-count: 5\nmax-text-cols-count: 2\nmax-total-rows: 9999999\nmax-query-cost: 9999\nspaghetti-query-length: 2048\nallow-drop-index: false\nmax-in-count: 10\nmax-index-bytes-percolumn: 767\nmax-index-bytes: 3072\nallow-charsets:\n- utf8\n- utf8mb4\nallow-collates: []\nallow-engines:\n- innodb\nmax-index-count: 10\nmax-column-count: 40\nmax-value-count: 100\nindex-prefix: idx_\nunique-key-prefix: uk_\nmax-subquery-depth: 5\nmax-varchar-length: 1024\ncolumn-not-allow-type:\n- boolean\nmin-cardinality: 0\nexplain-sql-report-type: pretty\nexplain-type: extended\nexplain-format: traditional\nexplain-warn-select-type:\n- \"\"\nexplain-warn-access-type:\n- ALL\nexplain-max-keys: 3\nexplain-min-keys: 0\nexplain-max-rows: 10000\nexplain-warn-extra:\n- Using temporary\n- Using filesort\nexplain-max-filtered: 100\nexplain-warn-scalability:\n- O(n)\nshow-warnings: false\nshow-last-query-cost: false\nquery: \"\"\nlist-heuristic-rules: false\nlist-rewrite-rules: false\nlist-test-sqls: false\nlist-report-types: false\nverbose: false\ndry-run: true\nmax-pretty-sql-length: 1024\n"
  },
  {
    "path": "test/fixture/test_Simple_Query_Optimizer.golden",
    "content": "# Query: 5767EE37339B2402\n\n★ ★ ★ ★ ☆ 85分\n\n```sql\n\nSELECT  \n  * \nFROM  \n  film  \nWHERE  \n  LENGTH  > 120\n```\n\n##  Explain信息\n\n| id | select\\_type | table | partitions | type | possible_keys | key | key\\_len | ref | rows | filtered | scalability | Extra |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1  | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |\n\n\n\n### Explain信息解读\n\n#### SelectType信息解读\n\n* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).\n\n#### Type信息解读\n\n* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.\n\n#### Extra信息解读\n\n* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.\n\n\n##  为sakila库的film表添加索引\n\n* **Item:**  IDX.001\n\n* **Severity:**  L2\n\n* **Content:**  为列length添加索引，散粒度为: n%; \n\n* **Case:** ALTER TABLE \\`sakila\\`.\\`film\\` add index \\`idx\\_length\\` (\\`length\\`) ;\n\n\n\n## 不建议使用 SELECT * 类型查询\n\n* **Item:**  COL.001\n\n* **Severity:**  L1\n\n* **Content:**  当表结构变更时，使用 \\* 通配符选择所有列将导致查询的含义和行为会发生更改，可能导致查询返回更多的数据。\n\n"
  },
  {
    "path": "test/main.bats",
    "content": "#!/usr/bin/env bats\n\nload test_helper\n\n# 1. 检查版本输出格式是否正确\n# 2. 检查版本是否为当天编译的\n@test \"Test soar version\" {\n  run ${SOAR_BIN} -version\n  [ \"$status\" -eq 0 ]\n  [ \"${lines[0]%% *}\" == \"Version:\" ]\n  [ \"${lines[1]%% *}\" == \"Branch:\" ]\n  [ \"${lines[2]%% *}\" == \"Compile:\" ]\n  [ $(expr \"${lines[2]}\" : \"Compile: $(date +'%Y-%m-%d').*\") -ne 0 ]   # 检查当前版本是否为今日编译的\n}\n\n# 3. 无参数执行是否正确\n@test \"No arguments prints message\" {\n  run ${SOAR_BIN}\n  [ $status -eq 1 ]\n  [ \"${lines[0]}\" == 'Args format error, use --help see how to use it!' ]\n}\n\n# 4. 检查输出的默认值是否改变 soar -print-config  加log-outpt 是因为日志默认是相对路径 \n@test \"Run default printconfig cases\" {\n  ${SOAR_BIN} -print-config -log-output=/dev/null  > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# 5. soar 使用 config 配置文件路径是否正确\n# 13. soar -check-config 数据库连接配置检查 *\n# soar 数据库测试（线上、线下、-allow-online-as-test）\n@test \"Check config cases\" {\n  run ${SOAR_BIN_ENV} -check-config\n  [ $status -eq 0 ]\n  [ -z ${output} ]\n}\n\n# 6. soar 使用配置文件修改默认参数是否正确\n# 注意：不启用的配置为默认配置项目\n@test \"Check the default config of the changes\" {\n  ${SOAR_BIN} -config ${BATS_FIXTURE_DIRNAME}/${BATS_TEST_NAME}.golden -print-config  -log-output=/dev/null > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# 8. 执行 soar -query 为文件时是否正常\n@test \"Check soar query for input file\" {\n  ${SOAR_BIN} -query <(${SOAR_BIN} -list-test-sqls) > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# 9. 管道输入 SQL 是否正常\n@test \"Check soar for pipe input\" {\n  ${SOAR_BIN} -list-test-sqls |${SOAR_BIN} > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n# 10. report 为 json 格式是否正常\n@test \"Check soar report for json\" {\n  ${SOAR_BIN} -query \"select * from film\" \\\n    -report-type json > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# 10. report 为 markdown 格式是否正常\n@test \"Check soar report for markdown\" {\n  ${SOAR_BIN} -query \"select * from film\" \\\n    -report-type markdown > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# 11. report 格式 html 检查\n@test \"Check soar report for html\" {\n  ${SOAR_BIN} -query \"select * from film\" \\\n    -report-title \"soar report check\" \\\n    -report-type html > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# 12. 黑名单功能是否正常\n# soar 的日志和黑名单的相对路径都相对于 soar 的二进制文件路径说的\n@test \"Check soar blacklist\" {\n  run ${SOAR_BIN} -blacklist ../etc/soar.blacklist -query \"show processlist;\"\n  [ $status -eq 0 ]\n  [ -z ${output} ]\n}\n\n# 13. soar -check-config 数据库连接配置检查 *\n# 参见 5\n\n# 14. soar -help 检查\n@test \"Check soar help\" {\n  run ${SOAR_BIN} -help\n  [ $status -eq 0 ]\n  [ \"${#lines[@]}\" -gt 30 ]\n}\n\n# 15. soar 数据库测试（线上、线下、-allow-online-as-test）\n# 参见 5\n\n# 16. 语法检查（正确）\n@test \"Syntax Check OK\" {\n  run ${SOAR_BIN} -query \"select * from film\" -only-syntax-check\n  [ $status -eq 0 ]\n  [ -z $ouput ]\n}\n# 16. 语法检查（错误）\n@test \"Syntax Check Error\" {\n  run ${SOAR_BIN} -query \"select * frm film\" -only-syntax-check\n  [ $status -eq 1 ]\n  [ -n $ouput ]\n}\n\n# 17. dsn 检查\n@test \"Check soar test dsn root:passwd@host:port/db\" {\n  run ${SOAR_BIN} -online-dsn=\"root:pase@D@192.168.12.11:3306/testDB\" -print-config\n  [ $(expr \"$output\" : \".*user: root\") -ne 0 ]\n  [ $(expr \"$output\" : \".*addr: 192.168.12.11:3306\") -ne 0 ]\n  [ $(expr \"$output\" : \".*schema: testDB\") -ne 0 ]\n  [ $(expr \"$output\" : \".*charset: utf8\") -ne 0 ]\n}\n\n# 18. 日志中是否含有密码\n@test \"Check log has password\" {\n  ${SOAR_BIN_ENV} -query \"select * from film\" -log-level=7\n  run grep \"1tIsB1g3rt\" ${SOAR_BIN}.log\n  [ ${status} -eq 1 ]\n}\n\n# 18. 输出中是否含有密码\n@test \"Check stdout has password\" {\n  run ${SOAR_BIN_ENV} -query \"select * from film\" -log-level=7\n  [ $(expr \"$output\" : \".*1tIsB1g3rt.*\") -eq 0 ]\n  [ ${status} -eq 0 ]\n}\n\n# 20. 单条 SQL 中 JOIN 表的最大数量超过 2\n@test \"Check Max Join Table Count Overflow\" {\n  ${SOAR_BIN}  -max-join-table-count 2 -query=\"select a from b join c join d\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# 21. 单条 SQL 中 JOIN 表未超过时是否正常默认为 5\n@test \"Check Max Join Table Count Default\" {\n  ${SOAR_BIN} -query=\"select a from b join c join d\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n"
  },
  {
    "path": "test/other.bats",
    "content": "#!/usr/bin/env bats\n\nload test_helper\n"
  },
  {
    "path": "test/query.bats",
    "content": "#!/usr/bin/env bats\n\nload test_helper\n\n@test \"Check Query Optimizer\" {\n  run ${SOAR_BIN} -query \"select * from film where length > 120\"\n  [ $status -eq 0 ]\n}\n\n#\n@test \"Check get tables from SQL\" {\n  ${SOAR_BIN} -list-test-sqls | ${SOAR_BIN} -report-type tables > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  ${SOAR_BIN} -list-test-sqls | ${SOAR_BIN} -report-type tables -test-dsn \"/sakila\" >> ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# SQL 语法检查\n\n# 1. soar SQL 分隔符是否正常 (-delimiter)\n@test \"Check Soar Delimiter\" {\n  ${SOAR_BIN} -delimiter \"@\" -query \"select b from c @ select a from b\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 2. max-column-count （ddl 中才会有提示）\n@test \"Check Soar Max Column Count\" {\n  ${SOAR_BIN} -query \"create table a (a int,b int,c int,d int)\" --max-column-count 3 > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# SQL 指纹检查\n\n# 1. 检测各种类型的 SQL 语句，以及多条 SQL 语句的情况下，指纹是否正确\n@test \"Check Soar SQL Fingerprint\" {\n  ${SOAR_BIN} -list-test-sqls | ${SOAR_BIN} -report-type \"fingerprint\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 1. 检测各种类型的 SQL 语句，以及多条SQL语句的情况下，压缩是否正确\n# 1. 检测各种类型的 SQL 语句，以及多条SQL语句的情况下，美化是否正确\n@test \"Check Soar SQL pretty And Compress \" {\n  ${SOAR_BIN} -list-test-sqls |${SOAR_BIN} -report-type \"pretty\"| ${SOAR_BIN} -report-type \"compress\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# SQL 改写检查\n\n# 1. 检查 SQL 改写 dml2select\n@test \"Check Soar SQL Rewrite Dml2select \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"dml2select\" \\\n  -query \"DELETE FROM film WHERE length > 100\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 2. 检查 SQL 改写 Star2columns\n@test \"Check Soar SQL Rewrite Star2columns \" {\n  ${SOAR_BIN_ENV} -report-type \"rewrite\" -rewrite-rules \"star2columns\" \\\n  -query \"select * from film where length > 120\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 4. 检查 SQL 改写 Having\n@test \"Check Soar SQL Rewrite Having \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"having\" \\\n  -query \"SELECT state, COUNT(*) FROM Drivers GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 5. 检查 SQL 改写 orderbynull\n@test \"Check Soar SQL Rewrite Orderbynull \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"orderbynull\" \\\n  -query \"SELECT sum(col1) FROM tbl GROUP BY col\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 6. 检查 SQL 改写 unionall\n@test \"Check Soar SQL Rewrite Unionall \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"unionall\" \\\n  -query \"select country_id from city union select country_id from country\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 7. 检查 SQL 改写 or2in\n@test \"Check Soar SQL Rewrite Or2in \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"or2in\" \\\n  -query \"select country_id from city where col1 = 1 or (col2 = 1 or col2 = 2 ) or col1 = 3;\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 7. 检查 SQL 改写 dmlorderby\n@test \"Check Soar SQL Rewrite Dmlorderby \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"dmlorderby\" \\\n  -query \"DELETE FROM tbl WHERE col1=1 ORDER BY col\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 8. 检查 SQL 改写 distinctstar\n@test \"Check Soar SQL Rewrite Distinctstar \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"distinctstar\" \\\n  -query \"SELECT DISTINCT * FROM film;\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 9. 检查 SQL 改写 standard\n@test \"Check Soar SQL Rewrite Standard \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"standard\" \\\n  -query \"SELECT sum(col1) FROM tbl GROUP BY 1;\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 10. 检查 SQL 改写 mergealter\n# Linux macOS sort 排序不一致 https://unix.stackexchange.com/questions/362728/why-does-gnu-sort-sort-differently-on-my-osx-machine-and-linux-machine\n@test \"Check Soar SQL Rewrite Mergealter \" {\n  ${SOAR_BIN} -list-test-sqls |${SOAR_BIN} -report-type rewrite -rewrite-rules mergealter | sort -bdfi > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  echo \"${output}\"\n  [ $status -eq 0 ]\n}\n\n# 11. 检查 SQL 改写 alwaystrue\n@test \"Check Soar SQL Rewrite Alwaystrue \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"alwaystrue\" \\\n  -query \"SELECT count(col) FROM tbl where 'a'= 'a' or ('b' = 'b' and a = 'b');\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 12. 检查 SQL 改写 countstar\n@test \"Check Soar SQL Rewrite Countstar \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"countstar\" \\\n  -query \"SELECT count(col) FROM tbl GROUP BY 1;\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n\n# 16. 检查 SQL 改写 truncate\n@test \"Check Soar SQL Rewrite Truncate \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"truncate\" \\\n  -query \"DELETE FROM tbl\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 17. 检查 SQL 改写 rmparenthesis\n@test \"Check Soar SQL Rewrite Rmparenthesis \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"rmparenthesis\" \\\n  -query \"select col from a where (col = 1)\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n\n# 18. 检查 SQL 改写 delimiter\n@test \"Check Soar SQL Rewrite Delimiter \" {\n  ${SOAR_BIN} -report-type \"rewrite\" -rewrite-rules \"delimiter\" \\\n  -query \"use sakila\" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\n  run golden_diff\n  [ $status -eq 0 ]\n}\n"
  },
  {
    "path": "test/sql/README.md",
    "content": "# Test Database\n\n## sakila\n\n* Download MySQL sakila database from http://downloads.mysql.com/docs/sakila-db.tar.gz .\n* InnoDB added FULLTEXT support in 5.6.10, you should add version comment `/*!50610 xxx */` for `film_text` table and it's triggers.\n* Merge schema and data into one file `sakila.sql`\n\n## world\\_x\n\nworld\\_x contain JSON datatype, SOAR use this database for JSON testing.\n\n* Download MySQL world\\_x database from http://downloads.mysql.com/docs/world_x-db.tar.gz .\n* MySQL support JSON datatype since 5.7.8, you should add version comment `/*!50708 xxx */` for `city`, `countryinfo`.\n* Merge `sakila.sql`, `world_x.sql` into init.sql.\n\n```bash\ngzip init.sql\n```\n"
  },
  {
    "path": "test/test_helper.bash",
    "content": "setup() {\n  export SOAR_DEV_DIRNAME=\"${BATS_TEST_DIRNAME}/../\"\n  export SOAR_BIN=\"${SOAR_DEV_DIRNAME}/bin/soar\" \n  export SOAR_BIN_ENV=\"${SOAR_DEV_DIRNAME}/bin/soar -config ${SOAR_DEV_DIRNAME}/etc/soar.yaml\" \n  export BATS_TMP_DIRNAME=\"${BATS_TEST_DIRNAME}/tmp\"\n  export BATS_FIXTURE_DIRNAME=\"${BATS_TEST_DIRNAME}/fixture\"\n  export LC_ALL=C # Linux macOS 下 sort 排序问题\n  mkdir -p \"${BATS_TMP_DIRNAME}\"\n}\n\n# golden_diff like gofmt golden file check method, use this function check output different with template\ngolden_diff() {\n  diff \"${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden\" \"${BATS_FIXTURE_DIRNAME}/${BATS_TEST_NAME}.golden\"\n}\n"
  },
  {
    "path": "tools.json",
    "content": "{\n  \"Tools\": [\n    {\n      \"Repository\": \"gopkg.in/alecthomas/gometalinter.v2\",\n      \"Commit\": \"46cc1ea3778b247666c2949669a3333c532fa9c6\"\n    },\n    {\n      \"Repository\": \"github.com/client9/misspell/cmd/misspell\",\n      \"Commit\": \"7888c6b6ce89353cd98e196bce3c3f9e4cdf31f6\"\n    },\n    {\n      \"Repository\": \"github.com/chzchzchz/goword\",\n      \"Commit\": \"a9744cb52b033fe5c269df48eeef2c954526cd79\"\n    },\n    {\n      \"Repository\": \"github.com/gordonklaus/ineffassign\",\n      \"Commit\": \"7bae11eba15a3285c75e388f77eb6357a2d73ee2\"\n    },\n    {\n      \"Repository\": \"github.com/dnephin/govet\",\n      \"Commit\": \"4a96d43e39d340b63daa8bc5576985aa599885f6\"\n    },\n    {\n      \"Repository\": \"github.com/securego/gosec/cmd/gosec\",\n      \"Commit\": \"5fb530cda357c16175f2c049577d2030de735b28\"\n    },\n    {\n      \"Repository\": \"github.com/kisielk/errcheck\",\n      \"Commit\": \"55d8f507faff4d6eddd0c41a3e713e2567fca4e5\"\n    },\n    {\n      \"Repository\": \"github.com/mgechev/revive\",\n      \"Commit\": \"7773f47324c2bf1c8f7a5500aff2b6c01d3ed73b\"\n    },\n    {\n      \"Repository\": \"github.com/golangci/golangci-lint/cmd/golangci-lint\",\n      \"Commit\": \"a2b901227c37337bce9860499a413db2b464481b\"\n    },\n    {\n      \"Repository\": \"honnef.co/go/tools/cmd/megacheck\",\n      \"Commit\": \"88497007e8588ea5b6baee991f74a1607e809487\"\n    }\n  ],\n  \"RetoolVersion\": \"1.3.7\"\n}"
  }
]