├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── conf.py ├── index.rst ├── installation.rst ├── make.bat ├── statements.rst └── structure.rst ├── mysqlparse ├── __init__.py └── grammar │ ├── __init__.py │ ├── alter_table.py │ ├── column_definition.py │ ├── create_table.py │ ├── data_type.py │ ├── drop_table.py │ ├── identifier.py │ ├── rename_table.py │ ├── sql_file.py │ └── utils.py ├── requirements_dev.txt ├── requirements_docs.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── fixtures │ └── test.sql ├── grammar │ ├── __init__.py │ ├── test_alter_table.py │ ├── test_column_definition.py │ ├── test_create_table.py │ ├── test_data_type.py │ ├── test_drop_table.py │ ├── test_identifier.py │ ├── test_rename_table.py │ └── test_sql_file.py └── test_mysqlparse.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3.6 3 | env: 4 | - TOX_ENV=py27 5 | - TOX_ENV=py36 6 | - TOX_ENV=lint 7 | install: pip install tox 8 | script: tox -e $TOX_ENV 9 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Julius Seporaitis 9 | 10 | Contributors 11 | ------------ 12 | 13 | * Adam Johnson 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/seporaitis/mysqlparse/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Have in mind, that unless it's something terribly serious - chances 25 | are high that it'll not get my attention. Pull Requests are very 26 | appreciated in that case. 27 | 28 | Fix Bugs 29 | ~~~~~~~~ 30 | 31 | Look through the GitHub issues for bugs. Anything tagged with "bug" 32 | is open to whoever wants to implement it. 33 | 34 | Implement Features 35 | ~~~~~~~~~~~~~~~~~~ 36 | 37 | Look through the GitHub issues for features. Anything tagged with "enhancement" 38 | is open to whoever wants to implement it. 39 | 40 | Write Documentation 41 | ~~~~~~~~~~~~~~~~~~~ 42 | 43 | mysqlparse could always use more documentation, whether as part of the 44 | official mysqlparse docs, in docstrings, or even on the web in blog posts, 45 | articles, and such. 46 | 47 | Submit Feedback 48 | ~~~~~~~~~~~~~~~ 49 | 50 | The best way to send feedback is to file an issue at https://github.com/seporaitis/mysqlparse/issues. 51 | 52 | If you are proposing a feature: 53 | 54 | * Explain in detail how it would work. 55 | * Keep the scope as narrow as possible, to make it easier to implement. 56 | * Remember that this is a volunteer-driven project, and that contributions 57 | are welcome :) 58 | 59 | Get Started! 60 | ------------ 61 | 62 | Ready to contribute? Here's how to set up `mysqlparse` for local development. 63 | 64 | 1. Fork the `mysqlparse` repo on GitHub. 65 | 2. Clone your fork locally:: 66 | 67 | $ git clone git@github.com:seporaitis/mysqlparse.git 68 | 69 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 70 | 71 | $ mkvirtualenv mysqlparse 72 | $ cd mysqlparse/ 73 | $ python setup.py develop 74 | 75 | 4. Create a branch for local development:: 76 | 77 | $ git checkout -b name-of-your-bugfix-or-feature 78 | 79 | Now you can make your changes locally. 80 | 81 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: 82 | 83 | $ python setup.py test 84 | $ tox -epy35 -epy35-lint 85 | 86 | To get flake8 and tox, just pip install them into your 87 | virtualenv. ``-epy35`` can be changed to ``-epy27`` to run test 88 | suit against Python2, given that you have a working installation. 89 | 90 | 6. Commit your changes and push your branch to GitHub:: 91 | 92 | $ git add . 93 | $ git commit -m "Your detailed description of your changes." 94 | $ git push origin name-of-your-bugfix-or-feature 95 | 96 | 7. Submit a pull request through the GitHub website. 97 | 98 | Pull Request Guidelines 99 | ----------------------- 100 | 101 | Before you submit a pull request, check that it meets these guidelines: 102 | 103 | 1. The pull request should include tests. 104 | 2. If the pull request adds functionality, the docs should be updated. Put 105 | your new functionality into a function with a docstring, and add the 106 | feature to the list in HISTORY.rst. 107 | 3. The pull request should work for Python 2.7, 3.3, and 3.4, and 3.5. Check 108 | https://travis-ci.org/seporaitis/mysqlparse/pull_requests 109 | and make sure that the tests pass for all supported Python versions. 110 | 111 | Tips 112 | ---- 113 | 114 | To run a subset of tests:: 115 | 116 | $ tox -epy35 -- -s tests.grammar.test_alter_table 117 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | History 4 | ------- 5 | 6 | PENDING 7 | ------- 8 | 9 | * Add support for database names (contributed by @jgysland). 10 | * Add explicit support for MySQL identifiers. 11 | * Add support for basic ``CREATE TABLE`` statements. 12 | * (Insert new release notes below this line) 13 | 14 | 15 | 0.1.6 (2017-06-16) 16 | ------------------ 17 | 18 | * Add support for ``ALTER TABLE .. CHANGE COLUMN`` statements (contributed by @jgysland) 19 | 20 | 21 | 0.1.5 (2016-08-23) 22 | ------------------ 23 | 24 | * Change the licence from ``AGPLv3`` to ``MIT`` licence. 25 | 26 | 27 | 0.1.4 (2016-02-28) 28 | ------------------ 29 | 30 | * Brought back ``HISTORY.rst``. 31 | * Add ``mysqlparse.parse`` function. 32 | * Add ``six`` as a dependency to make writing Py2 and Py3 compatible code easier. 33 | * Add support for ``ALTER TABLE .. MODIFY [COLUMN]`` statements. 34 | * Add support for ``ALTER TABLE .. DROP *`` statements. 35 | * Move version string from ``setup.py`` into ``mysqlparse``. 36 | 37 | 38 | 0.1.3 (2016-02-20) 39 | ------------------ 40 | 41 | * Updated ``README.rst``. 42 | * Add support for ``ALTER TABLE ... ADD INDEX`` statements. 43 | 44 | 45 | 0.1.2 (2016-02-16) 46 | ------------------ 47 | 48 | * A little tidy up. 49 | * Removed ``defaultValue`` parse action. 50 | * Improved ``NULL`` handling. 51 | * Updated tests to pass. 52 | 53 | 54 | 0.1.1 (2016-02-15) 55 | ------------------ 56 | 57 | * Fixed packaging configuration. 58 | 59 | 60 | 0.1.0 (2016-02-15) 61 | ------------------ 62 | 63 | * First release with code parsing some of ``ALTER TABLE ... ADD COLUMN`` statements. 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Julius Seporaitis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include LICENSE 4 | include README.rst 5 | include HISTORY.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build clean 2 | define BROWSER_PYSCRIPT 3 | import os, webbrowser, sys 4 | try: 5 | from urllib import pathname2url 6 | except: 7 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 13 | 14 | help: 15 | @echo "clean - remove all build, test, coverage and Python artifacts" 16 | @echo "clean-build - remove build artifacts" 17 | @echo "clean-pyc - remove Python file artifacts" 18 | @echo "clean-test - remove test and coverage artifacts" 19 | @echo "lint - check style with flake8" 20 | @echo "test - run tests quickly with the default Python" 21 | @echo "test-all - run tests on every Python version with tox" 22 | @echo "coverage - check code coverage quickly with the default Python" 23 | @echo "release - package and upload a release" 24 | @echo "dist - package" 25 | @echo "docs - generate Sphinx HTML documentation" 26 | @echo "install - install the package to the active Python's site-packages" 27 | 28 | clean: clean-build clean-pyc clean-test 29 | 30 | clean-build: 31 | rm -fr build/ 32 | rm -fr dist/ 33 | rm -fr .eggs/ 34 | find . -name '*.egg-info' -exec rm -fr {} + 35 | find . -name '*.egg' -exec rm -f {} + 36 | 37 | clean-pyc: 38 | find . -name '*.pyc' -exec rm -f {} + 39 | find . -name '*.pyo' -exec rm -f {} + 40 | find . -name '*~' -exec rm -f {} + 41 | find . -name '__pycache__' -exec rm -fr {} + 42 | 43 | clean-test: 44 | rm -fr .tox/ 45 | rm -f .coverage 46 | rm -fr htmlcov/ 47 | 48 | lint: 49 | flake8 mysqlparse tests 50 | 51 | test: 52 | python setup.py test 53 | 54 | test-all: 55 | tox 56 | 57 | coverage: 58 | coverage run --source mysqlparse setup.py test 59 | coverage report -m 60 | coverage html 61 | $(BROWSER) htmlcov/index.html 62 | 63 | release: clean 64 | python setup.py sdist upload 65 | python setup.py bdist_wheel upload 66 | 67 | dist: clean 68 | python setup.py sdist 69 | python setup.py bdist_wheel 70 | ls -l dist 71 | 72 | docs: 73 | $(MAKE) -C docs clean 74 | $(MAKE) -C docs html 75 | 76 | install: clean 77 | python setup.py install 78 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | mysqlparse 3 | ========== 4 | 5 | .. image:: https://img.shields.io/pypi/v/mysqlparse.svg 6 | :target: https://pypi.python.org/pypi/mysqlparse 7 | 8 | .. image:: https://img.shields.io/travis/seporaitis/mysqlparse/master.svg 9 | :target: https://travis-ci.org/seporaitis/mysqlparse 10 | 11 | .. image:: https://readthedocs.org/projects/mysqlparse/badge/?version=latest 12 | :target: http://mysqlparse.readthedocs.io/en/latest/?badge=latest 13 | 14 | 15 | A highly experimental attempt to have a sane parser library to parse MySQL statements. 16 | 17 | At the moment - use it at your own risk! 18 | 19 | 20 | Features 21 | -------- 22 | 23 | * Parses SOME SQL strings. 24 | 25 | 26 | Example 27 | ------- 28 | 29 | Some properties that can be accessed. 30 | 31 | .. code-block:: python 32 | 33 | >>> import mysqlparse 34 | >>> sql = mysqlparse.parse(""" 35 | ... ALTER TABLE `django_user` ADD COLUMN `notes` LONGTEXT NOT NULL 36 | ... """) 37 | >>> print(sql.statements[0].statement_type) 38 | ALTER 39 | >>> print(sql.statements[0].table_name) 40 | `django_user` 41 | >>> print(sql.statements[0].ignore) 42 | False 43 | >>> print(sql.statements[0].alter_specification[0].alter_action) 44 | ADD COLUMN 45 | >>> print(sql.statements[0].alter_specification[0].column_name) 46 | `notes` 47 | >>> print(sql.statements[0].alter_specification[0].data_type) 48 | LONGTEXT 49 | >>> print(sql.statements[0].alter_specification[0].null) 50 | False 51 | >>> print(sql.statements[0].alter_specification[0].column_position) 52 | LAST 53 | 54 | Checking that the alter statement is backwards compatible with a 55 | previous version of SOME code, which does not know how to save 56 | ``notes`` as ``NOT NULL``: 57 | 58 | .. code-block:: python 59 | 60 | for statement in sql.statements: 61 | if statement != 'ALTER': 62 | continue 63 | for column in statement.alter_specification: 64 | if column.data_type == 'LONGTEXT': 65 | if column.null is False and column.default != 'NULL': 66 | print "{s.table_name}.{c.column_name} is `LONGTEXT NOT NULL` which may break the production system. Use `LONGTEXT DEFAULT NULL` instead.".format( 67 | s=statement, 68 | c=column, 69 | ) 70 | 71 | 72 | How can you help? 73 | ----------------- 74 | 75 | A short list of things that would help (from relatively easiest): 76 | 77 | * Raise an issue with an edge case statement that **should** parse, 78 | but doesn't. 79 | 80 | * Raise an issue with how you would like to use this library. 81 | 82 | * Document the available properties in the parsed object. 83 | 84 | * Add a missing test case or suggest a way to avoid so much repetition 85 | in tests checking the same statement, but with variations. 86 | 87 | * Suggest how to use ``pyparsing`` to do statement validation. 88 | 89 | * Maybe it is possible to generate ``pyparsing`` parser from the MySQL 90 | source code? 91 | 92 | * Add ability to unparse the parse (sub)trees back into valid SQL. 93 | 94 | 95 | Why? 96 | ---- 97 | 98 | Out of frustration for lack of a better tool. 99 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mysqlparse.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mysqlparse.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/mysqlparse" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/mysqlparse" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # mysqlparse documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Feb 27 16:25:51 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | cwd = os.getcwd() 24 | project_root = os.path.dirname(cwd) 25 | sys.path.insert(0, project_root) 26 | 27 | import mysqlparse 28 | 29 | # -- General configuration ------------------------------------------------ 30 | 31 | # If your documentation needs a minimal Sphinx version, state it here. 32 | #needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | #source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = u'mysqlparse' 55 | copyright = u'2016, Julius Šėporaitis' 56 | author = u'Julius Šėporaitis' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = mysqlparse.__version__ 64 | # The full version, including alpha/beta/rc tags. 65 | release = mysqlparse.__version__ 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to some 75 | # non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built documents. 106 | #keep_warnings = False 107 | 108 | # If true, `todo` and `todoList` produce output, else they produce nothing. 109 | todo_include_todos = False 110 | 111 | 112 | # -- Options for HTML output ---------------------------------------------- 113 | 114 | # The theme to use for HTML and HTML Help pages. See the documentation for 115 | # a list of builtin themes. 116 | #html_theme = 'alabaster' 117 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 118 | 119 | if not on_rtd: # only import and set the theme if we're building docs locally 120 | import sphinx_rtd_theme 121 | html_theme = 'sphinx_rtd_theme' 122 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 123 | 124 | # Theme options are theme-specific and customize the look and feel of a theme 125 | # further. For a list of options available for each theme, see the 126 | # documentation. 127 | #html_theme_options = {} 128 | 129 | # Add any paths that contain custom themes here, relative to this directory. 130 | #html_theme_path = [] 131 | 132 | # The name for this set of Sphinx documents. If None, it defaults to 133 | # " v documentation". 134 | #html_title = None 135 | 136 | # A shorter title for the navigation bar. Default is the same as html_title. 137 | #html_short_title = None 138 | 139 | # The name of an image file (relative to this directory) to place at the top 140 | # of the sidebar. 141 | #html_logo = None 142 | 143 | # The name of an image file (within the static path) to use as favicon of the 144 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 145 | # pixels large. 146 | #html_favicon = None 147 | 148 | # Add any paths that contain custom static files (such as style sheets) here, 149 | # relative to this directory. They are copied after the builtin static files, 150 | # so a file named "default.css" will overwrite the builtin "default.css". 151 | html_static_path = ['_static'] 152 | 153 | # Add any extra paths that contain custom files (such as robots.txt or 154 | # .htaccess) here, relative to this directory. These files are copied 155 | # directly to the root of the documentation. 156 | #html_extra_path = [] 157 | 158 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 159 | # using the given strftime format. 160 | #html_last_updated_fmt = '%b %d, %Y' 161 | 162 | # If true, SmartyPants will be used to convert quotes and dashes to 163 | # typographically correct entities. 164 | #html_use_smartypants = True 165 | 166 | # Custom sidebar templates, maps document names to template names. 167 | #html_sidebars = {} 168 | 169 | # Additional templates that should be rendered to pages, maps page names to 170 | # template names. 171 | #html_additional_pages = {} 172 | 173 | # If false, no module index is generated. 174 | #html_domain_indices = True 175 | 176 | # If false, no index is generated. 177 | #html_use_index = True 178 | 179 | # If true, the index is split into individual pages for each letter. 180 | #html_split_index = False 181 | 182 | # If true, links to the reST sources are added to the pages. 183 | #html_show_sourcelink = True 184 | 185 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 186 | #html_show_sphinx = True 187 | 188 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 189 | #html_show_copyright = True 190 | 191 | # If true, an OpenSearch description file will be output, and all pages will 192 | # contain a tag referring to it. The value of this option must be the 193 | # base URL from which the finished HTML is served. 194 | #html_use_opensearch = '' 195 | 196 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 197 | #html_file_suffix = None 198 | 199 | # Language to be used for generating the HTML full-text search index. 200 | # Sphinx supports the following languages: 201 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 202 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 203 | #html_search_language = 'en' 204 | 205 | # A dictionary with options for the search language support, empty by default. 206 | # Now only 'ja' uses this config value 207 | #html_search_options = {'type': 'default'} 208 | 209 | # The name of a javascript file (relative to the configuration directory) that 210 | # implements a search results scorer. If empty, the default will be used. 211 | #html_search_scorer = 'scorer.js' 212 | 213 | # Output file base name for HTML help builder. 214 | htmlhelp_basename = 'mysqlparsedoc' 215 | 216 | # -- Options for LaTeX output --------------------------------------------- 217 | 218 | latex_elements = { 219 | # The paper size ('letterpaper' or 'a4paper'). 220 | #'papersize': 'letterpaper', 221 | 222 | # The font size ('10pt', '11pt' or '12pt'). 223 | #'pointsize': '10pt', 224 | 225 | # Additional stuff for the LaTeX preamble. 226 | #'preamble': '', 227 | 228 | # Latex figure (float) alignment 229 | #'figure_align': 'htbp', 230 | } 231 | 232 | # Grouping the document tree into LaTeX files. List of tuples 233 | # (source start file, target name, title, 234 | # author, documentclass [howto, manual, or own class]). 235 | latex_documents = [ 236 | (master_doc, 'mysqlparse.tex', u'mysqlparse Documentation', 237 | u'Julius Šėporaitis', 'manual'), 238 | ] 239 | 240 | # The name of an image file (relative to this directory) to place at the top of 241 | # the title page. 242 | #latex_logo = None 243 | 244 | # For "manual" documents, if this is true, then toplevel headings are parts, 245 | # not chapters. 246 | #latex_use_parts = False 247 | 248 | # If true, show page references after internal links. 249 | #latex_show_pagerefs = False 250 | 251 | # If true, show URL addresses after external links. 252 | #latex_show_urls = False 253 | 254 | # Documents to append as an appendix to all manuals. 255 | #latex_appendices = [] 256 | 257 | # If false, no module index is generated. 258 | #latex_domain_indices = True 259 | 260 | 261 | # -- Options for manual page output --------------------------------------- 262 | 263 | # One entry per manual page. List of tuples 264 | # (source start file, name, description, authors, manual section). 265 | man_pages = [ 266 | (master_doc, 'mysqlparse', u'mysqlparse Documentation', 267 | [author], 1) 268 | ] 269 | 270 | # If true, show URL addresses after external links. 271 | #man_show_urls = False 272 | 273 | 274 | # -- Options for Texinfo output ------------------------------------------- 275 | 276 | # Grouping the document tree into Texinfo files. List of tuples 277 | # (source start file, target name, title, author, 278 | # dir menu entry, description, category) 279 | texinfo_documents = [ 280 | (master_doc, 'mysqlparse', u'mysqlparse Documentation', 281 | author, 'mysqlparse', 'One line description of project.', 282 | 'Miscellaneous'), 283 | ] 284 | 285 | # Documents to append as an appendix to all manuals. 286 | #texinfo_appendices = [] 287 | 288 | # If false, no module index is generated. 289 | #texinfo_domain_indices = True 290 | 291 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 292 | #texinfo_show_urls = 'footnote' 293 | 294 | # If true, do not generate a @detailmenu in the "Top" node's menu. 295 | #texinfo_no_detailmenu = False 296 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. mysqlparse documentation master file, created by 2 | sphinx-quickstart on Sat Feb 27 16:25:51 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | mysqlparse documentation 7 | ====================================== 8 | 9 | The purpose of ``mysqlparse`` library is to provide structural access 10 | to MySQL queries. It looks better with an example: 11 | 12 | .. code-block:: python 13 | 14 | >>> import mysqlparse 15 | >>> sql = mysqlparse.parse(""" 16 | ... ALTER TABLE `django_user` ADD COLUMN `notes` LONGTEXT NOT NULL 17 | ... """) 18 | >>> print(sql.statements[0].statement_type) 19 | ALTER 20 | >>> print(sql.statements[0].table_name) 21 | `django_user` 22 | >>> print(sql.statements[0].ignore) 23 | False 24 | >>> print(sql.statements[0].alter_specification[0].alter_action) 25 | ADD COLUMN 26 | >>> print(sql.statements[0].alter_specification[0].column_name) 27 | `notes` 28 | >>> print(sql.statements[0].alter_specification[0].data_type) 29 | LONGTEXT 30 | >>> print(sql.statements[0].alter_specification[0].null) 31 | False 32 | >>> print(sql.statements[0].alter_specification[0].column_position) 33 | LAST 34 | 35 | 36 | Contents 37 | -------- 38 | 39 | .. toctree:: 40 | :maxdepth: 1 41 | 42 | installation 43 | statements 44 | structure 45 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Requirements and Installation 2 | ============================= 3 | 4 | Requirements 5 | ------------ 6 | 7 | Tested with: 8 | 9 | * Python: 2.7, 3.3, 3.4, 3.5. 10 | * pyparsing: 2.2.0+ 11 | * six: 1.10.0+ 12 | 13 | 14 | Installation 15 | ------------ 16 | 17 | Install with ``pip``: 18 | 19 | .. code-block:: console 20 | 21 | $ pip install mysqlparse 22 | 23 | Or add it to your project's ``requirements.txt``. 24 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\mysqlparse.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\mysqlparse.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/statements.rst: -------------------------------------------------------------------------------- 1 | Supported Statements 2 | ==================== 3 | 4 | This is how ``mysqlparse`` coverage compares to `MySQL ALTER TABLE`_ 5 | statement syntax: 6 | 7 | .. code-block:: sql 8 | 9 | ALTER TABLE tbl_name 10 | [alter_specification [, alter_specification] ...] 11 | 12 | alter_specification: 13 | ADD [COLUMN] col_name column_definition 14 | [FIRST | AFTER col_name] 15 | | ADD {INDEX | KEY} [index_name] 16 | [index_type] (index_col_name, ...) [index_option] ... 17 | | CHANGE [COLUMN] old_col_name new_col_name column_definition 18 | [FIRST | AFTER col_name] 19 | | MODIFY [COLUMN] col_name column_definition 20 | [FIRST | AFTER col_name] 21 | | DROP [COLUMN] col_name 22 | | DROP PRIMARY KEY 23 | | DROP {INDEX | KEY} index_name 24 | | DROP FOREIGN KEY fk_symbol 25 | 26 | index_type: 27 | USING {BTREE | HASH} 28 | 29 | index_option: 30 | KEY_BLOCK_SIZE [=] value 31 | | index_type 32 | | WITH PARSER parser_name 33 | | COMMENT 'string' 34 | 35 | column_definition: 36 | data_type [NOT NULL | NULL] [DEFAULT default_value] 37 | [AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY] 38 | [COMMENT 'string'] 39 | 40 | data_type: 41 | BIT[(length)] 42 | | TINYINT[(length)] [UNSIGNED] [ZEROFILL] 43 | | SMALLINT[(length)] [UNSIGNED] [ZEROFILL] 44 | | MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL] 45 | | INT[(length)] [UNSIGNED] [ZEROFILL] 46 | | INTEGER[(length)] [UNSIGNED] [ZEROFILL] 47 | | BIGINT[(length)] [UNSIGNED] [ZEROFILL] 48 | | REAL[(length,decimals)] [UNSIGNED] [ZEROFILL] 49 | | DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL] 50 | | FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL] 51 | | DECIMAL[(length[,decimals])] [UNSIGNED] [ZEROFILL] 52 | | NUMERIC[(length[,decimals])] [UNSIGNED] [ZEROFILL] 53 | | DATE 54 | | TIME[(fsp)] 55 | | TIMESTAMP[(fsp)] 56 | | DATETIME[(fsp)] 57 | | YEAR 58 | | CHAR[(length)] [BINARY] 59 | [CHARACTER SET charset_name] [COLLATE collation_name] 60 | | VARCHAR(length) [BINARY] 61 | [CHARACTER SET charset_name] [COLLATE collation_name] 62 | | BINARY[(length)] 63 | | VARBINARY(length) 64 | | TINYBLOB 65 | | BLOB 66 | | MEDIUMBLOB 67 | | LONGBLOB 68 | | TINYTEXT [BINARY] 69 | [CHARACTER SET charset_name] [COLLATE collation_name] 70 | | TEXT [BINARY] 71 | [CHARACTER SET charset_name] [COLLATE collation_name] 72 | | MEDIUMTEXT [BINARY] 73 | [CHARACTER SET charset_name] [COLLATE collation_name] 74 | | LONGTEXT [BINARY] 75 | [CHARACTER SET charset_name] [COLLATE collation_name] 76 | | ENUM(value1,value2,value3,...) 77 | [CHARACTER SET charset_name] [COLLATE collation_name] 78 | | SET(value1,value2,value3,...) 79 | [CHARACTER SET charset_name] [COLLATE collation_name] 80 | 81 | 82 | This is how ``mysqlparse`` coverage compares to `MySQL CREATE TABLE`_ 83 | statement syntax: 84 | 85 | .. code-block:: sql 86 | 87 | CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name 88 | (create_definition,...) 89 | [table_options] 90 | 91 | create_definition: 92 | col_name column_definition 93 | 94 | column_definition: 95 | data_type [NOT NULL | NULL] [DEFAULT default_value] 96 | [AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY] 97 | [COMMENT 'string'] 98 | 99 | data_type: 100 | BIT[(length)] 101 | | TINYINT[(length)] [UNSIGNED] [ZEROFILL] 102 | | SMALLINT[(length)] [UNSIGNED] [ZEROFILL] 103 | | MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL] 104 | | INT[(length)] [UNSIGNED] [ZEROFILL] 105 | | INTEGER[(length)] [UNSIGNED] [ZEROFILL] 106 | | BIGINT[(length)] [UNSIGNED] [ZEROFILL] 107 | | REAL[(length,decimals)] [UNSIGNED] [ZEROFILL] 108 | | DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL] 109 | | FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL] 110 | | DECIMAL[(length[,decimals])] [UNSIGNED] [ZEROFILL] 111 | | NUMERIC[(length[,decimals])] [UNSIGNED] [ZEROFILL] 112 | | DATE 113 | | TIME[(fsp)] 114 | | TIMESTAMP[(fsp)] 115 | | DATETIME[(fsp)] 116 | | YEAR 117 | | CHAR[(length)] [BINARY] 118 | [CHARACTER SET charset_name] [COLLATE collation_name] 119 | | VARCHAR(length) [BINARY] 120 | [CHARACTER SET charset_name] [COLLATE collation_name] 121 | | BINARY[(length)] 122 | | VARBINARY(length) 123 | | TINYBLOB 124 | | BLOB 125 | | MEDIUMBLOB 126 | | LONGBLOB 127 | | TINYTEXT [BINARY] 128 | [CHARACTER SET charset_name] [COLLATE collation_name] 129 | | TEXT [BINARY] 130 | [CHARACTER SET charset_name] [COLLATE collation_name] 131 | | MEDIUMTEXT [BINARY] 132 | [CHARACTER SET charset_name] [COLLATE collation_name] 133 | | LONGTEXT [BINARY] 134 | [CHARACTER SET charset_name] [COLLATE collation_name] 135 | | ENUM(value1,value2,value3,...) 136 | [CHARACTER SET charset_name] [COLLATE collation_name] 137 | | SET(value1,value2,value3,...) 138 | [CHARACTER SET charset_name] [COLLATE collation_name] 139 | 140 | table_option: 141 | ENGINE [=] engine_name 142 | | AUTO_INCREMENT [=] value 143 | | AVG_ROW_LENGTH [=] value 144 | | [DEFAULT] CHARACTER SET [=] charset_name 145 | | CHECKSUM [=] {0 | 1} 146 | | [DEFAULT] COLLATE [=] collation_name 147 | | COMMENT [=] 'string' 148 | | COMPRESSION [=] {'ZLIB'|'LZ4'|'NONE'} 149 | | CONNECTION [=] 'connect_string' 150 | | DATA DIRECTORY [=] 'absolute path to directory' 151 | | DELAY_KEY_WRITE [=] {0 | 1} 152 | | ENCRYPTION [=] {'Y' | 'N'} 153 | | INDEX DIRECTORY [=] 'absolute path to directory' 154 | | INSERT_METHOD [=] { NO | FIRST | LAST } 155 | | KEY_BLOCK_SIZE [=] value 156 | | MAX_ROWS [=] value 157 | | MIN_ROWS [=] value 158 | | PACK_KEYS [=] {0 | 1 | DEFAULT} 159 | | PASSWORD [=] 'string' 160 | | ROW_FORMAT [=] {DEFAULT|DYNAMIC|FIXED|COMPRESSED|REDUNDANT|COMPACT} 161 | | STATS_AUTO_RECALC [=] {DEFAULT|0|1} 162 | | STATS_PERSISTENT [=] {DEFAULT|0|1} 163 | | STATS_SAMPLE_PAGES [=] value 164 | | TABLESPACE tablespace_name [STORAGE {DISK|MEMORY|DEFAULT}] 165 | | UNION [=] (tbl_name[,tbl_name]...) 166 | 167 | 168 | .. _`MySQL ALTER TABLE`: https://dev.mysql.com/doc/refman/5.7/en/alter-table.html 169 | .. _`MySQL CREATE TABLE`: https://dev.mysql.com/doc/refman/5.7/en/create-table.html 170 | -------------------------------------------------------------------------------- /docs/structure.rst: -------------------------------------------------------------------------------- 1 | Statements Structure 2 | ==================== 3 | 4 | ``mysqlparse.parse(file_or_string)`` 5 | ------------------------------------ 6 | 7 | Takes a file like object or string and returns 8 | ``pyparsing.ParseResults`` representing the SQL file structure. 9 | 10 | Assuming sql file is parsed like this: 11 | 12 | .. code-block:: python 13 | 14 | >>> with open('001_migrate.sql') as sql_file: 15 | >>> sql = mysqlparse.parse(sql_file) 16 | 17 | The following properties are accessible 18 | 19 | * ``sql.statements[] : list`` all individual sql statements, separated 20 | by ``;`` are accessible through this list. 21 | 22 | * ``.statement_type : str`` one of: 23 | 24 | * ``ALTER`` 25 | * ``CREATE`` 26 | 27 | * ``.create_type : str``, currently only ``TABLE``. 28 | * ``.temporary : boolean``, ``True`` if ``CREATE TEMPORARY TABLE``. 29 | * ``.overwrite : boolean``, ``False`` when ``IF NOT EXISTS`` is present. 30 | * ``.database_name : str``, ``None`` or database. 31 | name if the table identifier was with a dot 32 | (e.g. ``db_name.tbl_name``). 33 | * ``.table_name : str``, table name of ``ALTER 34 | TABLE`` statement. 35 | * ``.ignore : boolean``, ``True`` if it is ``ALTER 36 | IGNORE TABLE`` statement (support for it is removed as of MySQL 37 | 5.7.4). 38 | * ``.table_options[]: list`` list with ``key`` and ``value`` pairs 39 | for table options: 40 | 41 | * ``.key`` key 42 | * ``.value`` value 43 | 44 | * ``.alter_specification[] : list`` list of individual 45 | column alterations. 46 | 47 | * ``.alter_action : str`` one of: 48 | 49 | * ``ADD COLUMN`` 50 | * ``ADD INDEX`` 51 | * ``MODIFY COLUMN`` 52 | * ``CHANGE COLUMN`` 53 | * ``DROP COLUMN`` 54 | * ``DROP PRIMARY KEY`` 55 | * ``DROP INDEX`` 56 | * ``DROP KEY`` 57 | * ``DROP FOREIGN KEY``. 58 | 59 | * ``.column_name : str`` 60 | * ``.new_column_name : str`` name of the new column name in ``MODIFY COLUMN`` statements. 61 | * ``.null : boolean|str`` - ``True`` if the column is null, ``False`` - if not null and ``implicit`` if unspecified. 62 | * ``.default : str`` - default value of the column. 63 | * ``.auto_increment : boolean`` - ``True`` if the column is auto increment. 64 | * ``.index_type : str`` - ``unique_key`` if column is unique key, 65 | ``.primary_key`` if column is primary key, ``BTREE`` if it is 66 | btree, ``HASH`` if it is has. 67 | * ``.key_block_size : str`` key block size of index. 68 | * ``.parser_name : str`` name of the parser. 69 | * ``.comment : str`` - comment string. 70 | * ``.column_position : str`` one of: ``FIRST``, another column 71 | name or (default) ``LAST``. 72 | * ``.data_type : str`` one of: 73 | 74 | * ``BIT`` 75 | * ``TINYINT`` 76 | * ``SMALLINT`` 77 | * ``MEDIUMINT`` 78 | * ``INT`` 79 | * ``INTEGER`` 80 | * ``BIGINT`` 81 | * ``REAL`` 82 | * ``DOUBLE`` 83 | * ``FLOAT`` 84 | * ``DECIMAL`` 85 | * ``NUMERIC`` 86 | * ``DATE`` 87 | * ``TIME`` 88 | * ``TIMESTAMP`` 89 | * ``DATETIME`` 90 | * ``YEAR`` 91 | * ``CHAR`` 92 | * ``VARCHAR`` 93 | * ``BINARY`` 94 | * ``VARBINARY`` 95 | * ``TINYBLOB`` 96 | * ``BLOB`` 97 | * ``MEDIUMBLOB`` 98 | * ``LONGBLOB`` 99 | * ``TINYTEXT`` 100 | * ``TEXT`` 101 | * ``MEDIUMTEXT`` 102 | * ``LONGTEXT`` 103 | * ``ENUM`` 104 | * ``SET`` 105 | 106 | * ``.length : str`` - column length (as in ``INT(length)``). 107 | * ``.decimals : str`` - number of decimal places of a decimal type. 108 | * ``.unsigned : boolean`` - ``True`` if column is of ``UNSIGNED`` type. 109 | * ``.zerofill : boolean`` - ``True`` if column is of ``ZEROFILL`` type. 110 | * ``.binary : boolean`` - ``True`` if column is of ``BINARY`` type. 111 | * ``.character_set : str`` - character set of the column. 112 | * ``.collate : str`` - column collation name. 113 | -------------------------------------------------------------------------------- /mysqlparse/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import six 5 | 6 | __author__ = 'Julius Seporaitis' 7 | __email__ = 'julius@seporaitis.net' 8 | __version__ = '0.1.6' 9 | 10 | 11 | def parse(file_or_string): 12 | """Parse a file-like object or string. 13 | 14 | Args: 15 | file_or_string (file, str): File-like object or string. 16 | 17 | Returns: 18 | ParseResults: instance of pyparsing parse results. 19 | """ 20 | from mysqlparse.grammar.sql_file import sql_file_syntax 21 | 22 | if hasattr(file_or_string, 'read') and hasattr(file_or_string.read, '__call__'): 23 | return sql_file_syntax.parseString(file_or_string.read()) 24 | elif isinstance(file_or_string, six.string_types): 25 | return sql_file_syntax.parseString(file_or_string) 26 | else: 27 | raise TypeError("Expected file-like or string object, but got '{type_name}' instead.".format( 28 | type_name=type(file_or_string).__name__, 29 | )) 30 | -------------------------------------------------------------------------------- /mysqlparse/grammar/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seporaitis/mysqlparse/c327c5a1d8d6d143b67f789be7dc80357a1a5556/mysqlparse/grammar/__init__.py -------------------------------------------------------------------------------- /mysqlparse/grammar/alter_table.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from pyparsing import * 5 | 6 | from mysqlparse.grammar.column_definition import column_definition_syntax 7 | from mysqlparse.grammar.identifier import ( 8 | identifier_syntax, 9 | database_name_syntax 10 | ) 11 | 12 | # ADD COLUMN 13 | _column_name = identifier_syntax.setResultsName("column_name") 14 | _add = CaselessKeyword("ADD").setParseAction(replaceWith("ADD COLUMN")).setResultsName("alter_action") 15 | _add_column = CaselessKeyword("ADD COLUMN").setResultsName("alter_action") 16 | _column_position = Optional( 17 | Or([CaselessKeyword("FIRST"), Suppress(CaselessKeyword("AFTER")) + identifier_syntax]), 18 | default="LAST" 19 | ).setResultsName("column_position") 20 | _last_column = Empty().setParseAction(lambda s, l, toks: ["LAST"]).setResultsName("column_position") 21 | 22 | _alter_column_specification = [ 23 | (_add + _column_name + column_definition_syntax + _column_position), 24 | (_add_column + _column_name + column_definition_syntax + _column_position), 25 | (_add_column + delimitedList(_column_name + column_definition_syntax) + _last_column), 26 | ] 27 | 28 | # ADD INDEX 29 | _index_name = identifier_syntax.setResultsName("index_name") 30 | _add_index = Or([ 31 | CaselessKeyword("ADD INDEX").setResultsName("alter_action"), 32 | CaselessKeyword("ADD KEY").setParseAction(replaceWith("ADD INDEX")).setResultsName("alter_action"), 33 | ]) 34 | _index_type = Optional( 35 | Suppress(CaselessKeyword("USING")) + Or([CaselessKeyword("BTREE"), CaselessKeyword("HASH")]), 36 | default=None 37 | ).setResultsName("index_type") 38 | _index_direction = Optional(Or([CaselessKeyword("ASC"), CaselessKeyword("DESC")]), default=None).setResultsName("direction") 39 | _index_column = ( 40 | _column_name + 41 | Optional(Suppress("(") + Word(nums) + Suppress(")"), default=None).setResultsName("length") + 42 | _index_direction 43 | ) 44 | 45 | _index_option = ( 46 | Optional( 47 | Suppress(CaselessKeyword("KEY_BLOCK_SIZE")) + Suppress(Optional("=")) + Word(nums), 48 | default=None 49 | ).setResultsName("key_block_size") + 50 | _index_type + 51 | Optional( 52 | Suppress(CaselessKeyword("WITH PARSER")) + identifier_syntax, 53 | default=None, 54 | ).setResultsName("parser_name") + 55 | Optional( 56 | Suppress(CaselessKeyword("COMMENT")) + Or([QuotedString("'"), QuotedString('"')]), 57 | default=None, 58 | ).setResultsName("comment") 59 | ) 60 | 61 | 62 | _alter_index_specification = [ 63 | (_add_index + _index_name + _index_type + 64 | Suppress("(") + 65 | delimitedList(OneOrMore(Group(_index_column).setResultsName("index_columns", listAllMatches=True))) + 66 | Suppress(")") + 67 | _index_option), 68 | ] 69 | 70 | # MODIFY COLUMN 71 | _modify = CaselessKeyword("MODIFY").setParseAction(replaceWith("MODIFY COLUMN")).setResultsName("alter_action") 72 | _modify_column = CaselessKeyword("MODIFY COLUMN").setResultsName("alter_action") 73 | 74 | 75 | _modify_column_specification = [ 76 | (_modify + _column_name + column_definition_syntax + _column_position), 77 | (_modify_column + _column_name + column_definition_syntax + _column_position), 78 | (_modify_column + delimitedList(_column_name + column_definition_syntax) + _last_column), 79 | ] 80 | 81 | # CHANGE COLUMN 82 | _change = CaselessKeyword("CHANGE").setParseAction(replaceWith("CHANGE COLUMN")).setResultsName("alter_action") 83 | _change_column = CaselessKeyword("CHANGE COLUMN").setResultsName("alter_action") 84 | 85 | 86 | _change_column_specification = [ 87 | (_change + _column_name + 88 | identifier_syntax.setResultsName("new_column_name") + 89 | column_definition_syntax + _column_position), 90 | (_change_column + _column_name + 91 | identifier_syntax.setResultsName("new_column_name") + 92 | column_definition_syntax + _column_position), 93 | (_change_column + 94 | delimitedList(_column_name + 95 | identifier_syntax.setResultsName("new_column_name") + 96 | column_definition_syntax) + _last_column), 97 | ] 98 | 99 | # RENAME INDEX 100 | _rename_index_specification = [( 101 | CaselessKeyword("RENAME INDEX").setResultsName("alter_action") + 102 | identifier_syntax.setResultsName("old_index_name") + 103 | Suppress(CaselessKeyword("TO")) + 104 | identifier_syntax.setResultsName("new_index_name") 105 | )] 106 | 107 | # RENAME KEY 108 | _rename_key_specification = [( 109 | CaselessKeyword("RENAME KEY").setResultsName("alter_action") + 110 | identifier_syntax.setResultsName("old_key_name") + 111 | Suppress(CaselessKeyword("TO")) + 112 | identifier_syntax.setResultsName("new_key_name") 113 | )] 114 | 115 | # RENAME (TABLE) 116 | _rename_table_specification = [( 117 | CaselessKeyword("RENAME").setResultsName("alter_action") + 118 | ~FollowedBy(Or([CaselessKeyword("INDEX"), CaselessKeyword("KEY")])) + 119 | Suppress(Optional(Or([CaselessKeyword("TO"), CaselessKeyword("AS")]))) + 120 | database_name_syntax.setResultsName("new_database_name") + 121 | identifier_syntax.setResultsName("new_table_name") 122 | )] 123 | 124 | # DROP 125 | _fk_symbol = identifier_syntax.setResultsName("fk_symbol") 126 | 127 | _drop = CaselessKeyword("DROP").setParseAction(replaceWith("DROP COLUMN")).setResultsName("alter_action") 128 | _drop_column = CaselessKeyword("DROP COLUMN").setResultsName("alter_action") 129 | _drop_pk = CaselessKeyword("DROP PRIMARY KEY").setResultsName("alter_action") 130 | _drop_index = CaselessKeyword("DROP INDEX").setResultsName("alter_action") 131 | _drop_key = CaselessKeyword("DROP KEY").setParseAction(replaceWith("DROP INDEX")).setResultsName("alter_action") 132 | _drop_fk = CaselessKeyword("DROP FOREIGN KEY").setResultsName("alter_action") 133 | 134 | 135 | _drop_specification = [ 136 | (_drop + _column_name), 137 | (_drop_column + _column_name), 138 | (_drop_pk), 139 | (_drop_index + _index_name), 140 | (_drop_key + _index_name), 141 | (_drop_fk + _fk_symbol), 142 | ] 143 | 144 | 145 | _alter_specification_syntax = Forward() 146 | _alter_specification_syntax <<= ( 147 | (Or( 148 | _alter_column_specification + 149 | _alter_index_specification + 150 | _modify_column_specification + 151 | _change_column_specification + 152 | _rename_index_specification + 153 | _rename_key_specification + 154 | _rename_table_specification + 155 | _drop_specification 156 | )) 157 | ) 158 | 159 | _ignore = Optional( 160 | CaselessKeyword("IGNORE").setParseAction(replaceWith(True)), 161 | default=False, 162 | ).setResultsName("ignore") 163 | 164 | 165 | # 166 | # ALTER TABLE SYNTAX 167 | # 168 | # Source: http://dev.mysql.com/doc/refman/5.7/en/alter-table.html 169 | # 170 | 171 | alter_table_syntax = ( 172 | CaselessKeyword("ALTER").setResultsName("statement_type") + _ignore + Suppress(Optional(CaselessKeyword("TABLE"))) + 173 | database_name_syntax.setResultsName("database_name") + 174 | identifier_syntax.setResultsName("table_name") + 175 | delimitedList(Group(_alter_specification_syntax).setResultsName("alter_specification", listAllMatches=True)) + 176 | Suppress(Optional(";")) 177 | ) 178 | -------------------------------------------------------------------------------- /mysqlparse/grammar/column_definition.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from pyparsing import * 5 | 6 | from mysqlparse.grammar.data_type import data_type_syntax 7 | 8 | 9 | # 10 | # PARTIAL PARSERS 11 | # 12 | 13 | _nullable = Or([ 14 | CaselessKeyword("NOT NULL").setParseAction(replaceWith(False)), 15 | CaselessKeyword("NULL").setParseAction(replaceWith(True)) 16 | ]) 17 | _default = ( 18 | Suppress(CaselessKeyword("DEFAULT")) + 19 | Or([Word(nums), QuotedString("'"), CaselessKeyword("NULL")]).setName("default").setResultsName("default") 20 | ) 21 | _auto_increment = CaselessKeyword("AUTO_INCREMENT").setResultsName("auto_increment").setParseAction(replaceWith(True)) 22 | _index_type = Or([ 23 | (CaselessKeyword("UNIQUE") + Optional(CaselessKeyword("KEY"))).setParseAction(replaceWith("unique_key")), 24 | (Optional(CaselessKeyword("PRIMARY")) + CaselessKeyword("KEY")).setParseAction(replaceWith("primary_key")) 25 | ]).setResultsName("index_type") 26 | _comment = CaselessKeyword("COMMENT") + Or([QuotedString("'"), QuotedString('"')]).setResultsName("comment") 27 | 28 | 29 | # 30 | # COLUMN DEFINITION SYNTAX 31 | # 32 | # Source: http://dev.mysql.com/doc/refman/5.7/en/create-table.html 33 | # 34 | 35 | column_definition_syntax = Forward() 36 | column_definition_syntax <<= ( 37 | data_type_syntax + 38 | Optional(_nullable, default="implicit").setResultsName("null") + 39 | ZeroOrMore( 40 | MatchFirst([ 41 | _default, 42 | _auto_increment, 43 | _index_type, 44 | _comment, 45 | _nullable.setResultsName("null") 46 | ]) 47 | ) 48 | ) 49 | -------------------------------------------------------------------------------- /mysqlparse/grammar/create_table.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from pyparsing import * 5 | 6 | from mysqlparse.grammar.column_definition import column_definition_syntax 7 | from mysqlparse.grammar.identifier import identifier_syntax 8 | 9 | 10 | # 11 | # PARTIAL PARSERS 12 | # 13 | 14 | _column_specification_syntax = (identifier_syntax.setResultsName("column_name") + column_definition_syntax) 15 | 16 | _create_type = CaselessKeyword("TABLE").setResultsName("create_type") 17 | 18 | _temporary = Optional( 19 | CaselessKeyword("TEMPORARY").setParseAction(replaceWith(True)), 20 | default=False, 21 | ).setResultsName("temporary") 22 | 23 | _if_not_exists = Optional( 24 | CaselessKeyword("IF NOT EXISTS").setParseAction(replaceWith(False)), 25 | default=True, 26 | ).setResultsName("overwrite") 27 | 28 | _table_option = Word(alphas + "_").setResultsName("key") + Optional(Suppress("=")) + identifier_syntax.setResultsName("value") 29 | 30 | # 31 | # CREATE TABLE SYNTAX 32 | # 33 | # Source: https://dev.mysql.com/doc/refman/5.7/en/create-table.html 34 | # 35 | 36 | create_table_syntax = ( 37 | CaselessKeyword("CREATE").setResultsName("statement_type") + _temporary + 38 | _create_type + _if_not_exists + Word(alphanums + "`_").setResultsName("table_name") + 39 | Suppress("(") + 40 | delimitedList( 41 | OneOrMore(Group(_column_specification_syntax).setResultsName("column_specification", listAllMatches=True)) 42 | ) + 43 | Suppress(")") + 44 | Optional( 45 | ZeroOrMore(Group(_table_option)), default=[] 46 | ).setResultsName("table_options") + 47 | Suppress(Optional(";")) 48 | ) 49 | -------------------------------------------------------------------------------- /mysqlparse/grammar/data_type.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from pyparsing import * 5 | 6 | 7 | # 8 | # UTILITY FUNCTIONS 9 | # 10 | 11 | def define_basic_type(keyword, length_name=None): 12 | if not length_name: 13 | length_name = "length" 14 | return ( 15 | CaselessKeyword(keyword) + 16 | Optional(Suppress("(") + Word(nums).setName("integer") + Suppress(")")).setResultsName(length_name) 17 | ) 18 | 19 | 20 | def define_decimal_type(keyword): 21 | return ( 22 | CaselessKeyword(keyword) + 23 | Optional( 24 | Suppress("(") + Word(nums).setName("length").setResultsName("length") + 25 | Optional("," + Word(nums).setName("decimals").setResultsName("decimals")) + 26 | Suppress(")") 27 | ) + 28 | Optional(CaselessKeyword("UNSIGNED").setParseAction(replaceWith(True)).setResultsName("unsigned")) + 29 | Optional(CaselessKeyword("ZEROFILL").setParseAction(replaceWith(True)).setResultsName('zerofill')) 30 | ) 31 | 32 | 33 | def extend_to_integer_type(type_def): 34 | return ( 35 | type_def + 36 | Optional(CaselessKeyword("UNSIGNED").setParseAction(replaceWith(True)).setResultsName("unsigned")) + 37 | Optional(CaselessKeyword("ZEROFILL").setParseAction(replaceWith(True)).setResultsName("zerofill")) 38 | ) 39 | 40 | 41 | def extend_to_character_type(type_def, binary=True): 42 | if binary: 43 | type_def += Optional(CaselessKeyword("BINARY").setParseAction(replaceWith(True)).setResultsName("binary")) 44 | return ( 45 | type_def + 46 | Optional(CaselessKeyword("CHARACTER SET") + QuotedString("'").setResultsName("character_set")) + 47 | Optional(CaselessKeyword("COLLATE") + QuotedString("'").setResultsName("collation_name")) 48 | ) 49 | 50 | 51 | # 52 | # DATA TYPE SYNTAX 53 | # 54 | # Source: http://dev.mysql.com/doc/refman/5.7/en/create-table.html 55 | # 56 | 57 | data_type_syntax = Forward() 58 | data_type_syntax <<= ( 59 | define_basic_type("BIT") | 60 | 61 | extend_to_integer_type(define_basic_type("TINYINT")) | 62 | extend_to_integer_type(define_basic_type("SMALLINT")) | 63 | extend_to_integer_type(define_basic_type("MEDIUMINT")) | 64 | extend_to_integer_type(define_basic_type("INT")) | 65 | extend_to_integer_type(define_basic_type("INTEGER")) | 66 | extend_to_integer_type(define_basic_type("BIGINT")) | 67 | 68 | define_decimal_type("REAL") | 69 | define_decimal_type("DOUBLE") | 70 | define_decimal_type("FLOAT") | 71 | define_decimal_type("DECIMAL") | 72 | define_decimal_type("NUMERIC") | 73 | 74 | CaselessKeyword("DATE") | 75 | 76 | define_basic_type("TIME", "precision") | 77 | define_basic_type("TIMESTAMP", "precision") | 78 | define_basic_type("DATETIME", "precision") | 79 | 80 | CaselessKeyword("YEAR") | 81 | 82 | extend_to_character_type(define_basic_type("CHAR")) | 83 | extend_to_character_type( 84 | CaselessKeyword("VARCHAR") + 85 | Suppress("(") + Word(nums).setName("integer").setResultsName("length") + Suppress(")") 86 | ) | 87 | 88 | define_basic_type("BINARY") | 89 | ( 90 | CaselessKeyword("VARBINARY") + 91 | Suppress("(") + Word(nums).setName("integer").setResultsName("length") + Suppress(")") 92 | ) | 93 | 94 | CaselessKeyword("TINYBLOB") | 95 | CaselessKeyword("BLOB") | 96 | CaselessKeyword("MEDIUMBLOB") | 97 | CaselessKeyword("LONGBLOB") | 98 | 99 | extend_to_character_type(CaselessKeyword("TINYTEXT")) | 100 | extend_to_character_type(CaselessKeyword("TEXT")) | 101 | extend_to_character_type(CaselessKeyword("MEDIUMTEXT")) | 102 | extend_to_character_type(CaselessKeyword("LONGTEXT")) | 103 | 104 | extend_to_character_type( 105 | ( 106 | CaselessKeyword("ENUM") + 107 | Suppress("(") + 108 | delimitedList(QuotedString("'")).setName("enum_values").setResultsName("value_list") + 109 | Suppress(")") 110 | ), 111 | binary=False 112 | ) | 113 | extend_to_character_type( 114 | ( 115 | CaselessKeyword("SET") + 116 | Suppress("(") + 117 | delimitedList(QuotedString("'")).setName("set_values").setResultsName("value_list") + 118 | Suppress(")") 119 | ), 120 | binary=False 121 | ) 122 | ).setResultsName("data_type") 123 | -------------------------------------------------------------------------------- /mysqlparse/grammar/drop_table.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import ( 3 | absolute_import, 4 | division, 5 | print_function, 6 | unicode_literals 7 | ) 8 | 9 | from pyparsing import ( 10 | CaselessKeyword, 11 | Group, 12 | OneOrMore, 13 | Optional, 14 | delimitedList, 15 | replaceWith 16 | ) 17 | 18 | from mysqlparse.grammar.identifier import ( 19 | database_name_syntax, 20 | identifier_syntax 21 | ) 22 | 23 | _drop_type = CaselessKeyword("TABLE").setResultsName("drop_type") 24 | 25 | _temporary = Optional( 26 | CaselessKeyword("TEMPORARY").setParseAction(replaceWith(True)), 27 | default=False, 28 | ).setResultsName("temporary") 29 | 30 | _if_not_exists = Optional( 31 | CaselessKeyword("IF EXISTS").setParseAction(replaceWith(True)), 32 | default=False, 33 | ).setResultsName("if_exists") 34 | 35 | drop_table_syntax = ( 36 | CaselessKeyword("DROP").setResultsName("statement_type") + _temporary + 37 | CaselessKeyword("TABLE") + _if_not_exists + delimitedList( 38 | OneOrMore(Group(database_name_syntax.setResultsName("database_name") + 39 | identifier_syntax.setResultsName("table_name")) 40 | .setResultsName("dropped", listAllMatches=True)) 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /mysqlparse/grammar/identifier.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from pyparsing import * 5 | 6 | from mysqlparse.grammar.utils import stripQuotes 7 | 8 | 9 | # 10 | # SCHEMA OBJECT NAMES (IDENTIFIERS) 11 | # 12 | # Source: https://dev.mysql.com/doc/refman/5.7/en/identifiers.html 13 | # 14 | 15 | identifier_syntax = Or([ 16 | Word(alphanums + "_$"), 17 | QuotedString('"'), 18 | QuotedString("`"), 19 | QuotedString("'") 20 | ]).setParseAction(stripQuotes) 21 | 22 | database_name_syntax = (Optional(identifier_syntax + FollowedBy('.') + 23 | Suppress('.'), default=None) 24 | .setParseAction(lambda s, l, toks: toks[0])) 25 | -------------------------------------------------------------------------------- /mysqlparse/grammar/rename_table.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import ( 3 | absolute_import, 4 | division, 5 | print_function, 6 | unicode_literals 7 | ) 8 | 9 | from pyparsing import ( 10 | CaselessKeyword, 11 | Group, 12 | OneOrMore, 13 | Optional, 14 | Suppress, 15 | delimitedList 16 | ) 17 | 18 | from mysqlparse.grammar.identifier import ( 19 | identifier_syntax, 20 | database_name_syntax 21 | ) 22 | 23 | # 24 | # PARTIAL PARSERS 25 | # 26 | 27 | _tables_renamed = ( 28 | database_name_syntax.setResultsName("old_database_name") + 29 | identifier_syntax.setResultsName("old_table_name") + 30 | Suppress(CaselessKeyword("TO")) + 31 | database_name_syntax.setResultsName("new_database_name") + 32 | database_name_syntax + identifier_syntax.setResultsName("new_table_name") 33 | ) 34 | 35 | # 36 | # RENAME TABLE SYNTAX 37 | # 38 | # Source: https://dev.mysql.com/doc/refman/5.7/en/rename-table.html 39 | # 40 | 41 | rename_table_syntax = ( 42 | CaselessKeyword("RENAME").setResultsName("statement_type") + 43 | Suppress(Optional(CaselessKeyword("TABLE"))) + 44 | delimitedList( 45 | OneOrMore(Group(_tables_renamed) 46 | .setResultsName("table_renamed", listAllMatches=True)) 47 | ) + 48 | Suppress(Optional(";")) 49 | ) 50 | -------------------------------------------------------------------------------- /mysqlparse/grammar/sql_file.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from pyparsing import * 5 | 6 | from mysqlparse.grammar.alter_table import alter_table_syntax 7 | from mysqlparse.grammar.create_table import create_table_syntax 8 | from mysqlparse.grammar.rename_table import rename_table_syntax 9 | from mysqlparse.grammar.drop_table import drop_table_syntax 10 | 11 | 12 | sql_file_syntax = ( 13 | ZeroOrMore( 14 | Suppress( 15 | SkipTo( 16 | Or([ 17 | CaselessKeyword("ALTER"), 18 | CaselessKeyword("CREATE"), 19 | CaselessKeyword("RENAME"), 20 | CaselessKeyword("DROP"), 21 | ]) 22 | ) 23 | ) + 24 | Group( 25 | Or([ 26 | alter_table_syntax, 27 | create_table_syntax, 28 | rename_table_syntax, 29 | drop_table_syntax 30 | ]) 31 | ).setResultsName("statements", listAllMatches=True) 32 | ) 33 | ) 34 | -------------------------------------------------------------------------------- /mysqlparse/grammar/utils.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | 5 | def stripQuotes(s, loc, toks): 6 | """Strip the actual quotes from the tokens in `toks`. 7 | 8 | Note: This is because it turned out `QuotedString` was stripping 9 | first and last characters of a matched string regardless. If 10 | there's a way to avoid having this function - by all means it 11 | should be removed. 12 | 13 | """ 14 | return [t.strip("'") for t in toks] 15 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | bumpversion==0.5.3 2 | wheel==0.23.0 3 | watchdog==0.8.3 4 | flake8==2.4.1 5 | tox==2.1.1 6 | coverage==4.0 7 | -------------------------------------------------------------------------------- /requirements_docs.txt: -------------------------------------------------------------------------------- 1 | sphinx==1.3.5 2 | sphinx-autobuild==0.6.0 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [flake8] 5 | max-line-length = 130 6 | ignore = F403 7 | 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | from setuptools import find_packages, setup 6 | 7 | 8 | def get_version(package): 9 | """ 10 | Return package version as listed in `__version__` in `init.py`. 11 | """ 12 | init_py = open(os.path.join(package, '__init__.py')).read() 13 | return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) 14 | 15 | 16 | version = get_version('mysqlparse') 17 | 18 | 19 | with open('README.rst') as readme_file: 20 | readme = readme_file.read() 21 | 22 | with open('HISTORY.rst') as history_file: 23 | history = history_file.read().replace('.. :changelog:', '') 24 | 25 | requirements = [ 26 | 'pyparsing', 27 | 'six', 28 | ] 29 | 30 | test_requirements = [ 31 | 'pyparsing', 32 | 'six', 33 | ] 34 | 35 | setup( 36 | name='mysqlparse', 37 | version=version, 38 | description="A library for parsing SQL statements.", 39 | long_description=readme + '\n\n' + history, 40 | author="Julius Seporaitis", 41 | author_email='julius@seporaitis.net', 42 | url='https://github.com/seporaitis/mysqlparse', 43 | packages=find_packages(exclude=['tests', 'tests.*']), 44 | package_dir={ 45 | 'mysqlparse': 'mysqlparse', 46 | 'mysqlparse.grammar': 'mysqlparse/grammar' 47 | }, 48 | include_package_data=True, 49 | install_requires=requirements, 50 | license="MIT", 51 | zip_safe=False, 52 | keywords='sql parse pyparsing mysql database', 53 | classifiers=[ 54 | 'Development Status :: 3 - Alpha', 55 | 'Intended Audience :: Developers', 56 | 'License :: OSI Approved :: MIT License', 57 | 'Natural Language :: English', 58 | "Programming Language :: Python :: 2", 59 | 'Programming Language :: Python :: 2.7', 60 | 'Programming Language :: Python :: 3', 61 | 'Programming Language :: Python :: 3.6', 62 | ], 63 | test_suite='tests', 64 | tests_require=test_requirements 65 | ) 66 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seporaitis/mysqlparse/c327c5a1d8d6d143b67f789be7dc80357a1a5556/tests/__init__.py -------------------------------------------------------------------------------- /tests/fixtures/test.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test_table1 ( 2 | test_column INT(11) PRIMARY KEY AUTO_INCREMENT NOT NULL 3 | ); 4 | 5 | ALTER TABLE test_table2 ADD col_no0 BIT(8) NOT NULL DEFAULT 0 FIRST, 6 | ADD col_no1 LONGTEXT NOT NULL, 7 | ADD col_no2 VARCHAR(200) NULL, 8 | ADD col_no3 BIT(8) AFTER col0; 9 | 10 | CREATE TABLE test_table3 ( 11 | test_column INT(11) PRIMARY KEY AUTO_INCREMENT NOT NULL 12 | ); 13 | 14 | ALTER TABLE test_table4 ADD col_no0 BIT(8) NOT NULL DEFAULT 0 FIRST, 15 | ADD col_no1 LONGTEXT NOT NULL, 16 | ADD col_no2 VARCHAR(200) NULL, 17 | ADD col_no3 BIT(8) AFTER col0; 18 | -------------------------------------------------------------------------------- /tests/grammar/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seporaitis/mysqlparse/c327c5a1d8d6d143b67f789be7dc80357a1a5556/tests/grammar/__init__.py -------------------------------------------------------------------------------- /tests/grammar/test_alter_table.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import unittest 5 | 6 | from mysqlparse.grammar.alter_table import alter_table_syntax 7 | 8 | 9 | class AlterTableAddColumnSyntaxTest(unittest.TestCase): 10 | 11 | def test_alter_table_add(self): 12 | statement = alter_table_syntax.parseString(""" 13 | ALTER IGNORE TABLE test_test ADD col_no0 BIT(8) NOT NULL DEFAULT 0 FIRST, 14 | ADD col_no1 LONGTEXT NOT NULL, 15 | ADD col_no2 VARCHAR(200) NULL, 16 | ADD col_no3 BIT(8) AFTER col0; 17 | """) 18 | 19 | self.assertTrue(statement.ignore) 20 | self.assertEqual(statement.statement_type, 'ALTER') 21 | self.assertEqual(statement.table_name, 'test_test') 22 | self.assertEqual(statement.alter_specification[0].column_name, 'col_no0') 23 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 24 | self.assertEqual(statement.alter_specification[1].column_name, 'col_no1') 25 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 26 | self.assertEqual(statement.alter_specification[2].column_name, 'col_no2') 27 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 28 | self.assertEqual(statement.alter_specification[3].column_name, 'col_no3') 29 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 30 | 31 | def test_alter_table_add_column(self): 32 | statement = alter_table_syntax.parseString(""" 33 | ALTER TABLE test_test ADD COLUMN col0 BIT(8) NOT NULL DEFAULT 0 FIRST, 34 | ADD COLUMN col1 LONGTEXT NOT NULL, 35 | ADD COLUMN col2 VARCHAR(200) NULL, 36 | ADD COLUMN col3 BIT(8) AFTER col0; 37 | """) 38 | 39 | self.assertFalse(statement.ignore) 40 | self.assertEqual(statement.statement_type, 'ALTER') 41 | self.assertEqual(statement.table_name, 'test_test') 42 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 43 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 44 | self.assertEqual(statement.alter_specification[1].column_name, 'col1') 45 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 46 | self.assertEqual(statement.alter_specification[2].column_name, 'col2') 47 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 48 | self.assertEqual(statement.alter_specification[3].column_name, 'col3') 49 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 50 | 51 | def test_alter_table_add_column_mixed(self): 52 | statement = alter_table_syntax.parseString(""" 53 | ALTER TABLE test_test ADD col0 BIT(8) NOT NULL DEFAULT 0 FIRST, 54 | ADD COLUMN col1 LONGTEXT NOT NULL, 55 | ADD COLUMN col2 VARCHAR(200) NULL, 56 | ADD col3 BIT(8) AFTER col0; 57 | """) 58 | 59 | self.assertFalse(statement.ignore) 60 | self.assertEqual(statement.statement_type, 'ALTER') 61 | self.assertEqual(statement.table_name, 'test_test') 62 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 63 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 64 | self.assertEqual(statement.alter_specification[1].column_name, 'col1') 65 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 66 | self.assertEqual(statement.alter_specification[2].column_name, 'col2') 67 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 68 | self.assertEqual(statement.alter_specification[3].column_name, 'col3') 69 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 70 | 71 | 72 | class AlterTableAddIndexSyntaxTest(unittest.TestCase): 73 | 74 | def test_alter_table_add_index(self): 75 | statement = alter_table_syntax.parseString(""" 76 | ALTER TABLE test_test ADD col0 BIT(8) NOT NULL DEFAULT 0 FIRST, 77 | ADD INDEX index1 (col0, col1 (10), col2 (20) DESC); 78 | """) 79 | 80 | self.assertFalse(statement.ignore) 81 | self.assertEqual(statement.statement_type, 'ALTER') 82 | self.assertEqual(statement.table_name, 'test_test') 83 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 84 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 85 | self.assertEqual(statement.alter_specification[1].alter_action, 'ADD INDEX') 86 | self.assertEqual(statement.alter_specification[1].index_name, 'index1') 87 | self.assertFalse(statement.alter_specification[1].index_type) 88 | self.assertEqual(statement.alter_specification[1].index_columns[0].column_name, 'col0') 89 | self.assertFalse(statement.alter_specification[1].index_columns[0].length) 90 | self.assertFalse(statement.alter_specification[1].index_columns[0].direction) 91 | self.assertEqual(statement.alter_specification[1].index_columns[1].column_name, 'col1') 92 | self.assertEqual(statement.alter_specification[1].index_columns[1].length[0], '10') 93 | self.assertFalse(statement.alter_specification[1].index_columns[1].direction) 94 | self.assertEqual(statement.alter_specification[1].index_columns[2].column_name, 'col2') 95 | self.assertEqual(statement.alter_specification[1].index_columns[2].length[0], '20') 96 | self.assertEqual(statement.alter_specification[1].index_columns[2].direction, 'DESC') 97 | 98 | def test_alter_table_add_index_index_type(self): 99 | statement = alter_table_syntax.parseString(""" 100 | ALTER TABLE test_test ADD col0 BIT(8) NOT NULL DEFAULT 0 FIRST, 101 | ADD INDEX index1 USING BTREE (col0, col1 (10), col2 (20) DESC); 102 | """) 103 | 104 | self.assertFalse(statement.ignore) 105 | self.assertEqual(statement.statement_type, 'ALTER') 106 | self.assertEqual(statement.table_name, 'test_test') 107 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 108 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 109 | self.assertEqual(statement.alter_specification[1].alter_action, 'ADD INDEX') 110 | self.assertEqual(statement.alter_specification[1].index_name, 'index1') 111 | self.assertFalse(statement.alter_specification[1].index_type) 112 | self.assertEqual(statement.alter_specification[1].index_columns[0].column_name, 'col0') 113 | self.assertFalse(statement.alter_specification[1].index_columns[0].length) 114 | self.assertFalse(statement.alter_specification[1].index_columns[0].direction) 115 | self.assertEqual(statement.alter_specification[1].index_columns[1].column_name, 'col1') 116 | self.assertEqual(statement.alter_specification[1].index_columns[1].length[0], '10') 117 | self.assertFalse(statement.alter_specification[1].index_columns[1].direction) 118 | self.assertEqual(statement.alter_specification[1].index_columns[2].column_name, 'col2') 119 | self.assertEqual(statement.alter_specification[1].index_columns[2].length[0], '20') 120 | self.assertEqual(statement.alter_specification[1].index_columns[2].direction, 'DESC') 121 | 122 | def test_alter_table_add_index_index_option(self): 123 | statement = alter_table_syntax.parseString(""" 124 | ALTER TABLE test_test ADD col0 BIT(8) NOT NULL DEFAULT 0 FIRST, 125 | ADD INDEX index1 (col0, col1 (10), col2 (20) DESC) 126 | KEY_BLOCK_SIZE=256 127 | USING HASH 128 | WITH PARSER some_parser 129 | COMMENT 'test comment'; 130 | """) 131 | 132 | self.assertFalse(statement.ignore) 133 | self.assertEqual(statement.statement_type, 'ALTER') 134 | self.assertEqual(statement.table_name, 'test_test') 135 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 136 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 137 | self.assertEqual(statement.alter_specification[1].alter_action, 'ADD INDEX') 138 | self.assertEqual(statement.alter_specification[1].index_name, 'index1') 139 | self.assertEqual(statement.alter_specification[1].index_type[0], 'HASH') 140 | self.assertEqual(statement.alter_specification[1].index_columns[0].column_name, 'col0') 141 | self.assertFalse(statement.alter_specification[1].index_columns[0].length) 142 | self.assertFalse(statement.alter_specification[1].index_columns[0].direction) 143 | self.assertEqual(statement.alter_specification[1].index_columns[1].column_name, 'col1') 144 | self.assertEqual(statement.alter_specification[1].index_columns[1].length[0], '10') 145 | self.assertFalse(statement.alter_specification[1].index_columns[1].direction) 146 | self.assertEqual(statement.alter_specification[1].index_columns[2].column_name, 'col2') 147 | self.assertEqual(statement.alter_specification[1].index_columns[2].length[0], '20') 148 | self.assertEqual(statement.alter_specification[1].index_columns[2].direction, 'DESC') 149 | self.assertEqual(statement.alter_specification[1].key_block_size[0], '256') 150 | self.assertEqual(statement.alter_specification[1].parser_name[0], 'some_parser') 151 | self.assertEqual(statement.alter_specification[1].comment[0], 'test comment') 152 | 153 | 154 | class AlterTableModifyColumnSyntaxTest(unittest.TestCase): 155 | 156 | def test_alter_table_modify(self): 157 | statement = alter_table_syntax.parseString(""" 158 | ALTER IGNORE TABLE test_test MODIFY col_no0 BIT(8) NOT NULL DEFAULT 0 FIRST, 159 | MODIFY col_no1 LONGTEXT NOT NULL, 160 | MODIFY col_no2 VARCHAR(200) NULL, 161 | MODIFY col_no3 BIT(8) AFTER col0; 162 | """) 163 | 164 | self.assertTrue(statement.ignore) 165 | self.assertEqual(statement.statement_type, 'ALTER') 166 | self.assertEqual(statement.table_name, 'test_test') 167 | self.assertEqual(statement.alter_specification[0].column_name, 'col_no0') 168 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 169 | self.assertEqual(statement.alter_specification[1].column_name, 'col_no1') 170 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 171 | self.assertEqual(statement.alter_specification[2].column_name, 'col_no2') 172 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 173 | self.assertEqual(statement.alter_specification[3].column_name, 'col_no3') 174 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 175 | 176 | def test_alter_table_modify_column(self): 177 | statement = alter_table_syntax.parseString(""" 178 | ALTER TABLE test_test MODIFY COLUMN col0 BIT(8) NOT NULL DEFAULT 0 FIRST, 179 | MODIFY COLUMN col1 LONGTEXT NOT NULL, 180 | MODIFY COLUMN col2 VARCHAR(200) NULL, 181 | MODIFY COLUMN col3 BIT(8) AFTER col0; 182 | """) 183 | 184 | self.assertFalse(statement.ignore) 185 | self.assertEqual(statement.statement_type, 'ALTER') 186 | self.assertEqual(statement.table_name, 'test_test') 187 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 188 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 189 | self.assertEqual(statement.alter_specification[1].column_name, 'col1') 190 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 191 | self.assertEqual(statement.alter_specification[2].column_name, 'col2') 192 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 193 | self.assertEqual(statement.alter_specification[3].column_name, 'col3') 194 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 195 | 196 | def test_alter_table_modify_column_mixed(self): 197 | statement = alter_table_syntax.parseString(""" 198 | ALTER TABLE test_test MODIFY col0 BIT(8) NOT NULL DEFAULT 0 FIRST, 199 | MODIFY COLUMN col1 LONGTEXT NOT NULL, 200 | MODIFY COLUMN col2 VARCHAR(200) NULL, 201 | MODIFY col3 BIT(8) AFTER col0; 202 | """) 203 | 204 | self.assertFalse(statement.ignore) 205 | self.assertEqual(statement.statement_type, 'ALTER') 206 | self.assertEqual(statement.table_name, 'test_test') 207 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 208 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 209 | self.assertEqual(statement.alter_specification[1].column_name, 'col1') 210 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 211 | self.assertEqual(statement.alter_specification[2].column_name, 'col2') 212 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 213 | self.assertEqual(statement.alter_specification[3].column_name, 'col3') 214 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 215 | 216 | 217 | class AlterTableChangeColumnSyntaxTest(unittest.TestCase): 218 | 219 | def test_alter_table_change(self): 220 | statement = alter_table_syntax.parseString(""" 221 | ALTER IGNORE TABLE test_test CHANGE col_no0 col_0 BIT(8) NOT NULL DEFAULT 0 FIRST, 222 | CHANGE col_no1 col_1 LONGTEXT NOT NULL, 223 | CHANGE col_no2 col_2 VARCHAR(200) NULL, 224 | CHANGE col_no3 col_3 BIT(8) AFTER col0; 225 | """) 226 | 227 | self.assertTrue(statement.ignore) 228 | self.assertEqual(statement.statement_type, 'ALTER') 229 | self.assertEqual(statement.table_name, 'test_test') 230 | self.assertEqual(statement.alter_specification[0].column_name, 'col_no0') 231 | self.assertEqual(statement.alter_specification[0].new_column_name, 'col_0') 232 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 233 | self.assertEqual(statement.alter_specification[1].column_name, 'col_no1') 234 | self.assertEqual(statement.alter_specification[1].new_column_name, 'col_1') 235 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 236 | self.assertEqual(statement.alter_specification[2].column_name, 'col_no2') 237 | self.assertEqual(statement.alter_specification[2].new_column_name, 'col_2') 238 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 239 | self.assertEqual(statement.alter_specification[3].column_name, 'col_no3') 240 | self.assertEqual(statement.alter_specification[3].new_column_name, 'col_3') 241 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 242 | 243 | def test_alter_table_change_column(self): 244 | statement = alter_table_syntax.parseString(""" 245 | ALTER TABLE test_test CHANGE COLUMN col0 col_no0 BIT(8) NOT NULL DEFAULT 0 FIRST, 246 | CHANGE COLUMN col1 col_no1 LONGTEXT NOT NULL, 247 | CHANGE COLUMN col2 col_no2 VARCHAR(200) NULL, 248 | CHANGE COLUMN col3 col_no3 BIT(8) AFTER col0; 249 | """) 250 | 251 | self.assertFalse(statement.ignore) 252 | self.assertEqual(statement.statement_type, 'ALTER') 253 | self.assertEqual(statement.table_name, 'test_test') 254 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 255 | self.assertEqual(statement.alter_specification[0].new_column_name, 'col_no0') 256 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 257 | self.assertEqual(statement.alter_specification[1].column_name, 'col1') 258 | self.assertEqual(statement.alter_specification[1].new_column_name, 'col_no1') 259 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 260 | self.assertEqual(statement.alter_specification[2].column_name, 'col2') 261 | self.assertEqual(statement.alter_specification[2].new_column_name, 'col_no2') 262 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 263 | self.assertEqual(statement.alter_specification[3].column_name, 'col3') 264 | self.assertEqual(statement.alter_specification[3].new_column_name, 'col_no3') 265 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 266 | 267 | def test_alter_table_change_column_mixed(self): 268 | statement = alter_table_syntax.parseString(""" 269 | ALTER TABLE test_test CHANGE col0 col_no0 BIT(8) NOT NULL DEFAULT 0 FIRST, 270 | CHANGE COLUMN col1 col_no1 LONGTEXT NOT NULL, 271 | CHANGE COLUMN col2 col_no2 VARCHAR(200) NULL, 272 | CHANGE col3 col_no3 BIT(8) AFTER col0; 273 | """) 274 | 275 | self.assertFalse(statement.ignore) 276 | self.assertEqual(statement.statement_type, 'ALTER') 277 | self.assertEqual(statement.table_name, 'test_test') 278 | self.assertEqual(statement.alter_specification[0].column_name, 'col0') 279 | self.assertEqual(statement.alter_specification[0].new_column_name, 'col_no0') 280 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 281 | self.assertEqual(statement.alter_specification[1].column_name, 'col1') 282 | self.assertEqual(statement.alter_specification[1].new_column_name, 'col_no1') 283 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 284 | self.assertEqual(statement.alter_specification[2].column_name, 'col2') 285 | self.assertEqual(statement.alter_specification[2].new_column_name, 'col_no2') 286 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 287 | self.assertEqual(statement.alter_specification[3].column_name, 'col3') 288 | self.assertEqual(statement.alter_specification[3].new_column_name, 'col_no3') 289 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 290 | 291 | 292 | class AlterTableDropSyntaxTest(unittest.TestCase): 293 | 294 | def test_drop(self): 295 | statement = alter_table_syntax.parseString( 296 | "ALTER TABLE test_test DROP col_no0;" 297 | ) 298 | 299 | self.assertFalse(statement.ignore) 300 | self.assertEqual(statement.statement_type, 'ALTER') 301 | self.assertEqual(statement.table_name, 'test_test') 302 | self.assertEqual(statement.alter_specification[0].alter_action, 'DROP COLUMN') 303 | self.assertEqual(statement.alter_specification[0].column_name, 'col_no0') 304 | 305 | def test_drop_column(self): 306 | statement = alter_table_syntax.parseString( 307 | "ALTER TABLE test_test DROP COLUMN col_no0;" 308 | ) 309 | 310 | self.assertFalse(statement.ignore) 311 | self.assertEqual(statement.statement_type, 'ALTER') 312 | self.assertEqual(statement.table_name, 'test_test') 313 | self.assertEqual(statement.alter_specification[0].alter_action, 'DROP COLUMN') 314 | self.assertEqual(statement.alter_specification[0].column_name, 'col_no0') 315 | 316 | def test_drop_primary_key(self): 317 | statement = alter_table_syntax.parseString( 318 | "ALTER TABLE test_test DROP PRIMARY KEY;" 319 | ) 320 | 321 | self.assertFalse(statement.ignore) 322 | self.assertEqual(statement.statement_type, 'ALTER') 323 | self.assertEqual(statement.table_name, 'test_test') 324 | self.assertEqual(statement.alter_specification[0].alter_action, 'DROP PRIMARY KEY') 325 | 326 | def test_drop_index(self): 327 | statement = alter_table_syntax.parseString( 328 | "ALTER TABLE test_test DROP INDEX idx_no0;" 329 | ) 330 | 331 | self.assertFalse(statement.ignore) 332 | self.assertEqual(statement.statement_type, 'ALTER') 333 | self.assertEqual(statement.table_name, 'test_test') 334 | self.assertEqual(statement.alter_specification[0].alter_action, 'DROP INDEX') 335 | self.assertEqual(statement.alter_specification[0].index_name, 'idx_no0') 336 | 337 | def test_drop_key(self): 338 | statement = alter_table_syntax.parseString( 339 | "ALTER TABLE test_test DROP KEY idx_no0;" 340 | ) 341 | 342 | self.assertFalse(statement.ignore) 343 | self.assertEqual(statement.statement_type, 'ALTER') 344 | self.assertEqual(statement.table_name, 'test_test') 345 | self.assertEqual(statement.alter_specification[0].alter_action, 'DROP INDEX') 346 | self.assertEqual(statement.alter_specification[0].index_name, 'idx_no0') 347 | 348 | def test_drop_foreign_key(self): 349 | statement = alter_table_syntax.parseString( 350 | "ALTER TABLE test_test DROP FOREIGN KEY fk_no0;" 351 | ) 352 | 353 | self.assertFalse(statement.ignore) 354 | self.assertEqual(statement.statement_type, 'ALTER') 355 | self.assertEqual(statement.table_name, 'test_test') 356 | self.assertEqual(statement.alter_specification[0].alter_action, 'DROP FOREIGN KEY') 357 | self.assertEqual(statement.alter_specification[0].fk_symbol, 'fk_no0') 358 | 359 | def test_drop_mixed(self): 360 | statement = alter_table_syntax.parseString(""" 361 | ALTER TABLE test_test DROP col_no0, 362 | DROP COLUMN col_no1, 363 | DROP PRIMARY KEY, 364 | DROP INDEX idx_no0, 365 | DROP KEY idx_no1, 366 | DROP FOREIGN KEY fk_no0; 367 | """) 368 | 369 | self.assertFalse(statement.ignore) 370 | self.assertEqual(statement.statement_type, 'ALTER') 371 | self.assertEqual(statement.table_name, 'test_test') 372 | self.assertEqual(statement.alter_specification[0].alter_action, 'DROP COLUMN') 373 | self.assertEqual(statement.alter_specification[0].column_name, 'col_no0') 374 | self.assertEqual(statement.alter_specification[1].alter_action, 'DROP COLUMN') 375 | self.assertEqual(statement.alter_specification[1].column_name, 'col_no1') 376 | self.assertEqual(statement.alter_specification[2].alter_action, 'DROP PRIMARY KEY') 377 | self.assertEqual(statement.alter_specification[3].alter_action, 'DROP INDEX') 378 | self.assertEqual(statement.alter_specification[3].index_name, 'idx_no0') 379 | self.assertEqual(statement.alter_specification[4].alter_action, 'DROP INDEX') 380 | self.assertEqual(statement.alter_specification[4].index_name, 'idx_no1') 381 | self.assertEqual(statement.alter_specification[5].alter_action, 'DROP FOREIGN KEY') 382 | self.assertEqual(statement.alter_specification[5].fk_symbol, 'fk_no0') 383 | 384 | 385 | class AlterTableDatabaseNameTest(unittest.TestCase): 386 | 387 | def test_alter_table_database_name(self): 388 | statement = alter_table_syntax.parseString(""" 389 | ALTER IGNORE TABLE test_db.test_test CHANGE col_no0 col_0 BIT(8) NOT NULL DEFAULT 0 FIRST, 390 | CHANGE col_no1 col_1 LONGTEXT NOT NULL, 391 | CHANGE col_no2 col_2 VARCHAR(200) NULL, 392 | CHANGE col_no3 col_3 BIT(8) AFTER col0; 393 | """) 394 | 395 | self.assertTrue(statement.ignore) 396 | self.assertEqual(statement.statement_type, 'ALTER') 397 | self.assertEqual(statement.database_name, 'test_db') 398 | self.assertEqual(statement.table_name, 'test_test') 399 | self.assertEqual(statement.alter_specification[0].column_name, 'col_no0') 400 | self.assertEqual(statement.alter_specification[0].new_column_name, 'col_0') 401 | self.assertEqual(statement.alter_specification[0].column_position, 'FIRST') 402 | self.assertEqual(statement.alter_specification[1].column_name, 'col_no1') 403 | self.assertEqual(statement.alter_specification[1].new_column_name, 'col_1') 404 | self.assertEqual(statement.alter_specification[1].column_position, 'LAST') 405 | self.assertEqual(statement.alter_specification[2].column_name, 'col_no2') 406 | self.assertEqual(statement.alter_specification[2].new_column_name, 'col_2') 407 | self.assertEqual(statement.alter_specification[2].column_position, 'LAST') 408 | self.assertEqual(statement.alter_specification[3].column_name, 'col_no3') 409 | self.assertEqual(statement.alter_specification[3].new_column_name, 'col_3') 410 | self.assertEqual(statement.alter_specification[3].column_position, 'col0') 411 | 412 | 413 | class AlterTableRenameKeysIndexes(unittest.TestCase): 414 | 415 | def test_rename_index(self): 416 | statement = alter_table_syntax.parseString(""" 417 | ALTER TABLE test RENAME INDEX idx1 TO idx2; 418 | """) 419 | 420 | self.assertEqual(statement.statement_type, 'ALTER') 421 | self.assertEqual(statement.table_name, 'test') 422 | self.assertEqual(statement.alter_specification[0].old_index_name, 423 | 'idx1') 424 | self.assertEqual(statement.alter_specification[0].new_index_name, 425 | 'idx2') 426 | 427 | def test_rename_key(self): 428 | statement = alter_table_syntax.parseString(""" 429 | ALTER TABLE test RENAME KEY key1 TO key2; 430 | """) 431 | 432 | self.assertEqual(statement.statement_type, 'ALTER') 433 | self.assertEqual(statement.table_name, 'test') 434 | self.assertEqual(statement.alter_specification[0].old_key_name, 'key1') 435 | self.assertEqual(statement.alter_specification[0].new_key_name, 'key2') 436 | 437 | def test_rename_mixed_index_key(self): 438 | statement = alter_table_syntax.parseString(""" 439 | ALTER TABLE test 440 | RENAME INDEX idx1 TO idx2, 441 | RENAME KEY key1 TO key2; 442 | """) 443 | 444 | self.assertEqual(statement.statement_type, 'ALTER') 445 | self.assertEqual(statement.table_name, 'test') 446 | self.assertEqual(statement.alter_specification[0].old_index_name, 447 | 'idx1') 448 | self.assertEqual(statement.alter_specification[0].new_index_name, 449 | 'idx2') 450 | self.assertEqual(statement.alter_specification[1].old_key_name, 'key1') 451 | self.assertEqual(statement.alter_specification[1].new_key_name, 'key2') 452 | 453 | 454 | class AlterTableRename(unittest.TestCase): 455 | 456 | def test_rename_table(self): 457 | statements = [ 458 | "ALTER TABLE test1 RENAME test2;", 459 | "ALTER TABLE test1 RENAME TO test2;", 460 | "ALTER TABLE test1 RENAME AS test2;" 461 | ] 462 | for statement in statements: 463 | stmt = alter_table_syntax.parseString(statement) 464 | 465 | self.assertEqual(stmt.statement_type, 'ALTER') 466 | self.assertEqual(stmt.table_name, 'test1') 467 | self.assertEqual(stmt.alter_specification[0].new_table_name, 468 | 'test2') 469 | -------------------------------------------------------------------------------- /tests/grammar/test_column_definition.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import unittest 5 | 6 | from mysqlparse.grammar.column_definition import column_definition_syntax 7 | 8 | 9 | class ColumnDefinitionSyntax(unittest.TestCase): 10 | 11 | def test_plain_field(self): 12 | self.assertEqual(column_definition_syntax.parseString("VARCHAR(255)").null, 'implicit') 13 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255)").default) 14 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255)").auto_increment) 15 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255)").index_type) 16 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255)").comment) 17 | 18 | def test_nullable(self): 19 | self.assertEqual(column_definition_syntax.parseString("VARCHAR(255)").null, 'implicit') 20 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NOT NULL").null) 21 | self.assertTrue(column_definition_syntax.parseString("VARCHAR(255) NULL").null) 22 | 23 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NOT NULL").default) 24 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NULL").default) 25 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NOT NULL").auto_increment) 26 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NULL").auto_increment) 27 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NOT NULL").index_type) 28 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NULL").index_type) 29 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NOT NULL").comment) 30 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) NULL").comment) 31 | 32 | def test_default(self): 33 | self.assertEqual(column_definition_syntax.parseString("VARCHAR(255) DEFAULT 'test'").null, 'implicit') 34 | self.assertEqual(column_definition_syntax.parseString("VARCHAR(255) DEFAULT 'test'").default, 'test') 35 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) DEFAULT 'test'").auto_increment) 36 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) DEFAULT 'test'").index_type) 37 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) DEFAULT 'test'").comment) 38 | 39 | def test_auto_increment(self): 40 | self.assertEqual(column_definition_syntax.parseString("INT(11) AUTO_INCREMENT").null, 'implicit') 41 | self.assertFalse(column_definition_syntax.parseString("INT(11) AUTO_INCREMENT").default) 42 | self.assertTrue(column_definition_syntax.parseString("INT(11) AUTO_INCREMENT").auto_increment) 43 | self.assertFalse(column_definition_syntax.parseString("INT(11) AUTO_INCREMENT").index_type) 44 | self.assertFalse(column_definition_syntax.parseString("INT(11) AUTO_INCREMENT").comment) 45 | 46 | def test_unique_index(self): 47 | self.assertEqual(column_definition_syntax.parseString("INT(11) UNIQUE").null, 'implicit') 48 | self.assertFalse(column_definition_syntax.parseString("INT(11) UNIQUE").default) 49 | self.assertFalse(column_definition_syntax.parseString("INT(11) UNIQUE").auto_increment) 50 | self.assertEqual(column_definition_syntax.parseString("INT(11) UNIQUE").index_type, 'unique_key') 51 | self.assertFalse(column_definition_syntax.parseString("INT(11) UNIQUE").comment) 52 | 53 | self.assertEqual(column_definition_syntax.parseString("INT(11) UNIQUE KEY").null, 'implicit') 54 | self.assertFalse(column_definition_syntax.parseString("INT(11) UNIQUE KEY").default) 55 | self.assertFalse(column_definition_syntax.parseString("INT(11) UNIQUE KEY").auto_increment) 56 | self.assertEqual(column_definition_syntax.parseString("INT(11) UNIQUE KEY").index_type, 'unique_key') 57 | self.assertFalse(column_definition_syntax.parseString("INT(11) UNIQUE KEY").comment) 58 | 59 | def test_primary_key(self): 60 | self.assertEqual(column_definition_syntax.parseString("INT(11) KEY").null, 'implicit') 61 | self.assertFalse(column_definition_syntax.parseString("INT(11) KEY").default) 62 | self.assertFalse(column_definition_syntax.parseString("INT(11) KEY").auto_increment) 63 | self.assertEqual(column_definition_syntax.parseString("INT(11) KEY").index_type, 'primary_key') 64 | self.assertFalse(column_definition_syntax.parseString("INT(11) KEY").comment) 65 | 66 | self.assertEqual(column_definition_syntax.parseString("INT(11) PRIMARY KEY").null, 'implicit') 67 | self.assertFalse(column_definition_syntax.parseString("INT(11) PRIMARY KEY").default) 68 | self.assertFalse(column_definition_syntax.parseString("INT(11) PRIMARY KEY").auto_increment) 69 | self.assertEqual(column_definition_syntax.parseString("INT(11) PRIMARY KEY").index_type, 'primary_key') 70 | self.assertFalse(column_definition_syntax.parseString("INT(11) PRIMARY KEY").comment) 71 | 72 | def test_comment(self): 73 | self.assertEqual(column_definition_syntax.parseString("VARCHAR(255) COMMENT 'test'").null, 'implicit') 74 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) COMMENT 'test'").default) 75 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) COMMENT 'test'").auto_increment) 76 | self.assertFalse(column_definition_syntax.parseString("VARCHAR(255) COMMENT 'test'").index_type) 77 | self.assertEqual(column_definition_syntax.parseString("VARCHAR(255) COMMENT 'test'").comment, 'test') 78 | 79 | class ColumnDefinitionOrderSyntax(unittest.TestCase): 80 | 81 | def test_order_one(self): 82 | stmt = column_definition_syntax.parseString("INT(11) PRIMARY KEY AUTO_INCREMENT NOT NULL") 83 | self.assertFalse(stmt.null) 84 | self.assertEqual(stmt.index_type, 'primary_key') 85 | self.assertTrue(stmt.auto_increment) 86 | 87 | def test_order_two(self): 88 | stmt = column_definition_syntax.parseString("INT(11) AUTO_INCREMENT UNIQUE KEY NULL") 89 | self.assertTrue(stmt.null) 90 | self.assertEqual(stmt.index_type, 'unique_key') 91 | self.assertTrue(stmt.auto_increment) 92 | 93 | def test_order_three(self): 94 | stmt = column_definition_syntax.parseString("INT(11) NOT NULL AUTO_INCREMENT COMMENT 'test'") 95 | self.assertFalse(stmt.null) 96 | self.assertEqual(stmt.index_type, '') 97 | self.assertTrue(stmt.auto_increment) 98 | self.assertEqual(stmt.comment, 'test') 99 | -------------------------------------------------------------------------------- /tests/grammar/test_create_table.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import unittest 5 | 6 | from mysqlparse.grammar.create_table import create_table_syntax 7 | 8 | 9 | class CreateTableSyntaxTest(unittest.TestCase): 10 | 11 | def test_create_table(self): 12 | statement = create_table_syntax.parseString(""" 13 | CREATE TABLE test_test ( 14 | id INT(11) PRIMARY KEY, 15 | col_no1 VARCHAR(255) NOT NULL 16 | ); 17 | """) 18 | 19 | self.assertEqual(statement.statement_type, 'CREATE') 20 | self.assertFalse(statement.temporary) 21 | self.assertTrue(statement.overwrite) 22 | self.assertEqual(statement.table_name, 'test_test') 23 | self.assertEqual(statement.column_specification[0].column_name, "id") 24 | self.assertEqual(statement.column_specification[1].column_name, "col_no1") 25 | self.assertEqual(len(statement.table_options), 0) 26 | 27 | def test_create_table_with_options(self): 28 | statement = create_table_syntax.parseString(""" 29 | CREATE TABLE test_test ( 30 | id INT(11) PRIMARY KEY, 31 | col_no1 VARCHAR(255) NOT NULL 32 | ) ENGINE=MyISAM AUTO_INCREMENT id; 33 | """) 34 | 35 | self.assertEqual(statement.statement_type, 'CREATE') 36 | self.assertFalse(statement.temporary) 37 | self.assertTrue(statement.overwrite) 38 | self.assertEqual(statement.table_name, 'test_test') 39 | self.assertEqual(statement.column_specification[0].column_name, "id") 40 | self.assertEqual(statement.column_specification[1].column_name, "col_no1") 41 | self.assertEqual(len(statement.table_options), 2) 42 | self.assertEqual(statement.table_options[0].key, 'ENGINE') 43 | self.assertEqual(statement.table_options[0].value, 'MyISAM') 44 | self.assertEqual(statement.table_options[1].key, 'AUTO_INCREMENT') 45 | self.assertEqual(statement.table_options[1].value, 'id') 46 | 47 | def test_create_temporary_table(self): 48 | statement = create_table_syntax.parseString(""" 49 | CREATE TEMPORARY TABLE test_test ( 50 | id INT(11) PRIMARY KEY, 51 | col_no1 VARCHAR(255) NOT NULL 52 | ) ENGINE=MyISAM AUTO_INCREMENT id; 53 | """) 54 | 55 | self.assertEqual(statement.statement_type, 'CREATE') 56 | self.assertTrue(statement.temporary) 57 | self.assertTrue(statement.overwrite) 58 | self.assertEqual(statement.table_name, 'test_test') 59 | self.assertEqual(statement.column_specification[0].column_name, "id") 60 | self.assertEqual(statement.column_specification[1].column_name, "col_no1") 61 | self.assertEqual(statement.table_options[0].key, 'ENGINE') 62 | self.assertEqual(statement.table_options[0].value, 'MyISAM') 63 | self.assertEqual(statement.table_options[1].key, 'AUTO_INCREMENT') 64 | self.assertEqual(statement.table_options[1].value, 'id') 65 | 66 | def test_create_table_overwrite(self): 67 | statement = create_table_syntax.parseString(""" 68 | CREATE TABLE IF NOT EXISTS test_test ( 69 | id INT(11) PRIMARY KEY, 70 | col_no1 VARCHAR(255) NOT NULL 71 | ) ENGINE=MyISAM AUTO_INCREMENT id; 72 | """) 73 | 74 | self.assertEqual(statement.statement_type, 'CREATE') 75 | self.assertFalse(statement.temporary) 76 | self.assertFalse(statement.overwrite) 77 | self.assertEqual(statement.table_name, 'test_test') 78 | self.assertEqual(statement.column_specification[0].column_name, "id") 79 | self.assertEqual(statement.column_specification[1].column_name, "col_no1") 80 | self.assertEqual(statement.table_options[0].key, 'ENGINE') 81 | self.assertEqual(statement.table_options[0].value, 'MyISAM') 82 | self.assertEqual(statement.table_options[1].key, 'AUTO_INCREMENT') 83 | self.assertEqual(statement.table_options[1].value, 'id') 84 | 85 | def test_create_temporary_table_overwrite(self): 86 | statement = create_table_syntax.parseString(""" 87 | CREATE TEMPORARY TABLE IF NOT EXISTS test_test ( 88 | id INT(11) PRIMARY KEY, 89 | col_no1 VARCHAR(255) NOT NULL 90 | ) ENGINE=MyISAM AUTO_INCREMENT id; 91 | """) 92 | 93 | self.assertEqual(statement.statement_type, 'CREATE') 94 | self.assertTrue(statement.temporary) 95 | self.assertFalse(statement.overwrite) 96 | self.assertEqual(statement.table_name, 'test_test') 97 | self.assertEqual(statement.column_specification[0].column_name, "id") 98 | self.assertEqual(statement.column_specification[1].column_name, "col_no1") 99 | self.assertEqual(statement.table_options[0].key, 'ENGINE') 100 | self.assertEqual(statement.table_options[0].value, 'MyISAM') 101 | self.assertEqual(statement.table_options[1].key, 'AUTO_INCREMENT') 102 | self.assertEqual(statement.table_options[1].value, 'id') 103 | -------------------------------------------------------------------------------- /tests/grammar/test_data_type.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import unittest 5 | 6 | import pyparsing 7 | 8 | from mysqlparse.grammar.data_type import data_type_syntax 9 | 10 | 11 | class DataTypeSyntaxTest(unittest.TestCase): 12 | 13 | def test_bit(self): 14 | self.assertEquals(data_type_syntax.parseString("BIT").data_type, 'BIT') 15 | self.assertEquals(data_type_syntax.parseString("BIT(8)").data_type, 'BIT') 16 | self.assertEquals(data_type_syntax.parseString("BIT(8)").length[0], '8') 17 | 18 | def test_integers(self): 19 | type_list = ['TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT'] 20 | 21 | type_plain = "{type_name}".format 22 | type_with_length = "{type_name}(8)".format 23 | type_with_unsigned = "{type_name}(8) unsigned".format 24 | type_with_zerofill = "{type_name}(8) zerofill".format 25 | type_with_all_modifiers = "{type_name}(8) UNSIGNED ZEROFILL".format 26 | 27 | for type_name in type_list: 28 | self.assertEquals( 29 | data_type_syntax.parseString(type_plain(type_name=type_name)).data_type, 30 | type_name, 31 | ) 32 | 33 | self.assertEquals( 34 | data_type_syntax.parseString(type_with_length(type_name=type_name)).data_type, 35 | type_name, 36 | ) 37 | self.assertEquals( 38 | data_type_syntax.parseString(type_with_length(type_name=type_name)).length[0], 39 | '8', 40 | ) 41 | self.assertFalse( 42 | data_type_syntax.parseString(type_with_length(type_name=type_name)).unsigned, 43 | ) 44 | self.assertFalse( 45 | data_type_syntax.parseString(type_with_length(type_name=type_name)).zerofill, 46 | ) 47 | 48 | self.assertEquals( 49 | data_type_syntax.parseString(type_with_unsigned(type_name=type_name)).data_type, 50 | type_name, 51 | ) 52 | self.assertEquals( 53 | data_type_syntax.parseString(type_with_unsigned(type_name=type_name)).length[0], 54 | '8', 55 | ) 56 | self.assertTrue( 57 | data_type_syntax.parseString(type_with_unsigned(type_name=type_name)).unsigned, 58 | ) 59 | 60 | self.assertEquals( 61 | data_type_syntax.parseString(type_with_zerofill(type_name=type_name)).data_type, 62 | type_name, 63 | ) 64 | self.assertEquals( 65 | data_type_syntax.parseString(type_with_zerofill(type_name=type_name)).length[0], 66 | '8', 67 | ) 68 | self.assertTrue( 69 | data_type_syntax.parseString(type_with_zerofill(type_name=type_name)).zerofill, 70 | ) 71 | 72 | self.assertEquals( 73 | data_type_syntax.parseString(type_with_all_modifiers(type_name=type_name)).data_type, 74 | type_name, 75 | ) 76 | self.assertEquals( 77 | data_type_syntax.parseString(type_with_all_modifiers(type_name=type_name)).length[0], 78 | '8', 79 | ) 80 | self.assertTrue( 81 | data_type_syntax.parseString(type_with_all_modifiers(type_name=type_name)).unsigned, 82 | ) 83 | self.assertTrue( 84 | data_type_syntax.parseString(type_with_all_modifiers(type_name=type_name)).zerofill, 85 | ) 86 | 87 | def test_decimals(self): 88 | type_list = ['REAL', 'DOUBLE', 'FLOAT', 'DECIMAL', 'NUMERIC'] 89 | 90 | for type_name in type_list: 91 | self.assertEquals( 92 | data_type_syntax.parseString("{type_name}".format(type_name=type_name)).data_type, 93 | type_name, 94 | ) 95 | 96 | self.assertEquals( 97 | data_type_syntax.parseString("{type_name}(10)".format(type_name=type_name)).data_type, 98 | type_name, 99 | ) 100 | self.assertEquals( 101 | data_type_syntax.parseString("{type_name}(10)".format(type_name=type_name)).length, 102 | '10', 103 | ) 104 | self.assertEquals( 105 | data_type_syntax.parseString("{type_name}(10, 2)".format(type_name=type_name)).decimals, 106 | '2', 107 | ) 108 | self.assertFalse( 109 | data_type_syntax.parseString("{type_name}(10, 2)".format(type_name=type_name)).unsigned, 110 | ) 111 | self.assertFalse( 112 | data_type_syntax.parseString("{type_name}(10, 2)".format(type_name=type_name)).zerofill, 113 | ) 114 | 115 | self.assertTrue( 116 | data_type_syntax.parseString("{type_name}(10, 2) UNSIGNED".format(type_name=type_name)).unsigned, 117 | ) 118 | self.assertTrue( 119 | data_type_syntax.parseString("{type_name}(10, 2) ZEROFILL".format(type_name=type_name)).zerofill, 120 | ) 121 | 122 | self.assertTrue( 123 | data_type_syntax.parseString("{type_name}(10, 2) UNSIGNED ZEROFILL".format(type_name=type_name)).unsigned, 124 | ) 125 | self.assertTrue( 126 | data_type_syntax.parseString("{type_name}(10, 2) UNSIGNED ZEROFILL".format(type_name=type_name)).zerofill, 127 | ) 128 | 129 | def test_datetimes(self): 130 | self.assertEquals(data_type_syntax.parseString("DATE").data_type, 'DATE') 131 | self.assertEquals(data_type_syntax.parseString("YEAR").data_type, 'YEAR') 132 | 133 | type_list = ['TIME', 'TIMESTAMP', 'DATETIME'] 134 | 135 | type_plain = "{type_name}".format 136 | type_with_precision = "{type_name}(6)".format 137 | 138 | for type_name in type_list: 139 | self.assertEquals( 140 | data_type_syntax.parseString(type_plain(type_name=type_name)).data_type, 141 | type_name, 142 | ) 143 | 144 | self.assertEquals( 145 | data_type_syntax.parseString(type_with_precision(type_name=type_name)).data_type, 146 | type_name, 147 | ) 148 | self.assertEquals( 149 | data_type_syntax.parseString(type_with_precision(type_name=type_name)).precision[0], 150 | '6', 151 | ) 152 | 153 | def test_chars(self): 154 | self.assertEquals(data_type_syntax.parseString("CHAR").data_type, 'CHAR') 155 | self.assertEquals(data_type_syntax.parseString("CHAR(8)").length[0], '8') 156 | self.assertEquals(data_type_syntax.parseString("CHAR(8) BINARY").length[0], '8') 157 | self.assertEquals(data_type_syntax.parseString("CHAR(8) BINARY").binary, True) 158 | self.assertEquals(data_type_syntax.parseString("CHAR(8) CHARACTER SET 'utf8'").character_set, "utf8") 159 | self.assertEquals(data_type_syntax.parseString("CHAR(8) COLLATE 'utf8_general'").collation_name, "utf8_general") 160 | self.assertEquals( 161 | data_type_syntax.parseString( 162 | "CHAR(8) BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general'" 163 | ).character_set, 164 | "utf8" 165 | ) 166 | self.assertEquals( 167 | data_type_syntax.parseString( 168 | "CHAR(8) BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general'" 169 | ).collation_name, 170 | "utf8_general" 171 | ) 172 | self.assertTrue( 173 | data_type_syntax.parseString( 174 | "CHAR(8) BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general'" 175 | ).binary, 176 | ) 177 | 178 | def test_varchar(self): 179 | with self.assertRaises(pyparsing.ParseException): 180 | data_type_syntax.parseString("VARCHAR").data_type 181 | 182 | self.assertEquals(data_type_syntax.parseString("VARCHAR(8)").length[0], '8') 183 | self.assertEquals(data_type_syntax.parseString("VARCHAR(8) BINARY").length[0], '8') 184 | self.assertEquals(data_type_syntax.parseString("VARCHAR(8) BINARY").binary, True) 185 | self.assertEquals(data_type_syntax.parseString("VARCHAR(8) CHARACTER SET 'utf8'").character_set, "utf8") 186 | self.assertEquals( 187 | data_type_syntax.parseString("VARCHAR(8) COLLATE 'utf8_general'").collation_name, 188 | "utf8_general", 189 | ) 190 | self.assertEquals( 191 | data_type_syntax.parseString( 192 | "VARCHAR(8) BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general'" 193 | ).character_set, 194 | "utf8" 195 | ) 196 | self.assertEquals( 197 | data_type_syntax.parseString( 198 | "VARCHAR(8) BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general'" 199 | ).collation_name, 200 | "utf8_general" 201 | ) 202 | self.assertTrue( 203 | data_type_syntax.parseString( 204 | "VARCHAR(8) BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general'" 205 | ).binary, 206 | ) 207 | 208 | def test_binary(self): 209 | self.assertEquals(data_type_syntax.parseString("BINARY").data_type, 'BINARY') 210 | self.assertEquals(data_type_syntax.parseString("BINARY(8)").data_type, 'BINARY') 211 | self.assertEquals(data_type_syntax.parseString("BINARY(8)").length[0], '8') 212 | 213 | def test_varbinary(self): 214 | with self.assertRaises(pyparsing.ParseException): 215 | data_type_syntax.parseString("VARBINARY").data_type 216 | 217 | self.assertEquals(data_type_syntax.parseString("VARBINARY(8)").length[0], '8') 218 | 219 | def test_blobs(self): 220 | type_list = ['TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'] 221 | 222 | for type_name in type_list: 223 | self.assertEquals(data_type_syntax.parseString(type_name).data_type, type_name) 224 | 225 | def test_texts(self): 226 | type_list = ['TINYTEXT', 'TEXT', 'MEDIUMTEXT', 'LONGTEXT'] 227 | 228 | for type_name in type_list: 229 | self.assertEquals(data_type_syntax.parseString(type_name).data_type, type_name) 230 | 231 | self.assertEquals( 232 | data_type_syntax.parseString( 233 | "{type_name} BINARY".format(type_name=type_name) 234 | ).data_type, 235 | type_name, 236 | ) 237 | self.assertTrue( 238 | data_type_syntax.parseString( 239 | "{type_name} BINARY".format(type_name=type_name) 240 | ).binary, 241 | ) 242 | 243 | self.assertEquals( 244 | data_type_syntax.parseString( 245 | "{type_name} CHARACTER SET 'utf8'".format(type_name=type_name) 246 | ).data_type, 247 | type_name, 248 | ) 249 | self.assertEquals( 250 | data_type_syntax.parseString( 251 | "{type_name} CHARACTER SET 'utf8'".format(type_name=type_name) 252 | ).character_set, 253 | 'utf8', 254 | ) 255 | 256 | self.assertEquals( 257 | data_type_syntax.parseString( 258 | "{type_name} COLLATE 'utf8_general_ci'".format(type_name=type_name) 259 | ).data_type, 260 | type_name, 261 | ) 262 | self.assertEquals( 263 | data_type_syntax.parseString( 264 | "{type_name} COLLATE 'utf8_general_ci'".format(type_name=type_name) 265 | ).collation_name, 266 | 'utf8_general_ci', 267 | ) 268 | self.assertFalse( 269 | data_type_syntax.parseString( 270 | "{type_name} COLLATE 'utf8_general_ci'".format(type_name=type_name) 271 | ).binary, 272 | ) 273 | 274 | self.assertEquals( 275 | data_type_syntax.parseString( 276 | "{type_name} BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'".format(type_name=type_name) 277 | ).data_type, 278 | type_name, 279 | ) 280 | self.assertEquals( 281 | data_type_syntax.parseString( 282 | "{type_name} BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'".format(type_name=type_name) 283 | ).character_set, 284 | 'utf8', 285 | ) 286 | self.assertEquals( 287 | data_type_syntax.parseString( 288 | "{type_name} BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'".format(type_name=type_name) 289 | ).collation_name, 290 | 'utf8_general_ci', 291 | ) 292 | self.assertTrue( 293 | data_type_syntax.parseString( 294 | "{type_name} BINARY CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'".format(type_name=type_name) 295 | ).binary, 296 | ) 297 | 298 | def test_enumerables(self): 299 | type_list = ['ENUM', 'SET'] 300 | 301 | for type_name in type_list: 302 | self.assertEquals( 303 | data_type_syntax.parseString( 304 | "{type_name}('option1', 'option2', 'option3')".format(type_name=type_name) 305 | ).data_type, 306 | type_name, 307 | ) 308 | 309 | self.assertEquals( 310 | data_type_syntax.parseString( 311 | "{type_name}('option1', 'option2', 'option3')".format(type_name=type_name) 312 | ).value_list.asList(), 313 | ['option1', 'option2', 'option3'], 314 | ) 315 | 316 | self.assertEquals( 317 | data_type_syntax.parseString( 318 | "{type_name}('option1', 'option2', 'option3') CHARACTER SET 'utf8'".format(type_name=type_name) 319 | ).value_list.asList(), 320 | ['option1', 'option2', 'option3'], 321 | ) 322 | self.assertEquals( 323 | data_type_syntax.parseString( 324 | "{type_name}('option1', 'option2', 'option3') CHARACTER SET 'utf8'".format(type_name=type_name) 325 | ).character_set, 326 | 'utf8', 327 | ) 328 | 329 | self.assertEquals( 330 | data_type_syntax.parseString( 331 | "{type_name}('option1', 'option2', 'option3') CHARACTER SET 'utf8'".format(type_name=type_name) 332 | ).value_list.asList(), 333 | ['option1', 'option2', 'option3'], 334 | ) 335 | self.assertEquals( 336 | data_type_syntax.parseString( 337 | "{type_name}('option1', 'option2', 'option3') CHARACTER SET 'utf8'".format(type_name=type_name) 338 | ).character_set, 339 | 'utf8', 340 | ) 341 | self.assertEquals( 342 | data_type_syntax.parseString( 343 | "{type_name}('option1', 'option2', 'option3') CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'".format( 344 | type_name=type_name 345 | ) 346 | ).collation_name, 347 | 'utf8_general_ci', 348 | ) 349 | -------------------------------------------------------------------------------- /tests/grammar/test_drop_table.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import ( 3 | absolute_import, 4 | division, 5 | print_function, 6 | unicode_literals 7 | ) 8 | 9 | import unittest 10 | 11 | from mysqlparse.grammar.drop_table import drop_table_syntax 12 | 13 | 14 | class DropTableSyntaxTest(unittest.TestCase): 15 | 16 | def test_drop_table(self): 17 | statement = drop_table_syntax.parseString(""" 18 | DROP TABLE test; 19 | """) 20 | 21 | self.assertEqual(statement.statement_type, 'DROP') 22 | self.assertFalse(statement.temporary) 23 | self.assertFalse(statement.if_exists) 24 | self.assertFalse(statement.dropped[0].database_name) 25 | self.assertEqual(statement.dropped[0].table_name, 'test') 26 | 27 | def test_drop_multiple_tables(self): 28 | statement = drop_table_syntax.parseString(""" 29 | DROP TABLE test, test_db.test_table, yet_another_test_table; 30 | """) 31 | 32 | self.assertEqual(statement.statement_type, 'DROP') 33 | self.assertFalse(statement.temporary) 34 | self.assertFalse(statement.if_exists) 35 | self.assertFalse(statement.dropped[0].database_name) 36 | self.assertEqual(statement.dropped[0].table_name, 'test') 37 | self.assertEqual(statement.dropped[1].database_name, 'test_db') 38 | self.assertEqual(statement.dropped[1].table_name, 'test_table') 39 | self.assertFalse(statement.dropped[2].database_name) 40 | self.assertEqual(statement.dropped[2].table_name, 41 | 'yet_another_test_table') 42 | 43 | def test_drop_temporary_table(self): 44 | statement = drop_table_syntax.parseString(""" 45 | DROP TEMPORARY TABLE test; 46 | """) 47 | 48 | self.assertEqual(statement.statement_type, 'DROP') 49 | self.assertTrue(statement.temporary) 50 | self.assertFalse(statement.if_exists) 51 | self.assertFalse(statement.dropped[0].database_name) 52 | self.assertEqual(statement.dropped[0].table_name, 'test') 53 | 54 | def test_drop_table_if_exists(self): 55 | statement = drop_table_syntax.parseString(""" 56 | DROP TABLE IF EXISTS test; 57 | """) 58 | 59 | self.assertEqual(statement.statement_type, 'DROP') 60 | self.assertFalse(statement.temporary) 61 | self.assertTrue(statement.if_exists) 62 | self.assertFalse(statement.dropped[0].database_name) 63 | self.assertEqual(statement.dropped[0].table_name, 'test') 64 | -------------------------------------------------------------------------------- /tests/grammar/test_identifier.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import unittest 5 | 6 | from pyparsing import ParseException 7 | 8 | from mysqlparse.grammar.identifier import identifier_syntax 9 | 10 | 11 | class IdentifierSyntaxTest(unittest.TestCase): 12 | 13 | def test_valid_unquoted(self): 14 | statement = identifier_syntax.parseString("valid_$name") 15 | self.assertEqual(statement[0], "valid_$name") 16 | 17 | def test_valid_backquoted(self): 18 | statement = identifier_syntax.parseString("`valid_$name`") 19 | self.assertEqual(statement[0], "valid_$name") 20 | 21 | def test_valid_singlequoted(self): 22 | statement = identifier_syntax.parseString("'valid_$name'") 23 | self.assertEqual(statement[0], "valid_$name") 24 | 25 | def test_valid_doublequoted(self): 26 | statement = identifier_syntax.parseString('"valid_$name"') 27 | self.assertEqual(statement[0], "valid_$name") 28 | 29 | def test_valid_backquoted_nonstandard(self): 30 | statement = identifier_syntax.parseString("`valid $name`") 31 | self.assertEqual(statement[0], "valid $name") 32 | 33 | def test_valid_singlequoted_nonstandard(self): 34 | statement = identifier_syntax.parseString("'valid $name'") 35 | self.assertEqual(statement[0], "valid $name") 36 | 37 | def test_valid_doublequoted_nonstandard(self): 38 | statement = identifier_syntax.parseString('"valid $name"') 39 | self.assertEqual(statement[0], "valid $name") 40 | 41 | def test_invalid_missing_backquote(self): 42 | with self.assertRaises(ParseException): 43 | identifier_syntax.parseString("`valid $name") 44 | 45 | def test_invalid_missing_singlequote(self): 46 | with self.assertRaises(ParseException): 47 | identifier_syntax.parseString("'valid $name") 48 | 49 | def test_invalid_missing_doublequote(self): 50 | with self.assertRaises(ParseException): 51 | identifier_syntax.parseString('"valid $name') 52 | -------------------------------------------------------------------------------- /tests/grammar/test_rename_table.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import unittest 5 | 6 | from mysqlparse.grammar.rename_table import rename_table_syntax 7 | 8 | 9 | class RenameTableSyntaxTest(unittest.TestCase): 10 | 11 | def test_rename_table(self): 12 | statement = rename_table_syntax.parseString(""" 13 | RENAME TABLE test1 TO test2; 14 | """) 15 | 16 | self.assertEqual(statement.statement_type, 'RENAME') 17 | self.assertFalse(statement.table_renamed[0].old_database_name) 18 | self.assertEqual(statement.table_renamed[0].old_table_name, 'test1') 19 | self.assertFalse(statement.table_renamed[0].new_database_name) 20 | self.assertEqual(statement.table_renamed[0].new_table_name, 'test2') 21 | 22 | def test_rename_multiple_tables(self): 23 | statement = rename_table_syntax.parseString(""" 24 | RENAME TABLE 25 | test1 TO test2, 26 | test3 TO test4, 27 | test5 TO test6; 28 | """) 29 | 30 | self.assertEqual(statement.statement_type, 'RENAME') 31 | self.assertFalse(statement.table_renamed[0].old_database_name) 32 | self.assertEqual(statement.table_renamed[0].old_table_name, 'test1') 33 | self.assertFalse(statement.table_renamed[0].new_database_name) 34 | self.assertEqual(statement.table_renamed[0].new_table_name, 'test2') 35 | self.assertFalse(statement.table_renamed[1].old_database_name) 36 | self.assertEqual(statement.table_renamed[1].old_table_name, 'test3') 37 | self.assertFalse(statement.table_renamed[1].new_database_name) 38 | self.assertEqual(statement.table_renamed[1].new_table_name, 'test4') 39 | self.assertFalse(statement.table_renamed[2].old_database_name) 40 | self.assertEqual(statement.table_renamed[2].old_table_name, 'test5') 41 | self.assertFalse(statement.table_renamed[2].new_database_name) 42 | self.assertEqual(statement.table_renamed[2].new_table_name, 'test6') 43 | 44 | def test_rename_database_table(self): 45 | statement = rename_table_syntax.parseString(""" 46 | RENAME TABLE db1.test1 TO db2.test2; 47 | """) 48 | 49 | self.assertEqual(statement.statement_type, 'RENAME') 50 | self.assertEqual(statement.table_renamed[0].old_database_name, 'db1') 51 | self.assertEqual(statement.table_renamed[0].old_table_name, 'test1') 52 | self.assertEqual(statement.table_renamed[0].new_database_name, 'db2') 53 | self.assertEqual(statement.table_renamed[0].new_table_name, 'test2') 54 | 55 | def test_rename_mixed_databases_tables(self): 56 | statement = rename_table_syntax.parseString(""" 57 | RENAME TABLE 58 | db1.test1 TO db2.test2, 59 | test3 TO test4; 60 | """) 61 | 62 | self.assertEqual(statement.statement_type, 'RENAME') 63 | self.assertEqual(statement.table_renamed[0].old_database_name, 'db1') 64 | self.assertEqual(statement.table_renamed[0].old_table_name, 'test1') 65 | self.assertEqual(statement.table_renamed[0].new_database_name, 'db2') 66 | self.assertEqual(statement.table_renamed[0].new_table_name, 'test2') 67 | self.assertFalse(statement.table_renamed[1].old_database_name) 68 | self.assertEqual(statement.table_renamed[1].old_table_name, 'test3') 69 | self.assertFalse(statement.table_renamed[1].new_database_name) 70 | self.assertEqual(statement.table_renamed[1].new_table_name, 'test4') 71 | -------------------------------------------------------------------------------- /tests/grammar/test_sql_file.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import unittest 5 | 6 | from mysqlparse.grammar.sql_file import sql_file_syntax 7 | 8 | 9 | class SqlFileSyntaxTest(unittest.TestCase): 10 | 11 | def test_multiple_statements(self): 12 | sql_file = sql_file_syntax.parseString(""" 13 | CREATE TABLE test_table1 ( 14 | test_column1 INT(11) PRIMARY KEY AUTO_INCREMENT NOT NULL, 15 | test_column2 INT(11) NOT NULL 16 | ); 17 | 18 | ALTER TABLE test_table2 ADD col_no0 BIT(8) NOT NULL DEFAULT 0 FIRST, 19 | ADD col_no1 LONGTEXT NOT NULL, 20 | ADD col_no2 VARCHAR(200) NULL, 21 | ADD col_no3 BIT(8) AFTER col0; 22 | 23 | CREATE TABLE test_table3 ( 24 | test_column INT(11) PRIMARY KEY AUTO_INCREMENT NOT NULL 25 | ); 26 | 27 | ALTER TABLE test_table4 ADD col_no0 BIT(8) NOT NULL DEFAULT 0 FIRST, 28 | ADD col_no1 LONGTEXT NOT NULL, 29 | ADD col_no2 VARCHAR(200) NULL, 30 | ADD col_no3 BIT(8) AFTER col0; 31 | 32 | """) 33 | 34 | self.assertEqual(len(sql_file.statements), 4) 35 | self.assertEqual(sql_file.statements[0].table_name, 'test_table1') 36 | self.assertEqual(sql_file.statements[1].table_name, 'test_table2') 37 | self.assertEqual(sql_file.statements[2].table_name, 'test_table3') 38 | self.assertEqual(sql_file.statements[3].table_name, 'test_table4') 39 | -------------------------------------------------------------------------------- /tests/test_mysqlparse.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from os import path 5 | import unittest 6 | 7 | import mysqlparse 8 | 9 | 10 | class ParseTest(unittest.TestCase): 11 | 12 | def setUp(self): 13 | super(ParseTest, self).setUp() 14 | 15 | self.fixture_dir = path.join(path.dirname(__file__), "fixtures") 16 | 17 | def test_string(self): 18 | with open(path.join(self.fixture_dir, 'test.sql'), 'r') as f: 19 | sql_file = mysqlparse.parse(f.read()) 20 | 21 | self.assertEqual(len(sql_file.statements), 4) 22 | self.assertEqual(sql_file.statements[0].table_name, 'test_table1') 23 | self.assertEqual(sql_file.statements[1].table_name, 'test_table2') 24 | self.assertEqual(sql_file.statements[2].table_name, 'test_table3') 25 | self.assertEqual(sql_file.statements[3].table_name, 'test_table4') 26 | 27 | def test_file(self): 28 | with open(path.join(self.fixture_dir, 'test.sql'), 'r') as f: 29 | sql_file = mysqlparse.parse(f) 30 | 31 | self.assertEqual(len(sql_file.statements), 4) 32 | self.assertEqual(sql_file.statements[0].table_name, 'test_table1') 33 | self.assertEqual(sql_file.statements[1].table_name, 'test_table2') 34 | self.assertEqual(sql_file.statements[2].table_name, 'test_table3') 35 | self.assertEqual(sql_file.statements[3].table_name, 'test_table4') 36 | 37 | def test_typeerror(self): 38 | with self.assertRaises(TypeError) as ctx: 39 | mysqlparse.parse(None) 40 | 41 | self.assertEqual(str(ctx.exception), "Expected file-like or string object, but got 'NoneType' instead.") 42 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py36, py36-lint 3 | 4 | [testenv] 5 | commands = python setup.py test {posargs} 6 | 7 | [testenv:py36-lint] 8 | deps = 9 | flake8==2.5.4 10 | commands=flake8 mysqlparse tests {posargs} 11 | --------------------------------------------------------------------------------