first version
This commit is contained in:
448
tools/parser_sql/processors/SQLProcessor.php
Normal file
448
tools/parser_sql/processors/SQLProcessor.php
Normal file
@@ -0,0 +1,448 @@
|
||||
<?php
|
||||
/**
|
||||
* SQLProcessor.php
|
||||
*
|
||||
* This file implements the processor for the base SQL statements.
|
||||
*
|
||||
* Copyright (c) 2010-2012, Justin Swanhart
|
||||
* with contributions by André Rothe <arothe@phosco.info, phosco@gmx.de>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
require_once(dirname(__FILE__) . '/AbstractProcessor.php');
|
||||
require_once(dirname(__FILE__) . '/SQLChunkProcessor.php');
|
||||
|
||||
/**
|
||||
*
|
||||
* This class processes the base SQL statements.
|
||||
*
|
||||
* @author arothe
|
||||
*
|
||||
*/
|
||||
class SQLProcessor extends SQLChunkProcessor {
|
||||
|
||||
/*
|
||||
* This function breaks up the SQL statement into logical sections.
|
||||
* Some sections are then further handled by specialized processors.
|
||||
*/
|
||||
public function process($tokens) {
|
||||
$prev_category = "";
|
||||
$token_category = "";
|
||||
$skip_next = 0;
|
||||
$out = false;
|
||||
|
||||
$tokenCount = count($tokens);
|
||||
for ($tokenNumber = 0; $tokenNumber < $tokenCount; ++$tokenNumber) {
|
||||
|
||||
$token = $tokens[$tokenNumber];
|
||||
$trim = trim($token); // this removes also \n and \t!
|
||||
|
||||
// if it starts with an "(", it should follow a SELECT
|
||||
if ($trim !== "" && $trim[0] === "(" && $token_category === "") {
|
||||
$token_category = 'SELECT';
|
||||
}
|
||||
|
||||
/*
|
||||
* If it isn't obvious, when $skip_next is set, then we ignore the next real token, that is we ignore whitespace.
|
||||
*/
|
||||
if ($skip_next > 0) {
|
||||
if ($trim === "") {
|
||||
if ($token_category !== "") { # is this correct??
|
||||
$out[$token_category][] = $token;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#to skip the token we replace it with whitespace
|
||||
$trim = "";
|
||||
$token = "";
|
||||
$skip_next--;
|
||||
if ($skip_next > 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$upper = strtoupper($trim);
|
||||
switch ($upper) {
|
||||
|
||||
/* Tokens that get their own sections. These keywords have subclauses. */
|
||||
case 'SELECT':
|
||||
case 'ORDER':
|
||||
case 'DUPLICATE':
|
||||
case 'VALUES':
|
||||
case 'GROUP':
|
||||
case 'HAVING':
|
||||
case 'WHERE':
|
||||
case 'CALL':
|
||||
case 'PROCEDURE':
|
||||
case 'FUNCTION':
|
||||
case 'SERVER':
|
||||
case 'LOGFILE':
|
||||
case 'DEFINER':
|
||||
case 'RETURNS':
|
||||
case 'TABLESPACE':
|
||||
case 'TRIGGER':
|
||||
case 'DO':
|
||||
case 'FLUSH':
|
||||
case 'KILL':
|
||||
case 'RESET':
|
||||
case 'STOP':
|
||||
case 'PURGE':
|
||||
case 'EXECUTE':
|
||||
case 'PREPARE':
|
||||
case 'DEALLOCATE':
|
||||
if ($trim === 'DEALLOCATE') {
|
||||
$skip_next = 1;
|
||||
}
|
||||
$token_category = $upper;
|
||||
break;
|
||||
|
||||
case 'SET':
|
||||
if ($token_category !== 'TABLE') {
|
||||
$token_category = $upper;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'LIMIT':
|
||||
case 'PLUGIN':
|
||||
# no separate section
|
||||
if ($token_category === 'SHOW') {
|
||||
continue;
|
||||
}
|
||||
$token_category = $upper;
|
||||
break;
|
||||
|
||||
case 'FROM':
|
||||
# this FROM is different from FROM in other DML (not join related)
|
||||
if ($token_category === 'PREPARE') {
|
||||
continue 2;
|
||||
}
|
||||
# no separate section
|
||||
if ($token_category === 'SHOW') {
|
||||
continue;
|
||||
}
|
||||
$token_category = $upper;
|
||||
break;
|
||||
|
||||
case 'EXPLAIN':
|
||||
case 'DESCRIBE':
|
||||
case 'SHOW':
|
||||
$token_category = $upper;
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
if ($token_category === '') {
|
||||
// short version of DESCRIBE
|
||||
$token_category = $upper;
|
||||
}
|
||||
// else direction of ORDER-BY
|
||||
break;
|
||||
|
||||
case 'RENAME':
|
||||
// jump over TABLE keyword
|
||||
$token_category = $upper;
|
||||
$skip_next = 1;
|
||||
continue 2;
|
||||
|
||||
case 'DATABASE':
|
||||
case 'SCHEMA':
|
||||
if ($prev_category === 'DROP') {
|
||||
continue;
|
||||
}
|
||||
if ($prev_category === 'SHOW') {
|
||||
continue;
|
||||
}
|
||||
$token_category = $upper;
|
||||
break;
|
||||
|
||||
case 'EVENT':
|
||||
// issue 71
|
||||
if ($prev_category === 'DROP' || $prev_category === 'ALTER' || $prev_category === 'CREATE') {
|
||||
$token_category = $upper;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DATA':
|
||||
// prevent wrong handling of DATA as keyword
|
||||
if ($prev_category === 'LOAD') {
|
||||
$token_category = $upper;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'INTO':
|
||||
// prevent wrong handling of CACHE within LOAD INDEX INTO CACHE...
|
||||
if ($prev_category === 'LOAD') {
|
||||
$out[$prev_category][] = $upper;
|
||||
continue 2;
|
||||
}
|
||||
$token_category = $upper;
|
||||
break;
|
||||
|
||||
case 'USER':
|
||||
// prevent wrong processing as keyword
|
||||
if ($prev_category === 'CREATE' || $prev_category === 'RENAME' || $prev_category === 'DROP') {
|
||||
$token_category = $upper;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'VIEW':
|
||||
// prevent wrong processing as keyword
|
||||
if ($prev_category === 'CREATE' || $prev_category === 'ALTER' || $prev_category === 'DROP') {
|
||||
$token_category = $upper;
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* These tokens get their own section, but have no subclauses. These tokens identify the statement but have no specific subclauses of their own.
|
||||
*/
|
||||
case 'DELETE':
|
||||
case 'ALTER':
|
||||
case 'INSERT':
|
||||
case 'TRUNCATE':
|
||||
case 'OPTIMIZE':
|
||||
case 'GRANT':
|
||||
case 'REVOKE':
|
||||
case 'HANDLER':
|
||||
case 'LOAD':
|
||||
case 'ROLLBACK':
|
||||
case 'SAVEPOINT':
|
||||
case 'UNLOCK':
|
||||
case 'INSTALL':
|
||||
case 'UNINSTALL':
|
||||
case 'ANALZYE':
|
||||
case 'BACKUP':
|
||||
case 'CHECKSUM':
|
||||
case 'REPAIR':
|
||||
case 'RESTORE':
|
||||
case 'USE':
|
||||
case 'HELP':
|
||||
$token_category = $upper;
|
||||
// set the category in case these get subclauses in a future version of MySQL
|
||||
$out[$upper][0] = $upper;
|
||||
continue 2;
|
||||
|
||||
case 'REPLACE':
|
||||
if ($prev_category === 'TABLE') {
|
||||
# part of the CREATE TABLE statement
|
||||
$out[$prev_category][] = $upper;
|
||||
continue 2;
|
||||
}
|
||||
// set the category in case these get subclauses in a future version of MySQL
|
||||
$token_category = $upper;
|
||||
$out[$upper][0] = $upper;
|
||||
continue 2;
|
||||
|
||||
case 'IGNORE':
|
||||
if ($prev_category === 'TABLE') {
|
||||
# part of the CREATE TABLE statement
|
||||
$out[$prev_category][] = $upper;
|
||||
continue 2;
|
||||
}
|
||||
$out['OPTIONS'][] = $upper;
|
||||
continue 2;
|
||||
break;
|
||||
|
||||
case 'CHECK':
|
||||
if ($prev_category === 'TABLE') {
|
||||
$out[$prev_category][] = $upper;
|
||||
continue 2;
|
||||
}
|
||||
$token_category = $upper;
|
||||
$out[$upper][0] = $upper;
|
||||
continue 2;
|
||||
|
||||
case 'CREATE':
|
||||
if ($prev_category === 'SHOW') {
|
||||
continue;
|
||||
}
|
||||
$token_category = $upper;
|
||||
break;
|
||||
|
||||
case 'TABLE':
|
||||
if ($prev_category === 'CREATE') {
|
||||
$out[$prev_category][] = $upper;
|
||||
$token_category = $upper;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'TEMPORARY':
|
||||
if ($prev_category === 'CREATE') {
|
||||
$out[$prev_category][] = $upper;
|
||||
$token_category = $prev_category;
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'IF':
|
||||
if ($prev_category === 'TABLE') {
|
||||
$token_category = 'CREATE';
|
||||
$out[$token_category] = array_merge($out[$token_category], $out[$prev_category]);
|
||||
$out[$prev_category] = array();
|
||||
$out[$token_category][] = $upper;
|
||||
$prev_category = $token_category;
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOT':
|
||||
if ($prev_category === 'CREATE') {
|
||||
$token_category = $prev_category;
|
||||
$out[$prev_category][] = $upper;
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'EXISTS':
|
||||
if ($prev_category === 'CREATE') {
|
||||
$out[$prev_category][] = $upper;
|
||||
$prev_category = $token_category = 'TABLE';
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CACHE':
|
||||
if ($prev_category === "" || $prev_category === 'RESET' || $prev_category === 'FLUSH'
|
||||
|| $prev_category === 'LOAD') {
|
||||
$token_category = $upper;
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
/* This is either LOCK TABLES or SELECT ... LOCK IN SHARE MODE */
|
||||
case 'LOCK':
|
||||
if ($token_category === "") {
|
||||
$token_category = $upper;
|
||||
$out[$upper][0] = $upper;
|
||||
} else {
|
||||
$trim = 'LOCK IN SHARE MODE';
|
||||
$skip_next = 3;
|
||||
$out['OPTIONS'][] = $trim;
|
||||
}
|
||||
continue 2;
|
||||
break;
|
||||
|
||||
case 'USING': /* USING in FROM clause is different from USING w/ prepared statement*/
|
||||
if ($token_category === 'EXECUTE') {
|
||||
$token_category = $upper;
|
||||
continue 2;
|
||||
}
|
||||
if ($token_category === 'FROM' && !empty($out['DELETE'])) {
|
||||
$token_category = $upper;
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
/* DROP TABLE is different from ALTER TABLE DROP ... */
|
||||
case 'DROP':
|
||||
if ($token_category !== 'ALTER') {
|
||||
$token_category = $upper;
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'FOR':
|
||||
if ($prev_category === 'SHOW') {
|
||||
continue;
|
||||
}
|
||||
$skip_next = 1;
|
||||
$out['OPTIONS'][] = 'FOR UPDATE';
|
||||
continue 2;
|
||||
break;
|
||||
|
||||
case 'UPDATE':
|
||||
if ($token_category === "") {
|
||||
$token_category = $upper;
|
||||
continue 2;
|
||||
}
|
||||
if ($token_category === 'DUPLICATE') {
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'START':
|
||||
$trim = "BEGIN";
|
||||
$out[$upper][0] = $upper;
|
||||
$skip_next = 1;
|
||||
break;
|
||||
|
||||
/* These tokens are ignored. */
|
||||
case 'TO':
|
||||
if ($token_category === 'RENAME') {
|
||||
break;
|
||||
}
|
||||
case 'BY':
|
||||
case 'ALL':
|
||||
case 'SHARE':
|
||||
case 'MODE':
|
||||
case ';':
|
||||
continue 2;
|
||||
break;
|
||||
|
||||
case 'KEY':
|
||||
if ($token_category === 'DUPLICATE') {
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
/* These tokens set particular options for the statement. They never stand alone. */
|
||||
case 'LOW_PRIORITY':
|
||||
case 'DELAYED':
|
||||
case 'FORCE':
|
||||
case 'QUICK':
|
||||
$out['OPTIONS'][] = $upper;
|
||||
continue 2;
|
||||
break;
|
||||
|
||||
case 'WITH':
|
||||
if ($token_category === 'GROUP') {
|
||||
$skip_next = 1;
|
||||
$out['OPTIONS'][] = 'WITH ROLLUP';
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'AS':
|
||||
break;
|
||||
|
||||
case '':
|
||||
case ',':
|
||||
case ';':
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// remove obsolete category after union (empty category because of
|
||||
// empty token before select)
|
||||
if ($token_category !== "" && ($prev_category === $token_category)) {
|
||||
$out[$token_category][] = $token;
|
||||
}
|
||||
|
||||
$prev_category = $token_category;
|
||||
}
|
||||
|
||||
return parent::process($out);
|
||||
}
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user