diff options
author | godzilla80@gmx.net <> | 2010-02-13 19:04:45 +0000 |
---|---|---|
committer | godzilla80@gmx.net <> | 2010-02-13 19:04:45 +0000 |
commit | 966fd66f217911d079c4bd6a87b09f4a0c5c4736 (patch) | |
tree | 26d4cda965ed5a6ddf2aeb805fcef42877584fd3 | |
parent | 879cced5e01d43378065c938483b55a35ff10834 (diff) |
NEW: Add Beta of master/slave senario solution
- add package System.Testing.Data.Analysis
- add package System.Testing.Data.Distributed
- add sub package System.Testing.Data.Distributed.MasterSlave
- add unittest for System.Testing.Data.Analysis
14 files changed, 1871 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes index 5abd3607..868a31ba 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2636,6 +2636,13 @@ framework/Testing/Data/ActiveRecord/TActiveFinder.php -text framework/Testing/Data/ActiveRecord/TActiveRecord.php -text framework/Testing/Data/ActiveRecord/TActiveRecordBehavior.php -text framework/Testing/Data/ActiveRecord/TActiveRecordCriteria.php -text +framework/Testing/Data/Analysis/TDbStatementAnalysis.php eol=lf +framework/Testing/Data/Analysis/TSimpleDbStatementAnalysis.php eol=lf +framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDataSourceConfig.php eol=lf +framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbConnection.php eol=lf +framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbTransaction.php eol=lf +framework/Testing/Data/Distributed/TDistributedDataSourceConfig.php eol=lf +framework/Testing/Data/Distributed/TDistributedDbConnection.php eol=lf framework/Testing/Data/Schema/TDbColumnSchema.php -text framework/Testing/Data/Schema/TDbCommandBuilder.php -text framework/Testing/Data/Schema/TDbCriteria.php -text @@ -3752,6 +3759,10 @@ tests/unit/Security/TSecurityManagerTest.php -text tests/unit/Security/TUserManagerTest.php -text tests/unit/Security/TUserTest.php -text tests/unit/TComponentTest.php -text +tests/unit/Testing/Data/Analysis/AllTests.php eol=lf +tests/unit/Testing/Data/Analysis/TDbStatementAnalysisParameterTest.php eol=lf +tests/unit/Testing/Data/Analysis/TDbStatementAnalysisTest.php eol=lf +tests/unit/Testing/Data/Analysis/TSimpleDbStatementAnalysisTest.php eol=lf tests/unit/Util/AllTests.php -text tests/unit/Util/TDateTimeStampTest.php -text tests/unit/Util/TLoggerTest.php -text @@ -11,6 +11,7 @@ NEW: Add TActiveTableRow (LCS Team) NEW: Add TActiveTableCell (LCS Team) ENH: Issue#173 - Add "dragdropextra" (superghosting) patch, mouse coordinates and key status to drag & drop controls (Christophe, DevWorx) NEW: Add TActiveMultiView (LCS Team) +NEW: Beta of master/slave senario solution (Yves) Version 3.1.6 to be released BUG: Issue#98 - Missing file in quickstart demo (Chrisotphe) diff --git a/framework/Exceptions/messages/messages.txt b/framework/Exceptions/messages/messages.txt index f9e30b1d..3bd6bbb1 100644 --- a/framework/Exceptions/messages/messages.txt +++ b/framework/Exceptions/messages/messages.txt @@ -468,6 +468,10 @@ ar_save_invalid = The {0} instance cannot be saved because it is either de ar_delete_invalid = The {0} instance cannot be deleted because it is either a new record or a record already deleted. datasource_dbconnection_invalid = TDataSourceConfig.DbConnection '{0}' is invalid. Please make sure it points to a valid application module. +distributeddatasource_child_required = {0} requires one '{1}' child element at minimum. +masterslavedbconnection_connection_exists = {0}.{1} connection already exists. +masterslavedbconnection_interface_required = {0}.{1} requires an instance implementing {2} interface. +slavedbconnection_requires_master = {0} requires a {1}. response_status_reason_missing = HTTP 1.1 need reason for extended status-codes response_status_reason_badchars = For HTTP 1.1 header, the token status-reason must not contain token CR or LF diff --git a/framework/Testing/Data/Analysis/TDbStatementAnalysis.php b/framework/Testing/Data/Analysis/TDbStatementAnalysis.php new file mode 100644 index 00000000..46f9b745 --- /dev/null +++ b/framework/Testing/Data/Analysis/TDbStatementAnalysis.php @@ -0,0 +1,238 @@ +<?php +/** + * IDbStatementAnalysis, TDbStatementAnalysisParameter, + * TDbStatementAnalysis, TDbStatementClassification file. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2010 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Analysis + */ + + /** + * IDbStatementAnalysis interface + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Analysis + * @since 4.0 + */ + interface IDbStatementAnalysis + { + /** + * @param TDbStatementAnalysisParamete + * @return TDbStatementClassification + */ + public static function doClassificationAnalysis(TDbStatementAnalysisParameter $param); + + /** + * @param TDbStatementAnalysisParameter + * @return TDbStatementClassification + */ + public function getClassificationAnalysis(TDbStatementAnalysisParameter $param); + + /** + * @param string PDO drivername of connection + */ + public function setDriverName($value); + + /** + * @return string PDO drivername of connection + */ + public function getDriverName(); + } + + /** + * TDbStatementAnalysisParameter class + * + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Analysis + * @since 4.0 + */ + class TDbStatementAnalysisParameter + { + /** + * @var string The SQL statement that should be analysed + */ + protected $_statement = null; + + /** + * TDbStatementClassification Defaults to 'UNKNOWN' + */ + protected $_defaultClassification = TDbStatementClassification::UNKNOWN; + + /** + * string|null PDO drivername of connection + */ + protected $_drivername = null; + + /** + * @param string The SQL statement that should be analysed + * @param TDbStatementClassification + * @param string|null PDO drivername of connection + */ + public function __construct($statement='', $defaultClassification=null, $drivername=null) + { + $this->setStatement($statement); + $this->setDefaultClassification($defaultClassification); + $this->setDriverName($drivername); + } + + /** + * @param string The SQL statement that should be analysed + */ + public function setStatement($value) + { + $this->_statement = (string)$value; + } + + /** + * @return string The SQL statement that should be analysed + */ + public function getStatement() + { + return $this->_statement; + } + + /** + * @param string|null PDO drivername of connection + */ + public function setDriverName($value) + { + $this->_drivername = ($value===null) ? null : (string)$value; + } + + /** + * @return string|null PDO drivername of connection + */ + public function getDriverName() + { + return $this->_drivername; + } + + /** + * @param TDbStatementClassification Defaults to 'UNKNOWN' + */ + public function setDefaultClassification($value) + { + if($value!==null) + $this->_defaultClassification = (string)$value; + else + $this->_defaultClassification = TDbStatementClassification::UNKNOWN; + } + + /** + * @return TDbStatementClassification + */ + public function getDefaultClassification() + { + return $this->_defaultClassification; + } + } + + /** + * TDbStatementAnalysis class + * + * Basic "dummy" implementation allways return {@link TDbStatementAnalysisParameter::getDefaultClassification DefaultClassification} + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Analysis + * @since 4.0 + */ + class TDbStatementAnalysis implements IDbStatementAnalysis + { + /** + * @var string|null PDO drivername of connection + */ + protected $_drivername = null; + + /** + * @param string|null PDO drivername of connection + */ + public function setDriverName($value) + { + $this->_drivername = ($value===null) ? null : (string)$value; + } + + /** + * @return string|null PDO drivername of connection + */ + public function getDriverName() + { + return $this->_drivername; + } + + /** + * @param TDbStatementAnalysisParamete + * @return TDbStatementClassification + */ + public static function doClassificationAnalysis(TDbStatementAnalysisParameter $param) + { + return $param->getDefaultClassification(); + } + + /** + * @param TDbStatementAnalysisParameter + * @return TDbStatementClassification + */ + public function getClassificationAnalysis(TDbStatementAnalysisParameter $param) + { + return $param->getDefaultClassification(); + } + } + + /** + * TDbStatementClassification + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Analysis + * @since 4.0 + */ + class TDbStatementClassification extends TEnumerable + { + /** + * Structured Query Language + */ + const SQL = 'SQL'; + + /** + * Data Definition Language + */ + const DDL = 'DDL'; + + /** + * Data Manipulation Language + */ + const DML = 'DML'; + + /** + * Data Control Language + */ + const DCL = 'DCL'; + + /** + * Transaction Control Language + */ + const TCL = 'TCL'; + + /** + * classification depends on subsequent statement(s) + */ + const CONTEXT = 'CONTEXT'; + + /** + * unable to detect real classification or multiple possibilities + */ + const UNKNOWN = 'UNKNOWN'; + } +?> diff --git a/framework/Testing/Data/Analysis/TSimpleDbStatementAnalysis.php b/framework/Testing/Data/Analysis/TSimpleDbStatementAnalysis.php new file mode 100644 index 00000000..e4ab3391 --- /dev/null +++ b/framework/Testing/Data/Analysis/TSimpleDbStatementAnalysis.php @@ -0,0 +1,134 @@ +<?php +/** + * TSimpleDbStatementAnalysis file. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2010 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Analysis + */ + + Prado::using('System.Testing.Data.Analysis.TDbStatementAnalysis'); + + /** + * TSimpleDbStatementAnalysis class + * + * IMPORTANT!!! + * BETA Version - Use with care and NOT in production environment (only tested with MySql) + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Analysis + * @since 4.0 + * @todo SELECT * FOR UPDATE (row lock) + * @todo SELECT * INTO [ TEMPORARY | TEMP ] [ TABLE ] new_table (PostgreSQL) + * @todo mysql conditional commands in multiline comments e.g. / *! MySQL specific code * / + */ + class TSimpleDbStatementAnalysis extends TDbStatementAnalysis + { + /** + * @var array mapping of commands to classification + */ + protected static $mappingClassificationAnalysis = array( + 'CREATE' => TDbStatementClassification::DDL, + 'DROP' => TDbStatementClassification::DDL, + 'ALTER' => TDbStatementClassification::DDL, + 'RENAME' => TDbStatementClassification::DDL, + + 'INSERT' => TDbStatementClassification::DML, + 'UPDATE' => TDbStatementClassification::DML, + 'DELETE' => TDbStatementClassification::DML, + 'REPLACE' => TDbStatementClassification::DML, + 'TRUNCATE' => TDbStatementClassification::DML, + 'LOAD' => TDbStatementClassification::DML, + + 'GRANT' => TDbStatementClassification::DCL, + 'REVOKE' => TDbStatementClassification::DCL, + + 'XA' => TDbStatementClassification::TCL, + 'SAVEPOINT' => TDbStatementClassification::TCL, + 'CHECKPOINT' => TDbStatementClassification::TCL, + 'RELEASE SAVEPOINT' => TDbStatementClassification::TCL, + 'START TRANSACTION' => TDbStatementClassification::TCL, + 'BEGIN' => TDbStatementClassification::TCL, + 'COMMIT' => TDbStatementClassification::TCL, + 'ROLLBACK' => TDbStatementClassification::TCL, + 'LOCK' => TDbStatementClassification::TCL, + 'UNLOCK' => TDbStatementClassification::TCL, + 'ABORT' => TDbStatementClassification::TCL, + 'END' => TDbStatementClassification::TCL, + + 'SELECT' => TDbStatementClassification::SQL, + + 'SHOW' => TDbStatementClassification::SQL, + 'DESCRIBE' => TDbStatementClassification::SQL, + 'EXPLAIN' => TDbStatementClassification::SQL, + 'PRAGMA' => TDbStatementClassification::SQL, + + 'SET' => TDbStatementClassification::CONTEXT, + 'USE' => TDbStatementClassification::CONTEXT, + + 'CALL' => TDbStatementClassification::UNKNOWN, + 'EXEC' => TDbStatementClassification::UNKNOWN, + 'PREPARE' => TDbStatementClassification::UNKNOWN, + 'EXECUTE' => TDbStatementClassification::UNKNOWN, + 'DEALLOCATE' => TDbStatementClassification::UNKNOWN, + ); + + /** + * @var array + */ + protected static $cacheClassificationAnalysis = array(); + + /** + * @var string + */ + protected static $regExpClassificationAnalysis = null; + + /** + * @param TDbStatementAnalysisParamete + * @return TDbStatementClassification + */ + public static function doClassificationAnalysis(TDbStatementAnalysisParameter $param) + { + $statement = $param->getStatement(); + $default = $param->getDefaultClassification(); + + $hash = md5($statement . '-' . $default); + + if( isset(self::$cacheClassificationAnalysis[$hash]) ) + return self::$cacheClassificationAnalysis[$hash]; + + self::$cacheClassificationAnalysis[$hash] = $default; + + $statement = preg_replace('/(?:--|\\#)[\x20\\t\\S]*\s+|\/\\*[\x20\\t\\n\\r\\S]*?\\*\//Ssmux', '', $statement); + $statement = preg_replace('/[\s]+/Smu', ' ', $statement); + $statement = trim($statement); + + if(self::$regExpClassificationAnalysis===null) + self::$regExpClassificationAnalysis = '/^(' . str_replace(' ', '\x20', implode('|', array_keys(self::$mappingClassificationAnalysis))) . ')+[\s]+.*|\k1/Siu'; + + $cmd = strToUpper(preg_replace(self::$regExpClassificationAnalysis, '\1', $statement)); + + if( isset(self::$mappingClassificationAnalysis[$cmd]) ) + self::$cacheClassificationAnalysis[$hash] = self::$mappingClassificationAnalysis[$cmd]; + + return self::$cacheClassificationAnalysis[$hash]; + } + + /** + * @param TDbStatementAnalysisParameter + * @return TDbStatementClassification + */ + public function getClassificationAnalysis(TDbStatementAnalysisParameter $param) + { + if( ($drivername = $this->getDriverName())!== null ) + $param->setDriverName($drivername); + + return self::doClassificationAnalysis($param); + } + } +?>
\ No newline at end of file diff --git a/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDataSourceConfig.php b/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDataSourceConfig.php new file mode 100644 index 00000000..ee486fe8 --- /dev/null +++ b/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDataSourceConfig.php @@ -0,0 +1,154 @@ +<?php +/** + * TMasterSlaveDataSourceConfig class file. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2010 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + */ + + Prado::using('System.Testing.Data.Distributed.TDistributedDataSourceConfig'); + Prado::using('System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbConnection'); + + /** + * TMasterSlaveDataSourceConfig module class provides <module> configuration for database connections in master/slave senario. + * + * IMPORTANT!!! + * BETA Version - Use with care and NOT in production environment (only tested with MySql) + * + * Example usage: mysql connection + * <code> + * <modules> + * <module id="db1" class="System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDataSourceConfig" + * ConnectionClass="System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbConnection" + * DistributedConnectionClass="System.Testing.Data.Distributed.MasterSlave.TSlaveDbConnection" + * DbConnection.StatementAnalyserClass="System.Testing.Data.Analysis.TSimpleDbStatementAnalysis"> + * <database ConnectionString="mysql:host=127.0.0.1;port=3306;dbname=mydatabase" Username="dbuser" Password="dbpass" /> + * <slave ConnectionString="mysql:host=127.0.0.1;port=3307;dbname=mydatabase" Username="dbuser" Password="dbpass" Weight="3" /> + * <slave ConnectionString="mysql:host=127.0.0.1;port=3308;dbname=mydatabase" Username="dbuser" Password="dbpass" Weight="2" /> + * <slave ConnectionString="mysql:host=127.0.0.1;port=3309;dbname=mydatabase" Username="dbuser" Password="dbpass" Weight="5" /> + * </module> + * </modules> + * </code> + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + * @since 4.0 + */ + class TMasterSlaveDataSourceConfig extends TDistributedDataSourceConfig + { + /** + * @var boolean + */ + private $_bMasterInitialized = false; + + /** + * @var boolean + */ + private $_bSlaveInitialized = false; + + /** + * Constructor + */ + public function __construct() + { + $this->setConnectionClass('System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbConnection'); + $this->setDistributedConnectionClass('System.Testing.Data.Distributed.MasterSlave.TSlaveDbConnection'); + } + + /** + * Initalize the database connection properties from attributes in slave tag. + * @param TXmlDocument xml configuration. + */ + protected function initChildConnectionData($xml) + { + parent::initChildConnectionData($xml, 'slave'); + } + + /** + * @return IMasterSlaveDbConnection + */ + public function getDbConnection() { + $id = $this->getID(); + static $result = array(); + + if(!isset($result[$id])) + $result[$id] = parent::getDbConnection(); + + if(!$this->bInitialized) + return $result[$id]; + + if($this->_bMasterInitialized) + return $result[$id]; + + $this->_bMasterInitialized = true; + + if(!$result[$id] instanceof IMasterSlaveDbConnection) + return $result[$id]; + + $slave = parent::getDistributedDbConnection(); + + if($slave instanceof ISlaveDbConnection && $slave->getMasterConnection()===null) + $slave->setMasterConnection($result[$id]); + + if($result[$id]->getSlaveConnection()===null) + $result[$id]->setSlaveConnection($slave); + + return $result[$id]; + } + + /** + * @return ISlaveDbConnection + */ + public function getDistributedDbConnection() { + $id = $this->getID(); + static $result = array(); + + if(!isset($result[$id])) + $result[$id] = parent::getDistributedDbConnection(); + + if(!$this->bInitialized) + return $result[$id]; + + if($this->_bSlaveInitialized) + return $result[$id]; + + $this->_bSlaveInitialized = true; + + if(!$result[$id] instanceof ISlaveDbConnection) + return $result[$id]; + + $master = parent::getDbConnection(); + + if($master instanceof IMasterSlaveDbConnection && ($master->getSlaveConnection()===null)) + $master->setSlaveConnection($result[$id]); + + if($result[$id]->getMasterConnection()===null) + $result[$id]->setMasterConnection($master); + + return $result[$id]; + } + + /** + * Alias for getDbConnection(). + * @return IMasterSlaveDbConnection database connection. + */ + public function getMasterDbConnection() + { + return $this->getDbConnection(); + } + + /** + * Alias for getDistributedDbConnection(). + * @return ISlaveDbConnection database connection. + */ + public function getSlaveDbConnection() + { + return $this->getDistributedDbConnection(); + } + } +?>
\ No newline at end of file diff --git a/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbConnection.php b/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbConnection.php new file mode 100644 index 00000000..e6b71931 --- /dev/null +++ b/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbConnection.php @@ -0,0 +1,477 @@ +<?php +/** + * IMasterSlaveDbConnection, ISlaveDbConnection inferface, + * TMasterSlaveDbConnection, TSlaveDbConnection class file. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2010 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + * @todo Test with Backport of Yii's DBO + */ + + Prado::using('System.Data.TDbConnection'); + Prado::using('System.Testing.Data.Distributed.TDistributedDbConnection'); + Prado::using('System.Collections.TQueue'); + Prado::using('System.Collections.TStack'); + Prado::using('System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbTransaction'); + + /** + * IMasterSlaveDbConnection interface + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + * @since 4.0 + */ + interface IMasterSlaveDbConnection extends IDistributedDbConnection + { + /** + * @return TQueue + */ + public function getStatementQueue(); + + /** + * @return ISlaveDbConnection|null + */ + public function getSlaveConnection(); + + /** + * @param ISlaveDbConnection|null + */ + public function setSlaveConnection($conn); + + /** + * @return TMasterSlaveDbConnectionForceMaster + */ + public function getForceMaster(); + + /** + * @param TMasterSlaveDbConnectionForceMaster + */ + public function setForceMaster($value); + + } + + /** + * ISlaveDbConnection interface + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + * @since 4.0 + */ + interface ISlaveDbConnection extends IDistributedDbConnection + { + /** + * @return IMasterSlaveDbConnection|null + */ + public function getMasterConnection(); + + /** + * @param IMasterSlaveDbConnection|null + */ + public function setMasterConnection($conn); + + /** + * @return TMasterSlaveDbConnectionForceMaster + */ + public function getForceMaster(); + + /** + * @param TMasterSlaveDbConnectionForceMaster + */ + public function setForceMaster($value); + } + + /** + * TMasterSlaveDbConnection class + * + * IMPORTANT!!! + * BETA Version - Use with care and NOT in production environment (only tested with MySql) + * + * TMasterSlaveDbConnection represents a master connection to a database in master/slave senario. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + * @since 4.0 + */ + class TMasterSlaveDbConnection extends TDistributedDbConnection implements IMasterSlaveDbConnection + { + /** + * @var TSlaveDbConnection|null + */ + private $_connSlave = null; + + /** + * @var TQueue + */ + private $_statementQueue = null; + + /** + * @var integer + * @see TMasterSlaveDbConnectionForceMaster + */ + private $_forceMaster = TMasterSlaveDbConnectionForceMaster::OFF_AUTOMATIC; + + private $_forceMasterStack = null; + + /** + * Constructor. + * Note, the DB connection is not established when this connection + * instance is created. Set {@link setActive Active} property to true + * to establish the connection. + * + * @param string The Data Source Name, or DSN, contains the information required to connect to the database. + * @param string The user name for the DSN string. + * @param string The password for the DSN string. + * @param string Charset used for DB Connection (MySql & pgsql only). If not set, will use the default charset of your database server + * @see http://www.php.net/manual/en/function.PDO-construct.php + */ + public function __construct($dsn='', $username='', $password='', $charset='') + { + parent::__construct($dsn, $username, $password, $charset); + parent::setTransactionClass('System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbTransaction'); + } + + /** + * @return TQueue + */ + public function getStatementQueue() + { + if($this->_statementQueue===null) + $this->_statementQueue = new TQueue(); + return $this->_statementQueue; + } + + /** + * @return ISlaveDbConnection|null + */ + public function getSlaveConnection() + { + return $this->_connSlave; + } + + /** + * @param ISlaveDbConnection|null + * @throws TDbConnectionException if the slave connection already exists + * @throws TDbConnectionException connection not instance of ISlaveDbConnection + */ + public function setSlaveConnection($conn) + { + if($this->_connSlave !== null) + throw new TDbConnectionException('masterslavedbconnection_connection_exists', get_class($this), 'SlaveConnection'); + + if($conn!==null && !$conn instanceof ISlaveDbConnection) + throw new TDbConnectionException('masterslavedbconnection_interface_required', get_class($this), 'SlaveConnection', 'ISlaveDbConnection'); + + $this->_connSlave = $conn; + + if($this->_connSlave===null) return; + if($this->_connSlave->getMasterConnection()!==null) return; + + $this->_connSlave->setMasterConnection($this); + } + + /** + * Creates a command for execution. + * @param string SQL statement associated with the new command. + * @return TDistributedDbCommand the DB command + * @throws TDbException if the connection is not active + */ + public function createCommand($sql) + { + $force = $this->getForceMaster(); + if($force == TMasterSlaveDbConnectionForceMaster::ON_MANUAL || $force == TMasterSlaveDbConnectionForceMaster::ON_TRANSACTION) + { + Prado::log('ForceMaster: ' . $force, TLogger::DEBUG, 'System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbConnection'); + return new TDistributedDbCommand($this, $sql, TDbStatementClassification::UNKNOWN); + } + + $bEnqueue = false; + $bMaster = true; + + $classification = $this->getStatementClassification($sql); + + switch($classification) { + case TDbStatementClassification::CONTEXT: + $bEnqueue = true; + $bMaster = true; + break; + case TDbStatementClassification::SQL: + $bMaster = false; + break; + case TDbStatementClassification::TCL: + $this->setForceMaster(TMasterSlaveDbConnectionForceMaster::ON_TCL); + case TDbStatementClassification::DDL: + case TDbStatementClassification::DML: + case TDbStatementClassification::DCL: + case TDbStatementClassification::UNKNOWN: + default: + $bMaster = true; + break; + } + + $bMaster = $bMaster || $this->getForceMaster(); + + $result = new TDistributedDbCommand(($bMaster ? $this : $this->getSlaveConnection()), $sql, $classification); + //$result = new TDistributedDbCommand($this, $sql, $classification); + + if($bEnqueue) + $this->getStatementQueue()->enqueue($result); + + return $result; + } + + /** + * @return TMasterSlaveDbConnectionForceMaster + */ + public function getForceMaster() + { + return $this->_forceMaster; + } + + /** + * @param TMasterSlaveDbConnectionForceMaster + */ + public function setForceMaster($value) + { + if($this->_forceMasterStack===null) + $this->_forceMasterStack = new TStack(); + + if($value) + { + $this->_forceMaster = (integer)$value; + $this->_forceMasterStack->push((integer)$value); + } + elseif($this->_forceMasterStack->count() > 0) + $this->_forceMaster = $this->_forceMasterStack->pop(); + else + $this->_forceMaster = (integer)$value; + } + + /** + * @return TDbConnectionServerRole + */ + public function getServerRole() + { + return TDbConnectionServerRole::Master; + } + } + + /** + * TSlaveDbConnection class + * + * IMPORTANT!!! + * BETA Version - Use with care and NOT in production environment (only tested with MySql) + * + * TSlaveDbConnection represents a readonly connection to a database in master/slave senario. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + * @since 4.0 + */ + class TSlaveDbConnection extends TDbConnection implements ISlaveDbConnection + { + /** + * @var TMasterSlaveDbConnection|null + */ + private $_connMaster = null; + + /** + * @return IMasterSlaveDbConnection|null + */ + public function getMasterConnection() + { + return $this->_connMaster; + } + + /** + * @param IMasterSlaveDbConnection|null + * @throws TDbConnectionException if the master connection already exists + * @throws TDbConnectionException connection not instance of IMasterSlaveDbConnection + */ + public function setMasterConnection($conn) + { + if($this->_connMaster!==null) + throw new TDbConnectionException('masterslavedbconnection_connection_exists', get_class($this), 'MasterConnection'); + + if($conn!==null && !$conn instanceof IMasterSlaveDbConnection) + throw new TDbConnectionException('masterslavedbconnection_interface_required', get_class($this), 'MasterConnection', 'IMasterSlaveDbConnection'); + + $this->_connMaster = $conn; + } + + /** + * Creates a command for execution. + * @param string SQL statement associated with the new command. + * @return TDistributedDbCommand the DB command + * @throws TDbException if the connection is not active + */ + public function createCommand($sql) + { + $force = $this->getForceMaster(); + if($force == TMasterSlaveDbConnectionForceMaster::ON_MANUAL || $force == TMasterSlaveDbConnectionForceMaster::ON_TRANSACTION) + { + Prado::log('ForceMaster: ' . $force, TLogger::DEBUG, 'System.Testing.Data.Distributed.MasterSlave.TSlaveDbConnection'); + return new TDistributedDbCommand($this->getMasterConnection(), $sql, TDbStatementClassification::UNKNOWN); + } + + $bEnqueue = false; + $bMaster = false; + + $classification = $this->getStatementClassification($sql); + + switch($classification) { + case TDbStatementClassification::SQL: + $bMaster = false; + break; + case TDbStatementClassification::CONTEXT: + $bEnqueue = true; + $bMaster = true; + break; + case TDbStatementClassification::TCL: + $this->setForceMaster(TMasterSlaveDbConnectionForceMaster::ON_TCL); + case TDbStatementClassification::DDL: + case TDbStatementClassification::DML: + case TDbStatementClassification::DCL: + case TDbStatementClassification::UNKNOWN: + default: + $bMaster = true; + break; + } + + $bMaster = $bMaster || $this->getForceMaster(); + + $result = new TDistributedDbCommand(($bMaster ? $this->getMasterConnection() : $this), $sql, $classification); + + if($bEnqueue) + $this->getMasterConnection()->getStatementQueue()->enqueue($result); + + return $result; + } + + /** + * Starts a transaction. + * @return TDbTransaction the transaction initiated + * @throws TDbException if no master connection exists or the connection is not active + */ + public function beginTransaction() + { + if($this->getMasterConnection() === null) + throw new TDbException('slavedbconnection_requires_master', getclass($this), 'MasterConnection'); + + return $this->getMasterConnection()->beginTransaction(); + } + + /** + * @return string Transaction class name to be created by calling {@link TDbConnection::beginTransaction}. + * @throws TDbException if no master connection exists + */ + public function getTransactionClass() + { + if($this->getMasterConnection() === null) + throw new TDbException('slavedbconnection_requires_master', getclass($this), 'MasterConnection'); + return $this->getMasterConnection()->getTransactionClass(); + } + + /** + * @param string Transaction class name to be created by calling {@link TDbConnection::beginTransaction}. + * @throws TDbException if no master connection exists + */ + public function setTransactionClass($value) + { + if($this->getMasterConnection() === null) + throw new TDbException('slavedbconnection_requires_master', getclass($this), 'MasterConnection'); + $this->getMasterConnection()->setTransactionClass($value); + } + + /** + * Gets the statement analyser of type given by + * {@link setStatementAnalyserClass StatementAnalyserClass }. + * @return IDbStatementAnalysis statement analyser. + * @throws TDbException if no master connection exists + */ + public function getStatementAnalyser() + { + if($this->getMasterConnection() === null) + throw new TDbException('slavedbconnection_requires_master', getclass($this), 'MasterConnection'); + return $this->getMasterConnection()->getStatementAnalyser(); + } + + /** + * The statement analyser class name to be created when {@link getStatementAnalyserClass} + * method is called. The {@link setStatementAnalyserClass StatementAnalyserClass} + * property must be set before calling {@link getStatementAnalyser} if you wish to + * create the connection using the given class name. + * @param string Statement analyser class name. + * @throws TDbException if no master connection exists + */ + public function setStatementAnalyserClass($value) + { + if($this->getMasterConnection() === null) + throw new TDbException('slavedbconnection_requires_master', getclass($this), 'MasterConnection'); + $this->getMasterConnection()->setStatementAnalyserClass($value); + } + + /** + * @param string Statement analyser class name to be created. + * @throws TDbException if no master connection exists + */ + public function getStatementAnalyserClass() + { + if($this->getMasterConnection() === null) + throw new TDbException('slavedbconnection_requires_master', getclass($this), 'MasterConnection'); + return $this->getMasterConnection()->getStatementAnalyserClass(); + } + + /** + * @return TMasterSlaveDbConnectionForceMaster + * @throws TDbException if no master connection exists + */ + public function getForceMaster() + { + if($this->getMasterConnection() === null) + throw new TDbException('slavedbconnection_requires_master', getclass($this), 'MasterConnection'); + return $this->getMasterConnection()->getForceMaster(); + } + + /** + * @param TMasterSlaveDbConnectionForceMaster + * @throws TDbException if no master connection exists + */ + public function setForceMaster($value) + { + if($this->getMasterConnection() === null) + throw new TDbException('slavedbconnection_requires_master', getclass($this), 'MasterConnection'); + $this->getMasterConnection()->setForceMaster($value); + } + + /** + * @return TDbConnectionServerRole + */ + public function getServerRole() + { + return TDbConnectionServerRole::Slave; + } + } + + /** + * TMasterSlaveDbConnectionForceMaster class + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + * @since 4.0 + */ + class TMasterSlaveDbConnectionForceMaster extends TEnumerable + { + const OFF_AUTOMATIC = 0; + const ON_MANUAL = 1; + const ON_TCL = -1; + const ON_TRANSACTION = -2; + } diff --git a/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbTransaction.php b/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbTransaction.php new file mode 100644 index 00000000..254afdb5 --- /dev/null +++ b/framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbTransaction.php @@ -0,0 +1,83 @@ +<?php +/** + * TMasterSlaveDbTransaction class file. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2010 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + */ + Prado::using('System.Data.TDbTransaction'); + + /** + * TMasterSlaveDbTransaction class + * + * IMPORTANT!!! + * BETA Version - Use with care and NOT in production environment (only tested with MySql) + * + * TMasterSlaveDbTransaction represents a DB transaction in master/slave senario. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed.MasterSlave + * @since 4.0 + */ + class TMasterSlaveDbTransaction extends TDbTransaction + { + /** + * @var boolean + */ + private $_compatible = false; + + /** + * Constructor. + * @param TDbConnection the connection associated with this transaction + * @see TDbConnection::beginTransaction + */ + public function __construct(TDbConnection $connection) + { + if($connection instanceof ISlaveDbConnection) + { + $this->_compatible = true; + $master = $connection->getMasterConnection(); + $master->setForceMaster(TMasterSlaveDbConnectionForceMaster::ON_TRANSACTION); + Prado::log('contstuct, ForceMaster: ON_TRANSACTION', TLogger::DEBUG, 'System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbTransaction'); + parent::__construct($master); + } + else + { + if($connection instanceof IMasterSlaveDbConnection) + { + $this->_compatible = true; + $connection->setForceMaster(TMasterSlaveDbConnectionForceMaster::ON_TRANSACTION); + Prado::log('contstuct, ForceMaster: ON_TRANSACTION', TLogger::DEBUG, 'System.TestingData.Distributed.MasterSlave.TMasterSlaveDbTransaction'); + } + parent::__construct($connection); + } + } + + /** + * Commits a transaction. + * @throws TDbException if the transaction or the DB connection is not active. + */ + public function commit() + { + if($this->_compatible) $this->getConnection()->setForceMaster(TMasterSlaveDbConnectionForceMaster::OFF_AUTOMATIC); + Prado::log('commit, ForceMaster: OFF_AUTOMATIC', TLogger::DEBUG, 'System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbTransaction'); + parent::commit(); + } + + /** + * Rolls back a transaction. + * @throws TDbException if the transaction or the DB connection is not active. + */ + public function rollback() + { + if($this->_compatible) $this->getConnection()->setForceMaster(TMasterSlaveDbConnectionForceMaster::OFF_AUTOMATIC); + Prado::log('rollback, ForceMaster: OFF_AUTOMATIC', TLogger::DEBUG, 'System.Testing.Data.Distributed.MasterSlave.TMasterSlaveDbTransaction'); + parent::rollback(); + } + } +?>
\ No newline at end of file diff --git a/framework/Testing/Data/Distributed/TDistributedDataSourceConfig.php b/framework/Testing/Data/Distributed/TDistributedDataSourceConfig.php new file mode 100644 index 00000000..b8c4e5a9 --- /dev/null +++ b/framework/Testing/Data/Distributed/TDistributedDataSourceConfig.php @@ -0,0 +1,181 @@ +<?php +/** + * TDistributedDataSourceConfig class file. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2010 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Distributed + */ + + Prado::using('System.Data.TDataSourceConfig'); + Prado::using('System.Testing.Data.Distributed.TDistributedDbConnection'); + + /** + * IDistributedDataSourceConfig module interface provides <module> configuration for database connections. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed + * @since 4.0 + */ + interface IDistributedDataSourceConfig /*extends IDataSourceConfig*/ { + /** + * @return string Database connection class name to be created for child connection. + */ + public function getDistributedConnectionClass(); + + /** + * @param string Database connection class name to be created for child connection. + */ + public function setDistributedConnectionClass($value); + } + + /** + * TDistributedDataSourceConfig module class provides <module> configuration for database connections. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed + * @since 4.0 + */ + class TDistributedDataSourceConfig extends TDataSourceConfig implements IDistributedDataSourceConfig + { + /** + * @var array + */ + private $_childConnectionAttributes = array(); + + /** + * @var string + */ + private $_connDistributedClass = 'System.Data.TDbConnection'; + + /** + * @var IDistributedDbConnection + */ + private $_connDistributed = null; + + /** + * @var boolean + */ + protected $bInitialized = false; + + /** + * @var boolean + */ + private $_hasDistributedConnectionData = false; + + /** + * Initalize the database connection properties from attributes in <database> tag. + * @param TXmlDocument xml configuration. + */ + public function init($xml) + { + parent::init($xml); + $this->initChildConnectionData($xml); + $this->bInitialized = true; + } + + /** + * Initalize the database connection properties from attributes in $tagName tag. + * @param TXmlDocument xml configuration. + * @param string Tagnames to parse. Defaults to 'child' + */ + protected function initChildConnectionData($xml, $tagName='child') + { + $c = 0; + foreach($xml->getElementsByTagName($tagName) as $item) + { + ++$c; + $this->_childConnectionAttributes[] = $item->getAttributes(); + } + + if($c===0) + throw new TConfigurationException('distributeddatasource_child_required', get_class($this), $tagName); + } + + /** + * @return string Database connection class name to be created for child connection. + */ + public function getDistributedConnectionClass() + { + return $this->_connDistributedClass; + } + + /** + * @param string Database connection class name to be created for child connection. + */ + public function setDistributedConnectionClass($value) + { + $this->_connDistributedClass=$value; + } + + /** + * @return IDistributedDbConnection + */ + public function getDistributedDbConnection() + { + $this->_hasDistributedConnectionData = false; + if($this->_connDistributed===null) + $this->_connDistributed = Prado::createComponent($this->getDistributedConnectionClass()); + + if($this->_hasDistributedConnectionData) + return $this->_connDistributed; + + $attribs = $this->getDistributedDbConnectionAttributes(); + + if($attribs===null) + return $this->_connDistributed; + + foreach($attribs as $name => $value) + $this->_connDistributed->setSubproperty($name, $value); + + $this->_hasDistributedConnectionData = true; + + return $this->_connDistributed; + } + + /** + * @return TMap + */ + protected function getDistributedDbConnectionAttributes() + { + $index = 0; + $c = count($this->_childConnectionAttributes); + + if($c > 1) { + $aSrc = array(); + $aTmp = array(); + + foreach($this->_childConnectionAttributes as $k => $item) + { + $weight = 1; + if( isset($item['Weight']) ) + $weight = $item['Weight']; + $aSrc[$k] = $weight; + } + + asort($aSrc); + + foreach($aSrc as $idx => $weight) + $aTmp = array_merge($aTmp, array_pad(array(), $weight*5, $idx)); + + $min = 0; + $max = count($aTmp)-1; + $factor = array_sum($aSrc) / count($aSrc); + $wrand = round($min + (pow(rand(0, $max) / $max, $factor) * ($max - $min))); + $index = $aTmp[$wrand]; + } + + $result = $this->_childConnectionAttributes[$index]; + + if( isset($result['Weight']) ) + unset($result['Weight']); + + return $result; + } + } +?>
\ No newline at end of file diff --git a/framework/Testing/Data/Distributed/TDistributedDbConnection.php b/framework/Testing/Data/Distributed/TDistributedDbConnection.php new file mode 100644 index 00000000..bb105d50 --- /dev/null +++ b/framework/Testing/Data/Distributed/TDistributedDbConnection.php @@ -0,0 +1,189 @@ +<?php +/** + * IDistributedDbConnection, TDistributedDbConnection inferface/class file. + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2010 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Distributed + */ + + Prado::using('System.Data.TDbConnection'); + Prado::using('System.Testing.Data.Analysis.TDbStatementAnalysis'); + + /** + * TDistributedDbConnection interface + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed + * @since 4.0 + */ + interface IDistributedDbConnection /*extends IDbConnection*/ + { + /** + * Gets the statement analyser of type given by + * {@link setStatementAnalyserClass StatementAnalyserClass }. + * @return IDbStatementAnalysis statement analyser. + */ + public function getStatementAnalyser(); + + /** + * The statement analyser class name to be created when {@link getStatementAnalyserClass} + * method is called. The {@link setStatementAnalyserClass StatementAnalyserClass} + * property must be set before calling {@link getStatementAnalyser} if you wish to + * create the connection using the given class name. + * @param string Statement analyser class name. + */ + public function setStatementAnalyserClass($value); + + /** + * @param string Statement analyser class name to be created. + */ + public function getStatementAnalyserClass(); + + /** + * @return TDbConnectionServerRole + */ + public function getServerRole(); + } + + /** + * TDistributedDbConnection class + * + * TDistributedDbConnection represents a conditional base connection class to a database + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @version $Id$ + * @package System.Testing.Data.Distributed + * @since 4.0 + */ + abstract class TDistributedDbConnection extends TDbConnection implements IDistributedDbConnection + { + /** + * @var string + */ + private $_statementAnalyserClass = 'System.Testing.Data.Analysis.TDbStatementAnalysis'; + + /** + * @var IDbStatementAnalysis + */ + private $_statementAnalyser = null; + + /** + * Gets the statement analyser of type given by + * {@link setStatementAnalyserClass StatementAnalyserClass }. + * @return IDbStatementAnalysis statement analyser. + */ + public function getStatementAnalyser() + { + if($this->_statementAnalyser === null) + { + $this->setActive(true); + $this->_statementAnalyser = Prado::createComponent($this->getStatementAnalyserClass()); + + if($this->getActive()) + $this->_statementAnalyser->setDriverName($this->getDriverName()); + } + return $this->_statementAnalyser; + } + + /** + * The statement analyser class name to be created when {@link getStatementAnalyser} + * method is called. The {@link setStatementAnalyserClass StatementAnalyserClass} + * property must be set before calling {@link getStatementAnalyser} if you wish to + * create the connection using the given class name. + * @param string Statement analyser class name. + */ + public function setStatementAnalyserClass($value) + { + if($this->_statementAnalyser === null) + $this->_statementAnalyserClass = $value; + } + + /** + * @param string Statement analyser class name to be created. + */ + public function getStatementAnalyserClass() + { + return $this->_statementAnalyserClass; + } + + /** + * @param string The SQL statement that should be analysed + * @param TDbStatementClassification + */ + protected function getStatementClassification($statement='', $defaultClassification=null) { + return $this->getStatementAnalyser()->getClassificationAnalysis(new TDbStatementAnalysisParameter($statement, $defaultClassification)); + } + } + + /** + * TDistributedDbCommand + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Distributed + * @since 4.0 + */ + class TDistributedDbCommand extends TDbCommand + { + /** + * @var TDbStatementClassification + */ + private $_statementClassification; + + /** + * Constructor. + * @param TDbConnection the database connection + * @param string the SQL statement to be executed + * @param TDbStatementClassification Defaults to 'UNKNOWN' + */ + public function __construct(TDbConnection $connection, $text, $classification=TDbStatementClassification::UNKNOWN) + { + $connection->setActive(true); + parent::__construct($connection, $text); + $this->_statementClassification = $classification; + Prado::log($classification . ', ' . $connection->getServerRole() . ': ' . preg_replace('/[\s]+/', ' ', $text), TLogger::DEBUG, 'System.Testing.Data.Distributed.TDistributedDbCommand'); + } + + /** + * @return TDbStatementClassification + */ + public function getStatementClassification() + { + return $this->_statementClassification; + } + } + + + /** + * TDbConnectionServerRole + * + * @author Yves Berkholz <godzilla80[at]gmx[dot]net> + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Testing.Data.Distributed + * @since 4.0 + */ + class TDbConnectionServerRole extends TEnumerable + { + /** + * Master Server (Read/Write) + */ + const Master = 'Master'; + + /** + * Slave Server (Read only) + */ + const Slave = 'Slave'; + + /** + * Mirror Server (Read/Write) for further use + */ + //const Mirror = 'Mirror'; + } +?>
\ No newline at end of file diff --git a/tests/unit/Testing/Data/Analysis/AllTests.php b/tests/unit/Testing/Data/Analysis/AllTests.php new file mode 100644 index 00000000..557dc9c6 --- /dev/null +++ b/tests/unit/Testing/Data/Analysis/AllTests.php @@ -0,0 +1,32 @@ +<?php +require_once dirname(__FILE__).'/../../../phpunit.php'; + +if(!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'Testing_Data_Analysis_AllTests::main'); +} + +require_once 'TDbStatementAnalysisParameterTest.php'; +require_once 'TDbStatementAnalysisTest.php'; +require_once 'TSimpleDbStatementAnalysisTest.php'; + +class Data_Analysis_AllTests { + + public static function main() { + PHPUnit_TextUI_TestRunner::run(self::suite()); + } + + public static function suite() { + $suite = new PHPUnit_Framework_TestSuite('System.Testing.Data.Analysis'); + + $suite->addTestSuite('TDbStatementAnalysisParameterTest'); + $suite->addTestSuite('TDbStatementAnalysisTest'); + $suite->addTestSuite('TSimpleDbStatementAnalysisTest'); + + return $suite; + } +} + +if(PHPUnit_MAIN_METHOD == 'Testing_Data_Analysis_AllTests::main') { + Testing_Data_Analysis_AllTests::main(); +} +?> diff --git a/tests/unit/Testing/Data/Analysis/TDbStatementAnalysisParameterTest.php b/tests/unit/Testing/Data/Analysis/TDbStatementAnalysisParameterTest.php new file mode 100644 index 00000000..8e070096 --- /dev/null +++ b/tests/unit/Testing/Data/Analysis/TDbStatementAnalysisParameterTest.php @@ -0,0 +1,76 @@ +<?php +require_once dirname(__FILE__).'/../../../phpunit.php'; + +Prado::using('System.Testing.Data.Analysis.TDbStatementAnalysis'); + +/** + * @package System.Testing.Data.Analysis + */ +class TDbStatementAnalysisParameterTest extends PHPUnit_Framework_TestCase +{ + private $analyserParameter; + + public function setUp() + { + $this->analyserParameter = new TDbStatementAnalysisParameter(); + } + + public function tearDown() + { + $this->analyserParameter = null; + } + + public function testConstruct() { + $this->analyserParameter = new TDbStatementAnalysisParameter(); + self::assertType('string', $this->analyserParameter->getStatement()); + self::assertEquals('', $this->analyserParameter->getStatement()); + self::assertEquals(TDbStatementClassification::UNKNOWN, $this->analyserParameter->getDefaultClassification()); + self::assertNull($this->analyserParameter->getDriverName()); + + $this->analyserParameter = new TDbStatementAnalysisParameter('SELECT 1', TDbStatementClassification::SQL, 'mysql'); + self::assertType('string', $this->analyserParameter->getStatement()); + self::assertEquals('SELECT 1', $this->analyserParameter->getStatement()); + self::assertEquals(TDbStatementClassification::SQL, $this->analyserParameter->getDefaultClassification()); + self::assertEquals('mysql', $this->analyserParameter->getDriverName()); + } + + public function testStatement() { + self::assertType('string', $this->analyserParameter->getStatement()); + self::assertEquals('', $this->analyserParameter->getStatement()); + + $this->analyserParameter->setStatement('SELECT 1'); + self::assertType('string', $this->analyserParameter->getStatement()); + self::assertEquals('SELECT 1', $this->analyserParameter->getStatement()); + + $this->analyserParameter->setStatement(null); + self::assertType('string', $this->analyserParameter->getStatement()); + self::assertEquals('', $this->analyserParameter->getStatement()); + } + + public function testDriverName() { + self::assertNull($this->analyserParameter->getDriverName()); + + $this->analyserParameter->setDriverName('mysql'); + self::assertEquals('mysql', $this->analyserParameter->getDriverName()); + + $this->analyserParameter->setDriverName('mssql'); + self::assertEquals('mssql', $this->analyserParameter->getDriverName()); + + $this->analyserParameter->setDriverName(null); + self::assertNull($this->analyserParameter->getDriverName()); + } + + public function testDefaultClassification() { + self::assertEquals(TDbStatementClassification::UNKNOWN, $this->analyserParameter->getDefaultClassification()); + + $this->analyserParameter->setDefaultClassification(TDbStatementClassification::SQL); + self::assertEquals(TDbStatementClassification::SQL, $this->analyserParameter->getDefaultClassification()); + + $this->analyserParameter->setDefaultClassification(TDbStatementClassification::DML); + self::assertEquals(TDbStatementClassification::DML, $this->analyserParameter->getDefaultClassification()); + + $this->analyserParameter->setDefaultClassification(null); + self::assertEquals(TDbStatementClassification::UNKNOWN, $this->analyserParameter->getDefaultClassification()); + } +} +?>
\ No newline at end of file diff --git a/tests/unit/Testing/Data/Analysis/TDbStatementAnalysisTest.php b/tests/unit/Testing/Data/Analysis/TDbStatementAnalysisTest.php new file mode 100644 index 00000000..70437816 --- /dev/null +++ b/tests/unit/Testing/Data/Analysis/TDbStatementAnalysisTest.php @@ -0,0 +1,62 @@ +<?php +require_once dirname(__FILE__).'/../../../phpunit.php'; + +Prado::using('System.Testing.Data.Analysis.TDbStatementAnalysis'); + +/** + * @package System.Testing.Data.Analysis + */ +class TDbStatementAnalysisTest extends PHPUnit_Framework_TestCase +{ + private $analyser; + + public function setUp() + { + $this->analyser = new TDbStatementAnalysis(); + } + + public function tearDown() + { + $this->analyser = null; + } + + public function testStaticClassificationAnalysis() + { + $parameter = new TDbStatementAnalysisParameter(); + self::assertEquals(TDbStatementClassification::UNKNOWN, TDbStatementAnalysis::doClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('SELECT 1'); + self::assertEquals(TDbStatementClassification::UNKNOWN, TDbStatementAnalysis::doClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('SELECT 1', TDbStatementClassification::SQL); + self::assertEquals(TDbStatementClassification::SQL, TDbStatementAnalysis::doClassificationAnalysis($parameter)); + } + + public function testDriverName() + { + self::assertNull($this->analyser->getDriverName()); + + $this->analyser->setDriverName('mysql'); + self::assertEquals('mysql', $this->analyser->getDriverName()); + + $this->analyser->setDriverName('mssql'); + self::assertEquals('mssql', $this->analyser->getDriverName()); + + $this->analyser->setDriverName(null); + self::assertNull($this->analyser->getDriverName()); + } + + public function testClassificationAnalysis() + { + $parameter = new TDbStatementAnalysisParameter(); + self::assertEquals(TDbStatementClassification::UNKNOWN, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('SELECT 1'); + self::assertEquals(TDbStatementClassification::UNKNOWN, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('SELECT 1', TDbStatementClassification::SQL); + self::assertEquals(TDbStatementClassification::SQL, $this->analyser->getClassificationAnalysis($parameter)); + } + +} +?>
\ No newline at end of file diff --git a/tests/unit/Testing/Data/Analysis/TSimpleDbStatementAnalysisTest.php b/tests/unit/Testing/Data/Analysis/TSimpleDbStatementAnalysisTest.php new file mode 100644 index 00000000..09ab0f48 --- /dev/null +++ b/tests/unit/Testing/Data/Analysis/TSimpleDbStatementAnalysisTest.php @@ -0,0 +1,229 @@ +<?php +require_once dirname(__FILE__).'/../../../phpunit.php'; + +Prado::using('System.Testing.Data.Analysis.TSimpleDbStatementAnalysis'); + +/** + * @package System.Testing.Data.Analysis + */ +class TSimpleDbStatementAnalysisTest extends PHPUnit_Framework_TestCase +{ + private $analyser; + + public function setUp() + { + $this->analyser = new TSimpleDbStatementAnalysis(); + } + + public function tearDown() + { + $this->analyser = null; + } + + public function testDriverName() + { + self::assertNull($this->analyser->getDriverName()); + + $this->analyser->setDriverName('mysql'); + self::assertEquals('mysql', $this->analyser->getDriverName()); + + $this->analyser->setDriverName('mssql'); + self::assertEquals('mssql', $this->analyser->getDriverName()); + + $this->analyser->setDriverName(null); + self::assertNull($this->analyser->getDriverName()); + } + + public function testClassificationAnalysisDDL() + { + $parameter = new TDbStatementAnalysisParameter('CREATE DATABASE `prado_system_data_sqlmap` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci'); + self::assertEquals(TDbStatementClassification::DDL, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('DROP TABLE IF EXISTS `dynamicparametertest1`'); + self::assertEquals(TDbStatementClassification::DDL, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter(' + CREATE TABLE `dynamicparametertest1` ( + `testname` varchar(50) NOT NULL, + `teststring` varchar(50) NOT NULL, + `testinteger` int(11) NOT NULL + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 + '); + self::assertEquals(TDbStatementClassification::DDL, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter(' + CREATE TABLE `tab3` + /* 1 multiline comment in one line */ + SELECT + t1.*, + t2.`foo` AS `bar` + FROM + # 1 single line shell comment + `tab1` t1 + # 2 single line shell comment + RIGHT JOIN `tab2` t2 ON ( + t2.tab1_id=t1.tab1_ref + AND + t2.`disabled` IS NULL + AND + (t2.`flags`&?)=? + ) + -- 1 single line comment + WHERE + /* + 2 multiline comment + in two lines + */ + t1.`idx`=? + AND + -- 2 single line comment + t1.`disabled`IS NULL + GROUP BY + t2.`foo` + HAVING + t2.tab1_id=1, + t2.disabled IS NULL + ORDER BY + `bar` DESC + '); + self::assertEquals(TDbStatementClassification::DDL, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('DROP TABLE `tab3`'); + self::assertEquals(TDbStatementClassification::DDL, $this->analyser->getClassificationAnalysis($parameter)); + } + + public function testClassificationAnalysisDML() + { + $parameter = new TDbStatementAnalysisParameter('TRUNCATE TABLE `dynamicparametertest1`'); + self::assertEquals(TDbStatementClassification::DML, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter(' + UPDATE `dynamicparametertest1` SET + `testinteger`=FLOOR(7 + (RAND() * 5)) + WHERE + `testname` IN( + SELECT `testname` FROM `dynamicparametertest2` + ) + '); + self::assertEquals(TDbStatementClassification::DML, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter(' + INSERT INTO `tab3` + /* 1 multiline comment in one line */ + SELECT + t1.*, + t2.`foo` AS `bar` + FROM + # 1 single line shell comment + `tab1` t1 + # 2 single line shell comment + RIGHT JOIN `tab2` t2 ON ( + t2.tab1_id=t1.tab1_ref + AND + t2.`disabled` IS NULL + AND + (t2.`flags`&?)=? + ) + -- 1 single line comment + WHERE + /* + 2 multiline comment + in two lines + */ + t1.`idx`=? + AND + -- 2 single line comment + t1.`disabled`IS NULL + GROUP BY + t2.`foo` + HAVING + t2.tab1_id=1, + t2.disabled IS NULL + ORDER BY + `bar` DESC + '); + self::assertEquals(TDbStatementClassification::DML, $this->analyser->getClassificationAnalysis($parameter)); + } + + public function testClassificationAnalysisSQL() + { + $parameter = new TDbStatementAnalysisParameter(' + /* 1 multiline comment in one line */ + SELECT + t1.*, + t2.`foo` AS `bar` + FROM + # 1 single line shell comment + `tab1` t1 + # 2 single line shell comment + RIGHT JOIN `tab2` t2 ON ( + t2.tab1_id=t1.tab1_ref + AND + t2.`disabled` IS NULL + AND + (t2.`flags`&?)=? + ) + -- 1 single line comment + WHERE + /* + 2 multiline comment + in two lines + */ + t1.`idx`=? + AND + -- 2 single line comment + t1.`disabled`IS NULL + GROUP BY + t2.`foo` + HAVING + t2.tab1_id=1, + t2.disabled IS NULL + ORDER BY + `bar` DESC + '); + self::assertEquals(TDbStatementClassification::SQL, $this->analyser->getClassificationAnalysis($parameter)); + } + + public function testClassificationAnalysisDCL() + { + $parameter = new TDbStatementAnalysisParameter(' + GRANT ALL ON `prado_system_data_sqlmap`.* + TO "prado_unitest"@"localhost" + IDENTIFIED BY "prado_system_data_sqlmap_unitest"'); + self::assertEquals(TDbStatementClassification::DCL, $this->analyser->getClassificationAnalysis($parameter)); + } + + public function testClassificationAnalysisTCL() + { + $parameter = new TDbStatementAnalysisParameter('START TRANSACTION'); + self::assertEquals(TDbStatementClassification::TCL, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('BEGIN'); + self::assertEquals(TDbStatementClassification::TCL, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('COMMIT'); + self::assertEquals(TDbStatementClassification::TCL, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('RELEASE SAVEPOINT'); + self::assertEquals(TDbStatementClassification::TCL, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('XA START'); + self::assertEquals(TDbStatementClassification::TCL, $this->analyser->getClassificationAnalysis($parameter)); + } + + public function testClassificationAnalysisUNKNOWN() + { + $parameter = new TDbStatementAnalysisParameter('CALL `sp_my_storedprocedure`("foobar")'); + self::assertEquals(TDbStatementClassification::UNKNOWN, $this->analyser->getClassificationAnalysis($parameter)); + } + + public function testClassificationAnalysisCONTEXT() + { + $parameter = new TDbStatementAnalysisParameter('SET NAMES "utf8"'); + self::assertEquals(TDbStatementClassification::CONTEXT, $this->analyser->getClassificationAnalysis($parameter)); + + $parameter = new TDbStatementAnalysisParameter('USE `prado_system_data_sqlmap`'); + self::assertEquals(TDbStatementClassification::CONTEXT, $this->analyser->getClassificationAnalysis($parameter)); + } +} +?>
\ No newline at end of file |