From 966fd66f217911d079c4bd6a87b09f4a0c5c4736 Mon Sep 17 00:00:00 2001 From: "godzilla80@gmx.net" <> Date: Sat, 13 Feb 2010 19:04:45 +0000 Subject: 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 --- .../MasterSlave/TMasterSlaveDataSourceConfig.php | 154 +++++++ .../MasterSlave/TMasterSlaveDbConnection.php | 477 +++++++++++++++++++++ .../MasterSlave/TMasterSlaveDbTransaction.php | 83 ++++ 3 files changed, 714 insertions(+) create mode 100644 framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDataSourceConfig.php create mode 100644 framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbConnection.php create mode 100644 framework/Testing/Data/Distributed/MasterSlave/TMasterSlaveDbTransaction.php (limited to 'framework/Testing/Data/Distributed/MasterSlave') 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 @@ + + * @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 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 + * + * + * + * + * + * + * + * + * + * + * + * @author Yves Berkholz + * @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 @@ + + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 @@ + + * @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 + * @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 -- cgit v1.2.3