summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes8
-rw-r--r--framework/Data/TDbCommand.php262
-rw-r--r--framework/Data/TDbConnection.php512
-rw-r--r--framework/Data/TDbDataReader.php208
-rw-r--r--framework/Data/TDbTransaction.php113
-rw-r--r--tests/unit/Data/TDbCommandTest.php131
-rw-r--r--tests/unit/Data/TDbConnectionTest.php114
-rw-r--r--tests/unit/Data/TDbDataReaderTest.php114
-rw-r--r--tests/unit/Data/TDbTransactionTest.php73
9 files changed, 1535 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
index 29c0c88d..5faafb57 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1398,6 +1398,10 @@ framework/Collections/TStack.php -text
framework/Configuration/Provider/TProviderBase.php -text
framework/Configuration/Provider/TProviderException.php -text
framework/Configuration/TProtectedConfiguration.php -text
+framework/Data/TDbCommand.php -text
+framework/Data/TDbConnection.php -text
+framework/Data/TDbDataReader.php -text
+framework/Data/TDbTransaction.php -text
framework/DataAccess/SQLMap/Configuration/TConfigDeserialize.php -text
framework/DataAccess/SQLMap/Configuration/TDiscriminator.php -text
framework/DataAccess/SQLMap/Configuration/TDomSqlMapBuilder.php -text
@@ -2309,6 +2313,10 @@ tests/test_tools/simpletest/simpletest.php -text
tests/test_tools/simpletest/test_case.php -text
tests/unit/Collections/TListTest.php -text
tests/unit/Collections/TMapTest.php -text
+tests/unit/Data/TDbCommandTest.php -text
+tests/unit/Data/TDbConnectionTest.php -text
+tests/unit/Data/TDbDataReaderTest.php -text
+tests/unit/Data/TDbTransactionTest.php -text
tests/unit/I18N/core/CultureInfoTest.php -text
tests/unit/I18N/core/DateFormatTest.php -text
tests/unit/I18N/core/DateTimeFormatInfoTest.php -text
diff --git a/framework/Data/TDbCommand.php b/framework/Data/TDbCommand.php
new file mode 100644
index 00000000..3d0e367a
--- /dev/null
+++ b/framework/Data/TDbCommand.php
@@ -0,0 +1,262 @@
+<?php
+/**
+ * TDbCommand class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Id $
+ * @package System.Data
+ */
+
+/**
+ * TDbCommand class.
+ *
+ * TDbCommand represents an SQL statement to execute against a database.
+ * It is usually created by calling {@link TDbConnection::createCommand}.
+ * The SQL statement to be executed may be set via {@link setText Text}.
+ *
+ * To execute a non-query SQL (such as insert, delete, update), call
+ * {@link execute}. To execute an SQL statement that returns result data set
+ * (such as select), use {@link query} or its convenient versions {@link queryRow}
+ * and {@link queryScalar}.
+ *
+ * TDbCommand supports SQL statment preparation and parameter binding.
+ * Call {@link bindParameter} to bind a PHP variable to a parameter in SQL.
+ * Call {@link bindValue} to bind a value to an SQL parameter.
+ * When binding a parameter, the SQL statement is automatically prepared.
+ * You may also call {@link prepare} to explicitly prepare an SQL statement.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Data
+ * @since 3.0
+ */
+class TDbCommand extends TComponent
+{
+ private $_connection;
+ private $_text='';
+ private $_statement=null;
+
+ /**
+ * Constructor.
+ * @param TDbConnection the database connection
+ * @param string the SQL statement to be executed
+ */
+ public function __construct(TDbConnection $connection,$text)
+ {
+ $this->_connection=$connection;
+ $this->setText($text);
+ }
+
+ /**
+ * @return string the SQL statement to be executed
+ */
+ public function getText()
+ {
+ return $this->_text;
+ }
+
+ /**
+ * Specifies the SQL statement to be executed.
+ * Any previous execution will be terminated or cancel.
+ * @param string the SQL statement to be executed
+ */
+ public function setText($value)
+ {
+ $this->_text=$value;
+ $this->cancel();
+ }
+
+ /**
+ * @return TDbConnection the connection associated with this command
+ */
+ public function getConnection()
+ {
+ return $this->_connection;
+ }
+
+ /**
+ * @return PDOStatement the underlying PDOStatement for this command
+ * It could be null if the statement is not created yet.
+ */
+ public function getPdoStatement()
+ {
+ return $this->_statement;
+ }
+
+ /**
+ * Prepares the SQL statement to be executed.
+ * For complex SQL statement that is to be executed multiple times,
+ * this may improve performance.
+ * For SQL statement with binding parameters, this method is invoked
+ * automatically.
+ */
+ public function prepare()
+ {
+ if($this->_statement==null)
+ {
+ try
+ {
+ $this->_statement=$this->getConnection()->getPdoInstance()->prepare($sql);
+ }
+ catch(Exception $e)
+ {
+ throw new TDbException('dbcommand_prepare_failed',$e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * Cancels the execution of the SQL statement.
+ */
+ public function cancel()
+ {
+ $this->_statement=null;
+ }
+
+ /**
+ * Binds a parameter to the SQL statement to be executed.
+ * @param mixed Parameter identifier. For a prepared statement
+ * using named placeholders, this will be a parameter name of
+ * the form :name. For a prepared statement using question mark
+ * placeholders, this will be the 1-indexed position of the parameter.
+ * @param mixed Name of the PHP variable to bind to the SQL statement parameter
+ * @param int SQL data type of the parameter
+ * @param int length of the data type
+ * @see http://www.php.net/manual/en/function.pdostatement-bindparam.php
+ */
+ public function bindParameter($name, &$value, $dataType=null, $length=null)
+ {
+ $this->prepare();
+ if($dataType===null)
+ $this->_statement->bindParam($name,$value);
+ else if($length===null)
+ $this->_statement->bindParam($name,$value,$dataType);
+ else
+ $this->_statement->bindParam($name,$value,$dataType,$length);
+ }
+
+ /**
+ * Binds a value to a parameter.
+ * @param mixed Parameter identifier. For a prepared statement
+ * using named placeholders, this will be a parameter name of
+ * the form :name. For a prepared statement using question mark
+ * placeholders, this will be the 1-indexed position of the parameter.
+ * @param mixed The value to bind to the parameter
+ * @param int SQL data type of the parameter
+ * @see http://www.php.net/manual/en/function.pdostatement-bindvalue.php
+ */
+ public function bindValue($name, $value, $dataType=null)
+ {
+ $this->prepare();
+ if($dataType===null)
+ $this->_statement->bindParam($name,$value);
+ else
+ $this->_statement->bindParam($name,$value,$dataType);
+ }
+
+ /**
+ * Executes the SQL statement.
+ * This method is meant only for executing non-query SQL statement.
+ * No result set will be returned.
+ * @return integer number of rows affected by the execution.
+ * @throws TDbException execution failed
+ */
+ public function execute()
+ {
+ try
+ {
+ if($this->_statement instanceof PDOStatement)
+ {
+ $this->_statement->execute();
+ return $this->_statement->rowCount();
+ }
+ else
+ return $this->getConnection()->getPdoInstance()->exec($this->getText());
+ }
+ catch(Exception $e)
+ {
+ throw new TDbException('dbcommand_execute_failed',$e->getMessage());
+ }
+ }
+
+ /**
+ * Executes the SQL statement and returns query result.
+ * This method is for executing an SQL query that returns result set.
+ * @return TDbDataReader the reader object for fetching the query result
+ * @throws TDbException execution failed
+ */
+ public function query()
+ {
+ try
+ {
+ if($this->_statement instanceof PDOStatement)
+ $this->_statement->execute();
+ else
+ $this->_statement=$this->getConnection()->getPdoInstance()->query($this->getText());
+ return new TDbDataReader($this);
+ }
+ catch(Exception $e)
+ {
+ throw new TDbException('dbcommand_query_failed',$e->getMessage());
+ }
+ }
+
+ /**
+ * Executes the SQL statement and returns the first row of the result.
+ * This is a convenient method of {@link query} when only the first row of data is needed.
+ * @param boolean whether the row should be returned as an associated array with
+ * column names as the keys or the array keys are column indexes (0-based).
+ * @return array the first row of the query result, false if not result.
+ * @throws TDbException execution failed
+ */
+ public function queryRow($fetchAssociative=true)
+ {
+ try
+ {
+ if($this->_statement instanceof PDOStatement)
+ $this->_statement->execute();
+ else
+ $this->_statement=$this->getConnection()->getPdoInstance()->query($sql);
+ $result=$this->_statement->fetch($fetchAssociative ? PDO::FETCH_ASSOC : PDO::FETCH_NUM);
+ $this->_statement->closeCursor();
+ return $result;
+ }
+ catch(Exception $e)
+ {
+ throw new TDbException('dbcommand_query_failed',$e->getMessage());
+ }
+ }
+
+ /**
+ * Executes the SQL statement and returns the value of the first column in the first row of data.
+ * This is a convenient method of {@link query} when only a single scalar
+ * value is needed (e.g. obtaining the count of the records).
+ * @return mixed the value of the first column in the first row of the query result.
+ * @throws TDbException execution failed or there is no data
+ */
+ public function queryScalar()
+ {
+ try
+ {
+ if($this->_statement instanceof PDOStatement)
+ $this->_statement->execute();
+ else
+ $this->_statement=$this->getConnection()->getPdoInstance()->query($sql);
+ $result=$this->_statement->fetchColumn();
+ $this->_statement->closeCursor();
+ if($result!==false)
+ return $result;
+ else
+ throw new TDbException('dbcommand_column_empty');
+ }
+ catch(Exception $e)
+ {
+ throw new TDbException('dbcommand_queryscalar_failed',$e->getMessage());
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/TDbConnection.php b/framework/Data/TDbConnection.php
new file mode 100644
index 00000000..cfb4fd72
--- /dev/null
+++ b/framework/Data/TDbConnection.php
@@ -0,0 +1,512 @@
+<?php
+/**
+ * TDbConnection class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Id $
+ * @package System.Data
+ */
+
+Prado::using('System.Data.TDbTransaction');
+Prado::using('System.Data.TDbCommand');
+
+/**
+ * TDbConnection class
+ *
+ * TDbConnection represents a connection to a database.
+ *
+ * TDbConnection works together with {@link TDbCommand}, {@link TDbDataReader}
+ * and {@link TDbTransaction} to provide data access to various DBMS
+ * in a common set of APIs, thanks to the {@link http://www.php.net/manual/en/ref.pdo.php PDO}
+ * PHP extension.
+ *
+ * To establish a connection, set {@link setActive Active} to true after
+ * specifying {@link setConnectionString ConnectionString}, {@link setUsername Username}
+ * and {@link setPassword Password}.
+ *
+ * The following example shows how to create a TDbConnection instance and establish
+ * the actual connection:
+ * <code>
+ * $connection=new TDbConnection($dsn,$username,$password);
+ * $connection->Active=true;
+ * </code>
+ *
+ * After the DB connection is established, one can execute an SQL statement like the following:
+ * <code>
+ * $command=$connection->createCommand($sqlStatement);
+ * $command->execute(); // a non-query SQL statement execution
+ * // or execute an SQL query and fetch the result set
+ * $reader=$command->query();
+ * foreach($reader as $row) ... // each $row is an array representing a row of data
+ * </code>
+ *
+ * One can do prepared SQL execution and bind parameters to the prepared SQL:
+ * <code>
+ * $command=$connection->createCommand($sqlStatement);
+ * $command->bindParameter($name1,$value1);
+ * $command->bindParameter($name2,$value2);
+ * $command->execute();
+ * </code>
+ *
+ * To use transaction, do like the following:
+ * <code>
+ * try
+ * {
+ * $transaction=$connection->beginTransaction();
+ * $connection->createCommand($sql1)->execute();
+ * $connection->createCommand($sql2)->execute();
+ * //.... other SQL executions
+ * $transaction->commit();
+ * }
+ * catch(Exception $e)
+ * {
+ * $transaction->rollBack();
+ * }
+ * </code>
+ *
+ * TDbConnection provides a set of methods to support setting and querying
+ * of certain DBMS attributes, such as {@link getNullConversion NullConversion}.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Data
+ * @since 3.0
+ */
+class TDbConnection extends TComponent
+{
+ private $_dsn;
+ private $_username;
+ private $_password;
+ private $_attributes=array();
+ private $_active=false;
+ private $_pdo=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.
+ * @see http://www.php.net/manual/en/function.pdo-construct.php
+ */
+ public function __construct($dsn='',$username='',$password='')
+ {
+ $this->_dsn=$dsn;
+ $this->_username=$username;
+ $this->_password=$password;
+ }
+
+ /**
+ * @return boolean whether the DB connection is established
+ */
+ public function getActive()
+ {
+ return $this->_active;
+ }
+
+ /**
+ * Open or close the DB connection.
+ * @param boolean whether to open or close DB connection
+ * @throws TDbException if connection fails
+ */
+ public function setActive($value)
+ {
+ $value=TPropertyValue::ensureBoolean($value);
+ if($value!==$this->_active)
+ {
+ if($value)
+ $this->open();
+ else
+ $this->close();
+ }
+ }
+
+ /**
+ * Opens DB connection if it is currently not
+ * @throws TDbException if connection fails
+ */
+ protected function open()
+ {
+ if($this->_pdo===null)
+ {
+ try
+ {
+ $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->_pdo=new PDO($this->getConnectionString(),$this->getUsername(),$this->getPassword(),$this->_attributes);
+ $this->_active=true;
+ }
+ catch(PDOException $e)
+ {
+ throw new TDbException('dbconnection_open_failed',$e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * Closes the currently active DB connection.
+ * It does nothing if the connection is already closed.
+ */
+ protected function close()
+ {
+ $this->_pdo=null;
+ $this->_active=false;
+ }
+
+ /**
+ * @return string The Data Source Name, or DSN, contains the information required to connect to the database.
+ */
+ public function getConnectionString()
+ {
+ return $this->_dsn;
+ }
+
+ /**
+ * @param string The Data Source Name, or DSN, contains the information required to connect to the database.
+ * @see http://www.php.net/manual/en/function.pdo-construct.php
+ */
+ public function setConnectionString($value)
+ {
+ $this->_dsn=$value;
+ }
+
+ /**
+ * @return string the username for establishing DB connection. Defaults to empty string.
+ */
+ public function getUsername()
+ {
+ return $this->_username;
+ }
+
+ /**
+ * @param string the username for establishing DB connection
+ */
+ public function setUsername($value)
+ {
+ $this->_username=$value;
+ }
+
+ /**
+ * @return string the password for establishing DB connection. Defaults to empty string.
+ */
+ public function getPassword()
+ {
+ return $this->_password;
+ }
+
+ /**
+ * @param string the password for establishing DB connection
+ */
+ public function setPassword($value)
+ {
+ $this->_password=$value;
+ }
+
+ /**
+ * @return PDO the PDO instance, null if the connection is not established yet
+ */
+ public function getPdoInstance()
+ {
+ return $this->_pdo;
+ }
+
+ /**
+ * Creates a command for execution.
+ * @param string SQL statement associated with the new command.
+ * @return TDbCommand the DB command
+ * @throws TDbException if the connection is not active
+ */
+ public function createCommand($sql)
+ {
+ if($this->getActive())
+ return new TDbCommand($this,$sql);
+ else
+ throw new TDbException('dbconnection_connection_inactive');
+ }
+
+ /**
+ * Starts a transaction.
+ * @return TDbTransaction the transaction initiated
+ * @throws TDbException if the connection is not active
+ */
+ public function beginTransaction()
+ {
+ if($this->getActive())
+ {
+ $this->_pdo->beginTransaction();
+ return new TDbTransaction($this);
+ }
+ else
+ throw new TDbException('dbconnection_connection_inactive');
+ }
+
+ /**
+ * Returns the ID of the last inserted row or sequence value.
+ * @param string name of the sequence object (required by some DBMS)
+ * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
+ * @see http://www.php.net/manual/en/function.pdo-lastinsertid.php
+ */
+ public function getLastInsertID($sequenceName='')
+ {
+ if($this->getActive())
+ return $this->_pdo->lastInsertId($sequenceName);
+ else
+ throw new TDbException('dbconnection_connection_inactive');
+ }
+
+ /**
+ * Quotes a string for use in a query.
+ * @param string string to be quoted
+ * @return string the properly quoted string
+ * @see http://www.php.net/manual/en/function.pdo-quote.php
+ */
+ public function quoteString($str)
+ {
+ if($this->getActive())
+ return $this->_pdo->quote($str);
+ else
+ throw new TDbException('dbconnection_connection_inactive');
+ }
+
+ /**
+ * @return TDbColumnCaseMode the case of the column names
+ */
+ public function getColumnCase()
+ {
+ switch($this->getAttribute(PDO::ATTR_CASE))
+ {
+ case PDO::CASE_NATURAL:
+ return TDbColumnCaseMode::Preserved;
+ case PDO::CASE_LOWER:
+ return TDbColumnCaseMode::LowerCase;
+ case PDO::CASE_UPPER:
+ return TDbColumnCaseMode::UpperCase;
+ default:
+ throw new TDbException('dbconnection_columncase_unknown');
+ }
+ }
+
+ /**
+ * @param TDbColumnCaseMode the case of the column names
+ */
+ public function setColumnCase($value)
+ {
+ switch(TPropertyValue::ensureEnum($value,'TDbColumnCaseMode'))
+ {
+ case TDbColumnCaseMode::Preserved:
+ $value=PDO::CASE_NATURAL;
+ break;
+ case TDbColumnCaseMode::LowerCase:
+ $value=PDO::CASE_LOWER;
+ break;
+ case TDbColumnCaseMode::UpperCase:
+ $value=PDO::CASE_UPPER;
+ break;
+ }
+ $this->setAttribute(PDO::ATTR_CASE,$value);
+ }
+
+ /**
+ * @return TDbNullConversionMode how the null and empty strings are converted
+ */
+ public function getNullConversion()
+ {
+ switch($this->getAttribute(PDO::ATTR_ORACLE_NULLS))
+ {
+ case PDO::NULL_NATURAL:
+ return TDbNullConversionMode::Preserved;
+ case PDO::NULL_EMPTY_STRING:
+ return TDbNullConversionMode::EmptyStringToNull;
+ case PDO::NULL_TO_STRING:
+ return TDbNullConversionMode::NullToEmptyString;
+ default:
+ throw new TDbException('dbconnection_nullconversion_unknown');
+ }
+ }
+
+ /**
+ * @param TDbNullConversionMode how the null and empty strings are converted
+ */
+ public function setNullConversion($value)
+ {
+ switch(TPropertyValue::ensureEnum($value,'TDbNullConversionMode'))
+ {
+ case TDbNullConversionMode::Preserved:
+ $value=PDO::NULL_NATURAL;
+ break;
+ case TDbNullConversionMode::EmptyStringToNull:
+ $value=PDO::NULL_EMPTY_STRING;
+ break;
+ case TDbNullConversionMode::NullToEmptyString:
+ $value=PDO::NULL_TO_STRING;
+ break;
+ }
+ $this->setAttribute(PDO::ATTR_ORACLE_NULLS,$value);
+ }
+
+ /**
+ * @return boolean whether creating or updating a DB record will be automatically committed.
+ * Some DBMS (such as sqlite) may not support this feature.
+ */
+ public function getAutoCommit()
+ {
+ return $this->getAttribute(PDO::ATTR_AUTOCOMMIT);
+ }
+
+ /**
+ * @param boolean whether creating or updating a DB record will be automatically committed.
+ * Some DBMS (such as sqlite) may not support this feature.
+ */
+ public function setAutoCommit($value)
+ {
+ $this->setAttribute(PDO::ATTR_AUTOCOMMIT,TPropertyValue::ensureBoolean($value));
+ }
+
+ /**
+ * @return string name of the DB driver
+ */
+ public function getDriverName()
+ {
+ return $this->getAttribute(PDO::ATTR_DRIVER_NAME);
+ }
+
+ /**
+ * @return string the version information of the DB driver
+ */
+ public function getClientVersion()
+ {
+ return $this->getAttribute(PDO::ATTR_CLIENT_VERSION);
+ }
+
+ /**
+ * @return string the status of the connection
+ * Some DBMS (such as sqlite) may not support this feature.
+ */
+ public function getConnectionStatus()
+ {
+ return $this->getAttribute(PDO::ATTR_CONNECTION_STATUS);
+ }
+
+ /**
+ * @return boolean whether the connection is persistent or not
+ * Some DBMS (such as sqlite) may not support this feature.
+ */
+ public function getPersistent()
+ {
+ return $this->getAttribute(PDO::ATTR_PERSISTENT);
+ }
+
+ /**
+ * @return boolean whether the connection performs data prefetching
+ */
+ public function getPrefetch()
+ {
+ return $this->getAttribute(PDO::ATTR_PREFETCH);
+ }
+
+ /**
+ * @return string the information of DBMS server
+ */
+ public function getServerInfo()
+ {
+ return $this->getAttribute(PDO::ATTR_SERVER_INFO);
+ }
+
+ /**
+ * @return string the version information of DBMS server
+ */
+ public function getServerVersion()
+ {
+ return $this->getAttribute(PDO::ATTR_SERVER_VERSION);
+ }
+
+ /**
+ * @return int timeout settings for the connection
+ */
+ public function getTimeout()
+ {
+ return $this->getAttribute(PDO::ATTR_TIMEOUT);
+ }
+
+ /**
+ * Obtains a specific DB connection attribute information.
+ * @param int the attribute to be queried
+ * @return mixed the corresponding attribute information
+ * @see http://www.php.net/manual/en/function.pdo-getattribute.php
+ */
+ public function getAttribute($name)
+ {
+ if($this->getActive())
+ return $this->_pdo->getAttribute($name);
+ else
+ throw new TDbException('dbconnection_connection_inactive');
+ }
+
+ /**
+ * Sets an attribute on the database connection.
+ * @param int the attribute to be set
+ * @param mixed the attribute value
+ * @see http://www.php.net/manual/en/function.pdo-setattribute.php
+ */
+ public function setAttribute($name,$value)
+ {
+ if($this->getActive())
+ $this->_pdo->setAttribute($name,$value);
+ else
+ $this->_attributes[$name]=$value;
+ }
+}
+
+/**
+ * TDbColumnCaseMode
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Data
+ * @since 3.0
+ */
+class TDbColumnCaseMode extends TEnumerable
+{
+ /**
+ * Column name cases are kept as is from the database
+ */
+ const Preserved='Preserved';
+ /**
+ * Column names are converted to lower case
+ */
+ const LowerCase='LowerCase';
+ /**
+ * Column names are converted to upper case
+ */
+ const UpperCase='UpperCase';
+}
+
+/**
+ * TDbNullConversionMode
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Data
+ * @since 3.0
+ */
+class TDbNullConversionMode extends TEnumerable
+{
+ /**
+ * No conversion is performed for null and empty values.
+ */
+ const Preserved='Preserved';
+ /**
+ * NULL is converted to empty string
+ */
+ const NullToEmptyString='NullToEmptyString';
+ /**
+ * Empty string is converted to NULL
+ */
+ const EmptyStringToNull='EmptyStringToNull';
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/TDbDataReader.php b/framework/Data/TDbDataReader.php
new file mode 100644
index 00000000..32b86c1d
--- /dev/null
+++ b/framework/Data/TDbDataReader.php
@@ -0,0 +1,208 @@
+<?php
+/**
+ * TDbDataReader class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Id $
+ * @package System.Data
+ */
+
+/**
+ * TDbDataReader class.
+ *
+ * TDbDataReader represents a forward-only stream of rows from a query result set.
+ *
+ * To read the current row of data, call {@link read}. The method {@link readAll}
+ * returns all the rows in a single array.
+ *
+ * TDbDataReader implements Iterator interface and thus can be used in foreach like the following:
+ * <code>
+ * foreach($reader as $row)
+ * // $row represents a row of data
+ * </code>
+ *
+ * It is possible to use a specific mode of data fetching by setting
+ * {@link setFetchMode FetchMode}. See {@link
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Data
+ * @since 3.0
+ */
+class TDbDataReader extends TComponent implements Iterator
+{
+ private $_statement;
+ private $_closed=false;
+ private $_row;
+ private $_index=-1;
+
+ /**
+ * Constructor.
+ * @param TDbCommand the command generating the query result
+ */
+ public function __construct(TDbCommand $command)
+ {
+ $this->_statement=$command->getPdoStatement();
+ $this->_statement->setFetchMode(PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Binds a column to a PHP variable.
+ * When rows of data are being fetched, the corresponding column value
+ * will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND.
+ * @param mixed Number of the column (1-indexed) or name of the column
+ * in the result set. If using the column name, be aware that the name
+ * should match the case of the column, as returned by the driver.
+ * @param mixed Name of the PHP variable to which the column will be bound.
+ * @param int Data type of the parameter
+ * @see http://www.php.net/manual/en/function.pdostatement-bindcolumn.php
+ */
+ public function bindColumn($column, &$value, $dataType=null)
+ {
+ if($dataType===null)
+ $this->_statement->bindColumn($column,$value);
+ else
+ $this->_statement->bindColumn($column,$value,$dataType);
+ }
+
+ /**
+ * @see http://www.php.net/manual/en/function.pdostatement-setfetchmode.php
+ */
+ public function setFetchMode($mode)
+ {
+ $params=func_get_args();
+ call_user_func_array(array($this->_statement,'setFetchMode'),$params);
+ }
+
+ /**
+ * Advances the reader to the next record in a result set.
+ * @return array|false the current record, false if no more row available
+ */
+ public function read()
+ {
+ return $this->_statement->fetch();
+ }
+
+ /**
+ * Reads the whole result set into an array.
+ * @return array|false the result set (each array element represents a row of data), false if no data is available.
+ */
+ public function readAll()
+ {
+ return $this->_statement->fetchAll();
+ }
+
+ /**
+ * Advances the reader to the next result when reading the results of a batch of statements.
+ * This method is only useful when there are multiple result sets
+ * returned by the query. Not all DBMS support this feature.
+ */
+ public function nextResult()
+ {
+ return $this->_statement->nextRowset();
+ }
+
+ /**
+ * Closes the reader.
+ * Any further data reading will result in an exception.
+ */
+ public function close()
+ {
+ $this->_statement->closeCursor();
+ $this->_closed=true;
+ }
+
+ /**
+ * @return boolean whether the reader is closed or not.
+ */
+ public function getIsClosed()
+ {
+ return $this->_closed;
+ }
+
+ /**
+ * @return boolean whether the result contains any row of data
+ */
+ public function getHasRows()
+ {
+ return $this->getRowCount()>0;
+ }
+
+ /**
+ * @return int number of rows contained in the result.
+ * Note, some DBMS may not give a meaningful count.
+ */
+ public function getRowCount()
+ {
+ return $this->_statement->rowCount();
+ }
+
+ /**
+ * @return int the number of columns in the result set, 0 if the result is empty
+ */
+ public function getColumnCount()
+ {
+ return $this->_statement->columnCount();
+ }
+
+ /**
+ * Resets the iterator to the initial state.
+ * This method is required by the interface Iterator.
+ * @throws TDbException if this method is invoked twice
+ */
+ public function rewind()
+ {
+ if($this->_index<0)
+ {
+ $this->_row=$this->_statement->fetch();
+ $this->_index=0;
+ }
+ else
+ throw new TDbException('dbdatareader_rewind_invalid');
+ }
+
+ /**
+ * Returns the index of the current row.
+ * This method is required by the interface Iterator.
+ * @return integer the index of the current row.
+ */
+ public function key()
+ {
+ return $this->_index;
+ }
+
+ /**
+ * Returns the current row.
+ * This method is required by the interface Iterator.
+ * @return mixed the current row.
+ */
+ public function current()
+ {
+ return $this->_row;
+ }
+
+ /**
+ * Moves the internal pointer to the next row.
+ * This method is required by the interface Iterator.
+ */
+ public function next()
+ {
+ $this->_row=$this->_statement->fetch();
+ $this->_index++;
+ }
+
+ /**
+ * Returns whether there is a row of data at current position.
+ * This method is required by the interface Iterator.
+ * @return boolean whether there is a row of data at current position.
+ */
+ public function valid()
+ {
+ return $this->_row!==false;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/TDbTransaction.php b/framework/Data/TDbTransaction.php
new file mode 100644
index 00000000..c3f68c97
--- /dev/null
+++ b/framework/Data/TDbTransaction.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * TDbTransaction class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Id $
+ * @package System.Data
+ */
+
+Prado::using('System.Data.TDbDataReader');
+
+/**
+ * TDbTransaction class.
+ *
+ * TDbTransaction represents a DB transaction.
+ * It is usually created by calling {@link TDbConnection::beginTransaction}.
+ *
+ * The following code is a common scenario of using transactions:
+ * <code>
+ * try
+ * {
+ * $transaction=$connection->beginTransaction();
+ * $connection->createCommand($sql1)->execute();
+ * $connection->createCommand($sql2)->execute();
+ * //.... other SQL executions
+ * $transaction->commit();
+ * }
+ * catch(Exception $e)
+ * {
+ * $transaction->rollBack();
+ * }
+ * </code>
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Data
+ * @since 3.0
+ */
+class TDbTransaction extends TComponent
+{
+ private $_connection=null;
+ private $_active;
+
+ /**
+ * Constructor.
+ * @param TDbConnection the connection associated with this transaction
+ * @see TDbConnection::beginTransaction
+ */
+ public function __construct(TDbConnection $connection)
+ {
+ $this->_connection=$connection;
+ $this->setActive(true);
+ }
+
+ /**
+ * Commits a transaction.
+ * @throws TDbException if the transaction or the DB connection is not active.
+ */
+ public function commit()
+ {
+ if($this->_active && $this->_connection->getActive())
+ {
+ $this->_connection->getPdoInstance()->commit();
+ $this->_active=false;
+ }
+ else
+ throw new TDbException('dbtransaction_transaction_inactive');
+ }
+
+ /**
+ * Rolls back a transaction.
+ * @throws TDbException if the transaction or the DB connection is not active.
+ */
+ public function rollback()
+ {
+ if($this->_active && $this->_connection->getActive())
+ {
+ $this->_connection->getPdoInstance()->rollBack();
+ $this->_active=false;
+ }
+ else
+ throw new TDbException('dbtransaction_transaction_inactive');
+ }
+
+ /**
+ * @return TDbConnection the DB connection for this transaction
+ */
+ public function getConnection()
+ {
+ return $this->_connection;
+ }
+
+ /**
+ * @return boolean whether this transaction is active
+ */
+ public function getActive()
+ {
+ return $this->_active;
+ }
+
+ /**
+ * @param boolean whether this transaction is active
+ */
+ protected function setActive($value)
+ {
+ $this->_active=TPropertyValue::ensureBoolean($value);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/unit/Data/TDbCommandTest.php b/tests/unit/Data/TDbCommandTest.php
new file mode 100644
index 00000000..f0c02f75
--- /dev/null
+++ b/tests/unit/Data/TDbCommandTest.php
@@ -0,0 +1,131 @@
+<?php
+
+require_once(dirname(__FILE__).'/../phpunit2.php');
+
+Prado::using('System.Data.*');
+
+define('TEST_DB_FILE',dirname(__FILE__).'/db/test.db');
+
+/**
+ * @package System.Data.PDO
+ */
+class TDbCommandTest extends PHPUnit2_Framework_TestCase
+{
+ private $_connection;
+
+ public function setUp()
+ {
+ @unlink(TEST_DB_FILE);
+ $this->_connection=new TDbConnection('sqlite:'.TEST_DB_FILE);
+ $this->_connection->Active=true;
+ $this->_connection->createCommand('CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(8))')->execute();
+ $this->_connection->createCommand('INSERT TABLE foo (name) VALUES (\'my name\')')->execute;
+ }
+
+ public function tearDown()
+ {
+ $this->_connection=null;
+ }
+
+ public function testGetText()
+ {
+ $sql='SELECT * FROM foo';
+ $command=$this->_connection->createCommand($sql);
+ $this->assertEquals($command->Text,$sql);
+ }
+
+ public function testSetText()
+ {
+ $sql='SELECT * FROM foo';
+ $newSql='SELECT id FROM foo';
+ $command=$this->_connection->createCommand($sql);
+ $command->query()->read();
+ $command->Text=$newSql;
+ $this->assertEquals($command->Text,$newSql);
+ $row=$command->query()->read();
+ $this->assertEquals($row['id'],1);
+ }
+
+/*
+ public function testActive()
+ {
+ $this->assertFalse($this->_connection2->Active);
+
+ $this->_connection2->Active=true;
+ $this->assertTrue($this->_connection2->Active);
+ $pdo=$this->_connection2->PdoInstance;
+ // test setting Active repeatedly doesn't re-connect DB
+ $this->_connection2->Active=true;
+ $this->assertTrue($pdo===$this->_connection2->PdoInstance);
+
+ $this->_connection2->Active=false;
+ $this->assertFalse($this->_connection2->Active);
+ }
+
+ public function testCreateCommand()
+ {
+ $sql='CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(8))';
+ try
+ {
+ $this->_connection2->createCommand($sql);
+ $this->fail('Expected exception is not raised');
+ }
+ catch(TDbException $e)
+ {
+ }
+
+ $command=$this->_connection->createCommand($sql);
+ $this->assertTrue($command instanceof TDbCommand);
+ }
+
+ public function testBeginTransaction()
+ {
+ $sql='INSERT INTO foo(id,name) VALUES (1,\'my name\')';
+ $transaction=$this->_connection->beginTransaction();
+ try
+ {
+ $this->_connection->createCommand($sql)->execute();
+ $this->_connection->createCommand($sql)->execute();
+ $this->fail('Expected exception not raised');
+ $transaction->commit();
+ }
+ catch(Exception $e)
+ {
+ $transaction->rollBack();
+ $reader=$this->_connection->createCommand('SELECT * FROM foo')->query();
+ $this->assertFalse($reader->read());
+ }
+ }
+
+ public function testLastInsertID()
+ {
+ $sql='INSERT INTO foo(name) VALUES (\'my name\')';
+ $this->_connection->createCommand($sql)->execute();
+ $value=$this->_connection->LastInsertID;
+ $this->assertEquals($this->_connection->LastInsertID,'1');
+ }
+
+ public function testQuoteString()
+ {
+ $str="this is 'my' name";
+ $expectedStr="'this is ''my'' name'";
+ $this->assertEquals($expectedStr,$this->_connection->quoteString($str));
+ }
+
+ public function testColumnNameCase()
+ {
+ $this->assertEquals(TDbColumnCaseMode::Preserved,$this->_connection->ColumnCase);
+ $this->_connection->ColumnCase=TDbColumnCaseMode::LowerCase;
+ $this->assertEquals(TDbColumnCaseMode::LowerCase,$this->_connection->ColumnCase);
+ }
+
+ public function testNullConversion()
+ {
+ $this->assertEquals(TDbNullConversionMode::Preserved,$this->_connection->NullConversion);
+ $this->_connection->NullConversion=TDbNullConversionMode::NullToEmptyString;
+ $this->assertEquals(TDbNullConversionMode::NullToEmptyString,$this->_connection->NullConversion);
+ }
+ */
+}
+
+?> \ No newline at end of file
diff --git a/tests/unit/Data/TDbConnectionTest.php b/tests/unit/Data/TDbConnectionTest.php
new file mode 100644
index 00000000..21d6730b
--- /dev/null
+++ b/tests/unit/Data/TDbConnectionTest.php
@@ -0,0 +1,114 @@
+<?php
+
+require_once(dirname(__FILE__).'/../phpunit2.php');
+
+Prado::using('System.Data.*');
+
+define('TEST_DB_FILE',dirname(__FILE__).'/db/test.db');
+define('TEST_DB_FILE2',dirname(__FILE__).'/db/test2.db');
+
+/**
+ * @package System.Data.PDO
+ */
+class TDbConnectionTest extends PHPUnit2_Framework_TestCase
+{
+ private $_connection1;
+ private $_connection2;
+
+ public function setUp()
+ {
+ @unlink(TEST_DB_FILE);
+ @unlink(TEST_DB_FILE2);
+ $this->_connection1=new TDbConnection('sqlite:'.TEST_DB_FILE);
+ $this->_connection1->Active=true;
+ $this->_connection1->createCommand('CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(8))')->execute();
+ $this->_connection2=new TDbConnection('sqlite:'.TEST_DB_FILE2);
+ }
+
+ public function tearDown()
+ {
+ $this->_connection1=null;
+ $this->_connection2=null;
+ }
+
+ public function testActive()
+ {
+ $this->assertFalse($this->_connection2->Active);
+
+ $this->_connection2->Active=true;
+ $this->assertTrue($this->_connection2->Active);
+ $pdo=$this->_connection2->PdoInstance;
+ // test setting Active repeatedly doesn't re-connect DB
+ $this->_connection2->Active=true;
+ $this->assertTrue($pdo===$this->_connection2->PdoInstance);
+
+ $this->_connection2->Active=false;
+ $this->assertFalse($this->_connection2->Active);
+ }
+
+ public function testCreateCommand()
+ {
+ $sql='CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(8))';
+ try
+ {
+ $this->_connection2->createCommand($sql);
+ $this->fail('Expected exception is not raised');
+ }
+ catch(TDbException $e)
+ {
+ }
+
+ $command=$this->_connection1->createCommand($sql);
+ $this->assertTrue($command instanceof TDbCommand);
+ }
+
+ public function testBeginTransaction()
+ {
+ $sql='INSERT INTO foo(id,name) VALUES (1,\'my name\')';
+ $transaction=$this->_connection1->beginTransaction();
+ try
+ {
+ $this->_connection1->createCommand($sql)->execute();
+ $this->_connection1->createCommand($sql)->execute();
+ $this->fail('Expected exception not raised');
+ $transaction->commit();
+ }
+ catch(Exception $e)
+ {
+ $transaction->rollBack();
+ $reader=$this->_connection1->createCommand('SELECT * FROM foo')->query();
+ $this->assertFalse($reader->read());
+ }
+ }
+
+ public function testLastInsertID()
+ {
+ $sql='INSERT INTO foo(name) VALUES (\'my name\')';
+ $this->_connection1->createCommand($sql)->execute();
+ $value=$this->_connection1->LastInsertID;
+ $this->assertEquals($this->_connection1->LastInsertID,'1');
+ }
+
+ public function testQuoteString()
+ {
+ $str="this is 'my' name";
+ $expectedStr="'this is ''my'' name'";
+ $this->assertEquals($expectedStr,$this->_connection1->quoteString($str));
+ }
+
+ public function testColumnNameCase()
+ {
+ $this->assertEquals(TDbColumnCaseMode::Preserved,$this->_connection1->ColumnCase);
+ $this->_connection1->ColumnCase=TDbColumnCaseMode::LowerCase;
+ $this->assertEquals(TDbColumnCaseMode::LowerCase,$this->_connection1->ColumnCase);
+ }
+
+ public function testNullConversion()
+ {
+ $this->assertEquals(TDbNullConversionMode::Preserved,$this->_connection1->NullConversion);
+ $this->_connection1->NullConversion=TDbNullConversionMode::NullToEmptyString;
+ $this->assertEquals(TDbNullConversionMode::NullToEmptyString,$this->_connection1->NullConversion);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/unit/Data/TDbDataReaderTest.php b/tests/unit/Data/TDbDataReaderTest.php
new file mode 100644
index 00000000..21d6730b
--- /dev/null
+++ b/tests/unit/Data/TDbDataReaderTest.php
@@ -0,0 +1,114 @@
+<?php
+
+require_once(dirname(__FILE__).'/../phpunit2.php');
+
+Prado::using('System.Data.*');
+
+define('TEST_DB_FILE',dirname(__FILE__).'/db/test.db');
+define('TEST_DB_FILE2',dirname(__FILE__).'/db/test2.db');
+
+/**
+ * @package System.Data.PDO
+ */
+class TDbConnectionTest extends PHPUnit2_Framework_TestCase
+{
+ private $_connection1;
+ private $_connection2;
+
+ public function setUp()
+ {
+ @unlink(TEST_DB_FILE);
+ @unlink(TEST_DB_FILE2);
+ $this->_connection1=new TDbConnection('sqlite:'.TEST_DB_FILE);
+ $this->_connection1->Active=true;
+ $this->_connection1->createCommand('CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(8))')->execute();
+ $this->_connection2=new TDbConnection('sqlite:'.TEST_DB_FILE2);
+ }
+
+ public function tearDown()
+ {
+ $this->_connection1=null;
+ $this->_connection2=null;
+ }
+
+ public function testActive()
+ {
+ $this->assertFalse($this->_connection2->Active);
+
+ $this->_connection2->Active=true;
+ $this->assertTrue($this->_connection2->Active);
+ $pdo=$this->_connection2->PdoInstance;
+ // test setting Active repeatedly doesn't re-connect DB
+ $this->_connection2->Active=true;
+ $this->assertTrue($pdo===$this->_connection2->PdoInstance);
+
+ $this->_connection2->Active=false;
+ $this->assertFalse($this->_connection2->Active);
+ }
+
+ public function testCreateCommand()
+ {
+ $sql='CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(8))';
+ try
+ {
+ $this->_connection2->createCommand($sql);
+ $this->fail('Expected exception is not raised');
+ }
+ catch(TDbException $e)
+ {
+ }
+
+ $command=$this->_connection1->createCommand($sql);
+ $this->assertTrue($command instanceof TDbCommand);
+ }
+
+ public function testBeginTransaction()
+ {
+ $sql='INSERT INTO foo(id,name) VALUES (1,\'my name\')';
+ $transaction=$this->_connection1->beginTransaction();
+ try
+ {
+ $this->_connection1->createCommand($sql)->execute();
+ $this->_connection1->createCommand($sql)->execute();
+ $this->fail('Expected exception not raised');
+ $transaction->commit();
+ }
+ catch(Exception $e)
+ {
+ $transaction->rollBack();
+ $reader=$this->_connection1->createCommand('SELECT * FROM foo')->query();
+ $this->assertFalse($reader->read());
+ }
+ }
+
+ public function testLastInsertID()
+ {
+ $sql='INSERT INTO foo(name) VALUES (\'my name\')';
+ $this->_connection1->createCommand($sql)->execute();
+ $value=$this->_connection1->LastInsertID;
+ $this->assertEquals($this->_connection1->LastInsertID,'1');
+ }
+
+ public function testQuoteString()
+ {
+ $str="this is 'my' name";
+ $expectedStr="'this is ''my'' name'";
+ $this->assertEquals($expectedStr,$this->_connection1->quoteString($str));
+ }
+
+ public function testColumnNameCase()
+ {
+ $this->assertEquals(TDbColumnCaseMode::Preserved,$this->_connection1->ColumnCase);
+ $this->_connection1->ColumnCase=TDbColumnCaseMode::LowerCase;
+ $this->assertEquals(TDbColumnCaseMode::LowerCase,$this->_connection1->ColumnCase);
+ }
+
+ public function testNullConversion()
+ {
+ $this->assertEquals(TDbNullConversionMode::Preserved,$this->_connection1->NullConversion);
+ $this->_connection1->NullConversion=TDbNullConversionMode::NullToEmptyString;
+ $this->assertEquals(TDbNullConversionMode::NullToEmptyString,$this->_connection1->NullConversion);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/unit/Data/TDbTransactionTest.php b/tests/unit/Data/TDbTransactionTest.php
new file mode 100644
index 00000000..9d34857d
--- /dev/null
+++ b/tests/unit/Data/TDbTransactionTest.php
@@ -0,0 +1,73 @@
+<?php
+
+require_once(dirname(__FILE__).'/../phpunit2.php');
+
+Prado::using('System.Data.*');
+
+define('TEST_DB_FILE',dirname(__FILE__).'/db/test.db');
+
+/**
+ * @package System.Data.PDO
+ */
+class TDbTransactionTest extends PHPUnit2_Framework_TestCase
+{
+ private $_connection;
+
+ public function setUp()
+ {
+ @unlink(TEST_DB_FILE);
+ $this->_connection=new TDbConnection('sqlite:'.TEST_DB_FILE);
+ $this->_connection->Active=true;
+ $this->_connection->createCommand('CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(8))')->execute();
+ }
+
+ public function tearDown()
+ {
+ $this->_connection=null;
+ }
+
+ public function testRollBack()
+ {
+ $sql='INSERT INTO foo(id,name) VALUES (1,\'my name\')';
+ $transaction=$this->_connection->beginTransaction();
+ try
+ {
+ $this->_connection->createCommand($sql)->execute();
+ $this->_connection->createCommand($sql)->execute();
+ $this->fail('Expected exception not raised');
+ $transaction->commit();
+ }
+ catch(Exception $e)
+ {
+ $this->assertTrue($transaction->Active);
+ $transaction->rollBack();
+ $this->assertFalse($transaction->Active);
+ $reader=$this->_connection->createCommand('SELECT * FROM foo')->query();
+ $this->assertFalse($reader->read());
+ }
+ }
+
+ public function testCommit()
+ {
+ $sql1='INSERT INTO foo(id,name) VALUES (1,\'my name\')';
+ $sql2='INSERT INTO foo(id,name) VALUES (2,\'my name\')';
+ $transaction=$this->_connection->beginTransaction();
+ try
+ {
+ $this->_connection->createCommand($sql1)->execute();
+ $this->_connection->createCommand($sql2)->execute();
+ $this->assertTrue($transaction->Active);
+ $transaction->commit();
+ $this->assertFalse($transaction->Active);
+ }
+ catch(Exception $e)
+ {
+ $transaction->rollBack();
+ $this->fail('Unexpected exception');
+ }
+ $result=$this->_connection->createCommand('SELECT * FROM foo')->query()->readAll();
+ $this->assertEquals(count($result),2);
+ }
+}
+
+?> \ No newline at end of file