summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgodzilla80@gmx.net <>2010-02-13 19:04:45 +0000
committergodzilla80@gmx.net <>2010-02-13 19:04:45 +0000
commit966fd66f217911d079c4bd6a87b09f4a0c5c4736 (patch)
tree26d4cda965ed5a6ddf2aeb805fcef42877584fd3
parent879cced5e01d43378065c938483b55a35ff10834 (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
-rw-r--r--.gitattributes11
-rw-r--r--HISTORY1
-rw-r--r--framework/Exceptions/messages/messages.txt4
-rw-r--r--framework/Testing/Data/Analysis/TDbStatementAnalysis.php238
-rw-r--r--framework/Testing/Data/Analysis/TSimpleDbStatementAnalysis.php134
-rw-r--r--framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDataSourceConfig.php154
-rw-r--r--framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbConnection.php477
-rw-r--r--framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbTransaction.php83
-rw-r--r--framework/Testing/Data/Distributed/TDistributedDataSourceConfig.php181
-rw-r--r--framework/Testing/Data/Distributed/TDistributedDbConnection.php189
-rw-r--r--tests/unit/Testing/Data/Analysis/AllTests.php32
-rw-r--r--tests/unit/Testing/Data/Analysis/TDbStatementAnalysisParameterTest.php76
-rw-r--r--tests/unit/Testing/Data/Analysis/TDbStatementAnalysisTest.php62
-rw-r--r--tests/unit/Testing/Data/Analysis/TSimpleDbStatementAnalysisTest.php229
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
diff --git a/HISTORY b/HISTORY
index 73d28b83..68d4d983 100644
--- a/HISTORY
+++ b/HISTORY
@@ -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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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