2011-06-17 21:06:54 +00:00
|
|
|
<?php
|
2011-06-18 11:10:40 +00:00
|
|
|
/**
|
|
|
|
* This file is part of MySQLDumper released under the GNU/GPL 2 license
|
|
|
|
* http://www.mysqldumper.net
|
|
|
|
*
|
|
|
|
* @package MySQLDumper
|
|
|
|
* @subpackage SQL-Browser
|
2011-06-19 13:47:44 +00:00
|
|
|
* @version SVN: $Rev$
|
|
|
|
* @author $Author$
|
2011-06-18 11:10:40 +00:00
|
|
|
*/
|
2011-06-17 21:06:54 +00:00
|
|
|
|
2011-06-18 11:10:40 +00:00
|
|
|
require_once "Msd/Sql/Parser/Interface.php";
|
|
|
|
/**
|
|
|
|
* Class to parse MySQL queries.
|
|
|
|
* This enables you to analyze and modify MySQL queries, which the user has entered.
|
|
|
|
*
|
|
|
|
* @package MySQLDumper
|
|
|
|
* @subpackage SQL-Browser
|
|
|
|
*/
|
2011-06-19 13:47:44 +00:00
|
|
|
class Msd_Sql_Parser implements Traversable
|
2011-06-17 21:06:54 +00:00
|
|
|
{
|
2011-06-18 11:10:40 +00:00
|
|
|
/**
|
|
|
|
* Saves the raw MySQL Query.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2011-06-17 21:06:54 +00:00
|
|
|
private $_rawQuery = null;
|
|
|
|
|
2011-06-19 13:47:44 +00:00
|
|
|
/**
|
|
|
|
* Parsed MySQL statements.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private $_parsedStatements = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Holds the summary of the parsing process.
|
|
|
|
* The summary contains the count of each statement in the query.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private $_parsingSummary = array();
|
|
|
|
|
2011-06-18 11:10:40 +00:00
|
|
|
/**
|
|
|
|
* Available MySQL statements, divided and sorted by their length.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2011-06-17 21:06:54 +00:00
|
|
|
protected $_sqlStatements = array(
|
|
|
|
2 => array(
|
|
|
|
'do', 'xa'
|
|
|
|
),
|
|
|
|
3 => array(
|
|
|
|
'set', 'use'
|
|
|
|
),
|
|
|
|
4 => array(
|
|
|
|
'call', 'drop', 'help', 'kill', 'load', 'lock', 'show'
|
|
|
|
),
|
|
|
|
5 => array(
|
|
|
|
'alter', 'check', 'flush', 'grant', 'purge', 'reset', 'slave', 'start'
|
|
|
|
),
|
|
|
|
6 => array(
|
|
|
|
'backup', 'change', 'commit', 'create', 'delete', 'insert', 'rename', 'repair', 'revoke', 'select',
|
|
|
|
'unlock', 'update'
|
|
|
|
),
|
|
|
|
7 => array(
|
|
|
|
'analyze', 'execute', 'handler', 'install', 'preload', 'prepare', 'release', 'replace', 'restore'
|
|
|
|
),
|
|
|
|
8 => array(
|
|
|
|
'checksum', 'describe', 'optimize', 'keycache', 'rollback', 'truncate'
|
|
|
|
),
|
|
|
|
9 => array(
|
|
|
|
'savepoint', 'uninstall'
|
|
|
|
),
|
|
|
|
10 => array(
|
|
|
|
'deallocate'
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
2011-06-18 11:10:40 +00:00
|
|
|
/**
|
|
|
|
* MySQL comment types.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2011-06-17 21:06:54 +00:00
|
|
|
protected $_sqlComments = array(
|
|
|
|
'--' => "\n", '/*' => '*/'
|
|
|
|
);
|
|
|
|
|
2011-06-18 11:10:40 +00:00
|
|
|
/**
|
|
|
|
* Class constructor.
|
|
|
|
* Creates a new instance of the MySQL parser and optionally assign the raw MySQL query.
|
|
|
|
*
|
|
|
|
* @param string $sqlQuery Raw MySQL query to parse
|
|
|
|
*/
|
2011-06-17 21:06:54 +00:00
|
|
|
public function __construct($sqlQuery = null)
|
|
|
|
{
|
|
|
|
if ($sqlQuery !== null) {
|
|
|
|
$this->_rawQuery = $sqlQuery;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-18 11:10:40 +00:00
|
|
|
/**
|
|
|
|
* Parses a raw MySQL query.
|
|
|
|
* This could include more than one MySQL statement.
|
|
|
|
*
|
|
|
|
* @throws Msd_Sql_Parser_Exception
|
|
|
|
*
|
|
|
|
* @param string $sqlQuery Raw MySQL query to parse
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2011-06-17 21:06:54 +00:00
|
|
|
public function parse($sqlQuery = null)
|
|
|
|
{
|
|
|
|
if ($sqlQuery === null) {
|
|
|
|
if ($this->_rawQuery === null) {
|
|
|
|
include_once 'Msd/Sql/Parser/Exception.php';
|
|
|
|
throw new Msd_Sql_Parser_Exception('You must specify a MySQL query for parsing!');
|
|
|
|
}
|
|
|
|
|
|
|
|
$sqlQuery = $this->_rawQuery;
|
|
|
|
}
|
|
|
|
|
|
|
|
$sqlQuery = trim($sqlQuery);
|
|
|
|
|
|
|
|
$statementCounter = 0;
|
|
|
|
$startPos = 0;
|
|
|
|
while ($startPos < strlen($sqlQuery)) {
|
|
|
|
$statementCounter++;
|
|
|
|
$firstSpace = strpos($sqlQuery, ' ', $startPos);
|
|
|
|
$statement = trim(strtolower(substr($sqlQuery, $startPos, $firstSpace - $startPos)));
|
|
|
|
$lengthCheck = strlen($statement);
|
|
|
|
if ($lengthCheck == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if ($lengthCheck == 1 || $statement{1} == ';' || $statement{1} == "\n") {
|
|
|
|
$startPos = $startPos + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$commentCheck = substr($statement, 0, 2);
|
|
|
|
if (array_key_exists($commentCheck, $this->_sqlComments)) {
|
|
|
|
$commentEnd = $this->_sqlComments[$commentCheck];
|
2011-06-19 13:47:44 +00:00
|
|
|
$endPos = strpos($sqlQuery, $commentEnd, $startPos) + strlen($commentEnd);
|
|
|
|
$comment = substr($sqlQuery, $startPos, $endPos - $startPos);
|
|
|
|
$this->_parseStatement($comment, 'Msd_Sql_Parser_Comment');
|
|
|
|
$startPos = $endPos;
|
2011-06-17 21:06:54 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$statementLength = strlen($statement);
|
2011-06-19 13:47:44 +00:00
|
|
|
if (
|
|
|
|
!isset($this->_sqlStatements[$statementLength]) ||
|
|
|
|
!in_array($statement, $this->_sqlStatements[$statementLength])
|
|
|
|
) {
|
2011-06-17 21:06:54 +00:00
|
|
|
include_once 'Msd/Sql/Parser/Exception.php';
|
|
|
|
throw new Msd_Sql_Parser_Exception("Unknown MySQL statement is found: '$statement'");
|
|
|
|
}
|
2011-06-18 11:10:40 +00:00
|
|
|
$parserClass = 'Msd_Sql_Parser_Statement_' . ucwords($statement);
|
2011-06-17 21:06:54 +00:00
|
|
|
$endPos = $this->_getStatementEndPos($sqlQuery, $startPos);
|
|
|
|
$completeStatement = trim(substr($sqlQuery, $startPos, $endPos - $startPos));
|
|
|
|
$startPos = $endPos + 1;
|
2011-06-18 11:10:40 +00:00
|
|
|
|
2011-06-19 13:47:44 +00:00
|
|
|
$this->_parsedStatements[] = $this->_parseStatement($completeStatement, $parserClass);
|
|
|
|
if (!isset($this->_parsingSummary[$statement])) {
|
|
|
|
$this->_parsingSummary[$statement] = 0;
|
|
|
|
}
|
|
|
|
$this->_parsingSummary[$statement]++;
|
2011-06-17 21:06:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-18 11:10:40 +00:00
|
|
|
/**
|
|
|
|
* Searches the end of a MySQL statement and returns its position.
|
|
|
|
*
|
|
|
|
* @param string $sqlQuery The complete MySQL query
|
|
|
|
* @param int $startPos Index, where to start the search.
|
|
|
|
*
|
|
|
|
* @return int End position of the statement
|
|
|
|
*/
|
2011-06-17 21:06:54 +00:00
|
|
|
private function _getStatementEndPos($sqlQuery, $startPos = 0)
|
|
|
|
{
|
|
|
|
$nextString = strpos($sqlQuery, "'", $startPos);
|
|
|
|
$nextSemicolon = strpos($sqlQuery, ';', $startPos);
|
|
|
|
if ($nextString === false) {
|
|
|
|
if ($nextSemicolon === false) {
|
|
|
|
return strlen($sqlQuery);
|
|
|
|
}
|
|
|
|
|
2011-06-19 11:48:08 +00:00
|
|
|
return $nextSemicolon;
|
2011-06-17 21:06:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
while ($nextString < $nextSemicolon) {
|
|
|
|
$nextString = strpos($sqlQuery, "'", $nextString + 1);
|
|
|
|
$nextSemicolon = strpos($sqlQuery, ';', $nextString + 1);
|
|
|
|
$nextString = strpos($sqlQuery, "'", $nextString + 1);
|
|
|
|
if ($nextString === false) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $nextSemicolon;
|
|
|
|
}
|
2011-06-18 11:10:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an instance of a statement parser class and invokes statement parsing.
|
|
|
|
*
|
|
|
|
* @param string $statement MySQL statement to parse
|
|
|
|
* @param string $parserClass Parser class to use
|
|
|
|
*
|
2011-06-19 13:47:44 +00:00
|
|
|
* @return array
|
2011-06-18 11:10:40 +00:00
|
|
|
*/
|
|
|
|
private function _parseStatement($statement, $parserClass)
|
|
|
|
{
|
|
|
|
$classFilename = str_replace('_', '/', $parserClass) . '.php';
|
|
|
|
include_once $classFilename;
|
|
|
|
$parserObject = new $parserClass;
|
|
|
|
|
|
|
|
if (!$parserObject instanceof Msd_Sql_Parser_Interface) {
|
|
|
|
throw new Msd_Sql_Parser_Exception('The given parser class must implement Msd_Sql_Parser_Interface!');
|
|
|
|
}
|
|
|
|
|
2011-06-19 13:47:44 +00:00
|
|
|
return $parserObject->parse($statement);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the parsing summary.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getSummary()
|
|
|
|
{
|
|
|
|
return $this->_parsingSummary;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rewind (reset) the internal pointer position af the parsed statements array.
|
|
|
|
*
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function rewind()
|
|
|
|
{
|
|
|
|
return reset($this->_parsedStatements);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the current value af the parsed statements array.
|
|
|
|
*
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function current()
|
|
|
|
{
|
|
|
|
return current($this->_parsedStatements);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the current key af the parsed statements array.
|
|
|
|
*
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function key()
|
|
|
|
{
|
|
|
|
return key($this->_parsedStatements);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Move the internal pointer af the parsed statements array to the next position.
|
|
|
|
*
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function next()
|
|
|
|
{
|
|
|
|
return next($this->_parsedStatements);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validates the internal pointer position af the parsed statements array.
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function valid()
|
|
|
|
{
|
|
|
|
return key($this->_parsedStatements) !== null;
|
2011-06-18 11:10:40 +00:00
|
|
|
}
|
2011-06-17 21:06:54 +00:00
|
|
|
}
|