* * 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); } } ?>