From eef38cec0c708cc98d96eca1251721e7cbc4055f Mon Sep 17 00:00:00 2001 From: DSB Date: Sun, 19 Jun 2011 21:26:17 +0000 Subject: [PATCH] SQL-Parser: - implemented a new sql-object that can be handed over to the statement parsing classes (Implemented only for select for now) QA Added library path constant to public/index.php --- application/controllers/SqlController.php | 5 +- library/Msd/Sql/Object.php | 208 +++++++++++++++++++ library/Msd/Sql/Parser.php | 119 +++-------- library/Msd/Sql/Parser/Exception.php | 3 +- library/Msd/Sql/Parser/Interface.php | 8 +- library/Msd/Sql/Parser/Statement/Comment.php | 2 +- library/Msd/Sql/Parser/Statement/Create.php | 2 +- library/Msd/Sql/Parser/Statement/Drop.php | 2 +- library/Msd/Sql/Parser/Statement/Insert.php | 2 +- library/Msd/Sql/Parser/Statement/Select.php | 8 +- library/Msd/Sql/Parser/Statement/Unlock.php | 2 +- public/index.php | 6 + 12 files changed, 261 insertions(+), 106 deletions(-) create mode 100644 library/Msd/Sql/Object.php diff --git a/application/controllers/SqlController.php b/application/controllers/SqlController.php index 7d85945..52d18ef 100644 --- a/application/controllers/SqlController.php +++ b/application/controllers/SqlController.php @@ -388,9 +388,10 @@ class SqlController extends Zend_Controller_Action $query = trim($query); if ($query > '') { $this->_db->selectDb($config->get('dynamic.dbActual')); - $parser = new Msd_Sql_Parser($query, true); + $sqlObject = new Msd_Sql_Object($query); + $parser = new Msd_Sql_Parser($sqlObject, true); $parser->parse(); - print_r($parser->getDebugOutput()); + //echo $parser->getDebugOutput(); $statements = $parser->getParsedStatements(); foreach ($statements as $statement) { try { diff --git a/library/Msd/Sql/Object.php b/library/Msd/Sql/Object.php new file mode 100644 index 0000000..bf606db --- /dev/null +++ b/library/Msd/Sql/Object.php @@ -0,0 +1,208 @@ +_data = $sqlData; + } + + /** + * Sets data of object + * + * @param string $sqlData The queries + * + * @return void + */ + public function setData($sqlData = '') + { + $this->_data = $sqlData; + } + + /** + * Append data to already given data. + * + * @param string $sqlData The sql-string to be appended + * + * @return void + */ + private function appendData($sqlData) + { + $this->_data .= $sqlData; + } + + /** + * Get the actual position of the pointer. + * + * @return int Pointer position + */ + public function getPointer() + { + return $this->_pointer; + } + + /** + * Set the actual position of the pointer. + * + * @param int $position Position of pointer + * + * @return void + */ + public function setPointer($position) + { + $this->_pointer = $position; + } + + /** + * Move pointer to the beginning of the next command. + * + * Skip all spaces and line-breaks until a character is found that might be the beginning + * of a new sql command. + * + * @return int New pointer position + */ + public function movePointerToNextCommand() + { + $pointer = $this->getPointer(); + $dataSize = strlen($this->_data); + $skip = array(';', ' ', "\n", "\r"); + while ($pointer < $dataSize && in_array($this->_data[$pointer], $skip)) { + $pointer++; + } + $this->setPointer($pointer); + return $pointer; + } + + /** + * Get some characters of data. + * + * @param int $nrOfCharacters Number of characters to get + * + * @return string Sql data from the pointer position to end or to the nr of chars to fetch + */ + public function getData($nrOfCharacters = null) + { + if ($nrOfCharacters > 0) { + return substr($this->_data, $this->_pointer, $nrOfCharacters); + } else { + return substr($this->_data, $this->_pointer); + } + } + + /** + * Get length of data string. + * + * @return int Length of data string + */ + public function getLength() + { + return strlen($this->_data); + } + + /** + * Check if pointer has reached the end of the data string. + * + * @return bool + */ + public function hasMoreToProcess() + { + if ($this->_pointer < $this->getLength()) { + return true; + } else { + return false; + } + } + + /** + * Set the parser state. + * + * @param string $state The parsing state we are actually in. + * + * @return void + */ + public function setState($state) + { + $this->_state = $state; + } + + /** + * Find the next unescaped occurance of $match. + * + * Begins to search at the actual postion of the pointer. + * + * @param string $match + * + * @return int + */ + public function getPosition($match = ';') + { + $pointer = $this->getPointer(); + $offset = $pointer; + $notFound = true; + $length = $this->getLength()-1; + while ($notFound && $offset < $length) { + //echo "
Checking: ". substr($this->_data, $offset); + $nextHit = strpos($this->_data, $match, $offset); + //echo "
Next hit is :".intval($nextHit); + if ($nextHit === false) { + $nextHit = $this->getLength() - $pointer; + } + // now check if we found an escaped occurance + $string = substr($this->_data, $pointer, $nextHit); + $string=str_replace('\\\\','',trim($string)); + $quotes=substr_count($string,'\''); + $escaped_quotes=substr_count($string,'\\\''); + if (($quotes-$escaped_quotes) % 2 == 0) { + // hit was not escaped - we found the match + $notFound = false; + } else { + // keep on looking, this was escaped + $offset = $pointer + $nextHit +1; + } + } + return $nextHit; + } +} diff --git a/library/Msd/Sql/Parser.php b/library/Msd/Sql/Parser.php index 9fedf96..e36089d 100644 --- a/library/Msd/Sql/Parser.php +++ b/library/Msd/Sql/Parser.php @@ -4,12 +4,11 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ -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. @@ -69,14 +68,12 @@ class Msd_Sql_Parser implements Iterator * 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 - * @param bool $debug If turned on, detection of queries is logged + * @param Msd_Sql_Object $sqlObject SQL-Object holding the data to be parsed + * @param bool $debug If turned on, detection of queries is logged */ - public function __construct($sqlQuery = null, $debug = false) + public function __construct(Msd_Sql_Object $sqlObject, $debug = false) { - if ($sqlQuery !== null) { - $this->_rawQuery = $sqlQuery; - } + $this->_sql = $sqlObject; $this->_debug = $debug; } @@ -86,61 +83,32 @@ class Msd_Sql_Parser implements Iterator * * @throws Msd_Sql_Parser_Exception * - * @param string $sqlQuery Raw MySQL query to parse - * * @return void */ - public function parse($sqlQuery = null) + public function parse() { - if ($sqlQuery === null) { - if ($this->_rawQuery === null) { - throw new Msd_Sql_Parser_Exception('You must specify a MySQL query for parsing!'); - } - $sqlQuery = $this->_rawQuery; - } - - $sqlQuery = trim($sqlQuery); - + // get first characters to extract the kind of query we have to process $statementCounter = 0; - $startPos = 0; - $queryLength = strlen($sqlQuery); - while ($startPos < $queryLength) { - $statementCounter++; - // move pointer to the next character we can interprete - while ($startPos < $queryLength && in_array($sqlQuery[$startPos], array(' ', "\n", "\r"))) { - $startPos++; - } + while ($this->_sql->hasMoreToProcess()) { + $this->_sql->movePointerToNextCommand(); + $endOfCommand = $this->_sql->getPosition(' '); + $sqlQuery = $this->_sql->getData($endOfCommand); + //echo "
Query beginn: ".$sqlQuery; - $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; - } + $parts = explode(' ', $sqlQuery); + $statement = strtolower($parts[0]); // check for comments or conditional comments - $commentCheck = substr($statement, 0, 2); + $commentCheck = substr($sqlQuery, 0, 2); if (isset($this->_sqlComments[$commentCheck]) || substr($statement, 0, 3) == '/*!') { - $commentEnd = $this->_sqlComments[$commentCheck]; - $endPos = strpos($sqlQuery, $commentEnd, $startPos) + strlen($commentEnd); - $comment = substr($sqlQuery, $startPos, $endPos - $startPos); - $this->_parseStatement($comment, 'Msd_Sql_Parser_Statement_Comment'); - $startPos = $endPos+1; - continue; + $statement = 'Comment'; } - $parserClass = 'Msd_Sql_Parser_Statement_' . ucwords($statement); - $endPos = $this->_getStatementEndPos($sqlQuery, $startPos); - $completeStatement = trim(substr($sqlQuery, $startPos, $endPos - $startPos)); - $startPos = $endPos + 1; - $foundStatement = $this->_parseStatement($completeStatement, $parserClass); + $foundStatement = $this->_parseStatement($this->_sql, ucwords($statement)); if ($this->_debug) { $this->_debugOutput .= '
Extracted statement: '.$foundStatement; } $this->_parsedStatements[] = $foundStatement; + $statementCounter++; // increment query type counter if (!isset($this->_parsingSummary[$statement])) { $this->_parsingSummary[$statement] = 0; @@ -149,57 +117,26 @@ class Msd_Sql_Parser implements Iterator } } - /** - * 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 - */ - private function _getStatementEndPos($sqlQuery, $startPos = 0) - { - $nextString = strpos($sqlQuery, "'", $startPos); - $nextSemicolon = strpos($sqlQuery, ';', $startPos); - if ($nextString === false) { - if ($nextSemicolon === false) { - return strlen($sqlQuery); - } - - return $nextSemicolon+1; - } - - while ($nextString < $nextSemicolon) { - $nextString = strpos($sqlQuery, "'", $nextString + 1); - $nextSemicolon = strpos($sqlQuery, ';', $nextString + 1); - $nextString = strpos($sqlQuery, "'", $nextString + 1); - if ($nextString === false) { - break; - } - } - - return $nextSemicolon; - } - /** * Creates an instance of a statement parser class and invokes statement parsing. * * @throws Msd_Sql_Parser_Exception * - * @param string $statement MySQL statement to parse - * @param string $parserClass Parser class to use + * @param Msd_Sql_Object $sqlObject MySQL statement to parse + * @param string $statement Parser class to use * * @return array */ - private function _parseStatement($statement, $parserClass) + private function _parseStatement(Msd_Sql_Object $sqlObject, $statement) { - try { - $parserObject = new $parserClass; - } catch (Exception $e) { - throw new Msd_Sql_Parser_Exception("Can't suitable parser for the statement!"); + $statementPath = '/Msd/Sql/Parser/Statement/' . $statement; + if ($statement !== 'Select') die("Not implemented yet: ".$statement); + if (!file_exists(LIBRARY_PATH . $statementPath . '.php')) { + throw new Msd_Sql_Parser_Exception("Can't find statement class for statement: " . $statement); } - - return $parserObject->parse($statement); + $statementClass = 'Msd_Sql_Parser_Statement_' . $statement; + $parserObject = new $statementClass; + return $parserObject->parse($sqlObject); } /** diff --git a/library/Msd/Sql/Parser/Exception.php b/library/Msd/Sql/Parser/Exception.php index 62e25bc..c392511 100644 --- a/library/Msd/Sql/Parser/Exception.php +++ b/library/Msd/Sql/Parser/Exception.php @@ -4,12 +4,11 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ -require_once 'Msd/Exception.php'; /** * Exception class for all SQL-Parser exceptions. * diff --git a/library/Msd/Sql/Parser/Interface.php b/library/Msd/Sql/Parser/Interface.php index b3eda03..b74d0fb 100644 --- a/library/Msd/Sql/Parser/Interface.php +++ b/library/Msd/Sql/Parser/Interface.php @@ -4,7 +4,7 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ @@ -13,7 +13,7 @@ * Interface definition for MySQL statement parsers. * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser */ interface Msd_Sql_Parser_Interface { @@ -22,9 +22,9 @@ interface Msd_Sql_Parser_Interface * * @abstract * - * @param string $statement MySQL statement. + * @param Msd_Sql_Object $sqlObject MySQL statement object. * * @return void */ - public function parse($statement); + public function parse(Msd_Sql_Object $sqlObject); } \ No newline at end of file diff --git a/library/Msd/Sql/Parser/Statement/Comment.php b/library/Msd/Sql/Parser/Statement/Comment.php index 9d56d3b..7b19fb5 100644 --- a/library/Msd/Sql/Parser/Statement/Comment.php +++ b/library/Msd/Sql/Parser/Statement/Comment.php @@ -4,7 +4,7 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ diff --git a/library/Msd/Sql/Parser/Statement/Create.php b/library/Msd/Sql/Parser/Statement/Create.php index 7ac296c..bea533b 100644 --- a/library/Msd/Sql/Parser/Statement/Create.php +++ b/library/Msd/Sql/Parser/Statement/Create.php @@ -4,7 +4,7 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ diff --git a/library/Msd/Sql/Parser/Statement/Drop.php b/library/Msd/Sql/Parser/Statement/Drop.php index d1bb8a0..58444ce 100644 --- a/library/Msd/Sql/Parser/Statement/Drop.php +++ b/library/Msd/Sql/Parser/Statement/Drop.php @@ -4,7 +4,7 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ diff --git a/library/Msd/Sql/Parser/Statement/Insert.php b/library/Msd/Sql/Parser/Statement/Insert.php index b27e06b..0799890 100644 --- a/library/Msd/Sql/Parser/Statement/Insert.php +++ b/library/Msd/Sql/Parser/Statement/Insert.php @@ -4,7 +4,7 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ diff --git a/library/Msd/Sql/Parser/Statement/Select.php b/library/Msd/Sql/Parser/Statement/Select.php index ba38217..c9d6a60 100644 --- a/library/Msd/Sql/Parser/Statement/Select.php +++ b/library/Msd/Sql/Parser/Statement/Select.php @@ -4,7 +4,7 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ @@ -25,8 +25,12 @@ class Msd_Sql_Parser_Statement_Select implements Msd_Sql_Parser_Interface * * @return void */ - public function parse($statement) + public function parse(Msd_Sql_Object $sql) { + $sql->setState('Select'); + $endOfStatement = $sql->getPosition(';'); + $statement = $sql->getData($endOfStatement); + $sql->setPointer($endOfStatement+1); return $statement; } } diff --git a/library/Msd/Sql/Parser/Statement/Unlock.php b/library/Msd/Sql/Parser/Statement/Unlock.php index fc9cc6a..986b48b 100644 --- a/library/Msd/Sql/Parser/Statement/Unlock.php +++ b/library/Msd/Sql/Parser/Statement/Unlock.php @@ -4,7 +4,7 @@ * http://www.mysqldumper.net * * @package MySQLDumper - * @subpackage SQL-Browser + * @subpackage SQL-Parser * @version SVN: $Rev$ * @author $Author$ */ diff --git a/public/index.php b/public/index.php index 7ce874c..4644fe3 100644 --- a/public/index.php +++ b/public/index.php @@ -9,6 +9,12 @@ defined('APPLICATION_PATH') || define( ) ); +defined('LIBRARY_PATH') || define( + 'LIBRARY_PATH', realpath( + dirname(__FILE__) . DS . '..' . DS . 'library' + ) +); + // Define application environment if (!defined('APPLICATION_ENV')) { $appEnvironment = getenv('APPLICATION_ENV');