summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Ungureanu <udan1107@gmail.com>2015-05-26 01:02:55 +0300
committerDan Ungureanu <udan1107@gmail.com>2015-06-08 19:37:46 +0300
commit0a52978705d59c50f785f0f9cf537161046beb21 (patch)
tree00051de2f829f6b1f5c95c54da89be1f298b77a8
downloadsql-parser-0a52978705d59c50f785f0f9cf537161046beb21.zip
sql-parser-0a52978705d59c50f785f0f9cf537161046beb21.tar.gz
sql-parser-0a52978705d59c50f785f0f9cf537161046beb21.tar.bz2
Initial commit.
-rw-r--r--.gitignore2
-rw-r--r--LICENSE.txt339
-rw-r--r--README.md7
-rw-r--r--composer.json27
-rw-r--r--composer.lock1048
-rw-r--r--phpunit.xml23
-rw-r--r--src/Context.php470
-rw-r--r--src/Exceptions/LexerException.php41
-rw-r--r--src/Exceptions/ParserException.php32
-rw-r--r--src/Fragment.php30
-rw-r--r--src/Fragments/ArrayFragment.php95
-rw-r--r--src/Fragments/CallKeyword.php86
-rw-r--r--src/Fragments/CreateDefFragment.php108
-rw-r--r--src/Fragments/DataTypeFragment.php92
-rw-r--r--src/Fragments/FieldDefFragment.php181
-rw-r--r--src/Fragments/FieldFragment.php141
-rw-r--r--src/Fragments/FromKeyword.php68
-rw-r--r--src/Fragments/IntoKeyword.php108
-rw-r--r--src/Fragments/LimitKeyword.php91
-rw-r--r--src/Fragments/OptionsFragment.php134
-rw-r--r--src/Fragments/OrderKeyword.php90
-rw-r--r--src/Fragments/ParamDefFragment.php125
-rw-r--r--src/Fragments/RenameKeyword.php117
-rw-r--r--src/Fragments/SelectKeyword.php83
-rw-r--r--src/Fragments/SetKeyword.php105
-rw-r--r--src/Fragments/ValuesKeyword.php115
-rw-r--r--src/Fragments/WhereKeyword.php100
-rw-r--r--src/Lexer.php561
-rw-r--r--src/Parser.php183
-rw-r--r--src/Statement.php153
-rw-r--r--src/Statements/CallStatement.php19
-rw-r--r--src/Statements/CreateStatement.php73
-rw-r--r--src/Statements/DeleteStatement.php74
-rw-r--r--src/Statements/InsertStatement.php78
-rw-r--r--src/Statements/RenameStatement.php23
-rw-r--r--src/Statements/ReplaceStatement.php63
-rw-r--r--src/Statements/SelectStatement.php123
-rw-r--r--src/Statements/UpdateStatement.php79
-rw-r--r--src/Token.php253
-rw-r--r--src/TokensList.php118
-rw-r--r--src/UtfString.php220
-rw-r--r--tests/bootstrap.php66
-rw-r--r--tests/data/lex.in1
-rw-r--r--tests/data/lex.outbin0 -> 2533 bytes
-rw-r--r--tests/data/lexBool.in1
-rw-r--r--tests/data/lexBool.out1
-rw-r--r--tests/data/lexComment.in4
-rw-r--r--tests/data/lexComment.out9
-rw-r--r--tests/data/lexDelimiter.in3
-rw-r--r--tests/data/lexDelimiter.out5
-rw-r--r--tests/data/lexKeyword.in1
-rw-r--r--tests/data/lexKeyword.out1
-rw-r--r--tests/data/lexNumber.in3
-rw-r--r--tests/data/lexNumber.out6
-rw-r--r--tests/data/lexOperator.in1
-rw-r--r--tests/data/lexOperator.out1
-rw-r--r--tests/data/lexString.in1
-rw-r--r--tests/data/lexString.out1
-rw-r--r--tests/data/lexStringErr1.in1
-rw-r--r--tests/data/lexStringErr1.outbin0 -> 2604 bytes
-rw-r--r--tests/data/lexSymbol.in2
-rw-r--r--tests/data/lexSymbol.out3
-rw-r--r--tests/data/lexSymbolErr1.in2
-rw-r--r--tests/data/lexSymbolErr1.outbin0 -> 4023 bytes
-rw-r--r--tests/data/lexSymbolErr2.in2
-rw-r--r--tests/data/lexSymbolErr2.outbin0 -> 4556 bytes
-rw-r--r--tests/data/lexSymbolErr3.in1
-rw-r--r--tests/data/lexSymbolErr3.outbin0 -> 1829 bytes
-rw-r--r--tests/data/lexWhitespace.in10
-rw-r--r--tests/data/lexWhitespace.out19
-rw-r--r--tests/data/parse.in1
-rw-r--r--tests/data/parse.out1
-rw-r--r--tests/data/parseArrayErr1.in1
-rw-r--r--tests/data/parseArrayErr1.outbin0 -> 4394 bytes
-rw-r--r--tests/data/parseArrayErr2.in1
-rw-r--r--tests/data/parseArrayErr2.outbin0 -> 5563 bytes
-rw-r--r--tests/data/parseArrayErr3.in1
-rw-r--r--tests/data/parseArrayErr3.out1
-rw-r--r--tests/data/parseCall.in1
-rw-r--r--tests/data/parseCall.out1
-rw-r--r--tests/data/parseCall2.in1
-rw-r--r--tests/data/parseCall2.out1
-rw-r--r--tests/data/parseCreateFunction.in6
-rw-r--r--tests/data/parseCreateFunction.out6
-rw-r--r--tests/data/parseCreateFunctionErr1.in6
-rw-r--r--tests/data/parseCreateFunctionErr1.outbin0 -> 9022 bytes
-rw-r--r--tests/data/parseCreateProcedure.in4
-rw-r--r--tests/data/parseCreateProcedure.out4
-rw-r--r--tests/data/parseCreateProcedure2.in4
-rw-r--r--tests/data/parseCreateProcedure2.out4
-rw-r--r--tests/data/parseCreateTable.in7
-rw-r--r--tests/data/parseCreateTable.out7
-rw-r--r--tests/data/parseDelete.in7
-rw-r--r--tests/data/parseDelete.out7
-rw-r--r--tests/data/parseInsert.in6
-rw-r--r--tests/data/parseInsert.out6
-rw-r--r--tests/data/parseRename.in1
-rw-r--r--tests/data/parseRename.out1
-rw-r--r--tests/data/parseRename2.in1
-rw-r--r--tests/data/parseRename2.out1
-rw-r--r--tests/data/parseReplace.in3
-rw-r--r--tests/data/parseReplace.out3
-rw-r--r--tests/data/parseReplace2.in4
-rw-r--r--tests/data/parseReplace2.out4
-rw-r--r--tests/data/parseSelect.in14
-rw-r--r--tests/data/parseSelect.out14
-rw-r--r--tests/data/parseSelectErr1.in14
-rw-r--r--tests/data/parseSelectErr1.out14
-rw-r--r--tests/data/parseSelectNested.in1
-rw-r--r--tests/data/parseSelectNested.out1
-rw-r--r--tests/data/parseUpdate.in5
-rw-r--r--tests/data/parseUpdate.out5
-rw-r--r--tests/data/parseUpdate2.in8
-rw-r--r--tests/data/parseUpdate2.out8
-rw-r--r--tests/lexer/IsMethodsTest.php123
-rw-r--r--tests/lexer/LexerTest.php104
-rw-r--r--tests/lexer/TokenTest.php76
-rw-r--r--tests/lexer/TokensList.php103
-rw-r--r--tests/parser/ArrayFragmentTest.php20
-rw-r--r--tests/parser/CallStatementTest.php15
-rw-r--r--tests/parser/CreateStatementTest.php30
-rw-r--r--tests/parser/DeleteStatementTest.php10
-rw-r--r--tests/parser/InsertStatementTest.php10
-rw-r--r--tests/parser/ParserTest.php66
-rw-r--r--tests/parser/RenameStatementTest.php15
-rw-r--r--tests/parser/ReplaceStatementTest.php15
-rw-r--r--tests/parser/SelectStatementTest.php22
-rw-r--r--tests/parser/UpdateStatementTest.php15
128 files changed, 6833 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4fc2819
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+vendor/
+coverage.xml
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..40b0e9d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# SQL Parser
+
+A validating SQL lexer and parser with a focus on MySQL dialect.
+
+This library was originally developed for phpMyAdmin during the Google Summer of Code 2015.
+
+This is an alpha version. For more information, please check: [Overview](https://github.com/udan11/sql-parser/wiki/Overview) or the [Examples](https://github.com/udan11/sql-parser/wiki/Examples). \ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..efe0e56
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "udan11/sql-parser",
+ "description": "A validating SQL lexer and parser with a focus on MySQL dialect.",
+ "license": "GPLv2",
+ "keywords": ["sql", "lexer", "parser", "analysis"],
+ "homepage": "https://github.com/udan11/sql-parser",
+ "support": {
+ "issues": "https://github.com/udan11/sql-parser/issues",
+ "source": "https://github.com/udan11/sql-parser"
+ },
+ "authors": [
+ {
+ "name": "Dan Ungureanu",
+ "email": "udan1107@gmail.com"
+ }
+ ],
+ "require": {},
+ "require-dev": {
+ "phpunit/php-code-coverage": "~2.0",
+ "phpunit/phpunit": "4.*"
+ },
+ "autoload": {
+ "psr-4": {
+ "SqlParser\\": "src"
+ }
+ }
+} \ No newline at end of file
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..eb408bd
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,1048 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "6699f231f94d0f83909de340d98260dc",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119",
+ "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3,<8.0-DEV"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "2.0.*@ALPHA"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Instantiator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://github.com/doctrine/instantiator",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2014-10-13 12:58:55"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "dflydev/markdown": "~1.0",
+ "erusev/parsedown": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "phpDocumentor": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "mike.vanriel@naenius.com"
+ }
+ ],
+ "time": "2015-02-03 12:10:50"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+ "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "phpdocumentor/reflection-docblock": "~2.0",
+ "sebastian/comparator": "~1.1"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Prophecy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2015-04-27 22:15:08"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "2.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "3703c4bb67c8700957dd41c843254658539d091d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/3703c4bb67c8700957dd41c843254658539d091d",
+ "reference": "3703c4bb67c8700957dd41c843254658539d091d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "phpunit/php-file-iterator": "~1.3",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-token-stream": "~1.3",
+ "sebastian/environment": "~1.0",
+ "sebastian/version": "~1.0"
+ },
+ "require-dev": {
+ "ext-xdebug": ">=2.1.4",
+ "phpunit/phpunit": "~4"
+ },
+ "suggest": {
+ "ext-dom": "*",
+ "ext-xdebug": ">=2.2.1",
+ "ext-xmlwriter": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2015-06-06 08:33:23"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb",
+ "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2015-04-02 05:19:05"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
+ "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "Text/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2014-01-30 17:20:04"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
+ "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "PHP/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2013-08-02 07:42:54"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "eab81d02569310739373308137284e0158424330"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/eab81d02569310739373308137284e0158424330",
+ "reference": "eab81d02569310739373308137284e0158424330",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "time": "2015-04-08 04:46:07"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "4.7.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "8e0c63329c8c4185296b8d357daa5c6bae43080f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e0c63329c8c4185296b8d357daa5c6bae43080f",
+ "reference": "8e0c63329c8c4185296b8d357daa5c6bae43080f",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "php": ">=5.3.3",
+ "phpspec/prophecy": "~1.3,>=1.3.1",
+ "phpunit/php-code-coverage": "~2.1",
+ "phpunit/php-file-iterator": "~1.4",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-timer": "~1.0",
+ "phpunit/phpunit-mock-objects": "~2.3",
+ "sebastian/comparator": "~1.1",
+ "sebastian/diff": "~1.2",
+ "sebastian/environment": "~1.2",
+ "sebastian/exporter": "~1.2",
+ "sebastian/global-state": "~1.0",
+ "sebastian/version": "~1.0",
+ "symfony/yaml": "~2.1|~3.0"
+ },
+ "suggest": {
+ "phpunit/php-invoker": "~1.1"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.7.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2015-06-06 08:36:08"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "2.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "253c005852591fd547fc18cd5b7b43a1ec82d8f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/253c005852591fd547fc18cd5b7b43a1ec82d8f7",
+ "reference": "253c005852591fd547fc18cd5b7b43a1ec82d8f7",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "~1.0,>=1.0.2",
+ "php": ">=5.3.3",
+ "phpunit/php-text-template": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ],
+ "time": "2015-05-29 05:19:18"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "1dd8869519a225f7f2b9eb663e225298fade819e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dd8869519a225f7f2b9eb663e225298fade819e",
+ "reference": "1dd8869519a225f7f2b9eb663e225298fade819e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/diff": "~1.2",
+ "sebastian/exporter": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "time": "2015-01-29 16:28:08"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3",
+ "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "http://www.github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff"
+ ],
+ "time": "2015-02-22 15:13:53"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5a8c7d31914337b69923db26c4221b81ff5a196e",
+ "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "time": "2015-01-01 10:01:08"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "84839970d05254c73cde183a721c7af13aede943"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/84839970d05254c73cde183a721c7af13aede943",
+ "reference": "84839970d05254c73cde183a721c7af13aede943",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2015-01-27 07:23:06"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
+ "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2014-10-06 09:23:50"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "3989662bbb30a29d20d9faa04a846af79b276252"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/3989662bbb30a29d20d9faa04a846af79b276252",
+ "reference": "3989662bbb30a29d20d9faa04a846af79b276252",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2015-01-24 09:48:32"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ab931d46cd0d3204a91e1b9a40c4bc13032b58e4",
+ "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "time": "2015-02-24 06:35:25"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "2.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "e96d8579fbed0c95ecf2a0501ec4f307a4aa6404"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/e96d8579fbed0c95ecf2a0501ec4f307a4aa6404",
+ "reference": "e96d8579fbed0c95ecf2a0501ec4f307a4aa6404",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.1.2"
+ },
+ "bin": [
+ "scripts/phpcs",
+ "scripts/phpcbf"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "CodeSniffer.php",
+ "CodeSniffer/CLI.php",
+ "CodeSniffer/Exception.php",
+ "CodeSniffer/File.php",
+ "CodeSniffer/Fixer.php",
+ "CodeSniffer/Report.php",
+ "CodeSniffer/Reporting.php",
+ "CodeSniffer/Sniff.php",
+ "CodeSniffer/Tokens.php",
+ "CodeSniffer/Reports/",
+ "CodeSniffer/Tokenizers/",
+ "CodeSniffer/DocGenerators/",
+ "CodeSniffer/Standards/AbstractPatternSniff.php",
+ "CodeSniffer/Standards/AbstractScopeSniff.php",
+ "CodeSniffer/Standards/AbstractVariableSniff.php",
+ "CodeSniffer/Standards/IncorrectPatternException.php",
+ "CodeSniffer/Standards/Generic/Sniffs/",
+ "CodeSniffer/Standards/MySource/Sniffs/",
+ "CodeSniffer/Standards/PEAR/Sniffs/",
+ "CodeSniffer/Standards/PSR1/Sniffs/",
+ "CodeSniffer/Standards/PSR2/Sniffs/",
+ "CodeSniffer/Standards/Squiz/Sniffs/",
+ "CodeSniffer/Standards/Zend/Sniffs/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "lead"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "http://www.squizlabs.com/php-codesniffer",
+ "keywords": [
+ "phpcs",
+ "standards"
+ ],
+ "time": "2015-04-28 23:28:20"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v2.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/Yaml.git",
+ "reference": "4a29a5248aed4fb45f626a7bbbd330291492f5c3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/Yaml/zipball/4a29a5248aed4fb45f626a7bbbd330291492f5c3",
+ "reference": "4a29a5248aed4fb45f626a7bbbd330291492f5c3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-05-02 15:21:08"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..818fccd
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="tests/bootstrap.php"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false">
+ <logging>
+ <log type="coverage-clover" target="coverage.xml" />
+ </logging>
+ <testsuites>
+ <testsuite name="Lexer's Test Suite">
+ <directory suffix=".php">./tests/lexer</directory>
+ </testsuite>
+ <testsuite name="Parser's Test Suite">
+ <directory suffix=".php">./tests/parser</directory>
+ </testsuite>
+ </testsuites>
+</phpunit> \ No newline at end of file
diff --git a/src/Context.php b/src/Context.php
new file mode 100644
index 0000000..8c2ac14
--- /dev/null
+++ b/src/Context.php
@@ -0,0 +1,470 @@
+<?php
+
+namespace SqlParser;
+
+/**
+ * Default MySQL context (based on MySQL 5.7).
+ */
+abstract class Context
+{
+
+ /**
+ * The maximum length of a keyword.
+ *
+ * @see static::$TOKEN_KEYWORD
+ *
+ * @var int
+ */
+ const KEYWORD_MAX_LENGTH = 30;
+
+ /**
+ * The maximum length of an operator.
+ *
+ * @see static::$TOKEN_OPERATOR
+ *
+ * @var int
+ */
+ const OPERATOR_MAX_LENGTH = 4;
+
+
+ // -------------------------------------------------------------------------
+ // Keywords.
+
+ /**
+ * List of official keywords.
+ *
+ * Because, PHP's associative arrays are basically hash tables, it is more
+ * efficient to store keywords as keys instead of values.
+ *
+ * @var array
+ */
+ public static $KEYWORDS = array(
+ 'AS' => 1, 'BY' => 1, 'IF' => 1, 'IN' => 1, 'IS' => 1, 'ON' => 1,
+ 'OR' => 1, 'TO' => 1,
+ 'ADD' => 1, 'ALL' => 1, 'AND' => 1, 'ASC' => 1, 'DEC' => 1, 'DIV' => 1,
+ 'FOR' => 1, 'GET' => 1, 'INT' => 1, 'KEY' => 1, 'MOD' => 1, 'NOT' => 1,
+ 'OUT' => 1, 'SET' => 1, 'SQL' => 1, 'SSL' => 1, 'USE' => 1, 'XOR' => 1,
+ 'BLOB' => 1, 'BOTH' => 1, 'CALL' => 1, 'CASE' => 1, 'CHAR' => 1,
+ 'DESC' => 1, 'DROP' => 1, 'DUAL' => 1, 'EACH' => 1, 'ELSE' => 1,
+ 'EXIT' => 1, 'FROM' => 1, 'GOTO' => 1, 'INT1' => 1, 'INT2' => 1,
+ 'INT3' => 1, 'INT4' => 1, 'INT8' => 1, 'INTO' => 1, 'JOIN' => 1,
+ 'KEYS' => 1, 'KILL' => 1, 'LEFT' => 1, 'LIKE' => 1, 'LOAD' => 1,
+ 'LOCK' => 1, 'LONG' => 1, 'LOOP' => 1, 'NULL' => 1, 'READ' => 1,
+ 'REAL' => 1, 'SHOW' => 1, 'THEN' => 1, 'TRUE' => 1, 'UNDO' => 1,
+ 'WHEN' => 1, 'WITH' => 1,
+ 'ALTER' => 1, 'CHECK' => 1, 'CROSS' => 1, 'FALSE' => 1, 'FETCH' => 1,
+ 'FLOAT' => 1, 'FORCE' => 1, 'GRANT' => 1, 'GROUP' => 1, 'INDEX' => 1,
+ 'INNER' => 1, 'INOUT' => 1, 'LABEL' => 1, 'LEAVE' => 1, 'LIMIT' => 1,
+ 'LINES' => 1, 'MATCH' => 1, 'ORDER' => 1, 'OUTER' => 1, 'PURGE' => 1,
+ 'RANGE' => 1, 'READS' => 1, 'RIGHT' => 1, 'RLIKE' => 1, 'TABLE' => 1,
+ 'UNION' => 1, 'USAGE' => 1, 'USING' => 1, 'WHERE' => 1, 'WHILE' => 1,
+ 'WRITE' => 1,
+ 'BEFORE' => 1, 'BIGINT' => 1, 'BINARY' => 1, 'CHANGE' => 1,
+ 'COLUMN' => 1, 'CREATE' => 1, 'CURSOR' => 1, 'DELETE' => 1,
+ 'DOUBLE' => 1, 'ELSEIF' => 1, 'EXISTS' => 1, 'FLOAT4' => 1,
+ 'FLOAT8' => 1, 'HAVING' => 1, 'IGNORE' => 1, 'INFILE' => 1,
+ 'INSERT' => 1, 'LINEAR' => 1, 'OPTION' => 1, 'REGEXP' => 1,
+ 'RENAME' => 1, 'REPEAT' => 1, 'RETURN' => 1, 'REVOKE' => 1,
+ 'SCHEMA' => 1, 'SELECT' => 1, 'SIGNAL' => 1, 'SONAME' => 1,
+ 'UNIQUE' => 1, 'UNLOCK' => 1, 'UPDATE' => 1, 'VALUES' => 1,
+ 'ANALYZE' => 1, 'BETWEEN' => 1, 'CASCADE' => 1, 'COLLATE' => 1,
+ 'CONVERT' => 1, 'DECIMAL' => 1, 'DECLARE' => 1, 'DEFAULT' => 1,
+ 'DELAYED' => 1, 'ESCAPED' => 1, 'EXPLAIN' => 1, 'FOREIGN' => 1,
+ 'INTEGER' => 1, 'ITERATE' => 1, 'LEADING' => 1, 'NATURAL' => 1,
+ 'NUMERIC' => 1, 'OUTFILE' => 1, 'PRIMARY' => 1, 'RELEASE' => 1,
+ 'REPLACE' => 1, 'REQUIRE' => 1, 'SCHEMAS' => 1, 'SPATIAL' => 1,
+ 'TINYINT' => 1, 'TRIGGER' => 1, 'UPGRADE' => 1, 'VARCHAR' => 1,
+ 'VARYING' => 1,
+ 'CONTINUE' => 1, 'DATABASE' => 1, 'DAY_HOUR' => 1, 'DESCRIBE' => 1,
+ 'DISTINCT' => 1, 'ENCLOSED' => 1, 'FULLTEXT' => 1, 'INTERVAL' => 1,
+ 'LONGBLOB' => 1, 'LONGTEXT' => 1, 'MAXVALUE' => 1, 'MODIFIES' => 1,
+ 'OPTIMIZE' => 1, 'RESIGNAL' => 1, 'RESTRICT' => 1, 'SMALLINT' => 1,
+ 'SPECIFIC' => 1, 'SQLSTATE' => 1, 'STARTING' => 1, 'TINYBLOB' => 1,
+ 'TINYTEXT' => 1, 'TRAILING' => 1, 'UNSIGNED' => 1, 'UTC_DATE' => 1,
+ 'UTC_TIME' => 1, 'ZEROFILL' => 1,
+ 'CHARACTER' => 1, 'CONDITION' => 1, 'DATABASES' => 1, 'LOCALTIME' => 1,
+ 'MEDIUMINT' => 1, 'MIDDLEINT' => 1, 'PARTITION' => 1, 'PRECISION' => 1,
+ 'PROCEDURE' => 1, 'SENSITIVE' => 1, 'SEPARATOR' => 1, 'VARBINARY' => 1,
+ 'ACCESSIBLE' => 1, 'ASENSITIVE' => 1, 'CONNECTION' => 1,
+ 'CONSTRAINT' => 1, 'DAY_MINUTE' => 1, 'DAY_SECOND' => 1,
+ 'MEDIUMBLOB' => 1, 'MEDIUMTEXT' => 1, 'OPTIONALLY' => 1,
+ 'READ_WRITE' => 1, 'REFERENCES' => 1, 'SQLWARNING' => 1,
+ 'TERMINATED' => 1, 'YEAR_MONTH' => 1,
+ 'DISTINCTROW' => 1, 'HOUR_MINUTE' => 1, 'HOUR_SECOND' => 1,
+ 'INSENSITIVE' => 1, 'MASTER_BIND' => 1,
+ 'CURRENT_DATE' => 1, 'CURRENT_TIME' => 1, 'CURRENT_USER' => 1,
+ 'LOW_PRIORITY' => 1, 'SQLEXCEPTION' => 1, 'VARCHARACTER' => 1,
+ 'DETERMINISTIC' => 1, 'HIGH_PRIORITY' => 1, 'MINUTE_SECOND' => 1,
+ 'STRAIGHT_JOIN' => 1, 'UTC_TIMESTAMP' => 1,
+ 'IO_AFTER_GTIDS' => 1, 'LOCALTIMESTAMP' => 1, 'SQL_BIG_RESULT' => 1,
+ 'DAY_MICROSECOND' => 1, 'IO_BEFORE_GTIDS' => 1, 'OPTIMIZER_COSTS' => 1,
+ 'HOUR_MICROSECOND' => 1, 'SQL_SMALL_RESULT' => 1,
+ 'CURRENT_TIMESTAMP' => 1,
+ 'MINUTE_MICROSECOND' => 1, 'NO_WRITE_TO_BINLOG' => 1,
+ 'SECOND_MICROSECOND' => 1,
+ 'SQL_CALC_FOUND_ROWS' => 1,
+ 'MASTER_SSL_VERIFY_SERVER_CERT' => 1,
+
+ /*
+ * Secondary group of keywords.
+ *
+ * Keywords below are either words that are used as keywords, but not
+ * defined as proper keywords or are group of keywords which are parsed
+ * easier when found grouped.
+ */
+
+ 'END' => 2,
+ 'INDEX' => 2, 'VALUE' => 2,
+ 'ENGINE' => 2,
+ 'COMMENT' => 2, 'RETURNS' => 2, 'STORAGE' => 2,
+ 'CHECKSUM' => 2, 'MAX_ROWS' => 2, 'MIN_ROWS' => 2, 'NOT NULL' => 2,
+ 'PASSWORD' => 2,
+ 'INDEX KEY' => 2, 'PACK_KEYS' => 2,
+ 'ROW_FORMAT' => 2, 'TABLESPACE' => 2,
+ 'UNIQUE KEY' => 2,
+ 'FOREIGN KEY' => 2, 'PRIMARY KEY' => 2, 'PRIMARY KEY' => 2,
+ 'SPATIAL KEY' => 2,
+ 'FULLTEXT KEY' => 2, 'UNIQUE INDEX' => 2,
+ 'CHARACTER SET' => 2, 'IF NOT EXISTS' => 2, 'INSERT_METHOD' => 2,
+ 'SPATIAL INDEX' => 2,
+ 'AUTO_INCREMENT' => 2, 'AVG_ROW_LENGTH' => 2, 'DATA DIRECTORY' => 2,
+ 'FULLTEXT INDEX' => 2, 'KEY_BLOCK_SIZE' => 2,
+ 'DEFAULT COLLATE' => 2,
+ 'DELAY_KEY_WRITE' => 2, 'INDEX DIRECTORY' => 2,
+ 'DEFAULT CHARACTER SET' => 2,
+ );
+
+ // -------------------------------------------------------------------------
+ // Keys and Data Types.
+
+ /**
+ * Types of keys.
+ *
+ * @var array
+ */
+ public static $KEY_TYPES = array(
+ 'FOREIGN KEY' => 1, 'FULLTEXT INDEX' => 1, 'FULLTEXT KEY' => 1,
+ 'INDEX KEY' => 1, 'INDEX' => 1, 'KEY' => 1, 'PRIMARY KEY' => 1,
+ 'SPATIAL INDEX' => 1, 'SPATIAL KEY' => 1, 'UNIQUE INDEX' => 1,
+ 'UNIQUE KEY' => 1, 'UNIQUE' => 1,
+ );
+
+ /**
+ * All data types.
+ *
+ * @var array
+ */
+ public static $DATA_TYPES = array(
+ 'ARRAY' => 1, 'BIGINT' => 1, 'BINARY VARYING' => 1, 'BINARY' => 1,
+ 'BOOLEAN' => 1, 'CHARACTER' => 1, 'CHARACTER' => 1, 'DATE' => 1,
+ 'DECIMAL' => 1, 'DOUBLE' => 1, 'FLOAT' => 1, 'FLOAT' => 1, 'INT' => 1,
+ 'INTEGER' => 1, 'INTERVAL' => 1, 'MULTISET' => 1, 'NUMERIC' => 1,
+ 'REAL' => 1, 'SMALLINT' => 1, 'TIME' => 1, 'TIMESTAMP' => 1,
+ 'VARBINARY' => 1, 'VARCHAR' => 1, 'XML' => 1,
+ );
+
+ // -------------------------------------------------------------------------
+ // Operators.
+
+ /**
+ * List of operators and their flags.
+ *
+ * @var array
+ */
+ public static $OPERATORS = array(
+
+ // Some operators (*, =) may have ambigous flags, because they depend on
+ // the context they are being used in.
+ // For example: 1. SELECT * FROM table; # SQL specific (wildcard)
+ // SELECT 2 * 3; # arithmetic
+ // 2. SELECT * FROM table WHERE foo = 'bar';
+ // SET @i = 0;
+
+ // @see Token::FLAG_OPERATOR_ARITHMETIC
+ '%' => 1, '*' => 1, '+' => 1, '-' => 1, '/' => 1,
+
+ // @see Token::FLAG_OPERATOR_LOGICAL
+ '!' => 2, '!==' => 2, '&&' => 2, '<' => 2, '<=' => 2,
+ '<=>' => 2, '<>' => 2, '=' => 2, '>' => 2, '>=' => 2,
+ '||' => 2,
+
+ // @see Token::FLAG_OPERATOR_BITWISE
+ '&' => 4, '<<' => 4, '>>' => 4, '^' => 4, '|' => 4,
+ '~' => 4,
+
+ // @see Token::FLAG_OPERATOR_ASSIGNMENT
+ ':=' => 8,
+
+ // @see Token::FLAG_OPERATOR_SQL
+ '(' => 16, ')' => 16, '.' => 16, ',' => 16,
+ );
+
+ // -------------------------------------------------------------------------
+ // SQL Modes.
+
+ /*
+ * Server SQL Modes
+ * https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html
+ */
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_allow_invalid_dates
+ const ALLOW_INVALID_DATES = 1;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_ansi_quotes
+ const ANSI_QUOTES = 2;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_error_for_division_by_zero
+ const ERROR_FOR_DIVISION_BY_ZERO = 4;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_high_not_precedence
+ const HIGH_NOT_PRECEDENCE = 8;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_ignore_space
+ const IGNORE_SPACE = 16;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_auto_create_user
+ const NO_AUTO_CREATE_USER = 32;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_auto_value_on_zero
+ const NO_AUTO_VALUE_ON_ZERO = 64;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_backslash_escapes
+ const NO_BACKSLASH_ESCAPES = 128;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_dir_in_create
+ const NO_DIR_IN_CREATE = 256;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_dir_in_create
+ const NO_ENGINE_SUBSTITUTION = 512;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_field_options
+ const NO_FIELD_OPTIONS = 1024;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_key_options
+ const NO_KEY_OPTIONS = 2048;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_table_options
+ const NO_TABLE_OPTIONS = 4096;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_unsigned_subtraction
+ const NO_UNSIGNED_SUBTRACTION = 8192;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_zero_date
+ const NO_ZERO_DATE = 16384;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_zero_in_date
+ const NO_ZERO_IN_DATE = 32768;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_only_full_group_by
+ const ONLY_FULL_GROUP_BY = 65536;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_pipes_as_concat
+ const PIPES_AS_CONCAT = 131072;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_real_as_float
+ const REAL_AS_FLOAT = 262144;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_strict_all_tables
+ const STRICT_ALL_TABLES = 524288;
+
+ // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_strict_trans_tables
+ const STRICT_TRANS_TABLES = 1048576;
+
+ /*
+ * Combination SQL Modes
+ * https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sql-mode-combo
+ */
+
+ // REAL_AS_FLOAT, PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE
+ const SQL_MODE_ANSI = 393234;
+
+ // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
+ // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS,
+ const SQL_MODE_DB2 = 138258;
+
+ // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
+ // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER
+ const SQL_MODE_MAXDB = 138290;
+
+ // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
+ // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS
+ const SQL_MODE_MSSQL = 138258;
+
+ // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
+ // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER
+ const SQL_MODE_ORACLE = 138290;
+
+ // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
+ // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS
+ const SQL_MODE_POSTGRESQL = 138258;
+
+ // STRICT_TRANS_TABLES, STRICT_ALL_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE,
+ // ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER
+ const SQL_MODE_TRADITIONAL = 1622052;
+
+ // -------------------------------------------------------------------------
+ // Keyword.
+
+ /**
+ * Checks if the given string is a keyword.
+ *
+ * @param string $str
+ *
+ * @return bool
+ */
+ public static function isKeyword($str)
+ {
+ $str = strtoupper($str);
+
+ return isset(static::$KEYWORDS[$str]);
+ }
+
+ // -------------------------------------------------------------------------
+ // Operator.
+
+ /**
+ * Checks if the given string is an operator.
+ *
+ * @param string $str
+ *
+ * @return int The appropriate flag for the operator.
+ */
+ public static function isOperator($str)
+ {
+ if (!isset(static::$OPERATORS[$str])) {
+ return null;
+ }
+ return static::$OPERATORS[$str];
+ }
+
+ // -------------------------------------------------------------------------
+ // Whitespace.
+
+ /**
+ * Checks if the given character is a whitespace.
+ *
+ * @param string $ch
+ *
+ * @return bool
+ */
+ public static function isWhitespace($ch)
+ {
+ return ($ch === ' ') || ($ch === "\r") || ($ch === "\n") || ($ch === "\t");
+ }
+
+ // -------------------------------------------------------------------------
+ // Comment.
+
+ /**
+ * Checks if the given string is the beginning of a whitespace.
+ *
+ * @param string $str
+ *
+ * @return int The appropriate flag for the comment type.
+ */
+ public static function isComment($str)
+ {
+ $len = strlen($str);
+ if ($str[0] === '#') {
+ return Token::FLAG_COMMENT_BASH;
+ } elseif (($len > 1) && ((($str[0] === '/') && ($str[1] === '*')) ||
+ (($str[0] === '*') && ($str[1] === '/')))) {
+ return Token::FLAG_COMMENT_C;
+ } elseif (($len > 2) && ($str[0] === '-') &&
+ ($str[1] === '-') && ($str[2] !== "\n") &&
+ (static::isWhitespace($str[2]))) {
+ return Token::FLAG_COMMENT_SQL;
+ }
+ return null;
+ }
+
+ // -------------------------------------------------------------------------
+ // Bool.
+
+ /**
+ * Checks if the given string is a boolean value.
+ * This actually check only for `TRUE` and `FALSE` because `1` or `0` are
+ * actually numbers and are parsed by specific methods.
+ *
+ * @param string $ch
+ *
+ * @return bool
+ */
+ public static function isBool($ch)
+ {
+ $ch = strtoupper($ch);
+ return ($ch === 'TRUE') || ($ch === 'FALSE');
+ }
+
+ // -------------------------------------------------------------------------
+ // Number.
+
+ /**
+ * Checks if the given character can be a part of a number.
+ *
+ * @param string $ch
+ *
+ * @return bool
+ */
+ public static function isNumber($ch)
+ {
+ return (($ch >= '0') && ($ch <= '9')) || ($ch === '.') ||
+ ($ch === '-') || ($ch === '+') || ($ch === 'e') || ($ch === 'E');
+ }
+
+ // -------------------------------------------------------------------------
+ // Symbol.
+
+ /**
+ * Checks if the given character is the beginning of a symbol. A symbol
+ * can be either a variable or a field name.
+ *
+ * @param string $ch
+ *
+ * @return int The appropriate flag for the symbol type.
+ */
+ public static function isSymbol($ch)
+ {
+ if ($ch[0] === '@') {
+ return Token::FLAG_SYMBOL_VARIABLE;
+ } elseif ($ch[0] === '`') {
+ return Token::FLAG_SYMBOL_BACKTICK;
+ }
+ return null;
+ }
+
+ // -------------------------------------------------------------------------
+ // String.
+
+ /**
+ * Checks if the given character is the beginning of a string.
+ *
+ * @param string $str
+ *
+ * @return int The appropriate flag for the string type.
+ */
+ public static function isString($str)
+ {
+ if ($str[0] === '\'') {
+ return Token::FLAG_STRING_SINGLE_QUOTES;
+ } elseif ($str[0] === '"') {
+ return Token::FLAG_STRING_DOUBLE_QUOTES;
+ }
+ return null;
+ }
+
+ // -------------------------------------------------------------------------
+ // Delimiter.
+
+ /**
+ * Checks if the given character can be a separator for two lexems.
+ *
+ * @param string $ch
+ *
+ * @return bool
+ */
+ public static function isSeparator($ch)
+ {
+ return !ctype_alnum($ch) && $ch !== '_';
+ }
+}
diff --git a/src/Exceptions/LexerException.php b/src/Exceptions/LexerException.php
new file mode 100644
index 0000000..f346d83
--- /dev/null
+++ b/src/Exceptions/LexerException.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace SqlParser\Exceptions;
+
+use SqlParser\Token;
+
+/**
+ * Exception thrown by the lexer.
+ */
+class LexerException extends \Exception
+{
+
+ /**
+ * The character that produced this error.
+ *
+ * @var string
+ */
+ public $ch;
+
+ /**
+ * The index of the character that produced this error.
+ *
+ * @var int
+ */
+ public $pos;
+
+ /**
+ * Constructor.
+ *
+ * @param string $message
+ * @param string $ch
+ * @param int $positiion
+ * @param int $code
+ */
+ public function __construct($message = '', $ch = '', $pos = 0, $code = 0)
+ {
+ parent::__construct($message, $code);
+ $this->ch = $ch;
+ $this->pos = $pos;
+ }
+}
diff --git a/src/Exceptions/ParserException.php b/src/Exceptions/ParserException.php
new file mode 100644
index 0000000..e23d03d
--- /dev/null
+++ b/src/Exceptions/ParserException.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace SqlParser\Exceptions;
+
+use SqlParser\Token;
+
+/**
+ * Exception thrown by the parser.
+ */
+class ParserException extends \Exception
+{
+
+ /**
+ * The token that produced this error.
+ *
+ * @var Token
+ */
+ public $token;
+
+ /**
+ * Constructor.
+ *
+ * @param string $message
+ * @param Token $token
+ * @param int $code
+ */
+ public function __construct($message = '', Token $token = null, $code = 0)
+ {
+ parent::__construct($message, $code);
+ $this->token = $token;
+ }
+}
diff --git a/src/Fragment.php b/src/Fragment.php
new file mode 100644
index 0000000..e9a4620
--- /dev/null
+++ b/src/Fragment.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace SqlParser;
+
+/**
+ * A fragment (of a statement) is a part of a statement that is common to
+ * multiple query types.
+ */
+abstract class Fragment
+{
+
+ /**
+ * Array which contains all tokens used to popoluate data inside this
+ * fragment.
+ *
+ * @var array
+ */
+ public $tokens = array();
+
+ /**
+ * Parses the tokens given by the lexer in the context of the given parser.
+ *
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return array
+ */
+ abstract public static function parse(Parser $parser, TokensList $list, array $options = array());
+}
diff --git a/src/Fragments/ArrayFragment.php b/src/Fragments/ArrayFragment.php
new file mode 100644
index 0000000..9ff8649
--- /dev/null
+++ b/src/Fragments/ArrayFragment.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * Parses an array.
+ */
+class ArrayFragment extends Fragment
+{
+
+ /**
+ * The array.
+ *
+ * @var array
+ */
+ public $array = array();
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return ArrayFragment
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = new ArrayFragment();
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 -----------------------[ ( ]------------------------> 1
+ *
+ * 1 ------------------[ array element ]-----------------> 2
+ *
+ * 2 ------------------------[ , ]-----------------------> 1
+ * 2 ------------------------[ ) ]-----------------------> -1
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if ($state === 0) {
+ if (($token->type !== Token::TYPE_OPERATOR) || ($token->value !== '(')) {
+ $parser->error('An open bracket was expected.', $token);
+ break;
+ }
+ $state = 1;
+ } elseif ($state === 1) {
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ')')) {
+ // Empty array.
+ break;
+ }
+ $ret->array[] = $token->value;
+ $ret->tokens[] = $token;
+ $state = 2;
+ } elseif ($state === 2) {
+ if (($token->type !== Token::TYPE_OPERATOR) || (($token->value !== ',') && ($token->value !== ')'))) {
+ $parser->error('Symbols \')\' or \',\' were expected', $token);
+ break;
+ }
+ if ($token->value === ',') {
+ $state = 1;
+ } else { // )
+ break;
+ }
+ }
+
+ }
+
+ return $ret;
+ }
+}
diff --git a/src/Fragments/CallKeyword.php b/src/Fragments/CallKeyword.php
new file mode 100644
index 0000000..00b7838
--- /dev/null
+++ b/src/Fragments/CallKeyword.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * Parses a function call.
+ */
+class CallKeyword extends Fragment
+{
+
+ /**
+ * The name of this function.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * The list of parameters
+ *
+ * @var array
+ */
+ public $parameters = array();
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return CallKeyword
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = new CallKeyword();
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 ----------------------[ name ]-----------------------> 1
+ *
+ * 1 --------------------[ parameters ]-------------------> -1
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if ($state === 0) {
+ $ret->name = $token->value;
+ $ret->tokens[] = $token;
+ $state = 1;
+ } elseif ($state === 1) {
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
+ $parameters = ArrayFragment::parse($parser, $list);
+ $ret->parameters = $parameters->array;
+ $ret->tokens = array_merge($ret->tokens, $parameters->tokens);
+ }
+ break;
+ }
+
+ }
+
+ return $ret;
+ }
+}
diff --git a/src/Fragments/CreateDefFragment.php b/src/Fragments/CreateDefFragment.php
new file mode 100644
index 0000000..708ce11
--- /dev/null
+++ b/src/Fragments/CreateDefFragment.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * Parses the definition that follows the `CREATE` keyword.
+ */
+class CreateDefFragment extends Fragment
+{
+
+ /**
+ * All table options.
+ *
+ * @var array
+ */
+ public static $TABLE_OPTIONS = array(
+ 'ENGINE' => array(1, 'var'),
+ 'AUTO_INCREMENT' => array(2, 'var'),
+ 'AVG_ROW_LENGTH' => array(3, 'var'),
+ 'DEFAULT CHARACTER SET' => array(4, 'var'),
+ 'CHARACTER SET' => array(4, 'var'),
+ 'CHECKSUM' => array(5, 'var'),
+ 'DEFAULT COLLATE' => array(5, 'var'),
+ 'COLLATE' => array(6, 'var'),
+ 'COMMENT' => array(7, 'var'),
+ 'CONNECTION' => array(8, 'var'),
+ 'DATA DIRECTORY' => array(9, 'var'),
+ 'DELAY_KEY_WRITE' => array(10, 'var'),
+ 'INDEX DIRECTORY' => array(11, 'var'),
+ 'INSERT_METHOD' => array(12, 'var'),
+ 'KEY_BLOCK_SIZE' => array(13, 'var'),
+ 'MAX_ROWS' => array(14, 'var'),
+ 'MIN_ROWS' => array(15, 'var'),
+ 'PACK_KEYS' => array(16, 'var'),
+ 'PASSWORD' => array(17, 'var'),
+ 'ROW_FORMAT' => array(18, 'var'),
+ 'TABLESPACE' => array(19, 'var'),
+ 'STORAGE' => array(20, 'var'),
+ 'UNION' => array(21, 'var'),
+ );
+
+ /**
+ * All function options.
+ *
+ * @var array
+ */
+ public static $FUNC_OPTIONS = array(
+ 'COMMENT' => array(1, 'var'),
+ 'LANGUAGE SQL' => 2,
+ 'DETERMINISTIC' => 3,
+ 'NOT DETERMINISTIC' => 3,
+ 'CONSTAINS SQL' => 4,
+ 'NO SQL' => 4,
+ 'READS SQL DATA' => 4,
+ 'MODIFIES SQL DATA' => 4,
+ 'SQL SEQURITY DEFINER' => array(5, 'var'),
+ );
+
+ /**
+ * The name of the new table.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return CreateDefFragment
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = new CreateDefFragment();
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
+ break;
+ }
+
+ $ret->tokens[] = $token;
+ $ret->name .= $token->value;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/DataTypeFragment.php b/src/Fragments/DataTypeFragment.php
new file mode 100644
index 0000000..acf3933
--- /dev/null
+++ b/src/Fragments/DataTypeFragment.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Context;
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `RETURN` keyword parser.
+ */
+class DataTypeFragment extends Fragment
+{
+
+ /**
+ * The data type returned.
+ *
+ * @var string
+ */
+ public $type;
+
+ /**
+ * The size of this variable.
+ *
+ * @var array
+ */
+ public $size;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return DataTypeFragment[]
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = new DataTypeFragment();
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 -------------------[ data type ]--------------------> 1
+ *
+ * 1 ------------------[ size (array) ]------------------> 4
+ * 1 ----------------------[ else ]----------------------> -1
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if ($state === 0) {
+ $ret->type = $token->value;
+ $ret->tokens[] = $token;
+ if (!isset(Context::$DATA_TYPES[$token->value])) {
+ $parser->error('Unrecognized data type.', $token);
+ }
+ $state = 1;
+ } elseif ($state === 1) {
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
+ $size = ArrayFragment::parse($parser, $list);
+ $ret->size = $size->array;
+ $ret->tokens = array_merge($ret->tokens, $size->tokens);
+ } else {
+ --$list->idx;
+ }
+ break;
+ }
+
+ }
+
+ if (empty($ret->type)) {
+ return null;
+ }
+
+ return $ret;
+ }
+}
diff --git a/src/Fragments/FieldDefFragment.php b/src/Fragments/FieldDefFragment.php
new file mode 100644
index 0000000..fe94184
--- /dev/null
+++ b/src/Fragments/FieldDefFragment.php
@@ -0,0 +1,181 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Context;
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * Parses the definition of a field.
+ *
+ * Used for parsing `CREATE TABLE` statement.
+ */
+class FieldDefFragment extends Fragment
+{
+
+ /**
+ * All field options.
+ *
+ * @var array
+ */
+ public static $FIELD_OPTIONS = array(
+ 'NOT NULL' => 1,
+ 'NULL' => 1,
+ 'DEFAULT' => array(2, 'var'),
+ 'AUTO_INCREMENT' => 3,
+ 'PRIMARY' => 4,
+ 'PRIMARY KEY' => 4,
+ 'UNIQUE' => 4,
+ 'UNIQUE KEY' => 4,
+ 'COMMENT' => array(5, 'var'),
+ 'COLUMN_FORMAT' => array(6, 'var'),
+ );
+
+ /**
+ * The name of the new column.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * The data type of thew new column.
+ *
+ * @var DataTypeFragment
+ */
+ public $type;
+
+ /**
+ * The array of indexes.
+ *
+ * @var array
+ */
+ public $indexes = array();
+
+ /**
+ * The options of the new field fragment.
+ *
+ * @var OptionsFragment
+ */
+ public $options;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return FieldDefFragment[]
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = new FieldDefFragment();
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 -----------------------[ ( ]------------------------> 1
+ *
+ * 1 -------------------[ CONSTRAINT ]-------------------> 4
+ * 1 --------------------[ key type ]--------------------> 5
+ * 1 -------------------[ column name ]------------------> 2
+ *
+ * 2 -------------------[ data type ]--------------------> 3
+ *
+ * 3 ---------------------[ size ]---------------------> 3
+ * 3 ---------------------[ options ]--------------------> 4
+ *
+ * 4 -----------------[ CONSTRAINT name ]----------------> 4
+ * 4 -----------------[ CONSTRAINT type ]----------------> 5
+ *
+ * 5 -------------------[ index names ]------------------> 6
+ *
+ * 6 ------------------------[ , ]-----------------------> 1
+ * 6 ------------------------[ ) ]-----------------------> -1
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if ($state === 0) {
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
+ $state = 1;
+ }
+ continue;
+ } elseif ($state === 1) {
+ if ($token->type === Token::TYPE_KEYWORD) {
+ if ($token->value === 'CONSTRAINT') {
+ $state = 4;
+ } elseif (isset(Context::$KEY_TYPES[$token->value])) {
+ $expr->type = $token->value;
+ $state = 5;
+ } else {
+ $parser->error('Unexpected keyword.', $token);
+ break; // TODO: Skip to the end of the query.
+ }
+ } else {
+ $expr->name = $token->value;
+ $state = 2;
+ }
+ } elseif ($state === 2) {
+ $expr->type = DataTypeFragment::parse($parser, $list);
+ $state = 3;
+ } elseif ($state === 3) {
+ $expr->options = OptionsFragment::parse($parser, $list, static::$FIELD_OPTIONS);
+ $state = 6;
+ } elseif ($state === 4) {
+ if (!isset(Context::$KEY_TYPES[$token->value])) {
+ $expr->name = $token->value;
+ } else {
+ $expr->type = $token->value;
+ $state = 5;
+ }
+ } elseif ($state === 5) {
+ $expr->indexes = ArrayFragment::parse($parser, $list);
+ $state = 6;
+ } elseif ($state === 6) {
+ $ret[] = $expr;
+ $expr = new FieldDefFragment();
+ if ($token->value === ',') {
+ $state = 1;
+ continue;
+ } elseif ($token->value === ')') {
+ ++$list->idx;
+ break;
+ }
+ }
+
+ $expr->tokens[] = $token;
+ }
+
+ // Last iteration was not saved.
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+
+ }
+}
diff --git a/src/Fragments/FieldFragment.php b/src/Fragments/FieldFragment.php
new file mode 100644
index 0000000..897a4cc
--- /dev/null
+++ b/src/Fragments/FieldFragment.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * Parses a reference to a field.
+ */
+class FieldFragment extends Fragment
+{
+
+ /**
+ * The name of this database.
+ *
+ * @var string
+ */
+ public $database;
+
+ /**
+ * The name of this table.
+ *
+ * @var string
+ */
+ public $table;
+
+ /**
+ * The name of the column.
+ *
+ * @var string
+ */
+ public $column;
+
+ /**
+ * The sub-expression.
+ *
+ * @var string
+ */
+ public $expr = '';
+
+ /**
+ * The alias of this expression.
+ *
+ * @var string
+ */
+ public $alias;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return FieldFragment
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = new FieldFragment();
+
+ /** @var bool Whether current tokens make an expression or a table reference. */
+ $isExpr = false;
+
+ /** @var int Counts brackets. */
+ $brackets = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ if ($isExpr) {
+ $ret->expr .= $token->token;
+ $ret->tokens[] = $token;
+ }
+ continue;
+ }
+
+ if ($token->type === Token::TYPE_KEYWORD) {
+ // Keywords may be found only between brackets.
+ if ($brackets === 0) {
+ break;
+ }
+ }
+
+ if ($token->type === Token::TYPE_OPERATOR) {
+ if ($token->value === '(') {
+ ++$brackets;
+ $isExpr = true;
+ } elseif ($token->value === ')') {
+ --$brackets;
+ if ($brackets < 0) {
+ $parser->error('Unexpected bracket.', $token);
+ $brackets = 0;
+ }
+ } elseif ($token->value === ',') {
+ if ($brackets === 0) {
+ break;
+ }
+ }
+ }
+
+ if (($token->type === Token::TYPE_NUMBER) || ($token->type === Token::TYPE_BOOL) ||
+ (($token->type === Token::TYPE_OPERATOR)) && ($token->value !== '.')) {
+ // Numbers, booleans and operators are usually part of expressions.
+ $isExpr = true;
+ }
+
+ if (!$isExpr) {
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) {
+ $ret->database = $ret->table;
+ $ret->table = $ret->column;
+ } else {
+ if (!empty($options['skipColumn'])) {
+ $ret->table = $token->value;
+ } else {
+ $ret->column = $token->value;
+ }
+ }
+ }
+
+ $ret->expr .= $token->token;
+ $ret->tokens[] = $token;
+ }
+
+ if (empty($ret->tokens)) {
+ return null;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/FromKeyword.php b/src/Fragments/FromKeyword.php
new file mode 100644
index 0000000..fd11eea
--- /dev/null
+++ b/src/Fragments/FromKeyword.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `FROM` keyword parser.
+ */
+class FromKeyword extends Fragment
+{
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return FieldFragment[]
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = new FieldFragment();
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ // No keyword is expected.
+ if ($token->type === Token::TYPE_KEYWORD) {
+ break;
+ }
+
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
+ $ret[] = $expr;
+ } else {
+ $expr = FieldFragment::parse($parser, $list, array('skipColumn' => true));
+ if ($expr === null) {
+ break;
+ }
+ }
+
+ }
+
+ // Last iteration was not saved.
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/IntoKeyword.php b/src/Fragments/IntoKeyword.php
new file mode 100644
index 0000000..9f6a116
--- /dev/null
+++ b/src/Fragments/IntoKeyword.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `INTO` keyword parser.
+ */
+class IntoKeyword extends Fragment
+{
+
+ /**
+ * The name of the table.
+ *
+ * @var string
+ */
+ public $table;
+
+ /**
+ * The name of the columns.
+ *
+ * @var array
+ */
+ public $fields;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return IntoKeyword
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = new IntoKeyword();
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 ------------------------[ ( ]------------------------> 1
+ *
+ * 1 --------------------[ field name ]-------------------> 2
+ *
+ * 2 ------------------------[ , ]------------------------> 1
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ // No keyword is expected.
+ if ($token->type === Token::TYPE_KEYWORD) {
+ break;
+ }
+
+ if ($token->type === Token::TYPE_OPERATOR) {
+ if ($token->value === '(') {
+ if (empty($ret->table)) {
+ $parser->error('Table name was expected.', $token);
+ }
+ $state = 1;
+ continue;
+ } elseif ($token->value === ',') {
+ if ($state !== 2) {
+ $parser->error('Field name was expected.', $token);
+ }
+ $state = 1;
+ continue;
+ }
+
+ // No other operator is expected.
+ break;
+ }
+
+ $ret->tokens[] = $token;
+ if ($state === 0) {
+ $ret->table .= $token->value;
+ } elseif ($state === 1) {
+ $ret->fields[] = $token->value;
+ $state = 2;
+ }
+
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/LimitKeyword.php b/src/Fragments/LimitKeyword.php
new file mode 100644
index 0000000..08fb3a4
--- /dev/null
+++ b/src/Fragments/LimitKeyword.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `lIMIT` keyword parser.
+ */
+class LimitKeyword extends Fragment
+{
+
+ /**
+ * The number of rows skipped.
+ *
+ * @var int
+ */
+ public $offset;
+
+ /**
+ * The number of rows to be returned.
+ *
+ * @var int
+ */
+ public $row_count;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return LimitKeyword
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = new LimitKeyword();
+
+ $offset = false;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ // NOTE: `OFFSET` is not a keyword.
+ if (($token->type === Token::TYPE_NONE) && ($token->value === 'OFFSET')) {
+ if ($offset) {
+ $parser->error('An offset was expected.');
+ }
+ $offset = true;
+ continue;
+ }
+
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
+ $ret->offset = $ret->row_count;
+ $ret->row_count = 0;
+ continue;
+ }
+
+ if ($offset) {
+ $ret->offset = $token->value;
+ $offset = false;
+ } else {
+ $ret->row_count = $token->value;
+ }
+
+ $ret->tokens[] = $token;
+
+ }
+
+ if ($offset) {
+ $parser->error('An offset was expected.');
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/OptionsFragment.php b/src/Fragments/OptionsFragment.php
new file mode 100644
index 0000000..ce52135
--- /dev/null
+++ b/src/Fragments/OptionsFragment.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * Parses a list of options.
+ */
+class OptionsFragment extends Fragment
+{
+
+ /**
+ * Array of selected options.
+ *
+ * @var array
+ */
+ public $options;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return OptionsFragment
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = new OptionsFragment();
+
+ /** @var int The ID that will be assigned to duplicate options. */
+ $lastAssignedId = count($options) + 1;
+
+ /** @var array The option that was processed last time. */
+ $lastOption = null;
+ $lastOptionId = 0;
+
+ $brackets = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if (isset($options[$token->value])) {
+ $lastOption = $options[$token->value];
+ $lastOptionId = is_array($lastOption) ? $lastOption[0] : $lastOption;
+
+ // Checking for option conflicts.
+ // For example, in `SELECT` statements the keywords `ALL` and `DISTINCT`
+ // conflict and if used together, they produce an invalid query.
+ // Usually, tokens can be identified in the array by the option ID,
+ // but if conflicts occur, a psuedo option ID is used.
+ // The first pseudo duplicate ID is the maximum value of the real
+ // options (e.g. if there are 5 options, the first fake ID is 6).
+ if (isset($ret->options[$lastOptionId])) {
+ $parser->error('This option conflicts with \'' . $ret->options[$lastOptionId] . '\'.', $token);
+ $lastOptionId = $lastAssignedId++;
+ }
+ } else {
+ // There is no option to be processed.
+ if ($lastOption === null) {
+ break;
+ }
+
+ // The only keywords that are expected are those which are
+ // options.
+ if ($token->type === Token::TYPE_KEYWORD) {
+ break;
+ }
+
+ }
+
+ if (is_array($lastOption)) {
+ if (empty($ret->options[$lastOptionId])) {
+ $ret->options[$lastOptionId] = array('name' => $token->value, 'value' => '');
+ } else {
+ if ($token->value !== '=') {
+ if ($token->value === '(') {
+ ++$brackets;
+ } elseif ($token->value === ')') {
+ --$brackets;
+ } else {
+ $ret->options[$lastOptionId]['value'] .= $token->value;
+ }
+ if ($brackets === 0) {
+ $lastOption = null;
+ }
+ }
+ }
+ } else {
+ $ret->options[$lastOptionId] = $token->value;
+ $lastOption = null;
+ }
+ $ret->tokens[] = $token;
+
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+
+ /**
+ * Checks if it has the specified option and returns it value or true.
+ *
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function has($key)
+ {
+ foreach ($this->options as $option) {
+ if ((is_array($option)) && ($key === $option['name'])) {
+ return $option['value'];
+ } elseif ($key === $option) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/Fragments/OrderKeyword.php b/src/Fragments/OrderKeyword.php
new file mode 100644
index 0000000..5ddc8ef
--- /dev/null
+++ b/src/Fragments/OrderKeyword.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `ORDER BY` keyword parser.
+ */
+class OrderKeyword extends Fragment
+{
+
+ /**
+ * The name of the column that is being used for ordering.
+ *
+ * @var string
+ */
+ public $column;
+
+ /**
+ * The order type.
+ *
+ * @var string
+ */
+ public $type = 'ASC';
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return OrderKeyword[]
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = new OrderKeyword();
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if ($token->type === Token::TYPE_KEYWORD) {
+ // Type of ordering. By default, it is `ASC`.
+ if (($token->value === 'ASC') || ($token->value === 'DESC')) {
+ $expr->type = $token->value;
+ $expr->tokens[] = $token;
+ continue;
+ }
+
+ // No other keyword is expected.
+ break;
+ }
+
+ // Saving field.
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->token === ',')) {
+ $ret[] = $expr;
+ $expr = new OrderKeyword();
+ continue;
+ }
+
+ $expr->tokens[] = $token;
+ $expr->column .= $token->token;
+
+ }
+
+ // Last iteration was not processed.
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/ParamDefFragment.php b/src/Fragments/ParamDefFragment.php
new file mode 100644
index 0000000..e6ecea9
--- /dev/null
+++ b/src/Fragments/ParamDefFragment.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Context;
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * The definition of a parameter of a function or procedure.
+ */
+class ParamDefFragment extends Fragment
+{
+
+ /**
+ * The name of the new column.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * Parameter's type (IN, OUT or INOUT).
+ *
+ * @var string
+ */
+ public $inOut;
+
+ /**
+ * The data type of thew new column.
+ *
+ * @var DataTypeFragment
+ */
+ public $type;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return ParamDefFragment[]
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = new ParamDefFragment();
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 -----------------------[ ( ]------------------------> 1
+ *
+ * 1 ----------------[ IN / OUT / INOUT ]----------------> 1
+ * 1 ----------------------[ name ]----------------------> 2
+ *
+ * 2 -------------------[ data type ]--------------------> 3
+ *
+ * 3 ------------------------[ , ]-----------------------> 1
+ * 3 ------------------------[ ) ]-----------------------> -1
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if ($state === 0) {
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
+ $state = 1;
+ }
+ continue;
+ } elseif ($state === 1) {
+ if (($token->value === 'IN') || ($token->value === 'OUT') || ($token->value === 'INOUT')) {
+ $expr->inOut = $token->value;
+ ++$list->idx;
+ } else {
+ $expr->name = $token->value;
+ $state = 2;
+ }
+ } elseif ($state === 2) {
+ $expr->type = DataTypeFragment::parse($parser, $list);
+ $state = 3;
+ } elseif ($state === 3) {
+ $ret[] = $expr;
+ $expr = new ParamDefFragment();
+ if ($token->value === ',') {
+ $state = 1;
+ continue;
+ } elseif ($token->value === ')') {
+ ++$list->idx;
+ break;
+ }
+ }
+
+ $expr->tokens[] = $token;
+ }
+
+ // Last iteration was not saved.
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+
+ }
+}
diff --git a/src/Fragments/RenameKeyword.php b/src/Fragments/RenameKeyword.php
new file mode 100644
index 0000000..7841b2a
--- /dev/null
+++ b/src/Fragments/RenameKeyword.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `RENAME TABLE` keyword parser.
+ */
+class RenameKeyword extends Fragment
+{
+
+ /**
+ * The old name.
+ *
+ * @var string
+ */
+ public $old;
+
+ /**
+ * The new name.
+ *
+ * @var string
+ */
+ public $new;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return RenameKeyword
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = new RenameKeyword();
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 ---------------------[ old name ]--------------------> 1
+ *
+ * 1 ------------------------[ TO ]-----------------------> 2
+ *
+ * 2 ---------------------[ old name ]--------------------> 3
+ *
+ * 3 ------------------------[ , ]------------------------> 0
+ * 3 -----------------------[ else ]----------------------> -1
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if ($token->type === Token::TYPE_KEYWORD) {
+ if (($state === 1) && ($token->value === 'TO')) {
+ $state = 2;
+ continue;
+ }
+
+ // No other keyword is expected.
+ break;
+ }
+
+ if ($token->type === Token::TYPE_OPERATOR) {
+ if (($state === 3) && ($token->value === ',')) {
+ $ret[] = $expr;
+ $expr = new RenameKeyword();
+ $state = 0;
+ continue;
+ }
+
+ // No other operator is expected.
+ break;
+ }
+
+ $expr->tokens[] = $token;
+ if ($state == 0) {
+ $expr->old = $token->value;
+ $state = 1;
+ } elseif ($state == 2) {
+ $expr->new = $token->value;
+ $state = 3;
+ }
+
+ }
+
+ // Last iteration was not saved.
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/SelectKeyword.php b/src/Fragments/SelectKeyword.php
new file mode 100644
index 0000000..f86d05d
--- /dev/null
+++ b/src/Fragments/SelectKeyword.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `SELECT` keyword parser.
+ */
+class SelectKeyword extends Fragment
+{
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return FieldFragment[]
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = null;
+
+ /** @var bool Whether an alias is expected. */
+ $alias = false;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ if ($token->type === Token::TYPE_KEYWORD) {
+ // No keyword is expected other than `AS`.
+ if ($token->value !== 'AS') {
+ break;
+ }
+ }
+
+ if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
+ $ret[] = $expr;
+ } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'AS')) {
+ $alias = true;
+ } else {
+ if ($alias) {
+ $expr->alias = $token->value;
+ $alias = false;
+ } else {
+ $expr = FieldFragment::parse($parser, $list);
+ if ($expr === null) {
+ break;
+ }
+ }
+ }
+
+ }
+
+ // Last iteration was not processed.
+ if (!empty($expr->tokens)) {
+ if ($alias) {
+ $parser->error('Alias was expected.', $token);
+ }
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/SetKeyword.php b/src/Fragments/SetKeyword.php
new file mode 100644
index 0000000..1e70f27
--- /dev/null
+++ b/src/Fragments/SetKeyword.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `SET` keyword parser.
+ */
+class SetKeyword extends Fragment
+{
+
+ /**
+ * The name of the column that is being updated.
+ *
+ * @var string
+ */
+ public $column;
+
+ /**
+ * The new value.
+ *
+ * @var string
+ */
+ public $value;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return SetKeyword[]
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = new SetKeyword();
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 -------------------[ field name ]--------------------> 1
+ *
+ * 1 ------------------------[ , ]------------------------> 0
+ * 1 ----------------------[ value ]----------------------> 1
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ // No keyword is expected.
+ if ($token->type === Token::TYPE_KEYWORD) {
+ break;
+ }
+
+ if ($token->type === Token::TYPE_OPERATOR) {
+ if ($token->value === ',') {
+ $ret[] = $expr;
+ $expr = new SetKeyword();
+ $state = 0;
+ continue;
+ } elseif ($token->value === '=') {
+ $state = 1;
+ }
+ }
+
+ $expr->tokens[] = $token;
+ if ($state === 0) {
+ $expr->column .= $token->value;
+ } else { // } else if ($state === 1) {
+ $expr->value = $token->value;
+ }
+
+ }
+
+ // Last iteration was not saved.
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/ValuesKeyword.php b/src/Fragments/ValuesKeyword.php
new file mode 100644
index 0000000..c6bd551
--- /dev/null
+++ b/src/Fragments/ValuesKeyword.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `VALUES` keyword parser.
+ */
+class ValuesKeyword extends Fragment
+{
+
+ /**
+ * An array with the values of the row to be inserted.
+ *
+ * @var array
+ */
+ public $values;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return ValuesKeyword
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = new ValuesKeyword();
+ $value = '';
+
+ /**
+ * The state of the parser.
+ *
+ * Below are the states of the parser.
+ *
+ * 0 ------------------------[ ( ]-----------------------> 1
+ *
+ * 1 ----------------------[ value ]---------------------> 2
+ *
+ * 2 ------------------------[ , ]-----------------------> 1
+ * 2 ------------------------[ ) ]-----------------------> 3
+ *
+ * 3 ---------------------[ options ]--------------------> 4
+ *
+ * @var int
+ */
+ $state = 0;
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ // No keyword is expected.
+ if ($token->type === Token::TYPE_KEYWORD) {
+ break;
+ }
+
+ if ($token->type === Token::TYPE_OPERATOR) {
+ if ($token->value === '(') {
+ $state = 1;
+ continue;
+ } elseif ($token->value === ',') {
+ if ($state !== 3) {
+ $expr->values[] = $value;
+ $value = '';
+ $state = 1;
+ }
+ continue;
+ } elseif ($token->value === ')') {
+ $state = 3;
+ $expr->values[] = $value;
+ $ret[] = $expr;
+ $value = '';
+ $expr = new ValuesKeyword();
+ continue;
+ }
+
+ // No other operator is expected.
+ break;
+ }
+
+ $expr->tokens[] = $token;
+ if ($state === 1) {
+ $value .= $token->value;
+ $state = 2;
+ }
+
+ }
+
+ // Last iteration was not saved.
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Fragments/WhereKeyword.php b/src/Fragments/WhereKeyword.php
new file mode 100644
index 0000000..786efbf
--- /dev/null
+++ b/src/Fragments/WhereKeyword.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace SqlParser\Fragments;
+
+use SqlParser\Fragment;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+/**
+ * `WHERE` keyword parser.
+ */
+class WhereKeyword extends Fragment
+{
+
+ /**
+ * Logical operators that can be used to chain expressions.
+ *
+ * @var array
+ */
+ private static $OPERATORS = array('&&', '(', ')', 'AND', 'OR', 'XOR', '||');
+
+ /**
+ * Whether this fragment is an operator.
+ *
+ * @var bool
+ */
+ public $isOperator = false;
+
+ /**
+ * The condition.
+ *
+ * @var string
+ */
+ public $condition;
+
+ /**
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param array $options
+ *
+ * @return WhereKeyword[]
+ */
+ public static function parse(Parser $parser, TokensList $list, array $options = array())
+ {
+ $ret = array();
+
+ $expr = new WhereKeyword();
+
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Skipping whitespaces and comments.
+ if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
+ continue;
+ }
+
+ // Conditions are delimited by logical operators.
+ if (in_array($token->value, static::$OPERATORS, true)) {
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ $expr = new WhereKeyword();
+ $expr->isOperator = true;
+ $expr->condition = $token->value;
+ $epxr->tokens[] = $token;
+ $ret[] = $expr;
+
+ $expr = new WhereKeyword();
+
+ continue;
+ }
+
+ // No keyword is expected.
+ if ($token->type === Token::TYPE_KEYWORD) {
+ break;
+ }
+
+ $expr->tokens[] = $token;
+ $expr->condition .= $token->token;
+
+ }
+
+ // Last iteration was not processed.
+ if (!empty($expr->tokens)) {
+ $ret[] = $expr;
+ }
+
+ --$list->idx;
+ return $ret;
+ }
+}
diff --git a/src/Lexer.php b/src/Lexer.php
new file mode 100644
index 0000000..416122c
--- /dev/null
+++ b/src/Lexer.php
@@ -0,0 +1,561 @@
+<?php
+
+namespace SqlParser;
+
+use SqlParser\Exceptions\LexerException;
+
+/**
+ * Performs lexical analysis over a SQL statement and splits it in multiple
+ * tokens.
+ *
+ * The output of the lexer is affected by the context of the SQL statement.
+ *
+ * @see Context
+ */
+class Lexer
+{
+
+ /**
+ * A list of methods that are used in lexing the SQL query.
+ *
+ * @var array
+ */
+ public static $PARSER_METHODS = array(
+
+ // It is best to put the parsers in order of their complexity
+ // (ascending) and their occurance rate (descending).
+ //
+ // Conflicts:
+ // 1. `parseNumber` and `parseOperator`
+ // They fight over `+` and `-`.
+ //
+ // 2. `parseComment` and `parseOperator`
+ // They fight over `/` (as in ```/*comment*/``` or ```a / b```)
+ //
+ // 3. `parseBool` and `parseKeyword`
+ // They fight over `TRUE` and `FALSE`.
+ //
+ // 4. `parseKeyword` and `parseUnknown`
+ // They fight over words. `parseUnknown` does not know about keywords.
+
+ 'parseWhitespace', 'parseNumber', 'parseComment', 'parseOperator',
+ 'parseBool', 'parseString', 'parseSymbol', 'parseKeyword',
+ 'parseUnknown'
+ );
+
+ /**
+ * Whether errors should throw exceptions or just be stored.
+ *
+ * @var bool
+ *
+ * @see static::$errors
+ */
+ public $strict = false;
+
+ /**
+ * The string to be parsed.
+ *
+ * @var string|UtfString
+ */
+ public $str = '';
+
+ /**
+ * The length of `$str`.
+ *
+ * By storing its length, a lot of time is saved, because parsing methods
+ * would call `strlen` everytime.
+ *
+ * @var int
+ */
+ public $len = 0;
+
+ /**
+ * The index of the last parsed character.
+ *
+ * @var int
+ */
+ public $last = 0;
+
+ /**
+ * Tokens extracted from given strings.
+ *
+ * @var TokensList
+ */
+ public $tokens;
+
+ /**
+ * Statements delimiter.
+ *
+ * @var string
+ */
+ public $delimiter = ';';
+
+ /**
+ * List of errors that occured during lexing.
+ *
+ * Usually, the lexing does not stop once an error occured because that
+ * error might be misdetected or a partial result (even a bad one) might be
+ * needed.
+ *
+ * @var LexerException[]
+ *
+ * @see Lexer::error()
+ */
+ public $errors = array();
+
+ /**
+ * Constructor.
+ *
+ * @param string|UtfString $str
+ * @param bool $strict
+ */
+ public function __construct($str, $strict = false)
+ {
+ $this->str = $str;
+ $this->len = ($str instanceof UtfString) ?
+ $str->length() : strlen($str);
+ $this->strict = $strict;
+ }
+
+ /**
+ * Parses the string and extracts lexems.
+ *
+ * @param string|UtfString $str
+ */
+ public function lex()
+ {
+ // TODO: Sometimes, static::parse* functions make unnecessary calls to
+ // is* functions. For a better performance, some rules can be deduced
+ // from context.
+ // For example, in `parseBool` there is no need to compare the token
+ // every time with `true` and `false`. The first step would be to
+ // compare with 'true' only and just after that add another letter from
+ // context and compare again with `false`.
+ // Another example is `parseComment`.
+
+ $tokens = new TokensList();
+
+ for ($this->last = 0, $lastIdx = 0; $this->last < $this->len; $lastIdx = ++$this->last) {
+ /** @var Token The new token. */
+ $token = null;
+
+ foreach (static::$PARSER_METHODS as $method) {
+ if (($token = $this->$method())) {
+ break;
+ }
+ }
+
+ if ($token === null) {
+ // @assert($this->last === $lastIdx);
+ $token = new Token($this->str[$this->last]);
+ if ($this->delimiter !== $this->str[$this->last]) {
+ $this->error('Unexpected character.', $this->str[$this->last], $this->last);
+ }
+ }
+ $token->position = $lastIdx;
+
+ $tokens->tokens[$tokens->count++] = $token;
+
+ // Handling delimiters.
+ if ($this->delimiter === '') {
+ // Updating the delimiter.
+ if ($token->type !== Token::TYPE_WHITESPACE) {
+ $this->delimiter = $token->value;
+ }
+ } elseif ($token->value === 'DELIMITER') {
+ // `DELIMITER` keyword found, looking for a delimiter.
+ $this->delimiter = '';
+ }
+
+ // Overwriting token if delimiter.
+ if ($token->value === $this->delimiter) {
+ $token->type = Token::TYPE_DELIMITER;
+ $token->flags = 0;
+ }
+ }
+
+ // Adding a final delimite at the end to mark the ending.
+ $tokens->tokens[$tokens->count++] = new Token(null, Token::TYPE_DELIMITER);
+
+ // Saving the tokens list.
+ $this->tokens = $tokens;
+ }
+
+ /**
+ * Creates a new error log.
+ *
+ * @param string $msg
+ * @param string $str
+ * @param int $code
+ */
+ public function error($msg = '', $str = '', $pos = 0, $code = 0)
+ {
+ $error = new LexerException($msg, $str, $pos, $code);
+ if ($this->strict) {
+ throw $error;
+ }
+ $this->errors[] = $error;
+ }
+
+ /**
+ * Parses a keyword.
+ *
+ * @return Token
+ */
+ public function parseKeyword()
+ {
+ $token = '';
+
+ /** @var Token Value to be returned. */
+ $ret = null;
+
+ /** @var int The value of `$this->last` where `$token` ends in `$this->str`. */
+ $iEnd = $this->last;
+
+ for ($j = 1; $j < Context::KEYWORD_MAX_LENGTH && $this->last < $this->len; ++$j, ++$this->last) {
+ $token .= $this->str[$this->last];
+ if (($this->last + 1 === $this->len) || (Context::isSeparator($this->str[$this->last + 1]))) {
+ if (Context::isKeyword($token)) {
+ $ret = new Token($token, Token::TYPE_KEYWORD);
+ $iEnd = $this->last;
+ // We don't break so we find longest keyword.
+ // For example, `OR` and `ORDER` have a common prefix `OR`.
+ // If we stopped at `OR`, the parsing would be invalid.
+ }
+ }
+ }
+
+ $this->last = $iEnd;
+ return $ret;
+ }
+
+ /**
+ * Parses an operator.
+ *
+ * @return Token
+ */
+ public function parseOperator()
+ {
+ $token = '';
+
+ /** @var Token|bool Value to be returned. */
+ $ret = false;
+
+ /** @var int The value of `$this->last` where `$token` ends in `$this->str`. */
+ $iEnd = $this->last;
+
+ for ($j = 1; $j < Context::OPERATOR_MAX_LENGTH && $this->last < $this->len; ++$j, ++$this->last) {
+ $token .= $this->str[$this->last];
+ if ($flags = Context::isOperator($token)) {
+ $ret = new Token($token, Token::TYPE_OPERATOR, $flags);
+ $iEnd = $this->last;
+ }
+ }
+
+ $this->last = $iEnd;
+ return $ret;
+ }
+
+ /**
+ * Parses a whitespace.
+ *
+ * @return Token
+ */
+ public function parseWhitespace()
+ {
+ $token = $this->str[$this->last];
+
+ if (!Context::isWhitespace($token)) {
+ return null;
+ }
+
+ while ((++$this->last < $this->len) && (Context::isWhitespace($this->str[$this->last]))) {
+ $token .= $this->str[$this->last];
+ }
+
+ --$this->last;
+ return new Token($token, Token::TYPE_WHITESPACE);
+ }
+
+ /**
+ * Parses a comment.
+ *
+ * @return Token
+ */
+ public function parseComment()
+ {
+ $iBak = $this->last;
+ $token = $this->str[$this->last];
+
+ // Bash style comments. (#comment\n)
+ if (Context::isComment($token)) {
+ while ((++$this->last < $this->len) && ($this->str[$this->last] !== "\n")) {
+ $token .= $this->str[$this->last];
+ }
+ $token .= $this->str[$this->last];
+ return new Token($token, Token::TYPE_COMMENT, Token::FLAG_COMMENT_BASH);
+ }
+
+ // C style comments. (/*comment*\/)
+ if (++$this->last < $this->len) {
+ $token .= $this->str[$this->last];
+ if (Context::isComment($token)) {
+ $flags = Token::FLAG_COMMENT_C;
+ if (($this->last + 1 < $this->len) && ($this->str[$this->last + 1] === '!')) {
+ // It is a MySQL-specific command.
+ $flags |= Token::FLAG_COMMENT_MYSQL_CMD;
+ }
+ while ((++$this->last < $this->len) &&
+ (($this->str[$this->last - 1] !== '*') || ($this->str[$this->last] !== '/'))) {
+ $token .= $this->str[$this->last];
+ }
+ $token .= $this->str[$this->last];
+ return new Token($token, Token::TYPE_COMMENT, $flags);
+ }
+ }
+
+ // SQL style comments. (-- comment\n)
+ if (++$this->last < $this->len) {
+ $token .= $this->str[$this->last];
+ if (Context::isComment($token)) {
+ if ($this->str[$this->last] !== "\n") {
+ // Checking if this comment did not end already (```--\n```).
+ while ((++$this->last < $this->len) && ($this->str[$this->last] !== "\n")) {
+ $token .= $this->str[$this->last];
+ }
+ if ($this->last < $this->len) {
+ $token .= $this->str[$this->last];
+ }
+ }
+ return new Token($token, Token::TYPE_COMMENT, Token::FLAG_COMMENT_SQL);
+ }
+ }
+
+ $this->last = $iBak;
+ return null;
+ }
+
+ /**
+ * Parses a boolean.
+ *
+ * @return Token
+ */
+ public function parseBool()
+ {
+ if ($this->last + 3 >= $this->len) {
+ // At least `min(strlen('TRUE'), strlen('FALSE'))` characters are
+ // required.
+ return null;
+ }
+
+ $iBak = $this->last;
+ $token = $this->str[$this->last] . $this->str[++$this->last]
+ . $this->str[++$this->last] . $this->str[++$this->last]; // _TRUE_ or _FALS_e
+
+ if (Context::isBool($token)) {
+ return new Token($token, Token::TYPE_BOOL);
+ } elseif (++$this->last < $this->len) {
+ $token .= $this->str[$this->last]; // fals_E_
+ if (Context::isBool($token)) {
+ return new Token($token, Token::TYPE_BOOL, 1);
+ }
+ }
+
+ $this->last = $iBak;
+ return null;
+ }
+
+ /**
+ * Parses a number.
+ *
+ * @return Token
+ */
+ public function parseNumber()
+ {
+ // A rudimentary state machine is being used to parse numbers due to
+ // the various forms of their notation.
+ //
+ // Below are the states of the machines and the conditions to change
+ // the state.
+ //
+ // 1 ---------------------[ + or - ]---------------------> 1
+ // 1 --------------------[ 0x or 0X ]--------------------> 2
+ // 1 ---------------------[ 0 to 9 ]---------------------> 3
+ // 1 ------------------------[ . ]-----------------------> 4
+ //
+ // 2 ---------------------[ 0 to F ]---------------------> 2
+ //
+ // 3 ---------------------[ 0 to 9 ]---------------------> 3
+ // 3 ------------------------[ . ]-----------------------> 4
+ // 3 ---------------------[ e or E ]---------------------> 5
+ //
+ // 4 ---------------------[ 0 to 9 ]---------------------> 4
+ // 4 ---------------------[ e or E ]---------------------> 5
+ //
+ // 5 ----------------[ + or - or 0 to 9 ]----------------> 6
+ //
+ // State 1 may be reached by negative numbers.
+ // State 2 is reached only by hex numbers.
+ // State 4 is reached only by float numbers.
+ // State 5 is reached only by numbers in approximate form.
+ //
+ // Valid final states are: 2, 3, 4 and 6. Any parsing that finished in a
+ // state other than these is invalid.
+ $iBak = $this->last;
+ $token = '';
+ $flags = 0;
+ $state = 1;
+ for (; $this->last < $this->len; ++$this->last) {
+ if ($state === 1) {
+ if ($this->str[$this->last] === '+') {
+ // Do nothing.
+ } elseif ($this->str[$this->last] === '-') {
+ $flags |= Token::FLAG_NUMBER_NEGATIVE;
+ // Do nothing.
+ } elseif (($this->str[$this->last] === '0') && ($this->last + 1 < $this->len) &&
+ (($this->str[$this->last + 1] === 'x') || ($this->str[$this->last + 1] === 'X'))) {
+ $token .= $this->str[$this->last++];
+ $state = 2;
+ } elseif (($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) {
+ $state = 3;
+ } elseif ($this->str[$this->last] === '.') {
+ $state = 4;
+ } else {
+ break;
+ }
+ } elseif ($state === 2) {
+ $flags |= Token::FLAG_NUMBER_HEX;
+ if ((($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) ||
+ (($this->str[$this->last] >= 'A') && ($this->str[$this->last] <= 'F')) ||
+ (($this->str[$this->last] >= 'a') && ($this->str[$this->last] <= 'f'))) {
+ // Do nothing.
+ } else {
+ break;
+ }
+ } elseif ($state === 3) {
+ if (($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) {
+ // Do nothing.
+ } elseif ($this->str[$this->last] === '.') {
+ $state = 4;
+ } elseif (($this->str[$this->last] === 'e') || ($this->str[$this->last] === 'E')) {
+ $state = 5;
+ } else {
+ break;
+ }
+ } elseif ($state === 4) {
+ $flags |= Token::FLAG_NUMBER_FLOAT;
+ if (($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) {
+ // Do nothing.
+ } elseif (($this->str[$this->last] === 'e') || ($this->str[$this->last] === 'E')) {
+ $state = 5;
+ } else {
+ break;
+ }
+ } elseif ($state === 5) {
+ $flags |= Token::FLAG_NUMBER_APPROXIMATE;
+ if (($this->str[$this->last] === '+') || ($this->str[$this->last] === '-') ||
+ ((($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')))) {
+ $state = 6;
+ } else {
+ break;
+ }
+ } elseif ($state === 6) {
+ if (($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) {
+ // Do nothing.
+ } else {
+ break;
+ }
+ }
+ $token .= $this->str[$this->last];
+ }
+ if (($state === 2) || ($state === 3) || (($token !== '.') && ($state === 4)) || ($state === 6)) {
+ --$this->last;
+ return new Token($token, Token::TYPE_NUMBER, $flags);
+ }
+ $this->last = $iBak;
+ return null;
+ }
+
+ /**
+ * Parses a string.
+ *
+ * @return Token
+ */
+ public function parseString()
+ {
+ $quote = $token = $this->str[$this->last];
+ if (!($flags = Context::isString($token))) {
+ return null;
+ }
+ while ((++$this->last < $this->len) && ($this->str[$this->last] !== $quote)) {
+ $token .= $this->str[$this->last];
+ if (($this->str[$this->last] === '\\') && (++$this->last < $this->len)) {
+ $token .= $this->str[$this->last];
+ }
+ }
+ if (($this->last >= $this->len) || ($this->str[$this->last] !== $quote)) {
+ $this->error('Ending quote ' . $quote . ' was expected.', '', $this->last);
+ } else {
+ $token .= $this->str[$this->last];
+ }
+ return new Token($token, Token::TYPE_STRING, $flags);
+ }
+
+ /**
+ * Parses a symbol.
+ *
+ * @return Token
+ */
+ public function parseSymbol()
+ {
+ // TODO: `@` can be used in queries like ```... FROM 'user'@'%' ...```.
+ $token = $this->str[$this->last];
+ if (!($flags = Context::isSymbol($token))) {
+ return null;
+ }
+ if ($flags === Token::FLAG_SYMBOL_VARIABLE) {
+ ++$this->last;
+ if (($name = static::parseString($this->str, $this->last, $this->len))) {
+ $token .= $name->token;
+ } elseif (($name = static::parseSymbol($this->str, $this->last, $this->len))) {
+ $token .= $name->token;
+ } else {
+ $name = static::parseUnknown($this->str, $this->last, $this->len);
+ if ($name === null) {
+ $this->error('Variable name was expected.', $this->str[$this->last], $this->last);
+ return null;
+ }
+ $token .= $name->token;
+ }
+ } elseif ($flags === Token::FLAG_SYMBOL_BACKTICK) {
+ $token = $this->str[$this->last];
+ while ((++$this->last < $this->len) && ($this->str[$this->last] !== '`')) {
+ $token .= $this->str[$this->last];
+ }
+ if ($this->last >= $this->len) {
+ $this->error('Ending backtick ` was expected.', '', $this->last);
+ } else {
+ $token .= $this->str[$this->last];
+ }
+ }
+ return new Token($token, Token::TYPE_SYMBOL, $flags);
+ }
+
+ /**
+ * Parses unknown parts of the query.
+ *
+ * @return Token
+ */
+ public function parseUnknown()
+ {
+ $token = $this->str[$this->last];
+ if (Context::isSeparator($token)) {
+ return null;
+ }
+ while ((++$this->last < $this->len) && (!Context::isSeparator($this->str[$this->last]))) {
+ $token .= $this->str[$this->last];
+ }
+ --$this->last;
+ return new Token($token);
+ }
+}
diff --git a/src/Parser.php b/src/Parser.php
new file mode 100644
index 0000000..9fab5ac
--- /dev/null
+++ b/src/Parser.php
@@ -0,0 +1,183 @@
+<?php
+
+namespace SqlParser;
+
+use SqlParser\Exceptions\ParserException;
+
+/**
+ * Takes multiple tokens (contained in a Lexer instance) as input and builds a
+ * parse tree.
+ */
+class Parser
+{
+
+ /**
+ * Array of classes that are used in parsing the SQL statements.
+ *
+ * @var array
+ */
+ public static $STATEMENT_PARSERS = array(
+
+ // Data Definition Statements.
+ // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-data-definition.html
+ 'ALTER' => '',
+ 'CREATE' => 'SqlParser\\Statements\\CreateStatement',
+ 'DROP' => '',
+ 'RENAME' => 'SqlParser\\Statements\\RenameStatement',
+ 'TRUNCATE' => '',
+
+ // Data Manipulation Statements.
+ // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-data-manipulation.html
+ 'CALL' => 'SqlParser\\Statements\\CallStatement',
+ 'DELETE' => 'SqlParser\\Statements\\DeleteStatement',
+ 'DO' => '',
+ 'HANDLER' => '',
+ 'INSERT' => 'SqlParser\\Statements\\InsertStatement',
+ 'LOAD' => '',
+ 'REPLACE' => 'SqlParser\\Statements\\ReplaceStatement',
+ 'SELECT' => 'SqlParser\\Statements\\SelectStatement',
+ 'UPDATE' => 'SqlParser\\Statements\\UpdateStatement',
+
+ // Prepared Statements.
+ // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html
+ 'PREPARE' => '',
+ 'EXECUTE' => '',
+ );
+
+ /**
+ * Array of classes that are used in parsing SQL fragments.
+ *
+ * @var array
+ */
+ public static $KEYWORD_PARSERS = array(
+
+ // Meta-keywords.
+ '_ARRAY' => 'SqlParser\\Fragments\\ArrayFragment',
+ '_CALL' => 'SqlParser\\Fragments\\CallKeyword',
+ '_CREATE_DEF' => 'SqlParser\\Fragments\\CreateDefFragment',
+ '_DATA_TYPE' => 'SqlParser\\Fragments\\CreateDefFragment',
+ '_FIELD' => 'SqlParser\\Fragments\\FieldFragment',
+ '_FIELD_DEF' => 'SqlParser\\Fragments\\FieldDefFragment',
+ '_OPTIONS' => 'SqlParser\\Fragments\\OptionsFragment',
+ '_RENAME' => 'SqlParser\\Fragments\\RenameKeyword',
+ '_SELECT' => 'SqlParser\\Fragments\\SelectKeyword',
+
+ 'FROM' => 'SqlParser\\Fragments\\FromKeyword',
+ 'GROUP' => 'SqlParser\\Fragments\\GroupFragment',
+ 'HAVING' => 'SqlParser\\Fragments\\WhereKeyword',
+ 'INTO' => 'SqlParser\\Fragments\\IntoKeyword',
+ 'LIMIT' => 'SqlParser\\Fragments\\LimitKeyword',
+ 'ORDER' => 'SqlParser\\Fragments\\OrderKeyword',
+ 'PARTITION' => 'SqlParser\\Fragments\\ArrayFragment',
+ 'SET' => 'SqlParser\\Fragments\\SetKeyword',
+ 'VALUE' => 'SqlParser\\Fragments\\ValuesKeyword',
+ 'VALUES' => 'SqlParser\\Fragments\\ValuesKeyword',
+ 'WHERE' => 'SqlParser\\Fragments\\WhereKeyword',
+
+ );
+
+ /**
+ * The list of tokens that are parsed.
+ *
+ * @var TokensList
+ */
+ public $list;
+
+ /**
+ * Whether errors should throw exceptions or just be stored.
+ *
+ * @var bool
+ *
+ * @see static::$errors
+ */
+ public $strict = false;
+
+ /**
+ * List of errors that occured during parsing.
+ *
+ * Usually, the parsing does not stop once an error occured because that
+ * error might be misdetected or a partial result (even a bad one) might be
+ * needed.
+ *
+ * @var ParserException[]
+ *
+ * @see Parser::error()
+ */
+ public $errors = array();
+
+ /**
+ * List of statements parsed.
+ *
+ * @var Statement[]
+ */
+ public $statements = array();
+
+ /**
+ * Constructor.
+ *
+ * @param Parser $parser
+ * @param TokensList $list
+ * @param bool $strict
+ */
+ public function __construct(TokensList $list, $strict = false)
+ {
+ $this->list = $list;
+ $this->strict = $strict;
+ }
+
+ /**
+ * Builds the parse trees.
+ */
+ public function parse()
+ {
+ $tokens = &$this->list->tokens;
+ $count = &$this->list->count;
+ $last = &$this->list->idx;
+
+ for (; $last < $count; ++$last) {
+ /** @var Token Token parsed at this moment. */
+ $token = $tokens[$last];
+
+ // Statements can start with keywords only.
+ // Comments, whitespaces, etc. are ignored.
+ if ($token->type !== Token::TYPE_KEYWORD) {
+ continue;
+ }
+
+ // Checking if it is a known statement that can be parsed.
+ if (empty(static::$STATEMENT_PARSERS[$token->value])) {
+ $this->error(
+ 'Unrecognized statement type "' . $token->value .'".',
+ $token
+ );
+ // TODO: Skip to first delimiter.
+ continue;
+ }
+
+ /** @var string The name of the class that is used for parsing. */
+ $class = static::$STATEMENT_PARSERS[$token->value];
+
+ /** @var Statement Processed statement. */
+ $stmt = new $class();
+
+ $stmt->parse($this, $this->list);
+ $this->statements[] = $stmt;
+ }
+ }
+
+ /**
+ * Creates a new error log.
+ *
+ * @param string $str
+ * @param Token $token
+ * @param int $code
+ */
+ public function error($str = '', Token $token = null, $code = 0)
+ {
+ $error = new ParserException($str, $token, $code);
+ if ($this->strict) {
+ throw $error;
+ }
+ $this->errors[] = $error;
+ }
+}
diff --git a/src/Statement.php b/src/Statement.php
new file mode 100644
index 0000000..c429fce
--- /dev/null
+++ b/src/Statement.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace SqlParser;
+
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Statement;
+use SqlParser\Token;
+
+use SqlParser\Fragments\ArrayFragment;
+use SqlParser\Fragments\CallKeyword;
+use SqlParser\Fragments\CreateDefFragment;
+use SqlParser\Fragments\DataTypeFragment;
+use SqlParser\Fragments\FieldDefFragment;
+use SqlParser\Fragments\FieldFragment;
+use SqlParser\Fragments\FromKeyword;
+use SqlParser\Fragments\IntoKeyword;
+use SqlParser\Fragments\LimitKeyword;
+use SqlParser\Fragments\OptionsFragment;
+use SqlParser\Fragments\OrderKeyword;
+use SqlParser\Fragments\ParamDefFragment;
+use SqlParser\Fragments\RenameKeyword;
+use SqlParser\Fragments\SelectKeyword;
+use SqlParser\Fragments\SetKeyword;
+use SqlParser\Fragments\ValuesKeyword;
+use SqlParser\Fragments\WhereKeyword;
+
+abstract class Statement
+{
+
+ /**
+ * The index of the first token used in this statement.
+ *
+ * @var int
+ */
+ public $first;
+
+ /**
+ * The index of the last token used in this statement.
+ *
+ * @var int
+ */
+ public $last;
+
+ /**
+ * Parses the statements defined by the tokens list.
+ *
+ * @param Parser $parser
+ * @param TokensList $list
+ */
+ public function parse(Parser $parser, TokensList $list)
+ {
+ for (; $list->idx < $list->count; ++$list->idx) {
+ /** @var Token Token parsed at this moment. */
+ $token = $list->tokens[$list->idx];
+
+ // End of statement.
+ if ($token->type === Token::TYPE_DELIMITER) {
+ break;
+ }
+
+ // Only keywords are relevant here. Other parts of the query are
+ // processed in the functions below.
+ if ($token->type !== Token::TYPE_KEYWORD) {
+ continue;
+ }
+
+ /** @var string The name of the class that is used for parsing. */
+ $class = null;
+
+ if (!empty(Parser::$KEYWORD_PARSERS[$token->value])) {
+ $class = Parser::$KEYWORD_PARSERS[$token->value];
+ } elseif (!empty(Parser::$STATEMENT_PARSERS[$token->value])) {
+ // The keyword we are processing right now is the beginning of a
+ // statement and they are usually handled differently.
+ } else {
+ $parser->error(
+ 'Unrecognized keyword "' . $token->value . '".',
+ $token
+ );
+ continue;
+ }
+
+ /** @var string The name of the field where the result is stored. */
+ $field = strtolower($token->value);
+
+ // Parsing options.
+ if (($class == null) && (isset(static::$OPTIONS))) {
+ ++$list->idx; // Skipping keyword.
+ $this->options = OptionsFragment::parse($parser, $list, static::$OPTIONS);
+ }
+
+ // Keyword specific code.
+ if ($token->value === 'CALL') {
+ ++$list->idx;
+ $this->call = CallKeyword::parse($parser, $list);
+ } elseif ($token->value === 'CREATE') {
+ ++$list->idx;
+ $this->name = CreateDefFragment::parse($parser, $list);
+ if ($this->options->has('TABLE')) {
+ ++$list->idx;
+ $this->fields = FieldDefFragment::parse($parser, $list);
+ ++$list->idx;
+ $this->tableOptions = OptionsFragment::parse($parser, $list, CreateDefFragment::$TABLE_OPTIONS);
+ } elseif (($this->options->has('PROCEDURE')) || ($this->options->has('FUNCTION'))) {
+ ++$list->idx;
+ $this->parameters = ParamDefFragment::parse($parser, $list);
+ if ($this->options->has('FUNCTION')) {
+ $token = $list->getNextOfType(Token::TYPE_KEYWORD);
+ if ($token->value !== 'RETURNS') {
+ $parser->error('\'RETURNS\' keyword was expected.', $token);
+ } else {
+ ++$list->idx;
+ $this->return = DataTypeFragment::parse($parser, $list);
+ }
+ }
+ ++$list->idx;
+ $this->funcOptions = OptionsFragment::parse($parser, $list, CreateDefFragment::$FUNC_OPTIONS);
+ ++$list->idx;
+ $this->body = array();
+ for (; $list->idx < $list->count; ++$list->idx) {
+ $token = $list->tokens[$list->idx];
+ $this->body[] = $token;
+ if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'END')) {
+ break;
+ }
+ }
+ $class = null; // The statement has been processed here.
+ }
+ } elseif (($token->value === 'GROUP') || ($token->value === 'ORDER')) {
+ $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'BY');
+ } elseif ($token->value === 'RENAME') {
+ $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'TABLE');
+ ++$list->idx;
+ $this->renames = RenameKeyword::parse($parser, $list);
+ } elseif ($token->value === 'SELECT') {
+ ++$list->idx; // Skipping last option.
+ $this->expr = SelectKeyword::parse($parser, $list);
+ } elseif ($token->value === 'UPDATE') {
+ ++$list->idx; // Skipping last option.
+ $this->from = FromKeyword::parse($parser, $list);
+ }
+
+ // Finally, processing the keyword (if possible).
+ if ($class !== null) {
+ ++$list->idx; // Skipping keyword.
+ $this->$field = $class::parse($parser, $list, array());
+ }
+ }
+
+ $this->last = --$list->idx; // Go back to last used token.
+ }
+}
diff --git a/src/Statements/CallStatement.php b/src/Statements/CallStatement.php
new file mode 100644
index 0000000..76681b5
--- /dev/null
+++ b/src/Statements/CallStatement.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace SqlParser\Statements;
+
+use SqlParser\Statement;
+
+/**
+ * `CALL` statement.
+ */
+class CallStatement extends Statement
+{
+
+ /**
+ * The name of the function and its parameters.
+ *
+ * @var CallKeyword
+ */
+ public $call;
+}
diff --git a/src/Statements/CreateStatement.php b/src/Statements/CreateStatement.php
new file mode 100644
index 0000000..edf0dbd
--- /dev/null
+++ b/src/Statements/CreateStatement.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace SqlParser\Statements;
+
+use SqlParser\Statement;
+
+/**
+ * `CREATE` statement.
+ */
+class CreateStatement extends Statement
+{
+
+ /**
+ * Options for `CREATE` statements.
+ *
+ * @var array
+ */
+ public static $OPTIONS = array(
+
+ 'DATABASE' => 1,
+ 'EVENT' => 1,
+ 'FUNCTION' => 1,
+ 'INDEX' => 1,
+ 'PROCEDURE' => 1,
+ 'SERVER' => 1,
+ 'TABLE' => 1,
+ 'TABLESPACE' => 1,
+ 'TRIGGER' => 1,
+ 'VIEW' => 1,
+
+ 'TEMPORARY' => 2,
+ 'IF NOT EXISTS' => 3,
+ );
+
+ /**
+ * The name of the enw table.
+ *
+ * @var CreateDefFragment
+ */
+ public $name;
+
+ /**
+ * The options of this query.
+ *
+ * @var OptionsFragment
+ *
+ * @see static::$OPTIONS
+ */
+ public $options;
+
+ /**
+ * The options of the table.
+ *
+ * @var OptionsFragment
+ *
+ * @see CreateDefFragment::$TABLE_OPTIONS
+ */
+ public $tableOptions;
+
+ /**
+ * Field created by this statement.
+ *
+ * @var FieldDefFragment[]
+ */
+ public $fields;
+
+ /**
+ * The body of this function or procedure.
+ *
+ * @var Token[]
+ */
+ public $body;
+}
diff --git a/src/Statements/DeleteStatement.php b/src/Statements/DeleteStatement.php
new file mode 100644
index 0000000..01dd8ad
--- /dev/null
+++ b/src/Statements/DeleteStatement.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace SqlParser\Statements;
+
+use SqlParser\Statement;
+
+/**
+ * `DELETE` statement.
+ *
+ * DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name
+ * [PARTITION (partition_name,...)]
+ * [WHERE where_condition]
+ * [ORDER BY ...]
+ * [LIMIT row_count]
+ *
+ */
+class DeleteStatement extends Statement
+{
+
+ /**
+ * Options for `DELETE` statements.
+ *
+ * @var array
+ */
+ public static $OPTIONS = array(
+ 'LOW_PRIORITY' => 1,
+ 'QUICK' => 2,
+ 'IGNORE' => 3,
+ );
+
+ /**
+ * The options of this query.
+ *
+ * @var OptionsFragment
+ *
+ * @see static::$OPTIONS
+ */
+ public $options;
+
+ /**
+ * Tables used as sources for this statement.
+ *
+ * @var FieldFragment[]
+ */
+ public $from;
+
+ /**
+ * Partitions used as source for this statement.
+ *
+ * @var ArrayFragment
+ */
+ public $partition;
+
+ /**
+ * Conditions used for filtering each row of the result set.
+ *
+ * @var WhereKeyword[]
+ */
+ public $where;
+
+ /**
+ * Specifies the order of the rows in the result set.
+ *
+ * @var OrderKeyword[]
+ */
+ public $order;
+
+ /**
+ * Conditions used for limiting the size of the result set.
+ *
+ * @var LimitKeyword
+ */
+ public $limit;
+}
diff --git a/src/Statements/InsertStatement.php b/src/Statements/InsertStatement.php
new file mode 100644
index 0000000..f24634a
--- /dev/null
+++ b/src/Statements/InsertStatement.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace SqlParser\Statements;
+
+use SqlParser\Statement;
+
+/**
+ * `INSERT` statement.
+ *
+ * INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
+ * [INTO] tbl_name
+ * [PARTITION (partition_name,...)]
+ * [(col_name,...)]
+ * {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
+ * [ ON DUPLICATE KEY UPDATE
+ * col_name=expr
+ * [, col_name=expr] ... ]
+ *
+ * or
+ *
+ * INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
+ * [INTO] tbl_name
+ * [PARTITION (partition_name,...)]
+ * SET col_name={expr | DEFAULT}, ...
+ * [ ON DUPLICATE KEY UPDATE
+ * col_name=expr
+ * [, col_name=expr] ... ]
+ *
+ * or
+ *
+ * INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
+ * [INTO] tbl_name
+ * [PARTITION (partition_name,...)]
+ * [(col_name,...)]
+ * SELECT ...
+ * [ ON DUPLICATE KEY UPDATE
+ * col_name=expr
+ * [, col_name=expr] ... ]
+ *
+ */
+class InsertStatement extends Statement
+{
+
+ /**
+ * Options for `INSERT` statements.
+ *
+ * @var array
+ */
+ public static $OPTIONS = array(
+ 'LOW_PRIORITY' => 1,
+ 'DELAYED' => 2,
+ 'HIGH_PRIORITY' => 3,
+ 'IGNORE' => 4,
+ );
+
+ /**
+ * The options of this query.
+ *
+ * @var OptionsFragment
+ *
+ * @see static::$OPTIONS
+ */
+ public $options;
+
+ /**
+ * Tables used as target for this statement.
+ *
+ * @var IntoKeyword
+ */
+ public $into;
+
+ /**
+ * Values to be inserted.
+ *
+ * @var ValuesKeyword
+ */
+ public $values;
+}
diff --git a/src/Statements/RenameStatement.php b/src/Statements/RenameStatement.php
new file mode 100644
index 0000000..bd3c767
--- /dev/null
+++ b/src/Statements/RenameStatement.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace SqlParser\Statements;
+
+use SqlParser\Statement;
+
+/**
+ * `RENAME` statement.
+ *
+ * RENAME TABLE tbl_name TO new_tbl_name
+ * [, tbl_name2 TO new_tbl_name2] ...
+ *
+ */
+class RenameStatement extends Statement
+{
+
+ /**
+ * The old and new names of the tables.
+ *
+ * @var RenameKeyword[]
+ */
+ public $renames;
+}
diff --git a/src/Statements/ReplaceStatement.php b/src/Statements/ReplaceStatement.php
new file mode 100644
index 0000000..8e69207
--- /dev/null
+++ b/src/Statements/ReplaceStatement.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace SqlParser\Statements;
+
+use SqlParser\Statement;
+
+/**
+ * `REPLACE` statement.
+ *
+ * REPLACE [LOW_PRIORITY | DELAYED]
+ * [INTO] tbl_name [(col_name,...)]
+ * {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
+ *
+ * or
+ *
+ * REPLACE [LOW_PRIORITY | DELAYED]
+ * [INTO] tbl_name
+ * SET col_name={expr | DEFAULT}, ...
+ *
+ */
+class ReplaceStatement extends Statement
+{
+
+ /**
+ * Options for `REPLACE` statements and their slot ID.
+ *
+ * @var array
+ */
+ public static $OPTIONS = array(
+ 'LOW_PRIORITY' => 1,
+ 'DELAYED' => 1,
+ );
+
+ /**
+ * The options of this query.
+ *
+ * @var OptionsFragment
+ *
+ * @see static::$OPTIONS
+ */
+ public $options;
+
+ /**
+ * Tables used as target for this statement.
+ *
+ * @var IntoKeyword
+ */
+ public $into;
+
+ /**
+ * Values to be replaced.
+ *
+ * @var ValuesKeyword
+ */
+ public $values;
+
+ /**
+ * The replaced values.
+ *
+ * @var SetKeyword[]
+ */
+ public $set;
+}
diff --git a/src/Statements/SelectStatement.php b/src/Statements/SelectStatement.php
new file mode 100644
index 0000000..b8f3e10
--- /dev/null
+++ b/src/Statements/SelectStatement.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace SqlParser\Statements;
+
+use SqlParser\Statement;
+
+/**
+ * `SELECT` statement.
+ *
+ * SELECT
+ * [ALL | DISTINCT | DISTINCTROW ]
+ * [HIGH_PRIORITY]
+ * [MAX_STATEMENT_TIME = N]
+ * [STRAIGHT_JOIN]
+ * [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
+ * [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
+ * select_expr [, select_expr ...]
+ * [FROM table_references
+ * [PARTITION partition_list]
+ * [WHERE where_condition]
+ * [GROUP BY {col_name | expr | position}
+ * [ASC | DESC], ... [WITH ROLLUP]]
+ * [HAVING where_condition]
+ * [ORDER BY {col_name | expr | position}
+ * [ASC | DESC], ...]
+ * [LIMIT {[offset,] row_count | row_count OFFSET offset}]
+ * [PROCEDURE procedure_name(argument_list)]
+ * [INTO OUTFILE 'file_name'
+ * [CHARACTER SET charset_name]
+ * export_options
+ * | INTO DUMPFILE 'file_name'
+ * | INTO var_name [, var_name]]
+ * [FOR UPDATE | LOCK IN SHARE MODE]]
+ *
+ */
+class SelectStatement extends Statement
+{
+
+ /**
+ * Options for `SELECT` statements and their slot ID.
+ *
+ * @var array
+ */
+ public static $OPTIONS = array(
+ 'ALL' => 1,
+ 'DISTINCT' => 1,
+ 'DISTINCTROW' => 1,
+ 'HIGH_PRIORITY' => 2,
+ 'MAX_STATEMENT_TIME' => array(3, 'var'),
+ 'STRAIGHT_JOIN' => 4,
+ 'SQL_SMALL_RESULT' => 5,
+ 'SQL_BIG_RESULT' => 6,
+ 'SQL_BUFFER_RESULT' => 7,
+ 'SQL_CACHE' => 8,
+ 'SQL_NO_CACHE' => 8,
+ 'SQL_CALC_FOUND_ROWS' => 9,
+ );
+
+ /**
+ * The options of this query.
+ *
+ * @var OptionsFragment
+ *
+ * @see static::$OPTIONS
+ */
+ public $options;
+
+ /**
+ * Expressions that are being selected by this statement.
+ *
+ * @var FieldFragment[]
+ */
+ public $expr;
+
+ /**
+ * Tables used as sources for this statement.
+ *
+ * @var FieldFragment[]
+ */
+ public $from;
+
+ /**
+ * Partitions used as source for this statement.
+ *
+ * @var ArrayFragment[]
+ */
+ public $partition;
+
+ /**
+ * Conditions used for filtering each row of the result set.
+ *
+ * @var WhereKeyword
+ */
+ public $where;
+
+ /**
+ * Conditions used for grouping the result set.
+ *
+ * @var GroupKeyword
+ */
+ public $group;
+
+ /**
+ * Conditions used for filtering the result set.
+ *
+ * @var WhereKeyword[]
+ */
+ public $having;
+
+ /**
+ * Specifies the order of the rows in the result set.
+ *
+ * @var OrderKeyword[]
+ */
+ public $order;
+
+ /**
+ * Conditions used for limiting the size of the result set.
+ *
+ * @var LimitKeyword
+ */
+ public $limit;
+}
diff --git a/src/Statements/UpdateStatement.php b/src/Statements/UpdateStatement.php
new file mode 100644
index 0000000..cdeab03
--- /dev/null
+++ b/src/Statements/UpdateStatement.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace SqlParser\Statements;
+
+use SqlParser\Statement;
+
+/**
+ * `UPDATE` statement.
+ *
+ * UPDATE [LOW_PRIORITY] [IGNORE] table_reference
+ * SET col_name1={expr1|DEFAULT} [, col_name2={expr2|DEFAULT}] ...
+ * [WHERE where_condition]
+ * [ORDER BY ...]
+ * [LIMIT row_count]
+ *
+ * or
+ *
+ * UPDATE [LOW_PRIORITY] [IGNORE] table_references
+ * SET col_name1={expr1|DEFAULT} [, col_name2={expr2|DEFAULT}] ...
+ * [WHERE where_condition]
+ *
+ */
+class UpdateStatement extends Statement
+{
+
+ /**
+ * Options for `UPDATE` statements and their slot ID.
+ *
+ * @var array
+ */
+ public static $OPTIONS = array(
+ 'LOW_PRIORITY' => 1,
+ 'IGNORE' => 2,
+ );
+
+ /**
+ * The options of this query.
+ *
+ * @var OptionsFragment
+ *
+ * @see static::$OPTIONS
+ */
+ public $options;
+
+ /**
+ * Tables used as sources for this statement.
+ *
+ * @var FieldFragment[]
+ */
+ public $from;
+
+ /**
+ * The updated values.
+ *
+ * @var SetKeyword[]
+ */
+ public $set;
+
+ /**
+ * Conditions used for filtering each row of the result set.
+ *
+ * @var WhereKeyword[]
+ */
+ public $where;
+
+ /**
+ * Specifies the order of the rows in the result set.
+ *
+ * @var OrderKeyword[]
+ */
+ public $order;
+
+ /**
+ * Conditions used for limiting the size of the result set.
+ *
+ * @var LimitKeyword
+ */
+ public $limit;
+}
diff --git a/src/Token.php b/src/Token.php
new file mode 100644
index 0000000..e597713
--- /dev/null
+++ b/src/Token.php
@@ -0,0 +1,253 @@
+<?php
+
+namespace SqlParser;
+
+/**
+ * A structure representing a lexeme that explicitly indicates its
+ * categorization for the purpose of parsing.
+ */
+class Token
+{
+
+ // Types of tokens (a vague description of a token's purpose).
+
+ /**
+ * This type is used when the token is invalid or its type cannot be
+ * determiend because of the ambigous context. Further analysis might be
+ * required to detect its type.
+ *
+ * @var int
+ */
+ const TYPE_NONE = 0;
+
+ /**
+ * SQL specific keywords: SELECT, UPDATE, INSERT, etc.
+ *
+ * @var int
+ */
+ const TYPE_KEYWORD = 1;
+
+ /**
+ * Any type of legal operator.
+ *
+ * Arithmetic operators: +, -, *, /, etc.
+ * Logical operators: ===, <>, !==, etc.
+ * Bitwise operators: &, |, ^, etc.
+ * Assignment operators: =, +=, -=, etc.
+ * SQL specific operators: . (e.g. .. WHERE database.table ..),
+ * * (e.g. SELECT * FROM ..)
+ *
+ * @var int
+ */
+ const TYPE_OPERATOR = 2;
+
+ /**
+ * Spaces, tabs, new lines, etc.
+ *
+ * @var int
+ */
+ const TYPE_WHITESPACE = 3;
+
+ /**
+ * Any type of legal comment.
+ *
+ * Bash (#), C (/* *\/) or SQL (--) comments:
+ *
+ * -- SQL-comment
+ *
+ * #Bash-like comment
+ *
+ * /*C-like comment*\/
+ *
+ * or:
+ *
+ * /*C-like
+ * comment*\/
+ *
+ * Backslashes were added to respect PHP's comments syntax.
+ *
+ * @var int
+ */
+ const TYPE_COMMENT = 4;
+
+ /**
+ * Boolean values: true or false.
+ *
+ * @var int
+ */
+ const TYPE_BOOL = 5;
+
+ /**
+ * Numbers: 4, 0x8, 15.16, 23e42, etc.
+ *
+ * @var int
+ */
+ const TYPE_NUMBER = 6;
+
+ /**
+ * Literal strings: 'string', "test".
+ * Some of these strings are actually symbols.
+ *
+ * @var int
+ */
+ const TYPE_STRING = 7;
+
+ /**
+ * Database, table names, variables, etc.
+ * For example: ```SELECT `foo`, `bar` FROM `database`.`table`;```
+ *
+ * @var int
+ */
+ const TYPE_SYMBOL = 8;
+
+ /**
+ * Delimits an unknown string.
+ * For example: ```SELECT * FROM test;```, `test` is a delimiter.
+ *
+ * @var int
+ */
+ const TYPE_DELIMITER = 9;
+
+ // Flags that describe the tokens in more detail.
+
+ // Numbers related flags.
+ const FLAG_NUMBER_HEX = 1;
+ const FLAG_NUMBER_FLOAT = 2;
+ const FLAG_NUMBER_APPROXIMATE = 4;
+ const FLAG_NUMBER_NEGATIVE = 8;
+
+ // Strings related flags.
+ const FLAG_STRING_SINGLE_QUOTES = 1;
+ const FLAG_STRING_DOUBLE_QUOTES = 2;
+
+ // Comments related flags.
+ const FLAG_COMMENT_BASH = 1;
+ const FLAG_COMMENT_C = 2;
+ const FLAG_COMMENT_SQL = 4;
+ const FLAG_COMMENT_MYSQL_CMD = 8;
+
+ // Operators related flags.
+ const FLAG_OPERATOR_ARITHMETIC = 1;
+ const FLAG_OPERATOR_LOGICAL = 2;
+ const FLAG_OPERATOR_BITWISE = 4;
+ const FLAG_OPERATOR_ASSIGNMENT = 8;
+ const FLAG_OPERATOR_SQL = 16;
+
+ // Symbols related flags.
+ const FLAG_SYMBOL_VARIABLE = 1;
+ const FLAG_SYMBOL_BACKTICK = 2;
+
+ /**
+ * The token it its raw string representation.
+ *
+ * @var string
+ */
+ public $token;
+
+ /**
+ * The value this token contains (i.e. token after some evaluation)
+ *
+ * @var mixed
+ */
+ public $value;
+
+ /**
+ * The type of this token.
+ *
+ * @var int
+ */
+ public $type;
+
+ /**
+ * The flags of this token.
+ *
+ * @var int
+ */
+ public $flags;
+
+ /**
+ * The position in the inial string where this token started.
+ *
+ * @var int
+ */
+ public $position;
+
+ /**
+ * Constructor.
+ *
+ * @param string $token
+ * @param int $type
+ * @param int $flags
+ * @param int $position
+ */
+ public function __construct($token, $type = 0, $flags = 0)
+ {
+ $this->token = $token;
+ $this->type = $type;
+ $this->flags = $flags;
+ $this->value = $this->extract();
+ }
+
+ /**
+ * Does little processing to the token to extract a value.
+ *
+ * If no processing can be done it will return the initial string.
+ *
+ * @return mixed
+ */
+ public function extract()
+ {
+ switch ($this->type) {
+ case Token::TYPE_KEYWORD:
+ return strtoupper($this->token);
+ case Token::TYPE_WHITESPACE:
+ return ' ';
+ case Token::TYPE_BOOL:
+ return strtoupper($this->token) === 'TRUE';
+ case Token::TYPE_NUMBER:
+ $ret = str_replace('--', '', $this->token); // e.g. ---42 === -42
+ if ($this->flags & Token::FLAG_NUMBER_HEX) {
+ if ($this->flags & Token::FLAG_NUMBER_NEGATIVE) {
+ $ret = str_replace('-', '', $this->token);
+ sscanf($ret, "%x", $ret);
+ $ret = -$ret;
+ } else {
+ sscanf($ret, "%x", $ret);
+ }
+ } elseif (($this->flags & Token::FLAG_NUMBER_APPROXIMATE) ||
+ ($this->flags & Token::FLAG_NUMBER_FLOAT)) {
+ sscanf($ret, "%f", $ret);
+ } else {
+ sscanf($ret, "%d", $ret);
+ }
+ return $ret;
+ case Token::TYPE_STRING:
+ return mb_substr($this->token, 1, -1); // trims quotes
+ case Token::TYPE_SYMBOL:
+ if ($this->flags & Token::FLAG_SYMBOL_VARIABLE) {
+ if ($this->token[1] === '`') {
+ return mb_substr($this->token, 2, -1); // trims @` and `
+ } else {
+ return mb_substr($this->token, 1); // trims @
+ }
+ } elseif ($this->flags & Token::FLAG_SYMBOL_BACKTICK) {
+ return mb_substr($this->token, 1, -1); // trims backticks
+ }
+ }
+ return $this->token;
+ }
+
+ /**
+ * Converts the token into an inline token by replacing tabs and new lines.
+ *
+ * @return string
+ */
+ public function getInlineToken()
+ {
+ return str_replace(
+ array("\r", "\n", "\t"),
+ array('\r', '\n', '\t'),
+ $this->token
+ );
+ }
+}
diff --git a/src/TokensList.php b/src/TokensList.php
new file mode 100644
index 0000000..405f4a1
--- /dev/null
+++ b/src/TokensList.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace SqlParser;
+
+class TokensList implements \ArrayAccess
+{
+
+ /**
+ * The array of tokens.
+ *
+ * @var array
+ */
+ public $tokens = array();
+
+ /**
+ * The count of tokens.
+ *
+ * @var int
+ */
+ public $count = 0;
+
+ /**
+ * The index of the next token to be returned.
+ *
+ * @var int
+ */
+ public $idx = 0;
+
+ /**
+ * Adds a new token.
+ *
+ * @param Token $token
+ */
+ public function add(Token $token)
+ {
+ $this->tokens[$this->count++] = $token;
+ }
+
+ /**
+ * Gets next token in list.
+ *
+ * @return Token
+ */
+ public function getNext()
+ {
+ if ($this->idx < $this->count) {
+ return $this->tokens[$this->idx++];
+ }
+ return null;
+ }
+
+ /**
+ * Gets next token of a specified type.
+ *
+ * @param int $type
+ *
+ * @return Token
+ */
+ public function getNextOfType($type)
+ {
+ for (; $this->idx < $this->count; ++$this->idx) {
+ if ($this->tokens[$this->idx]->type === $type) {
+ return $this->tokens[$this->idx++];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets next token of a specified type.
+ *
+ * @param int $type
+ * @param string $value
+ *
+ * @return Token
+ */
+ public function getNextOfTypeAndValue($type, $value)
+ {
+ for (; $this->idx < $this->count; ++$this->idx) {
+ if (($this->tokens[$this->idx]->type === $type) &&
+ ($this->tokens[$this->idx]->value === $value)) {
+ return $this->tokens[$this->idx++];
+ }
+ }
+ return null;
+ }
+
+ // ArrayAccess methods.
+
+ public function offsetSet($offset, $value)
+ {
+ if ($offset === null) {
+ $this->tokens[$this->count++] = $value;
+ } else {
+ $this->tokens[$offset] = $value;
+ }
+ }
+
+ public function offsetGet($offset)
+ {
+ return $offset < $this->count ? $this->tokens[$offset] : null;
+ }
+
+ public function offsetExists($offset)
+ {
+ return $offset < $this->count;
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->tokens[$offset]);
+ --$this->count;
+ for ($i = $offset; $i < $this->count; ++$i) {
+ $this->tokens[$i] = $this->tokens[$i + 1];
+ }
+ unset($this->tokens[$this->count]);
+ }
+}
diff --git a/src/UtfString.php b/src/UtfString.php
new file mode 100644
index 0000000..8815e58
--- /dev/null
+++ b/src/UtfString.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace SqlParser;
+
+/**
+ * Implements array-like access for UTF-8 strings.
+ *
+ * In this library, this class should be used to parse UTF-8 queries.
+ */
+class UtfString implements \ArrayAccess
+{
+
+ /**
+ * The raw, multibyte string.
+ *
+ * @var string
+ */
+ private $str = '';
+
+ /**
+ * The index of current byte.
+ *
+ * For ASCII strings, the byte index is equal to the character index.
+ *
+ * @var int
+ */
+ private $byteIdx = 0;
+
+ /**
+ * The index of current character.
+ *
+ * For non-ASCII strings, some characters occupy more than one byte and
+ * the character index will have a lower value than the byte index.
+ *
+ * @var int
+ */
+ private $charIdx = 0;
+
+ /**
+ * The length of the string (in bytes).
+ *
+ * @var int
+ */
+ private $buffLen = 0;
+
+ /**
+ * The length of the string (in characters).
+ *
+ * @var int
+ */
+ private $charLen = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param string $str
+ */
+ public function __construct($str)
+ {
+ $this->str = $str;
+ $this->byteIdx = 0;
+ $this->charIdx = 0;
+ // TODO: `strlen($str)` might return a wrong length when function
+ // overloading is enabled.
+ // https://php.net/manual/ro/mbstring.overload.php
+ $this->byteLen = strlen($str);
+ $this->charLen = mb_strlen($str);
+ }
+
+ /**
+ * Checks if the given offset exists.
+ *
+ * @param int $offset
+ *
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return $offset < $charLen;
+ }
+
+ /**
+ * Gets the character at given offset.
+ *
+ * @param int $offset
+ *
+ * @return string
+ */
+ public function offsetGet($offset)
+ {
+ if (($offset < 0) || ($offset >= $this->charLen)) {
+ return null;
+ }
+
+ $delta = $offset - $this->charIdx;
+
+ if ($delta > 0) {
+ // Fast forwarding.
+ while ($delta-- > 0) {
+ $this->byteIdx += static::getCharLength($this->str[$this->byteIdx]);
+ ++$this->charIdx;
+ }
+ } elseif ($delta < 0) {
+ // Rewinding.
+ while ($delta++ < 0) {
+ do {
+ $byte = ord($this->str[--$this->byteIdx]);
+ } while ((128 <= $byte) && ($byte < 192));
+ --$this->charIdx;
+ }
+ }
+
+ $bytesCount = static::getCharLength($this->str[$this->byteIdx]);
+
+ $ret = '';
+ for ($i = 0; $bytesCount-- > 0; ++$i) {
+ $ret .= $this->str[$this->byteIdx + $i];
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Sets the value of a character.
+ *
+ * @param int $offset
+ * @param string $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw new \Exception('Not implemented.');
+ }
+
+ /**
+ * Unsets an index.
+ *
+ * @param int $offset
+ */
+ public function offsetUnset($offset)
+ {
+ throw new \Exception('Not implemented.');
+ }
+
+ /**
+ * Gets the length of an UTF-8 character.
+ *
+ * According to RFC 3629, a UTF-8 character can have at most 4 bytes.
+ * However, this implemenation supports UTF-8 characters containing up to 6
+ * bytes.
+ *
+ * @see http://tools.ietf.org/html/rfc3629
+ *
+ * @param string $byte
+ *
+ * @return int
+ */
+ private static function getCharLength($byte)
+ {
+ $byte = ord($byte);
+ if ($byte < 128) {
+ return 1;
+ } elseif ($byte < 224) {
+ return 2;
+ } elseif ($byte < 240) {
+ return 3;
+ } elseif ($byte < 248) {
+ return 4;
+ } elseif ($byte === 252) {
+ return 5; // unofficial
+ }
+ return 6; // unofficial
+ }
+
+ /**
+ * Returns the number of remaining characters.
+ *
+ * @return int
+ */
+ public function remaining()
+ {
+ if ($this->charIdx < $this->charLen) {
+ return $this->charLen - $this->charIdx;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the length in characters of the string.
+ *
+ * @return int
+ */
+ public function length()
+ {
+ return $this->charLen;
+ }
+
+ /**
+ * Gets the values of the indexes.
+ *
+ * @param int &$byte
+ * @param int &$char
+ */
+ public function getIndexes(&$byte, &$char)
+ {
+ $byte = $this->byteIdx;
+ $char = $this->charIdx;
+ }
+
+ /**
+ * Sets the values of the indexes.
+ *
+ * @param int $byte
+ * @param int $char
+ */
+ public function setIndexes($byte = 0, $char = 0)
+ {
+ $this->byteIdx = $byte;
+ $this->charIdx = $char;
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..03a7aa4
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,66 @@
+<?php
+
+require('vendor/autoload.php');
+
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+
+abstract class TestCase extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Gets test's input and expected output.
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ public function getData($name)
+ {
+ $input = file_get_contents('tests/data/' . $name . '.in');
+ $output = unserialize(file_get_contents('tests/data/' . $name . '.out'));
+ return array($input, $output);
+ }
+
+ /**
+ * Tests the `Lexer`.
+ *
+ * @param string $name
+ *
+ * @return Lexer
+ */
+ public function runLexerTest($name)
+ {
+ list($input, $output) = $this->getData($name);
+
+ $lexer = new Lexer($input);
+ $lexer->lex();
+
+ $this->assertEquals($output, $lexer);
+
+ return $lexer;
+ }
+
+ /**
+ * Tests the `Parser`.
+ *
+ * @param string $name
+ *
+ * @return Parser
+ */
+ public function runParserTest($name)
+ {
+ list($input, $output) = $this->getData($name);
+
+ $lexer = new Lexer($input);
+ $lexer->lex();
+
+ $parser = new Parser($lexer->tokens);
+ $parser->parse();
+
+ $this->assertEquals($output, $parser);
+
+ return $parser;
+ }
+}
diff --git a/tests/data/lex.in b/tests/data/lex.in
new file mode 100644
index 0000000..675e193
--- /dev/null
+++ b/tests/data/lex.in
@@ -0,0 +1 @@
+SELECT \\ \ No newline at end of file
diff --git a/tests/data/lex.out b/tests/data/lex.out
new file mode 100644
index 0000000..c0d4ba2
--- /dev/null
+++ b/tests/data/lex.out
Binary files differ
diff --git a/tests/data/lexBool.in b/tests/data/lexBool.in
new file mode 100644
index 0000000..9cad582
--- /dev/null
+++ b/tests/data/lexBool.in
@@ -0,0 +1 @@
+SELECT true, FalSe \ No newline at end of file
diff --git a/tests/data/lexBool.out b/tests/data/lexBool.out
new file mode 100644
index 0000000..38ce22b
--- /dev/null
+++ b/tests/data/lexBool.out
@@ -0,0 +1 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:18:"SELECT true, FalSe";s:3:"len";i:18;s:4:"last";i:18;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:7:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"true";s:5:"value";b:1;s:4:"type";i:5;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:11;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:12;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"FalSe";s:5:"value";b:0;s:4:"type";i:5;s:5:"flags";i:1;s:8:"position";i:13;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:7;s:3:"idx";i:0;}s:9:"delimiter";s:1:";";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/lexComment.in b/tests/data/lexComment.in
new file mode 100644
index 0000000..961b250
--- /dev/null
+++ b/tests/data/lexComment.in
@@ -0,0 +1,4 @@
+# comment
+SELECT /*! STRAIGHT_JOIN */ col1 FROM table1, table2 /* select query */
+-- comment
+-- comment 2 \ No newline at end of file
diff --git a/tests/data/lexComment.out b/tests/data/lexComment.out
new file mode 100644
index 0000000..52ceeff
--- /dev/null
+++ b/tests/data/lexComment.out
@@ -0,0 +1,9 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:105:"# comment
+SELECT /*! STRAIGHT_JOIN */ col1 FROM table1, table2 /* select query */
+-- comment
+-- comment 2";s:3:"len";i:105;s:4:"last";i:106;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:19:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:10:"# comment
+";s:5:"value";s:10:"# comment
+";s:4:"type";i:4;s:5:"flags";i:1;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:10;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:16;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:20:"/*! STRAIGHT_JOIN */";s:5:"value";s:20:"/*! STRAIGHT_JOIN */";s:4:"type";i:4;s:5:"flags";i:10;s:8:"position";i:17;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:37;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"col1";s:5:"value";s:4:"col1";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:38;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:42;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:43;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"table1";s:5:"value";s:6:"table1";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:48;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:54;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:55;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"table2";s:5:"value";s:6:"table2";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:56;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:62;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:18:"/* select query */";s:5:"value";s:18:"/* select query */";s:4:"type";i:4;s:5:"flags";i:2;s:8:"position";i:63;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:81;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:11:"-- comment
+";s:5:"value";s:11:"-- comment
+";s:4:"type";i:4;s:5:"flags";i:4;s:8:"position";i:82;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:12:"-- comment 2";s:5:"value";s:12:"-- comment 2";s:4:"type";i:4;s:5:"flags";i:4;s:8:"position";i:93;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:19;s:3:"idx";i:0;}s:9:"delimiter";s:1:";";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/lexDelimiter.in b/tests/data/lexDelimiter.in
new file mode 100644
index 0000000..b5c99ed
--- /dev/null
+++ b/tests/data/lexDelimiter.in
@@ -0,0 +1,3 @@
+DELIMITER GO
+SELECT a,b FROM foo GO
+SELECT * FROM bar \ No newline at end of file
diff --git a/tests/data/lexDelimiter.out b/tests/data/lexDelimiter.out
new file mode 100644
index 0000000..e92fa75
--- /dev/null
+++ b/tests/data/lexDelimiter.out
@@ -0,0 +1,5 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:53:"DELIMITER GO
+SELECT a,b FROM foo GO
+SELECT * FROM bar";s:3:"len";i:53;s:4:"last";i:53;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:24:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"DELIMITER";s:5:"value";s:9:"DELIMITER";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:9;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"GO";s:5:"value";s:2:"GO";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:10;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:12;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:13;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:19;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"a";s:5:"value";s:1:"a";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:20;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:21;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"b";s:5:"value";s:1:"b";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:22;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:23;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:24;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:28;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:29;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:32;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"GO";s:5:"value";s:2:"GO";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:33;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:35;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:36;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:42;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"*";s:5:"value";s:1:"*";s:4:"type";i:2;s:5:"flags";i:1;s:8:"position";i:43;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:44;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:45;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:49;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"bar";s:5:"value";s:3:"bar";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:50;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:24;s:3:"idx";i:0;}s:9:"delimiter";s:2:"GO";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/lexKeyword.in b/tests/data/lexKeyword.in
new file mode 100644
index 0000000..7a8f142
--- /dev/null
+++ b/tests/data/lexKeyword.in
@@ -0,0 +1 @@
+SELECT 1 \ No newline at end of file
diff --git a/tests/data/lexKeyword.out b/tests/data/lexKeyword.out
new file mode 100644
index 0000000..4322b28
--- /dev/null
+++ b/tests/data/lexKeyword.out
@@ -0,0 +1 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:8:"SELECT 1";s:3:"len";i:8;s:4:"last";i:8;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:4:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:4;s:3:"idx";i:0;}s:9:"delimiter";s:1:";";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/lexNumber.in b/tests/data/lexNumber.in
new file mode 100644
index 0000000..23c5ea6
--- /dev/null
+++ b/tests/data/lexNumber.in
@@ -0,0 +1,3 @@
+SELECT 12, 34, 5.67, 0x89, -10, --11, +12, .15, 0xFFa, 0xfFA, 0XFfA, 1e-10, 1e10, .5e10;
+-- invalid number
+SELECT 12ex10; \ No newline at end of file
diff --git a/tests/data/lexNumber.out b/tests/data/lexNumber.out
new file mode 100644
index 0000000..47e5c8d
--- /dev/null
+++ b/tests/data/lexNumber.out
@@ -0,0 +1,6 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:121:"SELECT 12, 34, 5.67, 0x89, -10, --11, +12, .15, 0xFFa, 0xfFA, 0XFfA, 1e-10, 1e10, .5e10;
+-- invalid number
+SELECT 12ex10;";s:3:"len";i:121;s:4:"last";i:121;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:50:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"12";s:5:"value";i:12;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:9;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:10;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"34";s:5:"value";i:34;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:11;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:13;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:14;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"5.67";s:5:"value";d:5.6699999999999999;s:4:"type";i:6;s:5:"flags";i:2;s:8:"position";i:15;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:19;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:20;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"0x89";s:5:"value";i:137;s:4:"type";i:6;s:5:"flags";i:1;s:8:"position";i:21;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:25;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:26;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"-10";s:5:"value";i:-10;s:4:"type";i:6;s:5:"flags";i:8;s:8:"position";i:27;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:30;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:31;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"--11";s:5:"value";i:11;s:4:"type";i:6;s:5:"flags";i:8;s:8:"position";i:32;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:36;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:37;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"+12";s:5:"value";i:12;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:38;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:41;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:42;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:3:".15";s:5:"value";d:0.14999999999999999;s:4:"type";i:6;s:5:"flags";i:2;s:8:"position";i:43;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:46;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"0xFFa";s:5:"value";i:4090;s:4:"type";i:6;s:5:"flags";i:1;s:8:"position";i:48;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:53;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:54;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"0xfFA";s:5:"value";i:4090;s:4:"type";i:6;s:5:"flags";i:1;s:8:"position";i:55;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:60;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:61;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"0XFfA";s:5:"value";i:4090;s:4:"type";i:6;s:5:"flags";i:1;s:8:"position";i:62;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:67;}i:34;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:68;}i:35;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"1e-10";s:5:"value";d:1.0E-10;s:4:"type";i:6;s:5:"flags";i:4;s:8:"position";i:69;}i:36;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:74;}i:37;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:75;}i:38;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"1e10";s:5:"value";d:10000000000;s:4:"type";i:6;s:5:"flags";i:4;s:8:"position";i:76;}i:39;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:80;}i:40;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:81;}i:41;O:15:"SqlParser\Token":5:{s:5:"token";s:5:".5e10";s:5:"value";d:5000000000;s:4:"type";i:6;s:5:"flags";i:6;s:8:"position";i:82;}i:42;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:87;}i:43;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:88;}i:44;O:15:"SqlParser\Token":5:{s:5:"token";s:18:"-- invalid number
+";s:5:"value";s:18:"-- invalid number
+";s:4:"type";i:4;s:5:"flags";i:4;s:8:"position";i:89;}i:45;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:107;}i:46;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:113;}i:47;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"12ex10";s:5:"value";s:6:"12ex10";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:114;}i:48;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:120;}i:49;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:50;s:3:"idx";i:0;}s:9:"delimiter";s:1:";";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/lexOperator.in b/tests/data/lexOperator.in
new file mode 100644
index 0000000..ab544dd
--- /dev/null
+++ b/tests/data/lexOperator.in
@@ -0,0 +1 @@
+SELECT 1 + 2 \ No newline at end of file
diff --git a/tests/data/lexOperator.out b/tests/data/lexOperator.out
new file mode 100644
index 0000000..d177511
--- /dev/null
+++ b/tests/data/lexOperator.out
@@ -0,0 +1 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:12:"SELECT 1 + 2";s:3:"len";i:12;s:4:"last";i:12;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:8:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"+";s:5:"value";s:1:"+";s:4:"type";i:2;s:5:"flags";i:1;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:10;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"2";s:5:"value";i:2;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:11;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:8;s:3:"idx";i:0;}s:9:"delimiter";s:1:";";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/lexString.in b/tests/data/lexString.in
new file mode 100644
index 0000000..1aa9f71
--- /dev/null
+++ b/tests/data/lexString.in
@@ -0,0 +1 @@
+SELECT 'foo', "bar", "foo\\ bar" \ No newline at end of file
diff --git a/tests/data/lexString.out b/tests/data/lexString.out
new file mode 100644
index 0000000..9eff57b
--- /dev/null
+++ b/tests/data/lexString.out
@@ -0,0 +1 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:32:"SELECT 'foo', "bar", "foo\\ bar"";s:3:"len";i:32;s:4:"last";i:32;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:10:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"'foo'";s:5:"value";s:3:"foo";s:4:"type";i:7;s:5:"flags";i:1;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:12;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:13;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:5:""bar"";s:5:"value";s:3:"bar";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:14;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:19;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:20;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:11:""foo\\ bar"";s:5:"value";s:9:"foo\\ bar";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:21;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:10;s:3:"idx";i:0;}s:9:"delimiter";s:1:";";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/lexStringErr1.in b/tests/data/lexStringErr1.in
new file mode 100644
index 0000000..861a0b8
--- /dev/null
+++ b/tests/data/lexStringErr1.in
@@ -0,0 +1 @@
+SELECT 'foo', "bar", "foo\\ bar \ No newline at end of file
diff --git a/tests/data/lexStringErr1.out b/tests/data/lexStringErr1.out
new file mode 100644
index 0000000..eef71f2
--- /dev/null
+++ b/tests/data/lexStringErr1.out
Binary files differ
diff --git a/tests/data/lexSymbol.in b/tests/data/lexSymbol.in
new file mode 100644
index 0000000..c70205a
--- /dev/null
+++ b/tests/data/lexSymbol.in
@@ -0,0 +1,2 @@
+SET @idx := 1;
+SELECT @idx, @`idx`, @'idx' \ No newline at end of file
diff --git a/tests/data/lexSymbol.out b/tests/data/lexSymbol.out
new file mode 100644
index 0000000..ad2c523
--- /dev/null
+++ b/tests/data/lexSymbol.out
@@ -0,0 +1,3 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:42:"SET @idx := 1;
+SELECT @idx, @`idx`, @'idx'";s:3:"len";i:42;s:4:"last";i:42;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:19:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"SET";s:5:"value";s:3:"SET";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:3;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"@idx";s:5:"value";s:3:"idx";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:4;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:2:":=";s:5:"value";s:2:":=";s:4:"type";i:2;s:5:"flags";i:8;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:11;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:12;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:13;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:14;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:15;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:21;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"@idx";s:5:"value";s:3:"idx";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:22;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:26;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:27;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"@`idx`";s:5:"value";s:3:"idx";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:28;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:34;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:35;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"@'idx'";s:5:"value";s:5:"'idx'";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:36;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:19;s:3:"idx";i:0;}s:9:"delimiter";s:1:";";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/lexSymbolErr1.in b/tests/data/lexSymbolErr1.in
new file mode 100644
index 0000000..f61f8ad
--- /dev/null
+++ b/tests/data/lexSymbolErr1.in
@@ -0,0 +1,2 @@
+SET @idx := 1;
+SELECT @idx, @`idx`, @'idx \ No newline at end of file
diff --git a/tests/data/lexSymbolErr1.out b/tests/data/lexSymbolErr1.out
new file mode 100644
index 0000000..bd34306
--- /dev/null
+++ b/tests/data/lexSymbolErr1.out
Binary files differ
diff --git a/tests/data/lexSymbolErr2.in b/tests/data/lexSymbolErr2.in
new file mode 100644
index 0000000..ceb51ab
--- /dev/null
+++ b/tests/data/lexSymbolErr2.in
@@ -0,0 +1,2 @@
+SET @idx := 1;
+SELECT @idx, @`idx`, @ \ No newline at end of file
diff --git a/tests/data/lexSymbolErr2.out b/tests/data/lexSymbolErr2.out
new file mode 100644
index 0000000..fc40ba2
--- /dev/null
+++ b/tests/data/lexSymbolErr2.out
Binary files differ
diff --git a/tests/data/lexSymbolErr3.in b/tests/data/lexSymbolErr3.in
new file mode 100644
index 0000000..20bddac
--- /dev/null
+++ b/tests/data/lexSymbolErr3.in
@@ -0,0 +1 @@
+SELECT `idx \ No newline at end of file
diff --git a/tests/data/lexSymbolErr3.out b/tests/data/lexSymbolErr3.out
new file mode 100644
index 0000000..7dba4e6
--- /dev/null
+++ b/tests/data/lexSymbolErr3.out
Binary files differ
diff --git a/tests/data/lexWhitespace.in b/tests/data/lexWhitespace.in
new file mode 100644
index 0000000..a66a814
--- /dev/null
+++ b/tests/data/lexWhitespace.in
@@ -0,0 +1,10 @@
+
+
+
+SELECT
+ 'w h i t e s p a c e'
+
+
+
+
+ \ No newline at end of file
diff --git a/tests/data/lexWhitespace.out b/tests/data/lexWhitespace.out
new file mode 100644
index 0000000..adc713c
--- /dev/null
+++ b/tests/data/lexWhitespace.out
@@ -0,0 +1,19 @@
+O:15:"SqlParser\Lexer":7:{s:6:"strict";b:0;s:3:"str";s:79:"
+
+
+SELECT
+ 'w h i t e s p a c e'
+
+
+
+
+ ";s:3:"len";i:79;s:4:"last";i:79;s:6:"tokens";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:6:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"
+
+
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:3;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:9;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:22:"'w h i t e s p a c e'";s:5:"value";s:20:"w h i t e s p a c e";s:4:"type";i:7;s:5:"flags";i:1;s:8:"position";i:18;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:39:"
+
+
+
+
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:40;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:6;s:3:"idx";i:0;}s:9:"delimiter";s:1:";";s:6:"errors";a:0:{}} \ No newline at end of file
diff --git a/tests/data/parse.in b/tests/data/parse.in
new file mode 100644
index 0000000..027b7d6
--- /dev/null
+++ b/tests/data/parse.in
@@ -0,0 +1 @@
+SELECT 1; \ No newline at end of file
diff --git a/tests/data/parse.out b/tests/data/parse.out
new file mode 100644
index 0000000..7bf1795
--- /dev/null
+++ b/tests/data/parse.out
@@ -0,0 +1 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:5:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:5;s:3:"idx";i:5;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":11:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";N;s:6:"tokens";a:0:{}}s:4:"expr";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:1:"1";s:5:"alias";N;s:6:"tokens";a:1:{i:0;r:16;}}}s:4:"from";N;s:9:"partition";N;s:5:"where";N;s:5:"group";N;s:6:"having";N;s:5:"order";N;s:5:"limit";N;s:5:"first";N;s:4:"last";i:2;}}} \ No newline at end of file
diff --git a/tests/data/parseArrayErr1.in b/tests/data/parseArrayErr1.in
new file mode 100644
index 0000000..af9acdd
--- /dev/null
+++ b/tests/data/parseArrayErr1.in
@@ -0,0 +1 @@
+SELECT * FROM foo PARTITION bar, baz); \ No newline at end of file
diff --git a/tests/data/parseArrayErr1.out b/tests/data/parseArrayErr1.out
new file mode 100644
index 0000000..df17613
--- /dev/null
+++ b/tests/data/parseArrayErr1.out
Binary files differ
diff --git a/tests/data/parseArrayErr2.in b/tests/data/parseArrayErr2.in
new file mode 100644
index 0000000..b29b07a
--- /dev/null
+++ b/tests/data/parseArrayErr2.in
@@ -0,0 +1 @@
+SELECT * FROM foo PARTITION (bar baz) WHERE id > 0; \ No newline at end of file
diff --git a/tests/data/parseArrayErr2.out b/tests/data/parseArrayErr2.out
new file mode 100644
index 0000000..2b9fe09
--- /dev/null
+++ b/tests/data/parseArrayErr2.out
Binary files differ
diff --git a/tests/data/parseArrayErr3.in b/tests/data/parseArrayErr3.in
new file mode 100644
index 0000000..128a77a
--- /dev/null
+++ b/tests/data/parseArrayErr3.in
@@ -0,0 +1 @@
+SELECT * FROM foo PARTITION (bar, baz; \ No newline at end of file
diff --git a/tests/data/parseArrayErr3.out b/tests/data/parseArrayErr3.out
new file mode 100644
index 0000000..67bdcc6
--- /dev/null
+++ b/tests/data/parseArrayErr3.out
@@ -0,0 +1 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:17:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"*";s:5:"value";s:1:"*";s:4:"type";i:2;s:5:"flags";i:1;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:13;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:14;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:17;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"PARTITION";s:5:"value";s:9:"PARTITION";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:18;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:27;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:28;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"bar";s:5:"value";s:3:"bar";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:29;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:32;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:33;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"baz";s:5:"value";s:3:"baz";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:34;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:37;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:17;s:3:"idx";i:17;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":11:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";N;s:6:"tokens";a:0:{}}s:4:"expr";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:2:"* ";s:5:"alias";N;s:6:"tokens";a:2:{i:0;r:16;i:1;r:22;}}}s:4:"from";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";s:3:"foo";s:6:"column";N;s:4:"expr";s:3:"foo";s:5:"alias";N;s:6:"tokens";a:1:{i:0;r:40;}}}s:9:"partition";O:33:"SqlParser\Fragments\ArrayFragment":2:{s:5:"array";a:2:{i:0;s:3:"bar";i:1;s:3:"baz";}s:6:"tokens";a:2:{i:0;r:70;i:1;r:88;}}s:5:"where";N;s:5:"group";N;s:6:"having";N;s:5:"order";N;s:5:"limit";N;s:5:"first";N;s:4:"last";i:15;}}} \ No newline at end of file
diff --git a/tests/data/parseCall.in b/tests/data/parseCall.in
new file mode 100644
index 0000000..871e666
--- /dev/null
+++ b/tests/data/parseCall.in
@@ -0,0 +1 @@
+CALL foo(); \ No newline at end of file
diff --git a/tests/data/parseCall.out b/tests/data/parseCall.out
new file mode 100644
index 0000000..2eeeaac
--- /dev/null
+++ b/tests/data/parseCall.out
@@ -0,0 +1 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:7:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"CALL";s:5:"value";s:4:"CALL";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:4;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:5;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:10;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:7;s:3:"idx";i:7;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:34:"SqlParser\Statements\CallStatement":3:{s:4:"call";O:31:"SqlParser\Fragments\CallKeyword":3:{s:4:"name";s:3:"foo";s:10:"parameters";a:0:{}s:6:"tokens";a:1:{i:0;r:16;}}s:5:"first";N;s:4:"last";i:4;}}} \ No newline at end of file
diff --git a/tests/data/parseCall2.in b/tests/data/parseCall2.in
new file mode 100644
index 0000000..76fd802
--- /dev/null
+++ b/tests/data/parseCall2.in
@@ -0,0 +1 @@
+CALL foo(@bar, @baz); \ No newline at end of file
diff --git a/tests/data/parseCall2.out b/tests/data/parseCall2.out
new file mode 100644
index 0000000..a581e74
--- /dev/null
+++ b/tests/data/parseCall2.out
@@ -0,0 +1 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:11:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"CALL";s:5:"value";s:4:"CALL";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:4;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:5;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"@bar";s:5:"value";s:3:"bar";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:13;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:14;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"@baz";s:5:"value";s:3:"baz";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:15;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:19;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:20;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:11;s:3:"idx";i:11;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:34:"SqlParser\Statements\CallStatement":3:{s:4:"call";O:31:"SqlParser\Fragments\CallKeyword":3:{s:4:"name";s:3:"foo";s:10:"parameters";a:2:{i:0;s:3:"bar";i:1;s:3:"baz";}s:6:"tokens";a:3:{i:0;r:16;i:1;r:28;i:2;r:46;}}s:5:"first";N;s:4:"last";i:8;}}} \ No newline at end of file
diff --git a/tests/data/parseCreateFunction.in b/tests/data/parseCreateFunction.in
new file mode 100644
index 0000000..95ce5f6
--- /dev/null
+++ b/tests/data/parseCreateFunction.in
@@ -0,0 +1,6 @@
+CREATE FUNCTION F_TEST(uid INT) RETURNS VARCHAR
+BEGIN
+ DECLARE username VARCHAR DEFAULT "";
+ SELECT username INTO username FROM users WHERE ID = uid;
+ RETURN username;
+END \ No newline at end of file
diff --git a/tests/data/parseCreateFunction.out b/tests/data/parseCreateFunction.out
new file mode 100644
index 0000000..44dab6c
--- /dev/null
+++ b/tests/data/parseCreateFunction.out
@@ -0,0 +1,6 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:56:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"CREATE";s:5:"value";s:6:"CREATE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"FUNCTION";s:5:"value";s:8:"FUNCTION";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:15;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"F_TEST";s:5:"value";s:6:"F_TEST";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:16;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:22;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"uid";s:5:"value";s:3:"uid";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:23;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:26;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"INT";s:5:"value";s:3:"INT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:27;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:30;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:31;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"RETURNS";s:5:"value";s:7:"RETURNS";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:32;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:39;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"VARCHAR";s:5:"value";s:7:"VARCHAR";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:40;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"BEGIN";s:5:"value";s:5:"BEGIN";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:48;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:53;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"DECLARE";s:5:"value";s:7:"DECLARE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:58;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:65;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:66;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:74;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"VARCHAR";s:5:"value";s:7:"VARCHAR";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:75;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:82;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"DEFAULT";s:5:"value";s:7:"DEFAULT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:83;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:90;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"""";s:5:"value";s:0:"";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:91;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:93;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:94;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:99;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:105;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:106;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:114;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"INTO";s:5:"value";s:4:"INTO";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:115;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:119;}i:34;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:120;}i:35;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:128;}i:36;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:129;}i:37;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:133;}i:38;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:134;}i:39;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:139;}i:40;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:140;}i:41;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:145;}i:42;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"ID";s:5:"value";s:2:"ID";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:146;}i:43;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:148;}i:44;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:149;}i:45;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:150;}i:46;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"uid";s:5:"value";s:3:"uid";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:151;}i:47;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:154;}i:48;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:155;}i:49;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"RETURN";s:5:"value";s:6:"RETURN";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:160;}i:50;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:166;}i:51;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:167;}i:52;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:175;}i:53;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:176;}i:54;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"END";s:5:"value";s:3:"END";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:177;}i:55;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:56;s:3:"idx";i:56;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\CreateStatement":10:{s:4:"name";O:37:"SqlParser\Fragments\CreateDefFragment":2:{s:4:"name";s:6:"F_TEST";s:6:"tokens";a:1:{i:0;r:28;}}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;s:8:"FUNCTION";}s:6:"tokens";a:1:{i:0;r:16;}}s:12:"tableOptions";N;s:6:"fields";N;s:4:"body";a:40:{i:0;r:94;i:1;r:100;i:2;r:106;i:3;r:112;i:4;r:118;i:5;r:124;i:6;r:130;i:7;r:136;i:8;r:142;i:9;r:148;i:10;r:154;i:11;r:160;i:12;r:166;i:13;r:172;i:14;r:178;i:15;r:184;i:16;r:190;i:17;r:196;i:18;r:202;i:19;r:208;i:20;r:214;i:21;r:220;i:22;r:226;i:23;r:232;i:24;r:238;i:25;r:244;i:26;r:250;i:27;r:256;i:28;r:262;i:29;r:268;i:30;r:274;i:31;r:280;i:32;r:286;i:33;r:292;i:34;r:298;i:35;r:304;i:36;r:310;i:37;r:316;i:38;r:322;i:39;r:328;}s:5:"first";N;s:4:"last";i:54;s:10:"parameters";a:1:{i:0;O:36:"SqlParser\Fragments\ParamDefFragment":4:{s:4:"name";s:3:"uid";s:5:"inOut";N;s:4:"type";O:36:"SqlParser\Fragments\DataTypeFragment":3:{s:4:"type";s:3:"INT";s:4:"size";N;s:6:"tokens";a:1:{i:0;r:52;}}s:6:"tokens";a:2:{i:0;r:40;i:1;r:52;}}}s:6:"return";O:36:"SqlParser\Fragments\DataTypeFragment":3:{s:4:"type";s:7:"VARCHAR";s:4:"size";N;s:6:"tokens";a:1:{i:0;r:82;}}s:11:"funcOptions";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";N;s:6:"tokens";a:0:{}}}}} \ No newline at end of file
diff --git a/tests/data/parseCreateFunctionErr1.in b/tests/data/parseCreateFunctionErr1.in
new file mode 100644
index 0000000..5678b4e
--- /dev/null
+++ b/tests/data/parseCreateFunctionErr1.in
@@ -0,0 +1,6 @@
+CREATE FUNCTION F_TEST(uid INT)
+BEGIN
+ DECLARE username VARCHAR DEFAULT "";
+ SELECT username INTO username FROM users WHERE ID = uid;
+ RETURN username;
+END \ No newline at end of file
diff --git a/tests/data/parseCreateFunctionErr1.out b/tests/data/parseCreateFunctionErr1.out
new file mode 100644
index 0000000..7e1c534
--- /dev/null
+++ b/tests/data/parseCreateFunctionErr1.out
Binary files differ
diff --git a/tests/data/parseCreateProcedure.in b/tests/data/parseCreateProcedure.in
new file mode 100644
index 0000000..b5e35c1
--- /dev/null
+++ b/tests/data/parseCreateProcedure.in
@@ -0,0 +1,4 @@
+CREATE PROCEDURE P_TEST(uid INT)
+BEGIN
+ SELECT username FROM users WHERE ID = uid;
+END \ No newline at end of file
diff --git a/tests/data/parseCreateProcedure.out b/tests/data/parseCreateProcedure.out
new file mode 100644
index 0000000..86d7eb9
--- /dev/null
+++ b/tests/data/parseCreateProcedure.out
@@ -0,0 +1,4 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:32:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"CREATE";s:5:"value";s:6:"CREATE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"PROCEDURE";s:5:"value";s:9:"PROCEDURE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:16;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"P_TEST";s:5:"value";s:6:"P_TEST";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:17;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:23;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"uid";s:5:"value";s:3:"uid";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:24;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:27;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"INT";s:5:"value";s:3:"INT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:28;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:31;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:32;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"BEGIN";s:5:"value";s:5:"BEGIN";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:33;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:38;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:43;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:49;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:50;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:58;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:59;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:63;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:64;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:69;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:70;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:75;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"ID";s:5:"value";s:2:"ID";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:76;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:78;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:79;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:80;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"uid";s:5:"value";s:3:"uid";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:81;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:84;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:85;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"END";s:5:"value";s:3:"END";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:86;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:32;s:3:"idx";i:32;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\CreateStatement":9:{s:4:"name";O:37:"SqlParser\Fragments\CreateDefFragment":2:{s:4:"name";s:6:"P_TEST";s:6:"tokens";a:1:{i:0;r:28;}}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;s:9:"PROCEDURE";}s:6:"tokens";a:1:{i:0;r:16;}}s:12:"tableOptions";N;s:6:"fields";N;s:4:"body";a:20:{i:0;r:70;i:1;r:76;i:2;r:82;i:3;r:88;i:4;r:94;i:5;r:100;i:6;r:106;i:7;r:112;i:8;r:118;i:9;r:124;i:10;r:130;i:11;r:136;i:12;r:142;i:13;r:148;i:14;r:154;i:15;r:160;i:16;r:166;i:17;r:172;i:18;r:178;i:19;r:184;}s:5:"first";N;s:4:"last";i:30;s:10:"parameters";a:1:{i:0;O:36:"SqlParser\Fragments\ParamDefFragment":4:{s:4:"name";s:3:"uid";s:5:"inOut";N;s:4:"type";O:36:"SqlParser\Fragments\DataTypeFragment":3:{s:4:"type";s:3:"INT";s:4:"size";N;s:6:"tokens";a:1:{i:0;r:52;}}s:6:"tokens";a:2:{i:0;r:40;i:1;r:52;}}}s:11:"funcOptions";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";N;s:6:"tokens";a:0:{}}}}} \ No newline at end of file
diff --git a/tests/data/parseCreateProcedure2.in b/tests/data/parseCreateProcedure2.in
new file mode 100644
index 0000000..e24b92e
--- /dev/null
+++ b/tests/data/parseCreateProcedure2.in
@@ -0,0 +1,4 @@
+CREATE PROCEDURE P_TEST(IN uid INT, IN unused VARCHAR)
+BEGIN
+ SELECT username FROM users WHERE ID = uid;
+END \ No newline at end of file
diff --git a/tests/data/parseCreateProcedure2.out b/tests/data/parseCreateProcedure2.out
new file mode 100644
index 0000000..0a3df50
--- /dev/null
+++ b/tests/data/parseCreateProcedure2.out
@@ -0,0 +1,4 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:41:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"CREATE";s:5:"value";s:6:"CREATE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"PROCEDURE";s:5:"value";s:9:"PROCEDURE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:16;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"P_TEST";s:5:"value";s:6:"P_TEST";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:17;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:23;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"IN";s:5:"value";s:2:"IN";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:24;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:26;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"uid";s:5:"value";s:3:"uid";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:27;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:30;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"INT";s:5:"value";s:3:"INT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:31;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:34;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:35;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"IN";s:5:"value";s:2:"IN";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:36;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:38;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"unused";s:5:"value";s:6:"unused";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:39;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:45;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"VARCHAR";s:5:"value";s:7:"VARCHAR";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:46;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:53;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:54;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"BEGIN";s:5:"value";s:5:"BEGIN";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:55;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:60;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:65;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:71;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:72;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:80;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:81;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:85;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:86;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:91;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:92;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:97;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"ID";s:5:"value";s:2:"ID";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:98;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:100;}i:34;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:101;}i:35;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:102;}i:36;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"uid";s:5:"value";s:3:"uid";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:103;}i:37;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:106;}i:38;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:107;}i:39;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"END";s:5:"value";s:3:"END";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:108;}i:40;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:41;s:3:"idx";i:41;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\CreateStatement":9:{s:4:"name";O:37:"SqlParser\Fragments\CreateDefFragment":2:{s:4:"name";s:6:"P_TEST";s:6:"tokens";a:1:{i:0;r:28;}}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;s:9:"PROCEDURE";}s:6:"tokens";a:1:{i:0;r:16;}}s:12:"tableOptions";N;s:6:"fields";N;s:4:"body";a:20:{i:0;r:124;i:1;r:130;i:2;r:136;i:3;r:142;i:4;r:148;i:5;r:154;i:6;r:160;i:7;r:166;i:8;r:172;i:9;r:178;i:10;r:184;i:11;r:190;i:12;r:196;i:13;r:202;i:14;r:208;i:15;r:214;i:16;r:220;i:17;r:226;i:18;r:232;i:19;r:238;}s:5:"first";N;s:4:"last";i:39;s:10:"parameters";a:2:{i:0;O:36:"SqlParser\Fragments\ParamDefFragment":4:{s:4:"name";s:3:"uid";s:5:"inOut";s:2:"IN";s:4:"type";O:36:"SqlParser\Fragments\DataTypeFragment":3:{s:4:"type";s:3:"INT";s:4:"size";N;s:6:"tokens";a:1:{i:0;r:64;}}s:6:"tokens";a:3:{i:0;r:40;i:1;r:52;i:2;r:64;}}i:1;O:36:"SqlParser\Fragments\ParamDefFragment":4:{s:4:"name";s:6:"unused";s:5:"inOut";s:2:"IN";s:4:"type";O:36:"SqlParser\Fragments\DataTypeFragment":3:{s:4:"type";s:7:"VARCHAR";s:4:"size";N;s:6:"tokens";a:1:{i:0;r:106;}}s:6:"tokens";a:3:{i:0;r:82;i:1;r:94;i:2;r:106;}}}s:11:"funcOptions";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";N;s:6:"tokens";a:0:{}}}}} \ No newline at end of file
diff --git a/tests/data/parseCreateTable.in b/tests/data/parseCreateTable.in
new file mode 100644
index 0000000..e9f0911
--- /dev/null
+++ b/tests/data/parseCreateTable.in
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS users (
+ `id` INT NOT NULL AUTO_INCREMENT,
+ username VARCHAR(64) NULL,
+ `password` VARCHAR(256) DEFAULT '123456',
+ CONSTRAINT pk_id PRIMARY KEY (`id`),
+ UNIQUE (username)
+) ENGINE=InnoDB; \ No newline at end of file
diff --git a/tests/data/parseCreateTable.out b/tests/data/parseCreateTable.out
new file mode 100644
index 0000000..5708ee1
--- /dev/null
+++ b/tests/data/parseCreateTable.out
@@ -0,0 +1,7 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:65:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"CREATE";s:5:"value";s:6:"CREATE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"TABLE";s:5:"value";s:5:"TABLE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:12;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:13:"IF NOT EXISTS";s:5:"value";s:13:"IF NOT EXISTS";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:13;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:26;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:27;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:32;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:33;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:34;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"`id`";s:5:"value";s:2:"id";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:39;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:43;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"INT";s:5:"value";s:3:"INT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:44;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"NOT NULL";s:5:"value";s:8:"NOT NULL";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:48;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:56;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:14:"AUTO_INCREMENT";s:5:"value";s:14:"AUTO_INCREMENT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:57;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:71;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:72;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:77;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:85;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"VARCHAR";s:5:"value";s:7:"VARCHAR";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:86;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:93;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"64";s:5:"value";i:64;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:94;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:96;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:97;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"NULL";s:5:"value";s:4:"NULL";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:98;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:102;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:103;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:10:"`password`";s:5:"value";s:8:"password";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:108;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:118;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"VARCHAR";s:5:"value";s:7:"VARCHAR";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:119;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:126;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"256";s:5:"value";i:256;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:127;}i:34;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:130;}i:35;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:131;}i:36;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"DEFAULT";s:5:"value";s:7:"DEFAULT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:132;}i:37;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:139;}i:38;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"'123456'";s:5:"value";s:6:"123456";s:4:"type";i:7;s:5:"flags";i:1;s:8:"position";i:140;}i:39;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:148;}i:40;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:149;}i:41;O:15:"SqlParser\Token":5:{s:5:"token";s:10:"CONSTRAINT";s:5:"value";s:10:"CONSTRAINT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:154;}i:42;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:164;}i:43;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"pk_id";s:5:"value";s:5:"pk_id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:165;}i:44;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:170;}i:45;O:15:"SqlParser\Token":5:{s:5:"token";s:11:"PRIMARY KEY";s:5:"value";s:11:"PRIMARY KEY";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:171;}i:46;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:182;}i:47;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:183;}i:48;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"`id`";s:5:"value";s:2:"id";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:184;}i:49;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:188;}i:50;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:189;}i:51;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:190;}i:52;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"UNIQUE";s:5:"value";s:6:"UNIQUE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:195;}i:53;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:201;}i:54;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:202;}i:55;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:203;}i:56;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:211;}i:57;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:212;}i:58;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:213;}i:59;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:214;}i:60;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"ENGINE";s:5:"value";s:6:"ENGINE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:215;}i:61;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:221;}i:62;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"InnoDB";s:5:"value";s:6:"InnoDB";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:222;}i:63;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:228;}i:64;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:65;s:3:"idx";i:65;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\CreateStatement":7:{s:4:"name";O:37:"SqlParser\Fragments\CreateDefFragment":2:{s:4:"name";s:5:"users";s:6:"tokens";a:1:{i:0;r:40;}}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:2:{i:1;s:5:"TABLE";i:3;s:13:"IF NOT EXISTS";}s:6:"tokens";a:2:{i:0;r:16;i:1;r:28;}}s:12:"tableOptions";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;a:2:{s:4:"name";s:6:"ENGINE";s:5:"value";s:6:"InnoDB";}}s:6:"tokens";a:3:{i:0;r:364;i:1;r:370;i:2;r:376;}}s:6:"fields";a:5:{i:0;O:36:"SqlParser\Fragments\FieldDefFragment":5:{s:4:"name";s:2:"id";s:4:"type";O:36:"SqlParser\Fragments\DataTypeFragment":3:{s:4:"type";s:3:"INT";s:4:"size";N;s:6:"tokens";a:1:{i:0;r:76;}}s:7:"indexes";a:0:{}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:2:{i:1;s:8:"NOT NULL";i:3;s:14:"AUTO_INCREMENT";}s:6:"tokens";a:2:{i:0;r:88;i:1;r:100;}}s:6:"tokens";a:3:{i:0;r:64;i:1;r:76;i:2;r:88;}}i:1;O:36:"SqlParser\Fragments\FieldDefFragment":5:{s:4:"name";s:8:"username";s:4:"type";O:36:"SqlParser\Fragments\DataTypeFragment":3:{s:4:"type";s:7:"VARCHAR";s:4:"size";a:1:{i:0;i:64;}s:6:"tokens";a:2:{i:0;r:130;i:1;r:142;}}s:7:"indexes";a:0:{}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;s:4:"NULL";}s:6:"tokens";a:1:{i:0;r:160;}}s:6:"tokens";a:3:{i:0;r:118;i:1;r:130;i:2;r:160;}}i:2;O:36:"SqlParser\Fragments\FieldDefFragment":5:{s:4:"name";s:8:"password";s:4:"type";O:36:"SqlParser\Fragments\DataTypeFragment":3:{s:4:"type";s:7:"VARCHAR";s:4:"size";a:1:{i:0;i:256;}s:6:"tokens";a:2:{i:0;r:190;i:1;r:202;}}s:7:"indexes";a:0:{}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:2;a:2:{s:4:"name";s:7:"DEFAULT";s:5:"value";s:6:"123456";}}s:6:"tokens";a:2:{i:0;r:220;i:1;r:232;}}s:6:"tokens";a:3:{i:0;r:178;i:1;r:190;i:2;r:220;}}i:3;O:36:"SqlParser\Fragments\FieldDefFragment":5:{s:4:"name";s:5:"pk_id";s:4:"type";s:11:"PRIMARY KEY";s:7:"indexes";O:33:"SqlParser\Fragments\ArrayFragment":2:{s:5:"array";a:1:{i:0;s:2:"id";}s:6:"tokens";a:1:{i:0;r:292;}}s:7:"options";N;s:6:"tokens";a:4:{i:0;r:250;i:1;r:262;i:2;r:274;i:3;r:286;}}i:4;O:36:"SqlParser\Fragments\FieldDefFragment":5:{s:4:"name";N;s:4:"type";s:6:"UNIQUE";s:7:"indexes";O:33:"SqlParser\Fragments\ArrayFragment":2:{s:5:"array";a:1:{i:0;s:8:"username";}s:6:"tokens";a:1:{i:0;r:334;}}s:7:"options";N;s:6:"tokens";a:2:{i:0;r:316;i:1;r:328;}}}s:4:"body";N;s:5:"first";N;s:4:"last";i:62;}}} \ No newline at end of file
diff --git a/tests/data/parseDelete.in b/tests/data/parseDelete.in
new file mode 100644
index 0000000..36aaf2d
--- /dev/null
+++ b/tests/data/parseDelete.in
@@ -0,0 +1,7 @@
+DELETE LOW_PRIORITY
+FROM
+ `test`.users
+WHERE
+ `id`<3 AND (username="Dan" or username="Paul")
+ORDER BY
+ id; \ No newline at end of file
diff --git a/tests/data/parseDelete.out b/tests/data/parseDelete.out
new file mode 100644
index 0000000..9f4f7af
--- /dev/null
+++ b/tests/data/parseDelete.out
@@ -0,0 +1,7 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:37:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"DELETE";s:5:"value";s:6:"DELETE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:12:"LOW_PRIORITY";s:5:"value";s:12:"LOW_PRIORITY";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:19;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:20;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:24;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"`test`";s:5:"value";s:4:"test";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:29;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:".";s:5:"value";s:1:".";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:35;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:36;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:41;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:42;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"`id`";s:5:"value";s:2:"id";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:52;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"<";s:5:"value";s:1:"<";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:56;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"3";s:5:"value";i:3;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:57;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:58;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"AND";s:5:"value";s:3:"AND";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:59;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:62;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:63;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:64;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:72;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:5:""Dan"";s:5:"value";s:3:"Dan";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:73;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:78;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"or";s:5:"value";s:2:"OR";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:79;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:81;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:82;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:90;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:6:""Paul"";s:5:"value";s:4:"Paul";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:91;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:97;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:98;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"ORDER";s:5:"value";s:5:"ORDER";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:99;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:104;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"BY";s:5:"value";s:2:"BY";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:105;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:107;}i:34;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:112;}i:35;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:114;}i:36;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:37;s:3:"idx";i:37;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\DeleteStatement":8:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;s:12:"LOW_PRIORITY";}s:6:"tokens";a:1:{i:0;r:16;}}s:4:"from";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";s:4:"test";s:5:"table";s:5:"users";s:6:"column";N;s:4:"expr";s:12:"`test`.users";s:5:"alias";N;s:6:"tokens";a:3:{i:0;r:40;i:1;r:46;i:2;r:52;}}}s:9:"partition";N;s:5:"where";a:7:{i:0;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:0;s:9:"condition";s:6:"`id`<3";s:6:"tokens";a:3:{i:0;r:76;i:1;r:82;i:2;r:88;}}i:1;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:1;s:9:"condition";s:3:"AND";s:6:"tokens";a:0:{}}i:2;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:1;s:9:"condition";s:1:"(";s:6:"tokens";a:0:{}}i:3;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:0;s:9:"condition";s:14:"username="Dan"";s:6:"tokens";a:3:{i:0;r:118;i:1;r:124;i:2;r:130;}}i:4;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:1;s:9:"condition";s:2:"OR";s:6:"tokens";a:0:{}}i:5;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:0;s:9:"condition";s:15:"username="Paul"";s:6:"tokens";a:3:{i:0;r:154;i:1;r:160;i:2;r:166;}}i:6;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:1;s:9:"condition";s:1:")";s:6:"tokens";a:0:{}}}s:5:"order";a:1:{i:0;O:32:"SqlParser\Fragments\OrderKeyword":3:{s:6:"column";s:2:"id";s:4:"type";s:3:"ASC";s:6:"tokens";a:1:{i:0;r:208;}}}s:5:"limit";N;s:5:"first";N;s:4:"last";i:34;}}} \ No newline at end of file
diff --git a/tests/data/parseInsert.in b/tests/data/parseInsert.in
new file mode 100644
index 0000000..4f200e2
--- /dev/null
+++ b/tests/data/parseInsert.in
@@ -0,0 +1,6 @@
+INSERT LOW_PRIORITY
+INTO
+ users(`id`, `username`, `password`)
+VALUES
+ (1, "Dan", "5d41402abc4b2a76b9719d911017c592"),
+ (2, "Paul", "7d793037a0760186574b0282f2f435e7"); \ No newline at end of file
diff --git a/tests/data/parseInsert.out b/tests/data/parseInsert.out
new file mode 100644
index 0000000..5e8cbac
--- /dev/null
+++ b/tests/data/parseInsert.out
@@ -0,0 +1,6 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:41:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"INSERT";s:5:"value";s:6:"INSERT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:12:"LOW_PRIORITY";s:5:"value";s:12:"LOW_PRIORITY";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:19;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"INTO";s:5:"value";s:4:"INTO";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:20;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:24;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:29;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:34;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"`id`";s:5:"value";s:2:"id";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:35;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:39;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:40;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:10:"`username`";s:5:"value";s:8:"username";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:41;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:51;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:52;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:10:"`password`";s:5:"value";s:8:"password";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:53;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:63;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:64;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"VALUES";s:5:"value";s:6:"VALUES";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:65;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:71;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:76;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:77;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:78;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:79;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:5:""Dan"";s:5:"value";s:3:"Dan";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:80;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:85;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:86;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:34:""5d41402abc4b2a76b9719d911017c592"";s:5:"value";s:32:"5d41402abc4b2a76b9719d911017c592";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:87;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:121;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:122;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:123;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:128;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"2";s:5:"value";i:2;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:129;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:130;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:131;}i:34;O:15:"SqlParser\Token":5:{s:5:"token";s:6:""Paul"";s:5:"value";s:4:"Paul";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:132;}i:35;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:138;}i:36;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:139;}i:37;O:15:"SqlParser\Token":5:{s:5:"token";s:34:""7d793037a0760186574b0282f2f435e7"";s:5:"value";s:32:"7d793037a0760186574b0282f2f435e7";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:140;}i:38;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:174;}i:39;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:175;}i:40;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:41;s:3:"idx";i:41;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\InsertStatement":5:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;s:12:"LOW_PRIORITY";}s:6:"tokens";a:1:{i:0;r:16;}}s:4:"into";O:31:"SqlParser\Fragments\IntoKeyword":3:{s:5:"table";s:5:"users";s:6:"fields";a:3:{i:0;s:2:"id";i:1;s:8:"username";i:2;s:8:"password";}s:6:"tokens";a:4:{i:0;r:40;i:1;r:52;i:2;r:70;i:3;r:88;}}s:6:"values";a:2:{i:0;O:33:"SqlParser\Fragments\ValuesKeyword":2:{s:6:"values";a:3:{i:0;s:1:"1";i:1;s:3:"Dan";i:2;s:32:"5d41402abc4b2a76b9719d911017c592";}s:6:"tokens";a:3:{i:0;r:124;i:1;r:142;i:2;r:160;}}i:1;O:33:"SqlParser\Fragments\ValuesKeyword":2:{s:6:"values";a:3:{i:0;s:1:"2";i:1;s:4:"Paul";i:2;s:32:"7d793037a0760186574b0282f2f435e7";}s:6:"tokens";a:3:{i:0;r:190;i:1;r:208;i:2;r:226;}}}s:5:"first";N;s:4:"last";i:38;}}} \ No newline at end of file
diff --git a/tests/data/parseRename.in b/tests/data/parseRename.in
new file mode 100644
index 0000000..84ba79d
--- /dev/null
+++ b/tests/data/parseRename.in
@@ -0,0 +1 @@
+RENAME TABLE foo TO bar; \ No newline at end of file
diff --git a/tests/data/parseRename.out b/tests/data/parseRename.out
new file mode 100644
index 0000000..b15f045
--- /dev/null
+++ b/tests/data/parseRename.out
@@ -0,0 +1 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:11:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"RENAME";s:5:"value";s:6:"RENAME";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"TABLE";s:5:"value";s:5:"TABLE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:12;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:13;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:16;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"TO";s:5:"value";s:2:"TO";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:17;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:19;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"bar";s:5:"value";s:3:"bar";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:20;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:23;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:11;s:3:"idx";i:11;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\RenameStatement":3:{s:7:"renames";a:1:{i:0;O:33:"SqlParser\Fragments\RenameKeyword":3:{s:3:"old";s:3:"foo";s:3:"new";s:3:"bar";s:6:"tokens";a:2:{i:0;r:28;i:1;r:52;}}}s:5:"first";N;s:4:"last";i:8;}}} \ No newline at end of file
diff --git a/tests/data/parseRename2.in b/tests/data/parseRename2.in
new file mode 100644
index 0000000..7adb641
--- /dev/null
+++ b/tests/data/parseRename2.in
@@ -0,0 +1 @@
+RENAME TABLE foo TO bar, baz TO qux; \ No newline at end of file
diff --git a/tests/data/parseRename2.out b/tests/data/parseRename2.out
new file mode 100644
index 0000000..86055cc
--- /dev/null
+++ b/tests/data/parseRename2.out
@@ -0,0 +1 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:18:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"RENAME";s:5:"value";s:6:"RENAME";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"TABLE";s:5:"value";s:5:"TABLE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:12;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:13;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:16;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"TO";s:5:"value";s:2:"TO";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:17;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:19;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"bar";s:5:"value";s:3:"bar";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:20;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:23;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:24;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"baz";s:5:"value";s:3:"baz";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:25;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:28;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"TO";s:5:"value";s:2:"TO";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:29;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:31;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"qux";s:5:"value";s:3:"qux";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:32;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:35;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:18;s:3:"idx";i:18;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\RenameStatement":3:{s:7:"renames";a:2:{i:0;O:33:"SqlParser\Fragments\RenameKeyword":3:{s:3:"old";s:3:"foo";s:3:"new";s:3:"bar";s:6:"tokens";a:2:{i:0;r:28;i:1;r:52;}}i:1;O:33:"SqlParser\Fragments\RenameKeyword":3:{s:3:"old";s:3:"baz";s:3:"new";s:3:"qux";s:6:"tokens";a:2:{i:0;r:70;i:1;r:94;}}}s:5:"first";N;s:4:"last";i:15;}}} \ No newline at end of file
diff --git a/tests/data/parseReplace.in b/tests/data/parseReplace.in
new file mode 100644
index 0000000..e2511b6
--- /dev/null
+++ b/tests/data/parseReplace.in
@@ -0,0 +1,3 @@
+REPLACE LOW_PRIORITY
+INTO users(id, username)
+VALUES (1, 'Foo'), (2, 'Bar') \ No newline at end of file
diff --git a/tests/data/parseReplace.out b/tests/data/parseReplace.out
new file mode 100644
index 0000000..600303f
--- /dev/null
+++ b/tests/data/parseReplace.out
@@ -0,0 +1,3 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:31:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"REPLACE";s:5:"value";s:7:"REPLACE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:7;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:12:"LOW_PRIORITY";s:5:"value";s:12:"LOW_PRIORITY";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:8;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:20;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"INTO";s:5:"value";s:4:"INTO";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:21;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:25;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:26;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:31;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:32;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:34;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:35;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:36;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:44;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:45;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"VALUES";s:5:"value";s:6:"VALUES";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:46;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:52;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:53;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:54;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:55;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:56;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"'Foo'";s:5:"value";s:3:"Foo";s:4:"type";i:7;s:5:"flags";i:1;s:8:"position";i:57;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:62;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:63;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:64;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:65;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"2";s:5:"value";i:2;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:66;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:67;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:68;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"'Bar'";s:5:"value";s:3:"Bar";s:4:"type";i:7;s:5:"flags";i:1;s:8:"position";i:69;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:74;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:31;s:3:"idx";i:31;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:37:"SqlParser\Statements\ReplaceStatement":6:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;s:12:"LOW_PRIORITY";}s:6:"tokens";a:1:{i:0;r:16;}}s:4:"into";O:31:"SqlParser\Fragments\IntoKeyword":3:{s:5:"table";s:5:"users";s:6:"fields";a:2:{i:0;s:2:"id";i:1;s:8:"username";}s:6:"tokens";a:3:{i:0;r:40;i:1;r:52;i:2;r:70;}}s:6:"values";a:2:{i:0;O:33:"SqlParser\Fragments\ValuesKeyword":2:{s:6:"values";a:2:{i:0;s:1:"1";i:1;s:3:"Foo";}s:6:"tokens";a:2:{i:0;r:106;i:1;r:124;}}i:1;O:33:"SqlParser\Fragments\ValuesKeyword":2:{s:6:"values";a:2:{i:0;s:1:"2";i:1;s:3:"Bar";}s:6:"tokens";a:2:{i:0;r:154;i:1;r:172;}}}s:3:"set";N;s:5:"first";N;s:4:"last";i:29;}}} \ No newline at end of file
diff --git a/tests/data/parseReplace2.in b/tests/data/parseReplace2.in
new file mode 100644
index 0000000..505c14f
--- /dev/null
+++ b/tests/data/parseReplace2.in
@@ -0,0 +1,4 @@
+REPLACE LOW_PRIORITY
+INTO users
+SET id = 1,
+ username = 'Bar'; \ No newline at end of file
diff --git a/tests/data/parseReplace2.out b/tests/data/parseReplace2.out
new file mode 100644
index 0000000..330275f
--- /dev/null
+++ b/tests/data/parseReplace2.out
@@ -0,0 +1,4 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:24:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"REPLACE";s:5:"value";s:7:"REPLACE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:7;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:12:"LOW_PRIORITY";s:5:"value";s:12:"LOW_PRIORITY";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:8;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:20;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"INTO";s:5:"value";s:4:"INTO";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:21;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:25;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:26;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:31;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"SET";s:5:"value";s:3:"SET";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:32;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:35;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:36;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:38;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:39;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:40;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:41;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:42;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:43;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:48;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:56;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:57;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:58;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"'Bar'";s:5:"value";s:3:"Bar";s:4:"type";i:7;s:5:"flags";i:1;s:8:"position";i:59;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:64;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:24;s:3:"idx";i:24;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:37:"SqlParser\Statements\ReplaceStatement":6:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:1:{i:1;s:12:"LOW_PRIORITY";}s:6:"tokens";a:1:{i:0;r:16;}}s:4:"into";O:31:"SqlParser\Fragments\IntoKeyword":3:{s:5:"table";s:5:"users";s:6:"fields";N;s:6:"tokens";a:1:{i:0;r:40;}}s:6:"values";N;s:3:"set";a:2:{i:0;O:30:"SqlParser\Fragments\SetKeyword":3:{s:6:"column";s:2:"id";s:5:"value";i:1;s:6:"tokens";a:3:{i:0;r:64;i:1;r:76;i:2;r:88;}}i:1;O:30:"SqlParser\Fragments\SetKeyword":3:{s:6:"column";s:8:"username";s:5:"value";s:3:"Bar";s:6:"tokens";a:3:{i:0;r:106;i:1;r:118;i:2;r:130;}}}s:5:"first";N;s:4:"last";i:21;}}} \ No newline at end of file
diff --git a/tests/data/parseSelect.in b/tests/data/parseSelect.in
new file mode 100644
index 0000000..e4d786e
--- /dev/null
+++ b/tests/data/parseSelect.in
@@ -0,0 +1,14 @@
+SELECT ALL MAX_STATEMENT_TIME = 10
+ 1 + 2 AS result,
+ @idx,
+ id,
+ test.`users`.username AS `name`
+FROM
+ `test`.users, posts
+ PARTITION (p1, p2)
+WHERE
+ id > 0
+ORDER BY
+ username DESC,
+ id
+LIMIT 3 OFFSET 2; \ No newline at end of file
diff --git a/tests/data/parseSelect.out b/tests/data/parseSelect.out
new file mode 100644
index 0000000..d98e16b
--- /dev/null
+++ b/tests/data/parseSelect.out
@@ -0,0 +1,14 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:83:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"ALL";s:5:"value";s:3:"ALL";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:10;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:18:"MAX_STATEMENT_TIME";s:5:"value";s:18:"MAX_STATEMENT_TIME";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:11;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:29;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:30;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:31;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"10";s:5:"value";i:10;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:32;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:34;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:39;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:40;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"+";s:5:"value";s:1:"+";s:4:"type";i:2;s:5:"flags";i:1;s:8:"position";i:41;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:42;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"2";s:5:"value";i:2;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:43;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:44;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"AS";s:5:"value";s:2:"AS";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:45;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"result";s:5:"value";s:6:"result";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:48;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:54;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:55;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"@idx";s:5:"value";s:3:"idx";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:60;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:64;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:65;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:70;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:72;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:73;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"test";s:5:"value";s:4:"test";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:78;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:1:".";s:5:"value";s:1:".";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:82;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"`users`";s:5:"value";s:5:"users";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:83;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:1:".";s:5:"value";s:1:".";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:90;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:91;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:99;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"AS";s:5:"value";s:2:"AS";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:100;}i:34;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:102;}i:35;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"`name`";s:5:"value";s:4:"name";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:103;}i:36;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:109;}i:37;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:110;}i:38;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:114;}i:39;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"`test`";s:5:"value";s:4:"test";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:119;}i:40;O:15:"SqlParser\Token":5:{s:5:"token";s:1:".";s:5:"value";s:1:".";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:125;}i:41;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:126;}i:42;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:131;}i:43;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:132;}i:44;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"posts";s:5:"value";s:5:"posts";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:133;}i:45;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:138;}i:46;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"PARTITION";s:5:"value";s:9:"PARTITION";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:143;}i:47;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:152;}i:48;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:153;}i:49;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"p1";s:5:"value";s:2:"p1";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:154;}i:50;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:156;}i:51;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:157;}i:52;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"p2";s:5:"value";s:2:"p2";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:158;}i:53;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:160;}i:54;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:161;}i:55;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:162;}i:56;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:167;}i:57;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:172;}i:58;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:174;}i:59;O:15:"SqlParser\Token":5:{s:5:"token";s:1:">";s:5:"value";s:1:">";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:175;}i:60;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:176;}i:61;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"0";s:5:"value";i:0;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:177;}i:62;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:178;}i:63;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"ORDER";s:5:"value";s:5:"ORDER";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:179;}i:64;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:184;}i:65;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"BY";s:5:"value";s:2:"BY";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:185;}i:66;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:187;}i:67;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:192;}i:68;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:200;}i:69;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"DESC";s:5:"value";s:4:"DESC";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:201;}i:70;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:205;}i:71;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:206;}i:72;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:211;}i:73;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:213;}i:74;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"LIMIT";s:5:"value";s:5:"LIMIT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:214;}i:75;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:219;}i:76;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"3";s:5:"value";i:3;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:220;}i:77;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:221;}i:78;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"OFFSET";s:5:"value";s:6:"OFFSET";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:222;}i:79;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:228;}i:80;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"2";s:5:"value";i:2;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:229;}i:81;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:230;}i:82;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:83;s:3:"idx";i:83;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":11:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";a:2:{i:1;s:3:"ALL";i:3;a:2:{s:4:"name";s:18:"MAX_STATEMENT_TIME";s:5:"value";s:2:"10";}}s:6:"tokens";a:4:{i:0;r:16;i:1;r:28;i:2;r:40;i:3;r:52;}}s:4:"expr";a:4:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:6:"1 + 2 ";s:5:"alias";s:6:"result";s:6:"tokens";a:6:{i:0;r:64;i:1;r:70;i:2;r:76;i:3;r:82;i:4;r:88;i:5;r:94;}}i:1;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";N;s:6:"column";s:3:"idx";s:4:"expr";s:4:"@idx";s:5:"alias";N;s:6:"tokens";a:1:{i:0;r:130;}}i:2;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";N;s:6:"column";s:2:"id";s:4:"expr";s:2:"id";s:5:"alias";N;s:6:"tokens";a:1:{i:0;r:148;}}i:3;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";s:4:"test";s:5:"table";s:5:"users";s:6:"column";s:8:"username";s:4:"expr";s:21:"test.`users`.username";s:5:"alias";s:4:"name";s:6:"tokens";a:5:{i:0;r:166;i:1;r:172;i:2;r:178;i:3;r:184;i:4;r:190;}}}s:4:"from";a:2:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";s:4:"test";s:5:"table";s:5:"users";s:6:"column";N;s:4:"expr";s:12:"`test`.users";s:5:"alias";N;s:6:"tokens";a:3:{i:0;r:238;i:1;r:244;i:2;r:250;}}i:1;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";s:5:"posts";s:6:"column";N;s:4:"expr";s:5:"posts";s:5:"alias";N;s:6:"tokens";a:1:{i:0;r:268;}}}s:9:"partition";O:33:"SqlParser\Fragments\ArrayFragment":2:{s:5:"array";a:2:{i:0;s:2:"p1";i:1;s:2:"p2";}s:6:"tokens";a:2:{i:0;r:298;i:1;r:316;}}s:5:"where";a:1:{i:0;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:0;s:9:"condition";s:4:"id>0";s:6:"tokens";a:3:{i:0;r:346;i:1;r:358;i:2;r:370;}}}s:5:"group";N;s:6:"having";N;s:5:"order";a:2:{i:0;O:32:"SqlParser\Fragments\OrderKeyword":3:{s:6:"column";s:8:"username";s:4:"type";s:4:"DESC";s:6:"tokens";a:2:{i:0;r:406;i:1;r:418;}}i:1;O:32:"SqlParser\Fragments\OrderKeyword":3:{s:6:"column";s:2:"id";s:4:"type";s:3:"ASC";s:6:"tokens";a:1:{i:0;r:436;}}}s:5:"limit";O:32:"SqlParser\Fragments\LimitKeyword":3:{s:6:"offset";i:2;s:9:"row_count";i:3;s:6:"tokens";a:2:{i:0;r:460;i:1;r:484;}}s:5:"first";N;s:4:"last";i:80;}}} \ No newline at end of file
diff --git a/tests/data/parseSelectErr1.in b/tests/data/parseSelectErr1.in
new file mode 100644
index 0000000..5140a74
--- /dev/null
+++ b/tests/data/parseSelectErr1.in
@@ -0,0 +1,14 @@
+SELECT ALL MAX_STATEMENT_TIME = 10
+ 1 + 2 AS result,
+ @idx,
+ id,
+ test.`users`.username AS
+FROM
+ `test`.users
+ PARTITION (p1, p2)
+WHERE
+ id > 0
+ORDER BY
+ username DESC,
+ id
+LIMIT 2, 3; \ No newline at end of file
diff --git a/tests/data/parseSelectErr1.out b/tests/data/parseSelectErr1.out
new file mode 100644
index 0000000..4a129b6
--- /dev/null
+++ b/tests/data/parseSelectErr1.out
@@ -0,0 +1,14 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:77:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"ALL";s:5:"value";s:3:"ALL";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:10;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:18:"MAX_STATEMENT_TIME";s:5:"value";s:18:"MAX_STATEMENT_TIME";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:11;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:29;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:30;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:31;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"10";s:5:"value";i:10;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:32;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:34;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:39;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:40;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"+";s:5:"value";s:1:"+";s:4:"type";i:2;s:5:"flags";i:1;s:8:"position";i:41;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:42;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"2";s:5:"value";i:2;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:43;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:44;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"AS";s:5:"value";s:2:"AS";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:45;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"result";s:5:"value";s:6:"result";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:48;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:54;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:55;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"@idx";s:5:"value";s:3:"idx";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:60;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:64;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:65;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:70;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:72;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:73;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"test";s:5:"value";s:4:"test";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:78;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:1:".";s:5:"value";s:1:".";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:82;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:7:"`users`";s:5:"value";s:5:"users";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:83;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:1:".";s:5:"value";s:1:".";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:90;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:91;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:99;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"AS";s:5:"value";s:2:"AS";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:100;}i:34;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:102;}i:35;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:103;}i:36;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:107;}i:37;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"`test`";s:5:"value";s:4:"test";s:4:"type";i:8;s:5:"flags";i:2;s:8:"position";i:112;}i:38;O:15:"SqlParser\Token":5:{s:5:"token";s:1:".";s:5:"value";s:1:".";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:118;}i:39;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:119;}i:40;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:124;}i:41;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"PARTITION";s:5:"value";s:9:"PARTITION";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:129;}i:42;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:138;}i:43;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:139;}i:44;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"p1";s:5:"value";s:2:"p1";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:140;}i:45;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:142;}i:46;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:143;}i:47;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"p2";s:5:"value";s:2:"p2";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:144;}i:48;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:146;}i:49;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:147;}i:50;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:148;}i:51;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:153;}i:52;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:158;}i:53;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:160;}i:54;O:15:"SqlParser\Token":5:{s:5:"token";s:1:">";s:5:"value";s:1:">";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:161;}i:55;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:162;}i:56;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"0";s:5:"value";i:0;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:163;}i:57;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:164;}i:58;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"ORDER";s:5:"value";s:5:"ORDER";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:165;}i:59;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:170;}i:60;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"BY";s:5:"value";s:2:"BY";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:171;}i:61;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:173;}i:62;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:178;}i:63;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:186;}i:64;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"DESC";s:5:"value";s:4:"DESC";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:187;}i:65;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:191;}i:66;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:192;}i:67;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:197;}i:68;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:199;}i:69;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"LIMIT";s:5:"value";s:5:"LIMIT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:200;}i:70;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:205;}i:71;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"2";s:5:"value";i:2;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:206;}i:72;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:207;}i:73;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:208;}i:74;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"3";s:5:"value";i:3;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:209;}i:75;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:210;}i:76;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:77;s:3:"idx";i:77;}s:6:"strict";b:0;s:6:"errors";a:1:{i:0;O:36:"SqlParser\Exceptions\ParserException":8:{s:5:"token";r:214;s:10:"
diff --git a/tests/data/parseSelectNested.in b/tests/data/parseSelectNested.in
new file mode 100644
index 0000000..92afa74
--- /dev/null
+++ b/tests/data/parseSelectNested.in
@@ -0,0 +1 @@
+SELECT (SELECT 'foo') as Bar, (SELECT 'baz') as fOo; \ No newline at end of file
diff --git a/tests/data/parseSelectNested.out b/tests/data/parseSelectNested.out
new file mode 100644
index 0000000..c5c2ead
--- /dev/null
+++ b/tests/data/parseSelectNested.out
@@ -0,0 +1 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:24:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:14;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"'foo'";s:5:"value";s:3:"foo";s:4:"type";i:7;s:5:"flags";i:1;s:8:"position";i:15;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:20;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:21;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"as";s:5:"value";s:2:"AS";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:22;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:24;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"Bar";s:5:"value";s:3:"Bar";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:25;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:28;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:29;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:30;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:31;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:37;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"'baz'";s:5:"value";s:3:"baz";s:4:"type";i:7;s:5:"flags";i:1;s:8:"position";i:38;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:43;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:44;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"as";s:5:"value";s:2:"AS";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:45;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"fOo";s:5:"value";s:3:"fOo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:48;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:51;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:24;s:3:"idx";i:24;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":11:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";N;s:6:"tokens";a:0:{}}s:4:"expr";a:2:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:15:"(SELECT 'foo') ";s:5:"alias";s:3:"Bar";s:6:"tokens";a:6:{i:0;r:16;i:1;r:22;i:2;r:28;i:3;r:34;i:4;r:40;i:5;r:46;}}i:1;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:15:"(SELECT 'baz') ";s:5:"alias";s:3:"fOo";s:6:"tokens";a:6:{i:0;r:82;i:1;r:88;i:2;r:94;i:3;r:100;i:4;r:106;i:5;r:112;}}}s:4:"from";N;s:9:"partition";N;s:5:"where";N;s:5:"group";N;s:6:"having";N;s:5:"order";N;s:5:"limit";N;s:5:"first";N;s:4:"last";i:21;}}} \ No newline at end of file
diff --git a/tests/data/parseUpdate.in b/tests/data/parseUpdate.in
new file mode 100644
index 0000000..b35dbeb
--- /dev/null
+++ b/tests/data/parseUpdate.in
@@ -0,0 +1,5 @@
+UPDATE
+ users
+SET
+ username = "Dan",
+ id=155; \ No newline at end of file
diff --git a/tests/data/parseUpdate.out b/tests/data/parseUpdate.out
new file mode 100644
index 0000000..2222cff
--- /dev/null
+++ b/tests/data/parseUpdate.out
@@ -0,0 +1,5 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:18:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"UPDATE";s:5:"value";s:6:"UPDATE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:11;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:16;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"SET";s:5:"value";s:3:"SET";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:17;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:20;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:25;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:33;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:34;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:35;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:5:""Dan"";s:5:"value";s:3:"Dan";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:36;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:41;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:42;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:47;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:49;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"155";s:5:"value";i:155;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:50;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:53;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:18;s:3:"idx";i:18;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\UpdateStatement":8:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";N;s:6:"tokens";a:0:{}}s:4:"from";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";s:5:"users";s:6:"column";N;s:4:"expr";s:5:"users";s:5:"alias";N;s:6:"tokens";a:1:{i:0;r:16;}}}s:3:"set";a:2:{i:0;O:30:"SqlParser\Fragments\SetKeyword":3:{s:6:"column";s:8:"username";s:5:"value";s:3:"Dan";s:6:"tokens";a:3:{i:0;r:40;i:1;r:52;i:2;r:64;}}i:1;O:30:"SqlParser\Fragments\SetKeyword":3:{s:6:"column";s:2:"id";s:5:"value";i:155;s:6:"tokens";a:3:{i:0;r:82;i:1;r:88;i:2;r:94;}}}s:5:"where";N;s:5:"order";N;s:5:"limit";N;s:5:"first";N;s:4:"last";i:15;}}} \ No newline at end of file
diff --git a/tests/data/parseUpdate2.in b/tests/data/parseUpdate2.in
new file mode 100644
index 0000000..7162118
--- /dev/null
+++ b/tests/data/parseUpdate2.in
@@ -0,0 +1,8 @@
+UPDATE
+ users
+SET
+ username = "Dan",
+ id=155
+WHERE
+ username = "Paul"
+LIMIT 1 OFFSET 2; \ No newline at end of file
diff --git a/tests/data/parseUpdate2.out b/tests/data/parseUpdate2.out
new file mode 100644
index 0000000..c00163f
--- /dev/null
+++ b/tests/data/parseUpdate2.out
@@ -0,0 +1,8 @@
+O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:34:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"UPDATE";s:5:"value";s:6:"UPDATE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"users";s:5:"value";s:5:"users";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:11;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:16;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"SET";s:5:"value";s:3:"SET";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:17;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:20;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:25;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:33;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:34;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:35;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:5:""Dan"";s:5:"value";s:3:"Dan";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:36;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:41;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:42;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:47;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:49;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"155";s:5:"value";i:155;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:50;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:53;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:54;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"
+ ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:59;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:8:"username";s:5:"value";s:8:"username";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:64;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:72;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:73;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:74;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:6:""Paul"";s:5:"value";s:4:"Paul";s:4:"type";i:7;s:5:"flags";i:2;s:8:"position";i:75;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"
+";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:81;}i:25;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"LIMIT";s:5:"value";s:5:"LIMIT";s:4:"type";i:1;s:5:"flags";i:0;s:8:"position";i:82;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:87;}i:27;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:88;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:89;}i:29;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"OFFSET";s:5:"value";s:6:"OFFSET";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:90;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:96;}i:31;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"2";s:5:"value";i:2;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:97;}i:32;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:98;}i:33;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:34;s:3:"idx";i:34;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\UpdateStatement":8:{s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":2:{s:7:"options";N;s:6:"tokens";a:0:{}}s:4:"from";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":6:{s:8:"database";N;s:5:"table";s:5:"users";s:6:"column";N;s:4:"expr";s:5:"users";s:5:"alias";N;s:6:"tokens";a:1:{i:0;r:16;}}}s:3:"set";a:2:{i:0;O:30:"SqlParser\Fragments\SetKeyword":3:{s:6:"column";s:8:"username";s:5:"value";s:3:"Dan";s:6:"tokens";a:3:{i:0;r:40;i:1;r:52;i:2;r:64;}}i:1;O:30:"SqlParser\Fragments\SetKeyword":3:{s:6:"column";s:2:"id";s:5:"value";i:155;s:6:"tokens";a:3:{i:0;r:82;i:1;r:88;i:2;r:94;}}}s:5:"where";a:1:{i:0;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:10:"isOperator";b:0;s:9:"condition";s:15:"username="Paul"";s:6:"tokens";a:3:{i:0;r:118;i:1;r:130;i:2;r:142;}}}s:5:"order";N;s:5:"limit";O:32:"SqlParser\Fragments\LimitKeyword":3:{s:6:"offset";i:2;s:9:"row_count";i:1;s:6:"tokens";a:2:{i:0;r:166;i:1;r:190;}}s:5:"first";N;s:4:"last";i:31;}}} \ No newline at end of file
diff --git a/tests/lexer/IsMethodsTest.php b/tests/lexer/IsMethodsTest.php
new file mode 100644
index 0000000..6121a4d
--- /dev/null
+++ b/tests/lexer/IsMethodsTest.php
@@ -0,0 +1,123 @@
+<?php
+
+use SqlParser\Context;
+use SqlParser\Token;
+
+class IsMethodsTest extends PHPUnit_Framework_TestCase
+{
+
+ public function testIsKeyword()
+ {
+ $this->assertTrue(Context::isKeyword('SELECT'));
+ $this->assertTrue(Context::isKeyword('ALL'));
+ $this->assertTrue(Context::isKeyword('DISTINCT'));
+ $this->assertTrue(Context::isKeyword('FROM'));
+
+ $this->assertTrue(Context::isKeyword('PRIMARY KEY'));
+ $this->assertTrue(Context::isKeyword('CHARACTER SET'));
+
+ $this->assertFalse(Context::isKeyword('foo'));
+ $this->assertFalse(Context::isKeyword('bar baz'));
+ }
+
+ public function testIsOperator()
+ {
+ $this->assertEquals(Token::FLAG_OPERATOR_ARITHMETIC, Context::isOperator('%'));
+ $this->assertEquals(Token::FLAG_OPERATOR_LOGICAL, Context::isOperator('!'));
+ $this->assertEquals(Token::FLAG_OPERATOR_LOGICAL, Context::isOperator('&&'));
+ $this->assertEquals(Token::FLAG_OPERATOR_LOGICAL, Context::isOperator('<=>'));
+ $this->assertEquals(Token::FLAG_OPERATOR_BITWISE, Context::isOperator('&'));
+ $this->assertEquals(Token::FLAG_OPERATOR_ASSIGNMENT, Context::isOperator(':='));
+ $this->assertEquals(Token::FLAG_OPERATOR_SQL, Context::isOperator(','));
+
+ $this->assertEquals(Context::isOperator('a'), null);
+ }
+
+ public function testIsWhitespace()
+ {
+ $this->assertTrue(Context::isWhitespace(" "));
+ $this->assertTrue(Context::isWhitespace("\r"));
+ $this->assertTrue(Context::isWhitespace("\n"));
+ $this->assertTrue(Context::isWhitespace("\t"));
+
+ $this->assertFalse(Context::isWhitespace("a"));
+ $this->assertFalse(Context::isWhitespace("\b"));
+ $this->assertFalse(Context::isWhitespace("\u1000"));
+ }
+
+ public function testIsComment()
+ {
+ $this->assertEquals(Token::FLAG_COMMENT_BASH, Context::isComment('#'));
+ $this->assertEquals(Token::FLAG_COMMENT_C, Context::isComment('/*'));
+ $this->assertEquals(Token::FLAG_COMMENT_C, Context::isComment('*/'));
+ $this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment('-- '));
+ $this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment("--\t"));
+
+ $this->assertEquals(Token::FLAG_COMMENT_BASH, Context::isComment('# a comment'));
+ $this->assertEquals(Token::FLAG_COMMENT_C, Context::isComment('/*comment */'));
+ $this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment('-- my comment'));
+
+ $this->assertEquals(Context::isComment("--\n"), null);
+ $this->assertEquals(Context::isComment('--not a comment'), null);
+ }
+
+ public function testIsBool()
+ {
+ $this->assertTrue(Context::isBool('true'));
+ $this->assertTrue(Context::isBool('false'));
+
+ $this->assertFalse(Context::isBool('tru'));
+ $this->assertFalse(Context::isBool('falsee'));
+ }
+
+ public function testIsNumber()
+ {
+ $this->assertTrue(Context::isNumber('+'));
+ $this->assertTrue(Context::isNumber('-'));
+ $this->assertTrue(Context::isNumber('.'));
+ $this->assertTrue(Context::isNumber('0'));
+ $this->assertTrue(Context::isNumber('1'));
+ $this->assertTrue(Context::isNumber('2'));
+ $this->assertTrue(Context::isNumber('3'));
+ $this->assertTrue(Context::isNumber('4'));
+ $this->assertTrue(Context::isNumber('5'));
+ $this->assertTrue(Context::isNumber('6'));
+ $this->assertTrue(Context::isNumber('7'));
+ $this->assertTrue(Context::isNumber('8'));
+ $this->assertTrue(Context::isNumber('9'));
+ $this->assertTrue(Context::isNumber('e'));
+ $this->assertTrue(Context::isNumber('E'));
+ }
+
+ public function testIsString()
+ {
+ $this->assertEquals(Token::FLAG_STRING_SINGLE_QUOTES, Context::isString("'"));
+ $this->assertEquals(Token::FLAG_STRING_DOUBLE_QUOTES, Context::isString('"'));
+
+ $this->assertEquals(Token::FLAG_STRING_SINGLE_QUOTES, Context::isString("'foo bar'"));
+ $this->assertEquals(Token::FLAG_STRING_DOUBLE_QUOTES, Context::isString('"foo bar"'));
+
+ $this->assertEquals(Context::isString('foo bar'), null);
+ }
+
+ public function testIsSymbol()
+ {
+ $this->assertEquals(Token::FLAG_SYMBOL_VARIABLE, Context::isSymbol('@'));
+ $this->assertEquals(Token::FLAG_SYMBOL_BACKTICK, Context::isSymbol('`'));
+
+ $this->assertEquals(Token::FLAG_SYMBOL_VARIABLE, Context::isSymbol('@id'));
+ $this->assertEquals(Token::FLAG_SYMBOL_BACKTICK, Context::isSymbol('`id`'));
+
+ $this->assertEquals(Context::isSymbol('id'), null);
+ }
+
+ public function testisSeparator()
+ {
+ $this->assertTrue(Context::isSeparator('+'));
+ $this->assertTrue(Context::isSeparator('.'));
+
+ $this->assertFalse(Context::isSeparator('1'));
+ $this->assertFalse(Context::isSeparator('E'));
+ $this->assertFalse(Context::isSeparator('_'));
+ }
+}
diff --git a/tests/lexer/LexerTest.php b/tests/lexer/LexerTest.php
new file mode 100644
index 0000000..bc0ede3
--- /dev/null
+++ b/tests/lexer/LexerTest.php
@@ -0,0 +1,104 @@
+<?php
+
+use SqlParser\Exceptions\LexerException;
+use SqlParser\Lexer;
+
+class LexerTest extends TestCase
+{
+
+ public function testError()
+ {
+ $lexer = new Lexer('');
+
+ $lexer->error('error #1', 'foo', 1, 2);
+ $lexer->error('error #2', 'bar', 3, 4);
+
+ $this->assertEquals($lexer->errors, array(
+ new LexerException('error #1', 'foo', 1, 2),
+ new LexerException('error #2', 'bar', 3, 4),
+ ));
+ }
+
+ /**
+ * @expectedException SqlParser\Exceptions\LexerException
+ * @expectedExceptionMessage strict error
+ * @expectedExceptionCode 4
+ */
+ public function testErrorStrict()
+ {
+ $lexer = new Lexer('');
+ $lexer->strict = true;
+
+ $lexer->error('strict error', 'foo', 1, 4);
+ }
+
+ public function testLex()
+ {
+ $this->runLexerTest('lex');
+ }
+
+ public function testLexKeyword()
+ {
+ $this->runLexerTest('lexKeyword');
+ }
+
+ public function testLexOperator()
+ {
+ $this->runLexerTest('lexOperator');
+ }
+
+ public function testLexWhitespace()
+ {
+ $this->runLexerTest('lexWhitespace');
+ }
+
+ public function testLexComment()
+ {
+ $this->runLexerTest('lexComment');
+ }
+
+ public function testLexBool()
+ {
+ $this->runLexerTest('lexBool');
+ }
+
+ public function testLexNumber()
+ {
+ $this->runLexerTest('lexNumber');
+ }
+
+ public function testLexString()
+ {
+ $this->runLexerTest('lexString');
+ }
+
+ public function testLexStringErr1()
+ {
+ $this->runLexerTest('lexStringErr1');
+ }
+
+ public function testLexSymbol()
+ {
+ $this->runLexerTest('lexSymbol');
+ }
+
+ public function testLexSymbolErr1()
+ {
+ $this->runLexerTest('lexSymbolErr1');
+ }
+
+ public function testLexSymbolErr2()
+ {
+ $this->runLexerTest('lexSymbolErr2');
+ }
+
+ public function testLexSymbolErr3()
+ {
+ $this->runLexerTest('lexSymbolErr3');
+ }
+
+ public function testLexDelimiter()
+ {
+ $this->runLexerTest('lexDelimiter');
+ }
+}
diff --git a/tests/lexer/TokenTest.php b/tests/lexer/TokenTest.php
new file mode 100644
index 0000000..02e9ff1
--- /dev/null
+++ b/tests/lexer/TokenTest.php
@@ -0,0 +1,76 @@
+<?php
+
+use SqlParser\Token;
+
+class TokenTest extends PHPUnit_Framework_TestCase
+{
+
+ public function testExtractKeyword()
+ {
+ $tok = new Token('SelecT', Token::TYPE_KEYWORD);
+ $this->assertEquals($tok->value, 'SELECT');
+
+ $tok = new Token('aS', Token::TYPE_KEYWORD);
+ $this->assertEquals($tok->value, 'AS');
+ }
+
+ public function testExtractWhitespace()
+ {
+ $tok = new Token(" \t \r \n ", Token::TYPE_WHITESPACE);
+ $this->assertEquals($tok->value, ' ');
+ }
+
+ public function testExtractBool()
+ {
+ $tok = new Token('false', Token::TYPE_BOOL);
+ $this->assertFalse($tok->value);
+
+ $tok = new Token('True', Token::TYPE_BOOL);
+ $this->assertTrue($tok->value);
+ }
+
+ public function testExtractNumber()
+ {
+ $tok = new Token('--42', Token::TYPE_NUMBER, Token::FLAG_NUMBER_NEGATIVE);
+ $this->assertEquals($tok->value, 42);
+
+ $tok = new Token('---42', Token::TYPE_NUMBER, Token::FLAG_NUMBER_NEGATIVE);
+ $this->assertEquals($tok->value, -42);
+
+ $tok = new Token('0xFE', Token::TYPE_NUMBER, Token::FLAG_NUMBER_HEX);
+ $this->assertEquals($tok->value, 0xFE);
+
+ $tok = new Token('-0xEF', Token::TYPE_NUMBER, Token::FLAG_NUMBER_NEGATIVE | Token::FLAG_NUMBER_HEX);
+ $this->assertEquals($tok->value, -0xEF);
+
+ $tok = new Token('3.14', Token::TYPE_NUMBER, Token::FLAG_NUMBER_FLOAT);
+ $this->assertEquals($tok->value, 3.14);
+ }
+
+ public function testExtractString()
+ {
+ $tok = new Token('"foo bar "', Token::TYPE_STRING);
+ $this->assertEquals($tok->value, 'foo bar ');
+
+ $tok = new Token("' bar foo '", Token::TYPE_STRING);
+ $this->assertEquals($tok->value, ' bar foo ');
+ }
+
+ public function testExtractSymbol()
+ {
+ $tok = new Token('@foo', Token::TYPE_SYMBOL, Token::FLAG_SYMBOL_VARIABLE);
+ $this->assertEquals($tok->value, 'foo');
+
+ $tok = new Token('`foo`', Token::TYPE_SYMBOL, Token::FLAG_SYMBOL_BACKTICK);
+ $this->assertEquals($tok->value, 'foo');
+
+ $tok = new Token('@`foo`', Token::TYPE_SYMBOL, Token::FLAG_SYMBOL_VARIABLE);
+ $this->assertEquals($tok->value, 'foo');
+ }
+
+ public function testInlineToken()
+ {
+ $token = new Token(" \r \n \t ");
+ $this->assertEquals($token->getInlineToken(), ' \r \n \t ');
+ }
+}
diff --git a/tests/lexer/TokensList.php b/tests/lexer/TokensList.php
new file mode 100644
index 0000000..0415a8e
--- /dev/null
+++ b/tests/lexer/TokensList.php
@@ -0,0 +1,103 @@
+<?php
+
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+class TokensListCase extends TestCase
+{
+
+ /**
+ * Array of tokens that are used for testing.
+ *
+ * @var Token[]
+ */
+ public $tokens;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->tokens = array(
+ new Token('SELECT', Token::TYPE_KEYWORD),
+ new Token('*', Token::TYPE_OPERATOR),
+ new Token('FROM', Token::TYPE_KEYWORD),
+ new Token('`test`', Token::TYPE_SYMBOL)
+ );
+ }
+
+ /**
+ * Gets a list used for testing.
+ *
+ * @return TokensList
+ */
+ public function getList()
+ {
+ $list = new TokensList();
+ foreach ($this->tokens as $token) {
+ $list[] = $token;
+ }
+ return $list;
+ }
+
+ public function testAdd()
+ {
+ $list = new TokensList();
+ foreach ($this->tokens as $token) {
+ $list->add($token);
+ }
+ $this->assertEquals($this->getList(), $list);
+ }
+
+ public function testGetNext()
+ {
+ $list = $this->getList();
+ $this->assertEquals($this->tokens[0], $list->getNext());
+ $this->assertEquals($this->tokens[1], $list->getNext());
+ $this->assertEquals($this->tokens[2], $list->getNext());
+ $this->assertEquals($this->tokens[3], $list->getNext());
+ $this->assertEquals(null, $list->getNext());
+ }
+
+ public function testGetNextOfType()
+ {
+ $list = $this->getList();
+ $this->assertEquals($this->tokens[0], $list->getNextOfType(Token::TYPE_KEYWORD));
+ $this->assertEquals($this->tokens[2], $list->getNextOfType(Token::TYPE_KEYWORD));
+ $this->assertEquals(null, $list->getNextOfType(Token::TYPE_KEYWORD));
+ }
+
+ public function testGetNextOfTypeAndValue()
+ {
+ $list = $this->getList();
+ $this->assertEquals($this->tokens[0], $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'SELECT'));
+ $this->assertEquals(null, $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'SELECT'));
+ }
+
+ public function testArrayAccess()
+ {
+ $list = new TokensList();
+
+ // offsetSet(NULL, $value)
+ foreach ($this->tokens as $token) {
+ $list[] = $token;
+ }
+
+ // offsetSet($offset, $value)
+ $list[2] = $this->tokens[2];
+
+ // offsetGet($offset)
+ for ($i = 0, $count = count($this->tokens); $i < $count; ++$i) {
+ $this->assertEquals($this->tokens[$i], $list[$i]);
+ }
+
+ // offsetExists($offset)
+ $this->assertTrue(isset($list[2]));
+ $this->assertFalse(isset($list[5]));
+
+ // offsetUnset($offset)
+ unset($list[2]);
+ $this->assertEquals($this->tokens[3], $list[2]);
+
+ }
+}
diff --git a/tests/parser/ArrayFragmentTest.php b/tests/parser/ArrayFragmentTest.php
new file mode 100644
index 0000000..ad40385
--- /dev/null
+++ b/tests/parser/ArrayFragmentTest.php
@@ -0,0 +1,20 @@
+<?php
+
+class ArrayFragmentTest extends TestCase
+{
+
+ public function testArrayErr1()
+ {
+ $this->runParserTest('parseArrayErr1');
+ }
+
+ public function testArrayErr2()
+ {
+ $this->runParserTest('parseArrayErr2');
+ }
+
+ public function testArrayErr3()
+ {
+ $this->runParserTest('parseArrayErr3');
+ }
+}
diff --git a/tests/parser/CallStatementTest.php b/tests/parser/CallStatementTest.php
new file mode 100644
index 0000000..c558ce1
--- /dev/null
+++ b/tests/parser/CallStatementTest.php
@@ -0,0 +1,15 @@
+<?php
+
+class CallStatementTest extends TestCase
+{
+
+ public function testCall()
+ {
+ $this->runParserTest('parseCall');
+ }
+
+ public function testCall2()
+ {
+ $this->runParserTest('parseCall2');
+ }
+}
diff --git a/tests/parser/CreateStatementTest.php b/tests/parser/CreateStatementTest.php
new file mode 100644
index 0000000..b23e4ab
--- /dev/null
+++ b/tests/parser/CreateStatementTest.php
@@ -0,0 +1,30 @@
+<?php
+
+class CreateStatementTest extends TestCase
+{
+
+ public function testCreateTable()
+ {
+ $this->runParserTest('parseCreateTable');
+ }
+
+ public function testCreateProcedure()
+ {
+ $this->runParserTest('parseCreateProcedure');
+ }
+
+ public function testCreateProcedure2()
+ {
+ $this->runParserTest('parseCreateProcedure2');
+ }
+
+ public function testCreateFunction()
+ {
+ $this->runParserTest('parseCreateFunction');
+ }
+
+ public function testCreateFunctionErr1()
+ {
+ $this->runParserTest('parseCreateFunctionErr1');
+ }
+}
diff --git a/tests/parser/DeleteStatementTest.php b/tests/parser/DeleteStatementTest.php
new file mode 100644
index 0000000..dab28c7
--- /dev/null
+++ b/tests/parser/DeleteStatementTest.php
@@ -0,0 +1,10 @@
+<?php
+
+class DeleteStatementTest extends TestCase
+{
+
+ public function testDelete()
+ {
+ $this->runParserTest('parseDelete');
+ }
+}
diff --git a/tests/parser/InsertStatementTest.php b/tests/parser/InsertStatementTest.php
new file mode 100644
index 0000000..bce6065
--- /dev/null
+++ b/tests/parser/InsertStatementTest.php
@@ -0,0 +1,10 @@
+<?php
+
+class InsertStatementTest extends TestCase
+{
+
+ public function testInsert()
+ {
+ $this->runParserTest('parseInsert');
+ }
+}
diff --git a/tests/parser/ParserTest.php b/tests/parser/ParserTest.php
new file mode 100644
index 0000000..8a77086
--- /dev/null
+++ b/tests/parser/ParserTest.php
@@ -0,0 +1,66 @@
+<?php
+
+use SqlParser\Exceptions\ParserException;
+use SqlParser\Lexer;
+use SqlParser\Parser;
+use SqlParser\Token;
+use SqlParser\TokensList;
+
+class ParserTest extends TestCase
+{
+
+ public function testParse()
+ {
+ $this->runParserTest('parse');
+ }
+
+ public function testUnrecognizedStatement()
+ {
+ $lexer = new Lexer("SELECT 1; FROM");
+ $lexer->lex();
+ $parser = new Parser($lexer->tokens);
+ $parser->parse();
+ $this->assertEquals(
+ $parser->errors[0]->getMessage(),
+ 'Unrecognized statement type "FROM".'
+ );
+ }
+
+ public function testUnrecognizedKeyword()
+ {
+ $lexer = new Lexer("SELECT 1 FROM foo PARTITION(bar, baz) AS");
+ $lexer->lex();
+ $parser = new Parser($lexer->tokens);
+ $parser->parse();
+ $this->assertEquals(
+ $parser->errors[0]->getMessage(),
+ 'Unrecognized keyword "AS".'
+ );
+ }
+
+ public function testError()
+ {
+ $parser = new Parser(new TokensList());
+
+ $parser->error('error #1', new Token('foo'), 1);
+ $parser->error('error #2', new Token('bar'), 2);
+
+ $this->assertEquals($parser->errors, array(
+ new ParserException('error #1', new Token('foo'), 1),
+ new ParserException('error #2', new Token('bar'), 2),
+ ));
+ }
+
+ /**
+ * @expectedException SqlParser\Exceptions\ParserException
+ * @expectedExceptionMessage strict error
+ * @expectedExceptionCode 3
+ */
+ public function testErrorStrict()
+ {
+ $parser = new Parser(new TokensList());
+ $parser->strict = true;
+
+ $parser->error('strict error', new Token('foo'), 3);
+ }
+}
diff --git a/tests/parser/RenameStatementTest.php b/tests/parser/RenameStatementTest.php
new file mode 100644
index 0000000..2ae608d
--- /dev/null
+++ b/tests/parser/RenameStatementTest.php
@@ -0,0 +1,15 @@
+<?php
+
+class RenameStatementTest extends TestCase
+{
+
+ public function testRename()
+ {
+ $this->runParserTest('parseRename');
+ }
+
+ public function testRename2()
+ {
+ $this->runParserTest('parseRename2');
+ }
+}
diff --git a/tests/parser/ReplaceStatementTest.php b/tests/parser/ReplaceStatementTest.php
new file mode 100644
index 0000000..4366a15
--- /dev/null
+++ b/tests/parser/ReplaceStatementTest.php
@@ -0,0 +1,15 @@
+<?php
+
+class ReplaceStatementTest extends TestCase
+{
+
+ public function testReplace()
+ {
+ $this->runParserTest('parseReplace');
+ }
+
+ public function testReplace2()
+ {
+ $this->runParserTest('parseReplace2');
+ }
+}
diff --git a/tests/parser/SelectStatementTest.php b/tests/parser/SelectStatementTest.php
new file mode 100644
index 0000000..3ef76b2
--- /dev/null
+++ b/tests/parser/SelectStatementTest.php
@@ -0,0 +1,22 @@
+<?php
+
+class SelectStatementTest extends TestCase
+{
+
+ public function testSelect()
+ {
+ $parser = $this->runParserTest('parseSelect');
+ $stmt = $parser->statements[0];
+ $this->assertEquals(10, $stmt->options->has('MAX_STATEMENT_TIME'));
+ }
+
+ public function testSelectErr1()
+ {
+ $this->runParserTest('parseSelectErr1');
+ }
+
+ public function testSelectNested()
+ {
+ $this->runParserTest('parseSelectNested');
+ }
+}
diff --git a/tests/parser/UpdateStatementTest.php b/tests/parser/UpdateStatementTest.php
new file mode 100644
index 0000000..882d9ac
--- /dev/null
+++ b/tests/parser/UpdateStatementTest.php
@@ -0,0 +1,15 @@
+<?php
+
+class UpdateStatementTest extends TestCase
+{
+
+ public function testUpdate()
+ {
+ $this->runParserTest('parseUpdate');
+ }
+
+ public function testUpdate2()
+ {
+ $this->runParserTest('parseUpdate2');
+ }
+}