diff --git a/CHANGELOG.md b/CHANGELOG.md index 9733a98455..e15100439e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,3 +22,6 @@ All notable changes to this project will be documented in this file, in reverse bit on a regular file. - [#3](https://github.com/zendframework/zend-db/pull/3) updates the code to use closure binding (now that we're on 5.5+, this is possible). +- [#9](https://github.com/zendframework/zend-db/pull/9) thoroughly audits the + OCI8 (Oracle) driver, ensuring it provides feature parity with other drivers, + and fixes issues with subselects, limits, and offsets. diff --git a/src/Adapter/Adapter.php b/src/Adapter/Adapter.php index ad6f9b1dc5..c20b5c751e 100644 --- a/src/Adapter/Adapter.php +++ b/src/Adapter/Adapter.php @@ -333,8 +333,8 @@ protected function createPlatform($parameters) // PDO is only supported driver for quoting values in this platform return new Platform\SqlServer(($this->driver instanceof Driver\Pdo\Pdo) ? $this->driver : null); case 'Oracle': - // oracle does not accept a driver as an option, no driver specific quoting available - return new Platform\Oracle($options); + $driver = ($this->driver instanceof Driver\Oci8\Oci8 || $this->driver instanceof Driver\Pdo\Pdo) ? $this->driver : null; + return new Platform\Oracle($options, $driver); case 'Sqlite': // PDO is only supported driver for quoting values in this platform return new Platform\Sqlite(($this->driver instanceof Driver\Pdo\Pdo) ? $this->driver : null); diff --git a/src/Adapter/Driver/Oci8/Feature/RowCounter.php b/src/Adapter/Driver/Oci8/Feature/RowCounter.php new file mode 100644 index 0000000000..b9e5474088 --- /dev/null +++ b/src/Adapter/Driver/Oci8/Feature/RowCounter.php @@ -0,0 +1,75 @@ +getSql(); + if ($sql == '' || stripos(strtolower($sql), 'select') === false) { + return; + } + $countSql = 'SELECT COUNT(*) as "count" FROM (' . $sql . ')'; + $countStmt->prepare($countSql); + $result = $countStmt->execute(); + $countRow = $result->current(); + return $countRow['count']; + } + + /** + * @param string $sql + * @return null|int + */ + public function getCountForSql($sql) + { + if (stripos(strtolower($sql), 'select') === false) { + return; + } + $countSql = 'SELECT COUNT(*) as "count" FROM (' . $sql . ')'; + $result = $this->driver->getConnection()->execute($countSql); + $countRow = $result->current(); + return $countRow['count']; + } + + /** + * @param \Zend\Db\Adapter\Driver\Oci8\Statement|string $context + * @return callable + */ + public function getRowCountClosure($context) + { + $rowCounter = $this; + return function () use ($rowCounter, $context) { + /** @var $rowCounter RowCounter */ + return ($context instanceof Statement) + ? $rowCounter->getCountForStatement($context) + : $rowCounter->getCountForSql($context); + }; + } +} diff --git a/src/Adapter/Driver/Oci8/Oci8.php b/src/Adapter/Driver/Oci8/Oci8.php index c846df725d..ad2f9c5feb 100644 --- a/src/Adapter/Driver/Oci8/Oci8.php +++ b/src/Adapter/Driver/Oci8/Oci8.php @@ -12,9 +12,12 @@ use Zend\Db\Adapter\Driver\DriverInterface; use Zend\Db\Adapter\Exception; use Zend\Db\Adapter\Profiler; +use Zend\Db\Adapter\Driver\Feature\AbstractFeature; class Oci8 implements DriverInterface, Profiler\ProfilerAwareInterface { + const FEATURES_DEFAULT = 'default'; + /** * @var Connection */ @@ -35,20 +38,46 @@ class Oci8 implements DriverInterface, Profiler\ProfilerAwareInterface */ protected $profiler = null; + /** + * @var array + */ + protected $options = []; + + /** + * @var array + */ + protected $features = []; + /** * @param array|Connection|\oci8 $connection * @param null|Statement $statementPrototype * @param null|Result $resultPrototype + * @param array $options */ - public function __construct($connection, Statement $statementPrototype = null, Result $resultPrototype = null) - { + public function __construct( + $connection, + Statement $statementPrototype = null, + Result $resultPrototype = null, + array $options = [], + $features = self::FEATURES_DEFAULT + ) { if (!$connection instanceof Connection) { $connection = new Connection($connection); } + $options = array_intersect_key(array_merge($this->options, $options), $this->options); $this->registerConnection($connection); $this->registerStatementPrototype(($statementPrototype) ?: new Statement()); $this->registerResultPrototype(($resultPrototype) ?: new Result()); + if (is_array($features)) { + foreach ($features as $name => $feature) { + $this->addFeature($name, $feature); + } + } elseif ($features instanceof AbstractFeature) { + $this->addFeature($features->getName(), $features); + } elseif ($features === self::FEATURES_DEFAULT) { + $this->setupDefaultFeatures(); + } } /** @@ -129,6 +158,48 @@ public function getResultPrototype() return $this->resultPrototype; } + /** + * Add feature + * + * @param string $name + * @param AbstractFeature $feature + * @return self + */ + public function addFeature($name, $feature) + { + if ($feature instanceof AbstractFeature) { + $name = $feature->getName(); // overwrite the name, just in case + $feature->setDriver($this); + } + $this->features[$name] = $feature; + return $this; + } + + /** + * Setup the default features for Pdo + * + * @return self + */ + public function setupDefaultFeatures() + { + $this->addFeature(null, new Feature\RowCounter()); + return $this; + } + + /** + * Get feature + * + * @param string $name + * @return AbstractFeature|false + */ + public function getFeature($name) + { + if (isset($this->features[$name])) { + return $this->features[$name]; + } + return false; + } + /** * Get database platform name * @@ -146,7 +217,9 @@ public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCAS public function checkEnvironment() { if (!extension_loaded('oci8')) { - throw new Exception\RuntimeException('The Oci8 extension is required for this adapter but the extension is not loaded'); + throw new Exception\RuntimeException( + 'The Oci8 extension is required for this adapter but the extension is not loaded' + ); } } @@ -185,13 +258,18 @@ public function createStatement($sqlOrResource = null) /** * @param resource $resource - * @param null $isBuffered + * @param null $context * @return Result */ - public function createResult($resource, $isBuffered = null) + public function createResult($resource, $context = null) { $result = clone $this->resultPrototype; - $result->initialize($resource, $this->connection->getLastGeneratedValue(), $isBuffered); + $rowCount = null; + // special feature, oracle Oci counter + if ($context && ($rowCounter = $this->getFeature('RowCounter')) && oci_num_fields($resource) > 0) { + $rowCount = $rowCounter->getRowCountClosure($context); + } + $result->initialize($resource, $rowCount); return $result; } diff --git a/src/Adapter/Driver/Oci8/Result.php b/src/Adapter/Driver/Oci8/Result.php index fd621a4caf..b771d4b58a 100644 --- a/src/Adapter/Driver/Oci8/Result.php +++ b/src/Adapter/Driver/Oci8/Result.php @@ -21,9 +21,9 @@ class Result implements Iterator, ResultInterface protected $resource = null; /** - * @var bool + * @var null|int */ - protected $isBuffered = null; + protected $rowCount = null; /** * Cursor position @@ -42,7 +42,7 @@ class Result implements Iterator, ResultInterface * @var bool */ protected $currentComplete = false; - + /** * @var bool */ @@ -62,14 +62,18 @@ class Result implements Iterator, ResultInterface /** * Initialize * @param resource $resource + * @param null|int $generatedValue + * @param null|int $rowCount * @return Result */ - public function initialize($resource /*, $generatedValue, $isBuffered = null*/) + public function initialize($resource, $generatedValue = null, $rowCount = null) { if (!is_resource($resource) && get_resource_type($resource) !== 'oci8 statement') { throw new Exception\InvalidArgumentException('Invalid resource provided.'); } $this->resource = $resource; + $this->generatedValue = $generatedValue; + $this->rowCount = $rowCount; return $this; } @@ -134,7 +138,6 @@ public function current() return false; } } - return $this->currentData; } @@ -147,7 +150,6 @@ protected function loadData() { $this->currentComplete = true; $this->currentData = oci_fetch_assoc($this->resource); - if ($this->currentData !== false) { $this->position++; return true; @@ -191,17 +193,22 @@ public function valid() if ($this->currentComplete) { return ($this->currentData !== false); } - return $this->loadData(); } /** * Count - * @return int + * @return null|int */ public function count() { - // @todo OCI8 row count in Driver Result + if (is_int($this->rowCount)) { + return $this->rowCount; + } + if (is_callable($this->rowCount)) { + $this->rowCount = (int) call_user_func($this->rowCount); + return $this->rowCount; + } return; } @@ -214,7 +221,7 @@ public function getFieldCount() } /** - * @return mixed|null + * @return null */ public function getGeneratedValue() { diff --git a/src/Adapter/Driver/Oci8/Statement.php b/src/Adapter/Driver/Oci8/Statement.php index c14f830243..2ca9de9af0 100644 --- a/src/Adapter/Driver/Oci8/Statement.php +++ b/src/Adapter/Driver/Oci8/Statement.php @@ -260,7 +260,7 @@ public function execute($parameters = null) throw new Exception\RuntimeException($e['message'], $e['code']); } - $result = $this->driver->createResult($this->resource); + $result = $this->driver->createResult($this->resource, $this); return $result; } @@ -313,4 +313,17 @@ protected function bindParametersFromContainer() oci_bind_by_name($this->resource, $name, $value, $maxLength, $type); } } + + /** + * Perform a deep clone + */ + public function __clone() + { + $this->isPrepared = false; + $this->parametersBound = false; + $this->resource = null; + if ($this->parameterContainer) { + $this->parameterContainer = clone $this->parameterContainer; + } + } } diff --git a/src/Adapter/Platform/Oracle.php b/src/Adapter/Platform/Oracle.php index a589e09822..7b9ebe2cad 100644 --- a/src/Adapter/Platform/Oracle.php +++ b/src/Adapter/Platform/Oracle.php @@ -9,12 +9,23 @@ namespace Zend\Db\Adapter\Platform; +use Zend\Db\Adapter\Driver\DriverInterface; +use Zend\Db\Adapter\Driver\Oci8\Oci8; +use Zend\Db\Adapter\Driver\Pdo\Pdo; +use \Zend\Db\Adapter\Exception\InvalidArgumentException; + class Oracle extends AbstractPlatform { + /** + * @var null|Pdo|Oci8 + */ + protected $resource = null; + /** * @param array $options + * @param null|Oci8|Pdo $driver */ - public function __construct($options = []) + public function __construct($options = [], $driver = null) { if (isset($options['quote_identifiers']) && ($options['quote_identifiers'] == false @@ -22,6 +33,41 @@ public function __construct($options = []) ) { $this->quoteIdentifiers = false; } + + if ($driver) { + $this->setDriver($driver); + } + } + + /** + * @param Pdo|Oci8 $driver + * @throws InvalidArgumentException + * @return $this + */ + public function setDriver($driver) + { + if ($driver instanceof Oci8 + || ($driver instanceof Pdo && $driver->getDatabasePlatformName() == 'Oracle') + || ($driver instanceof Pdo && $driver->getDatabasePlatformName() == 'Sqlite') + || ($driver instanceof \oci8) + || ($driver instanceof PDO && $driver->getAttribute(PDO::ATTR_DRIVER_NAME) == 'oci') + ) { + $this->resource = $driver; + return $this; + } + + throw new InvalidArgumentException( + '$driver must be a Oci8 or Oracle PDO Zend\Db\Adapter\Driver, ' + . 'Oci8 instance, or Oci PDO instance' + ); + } + + /** + * @return null|Pdo|Oci8 + */ + public function getDriver() + { + return $this->resource; } /** @@ -49,11 +95,28 @@ public function quoteIdentifierChain($identifierChain) */ public function quoteValue($value) { + if ($this->resource instanceof DriverInterface) { + $this->resource = $this->resource->getConnection()->getResource(); + } + + if ($this->resource) { + if ($this->resource instanceof PDO) { + return $this->resource->quote($value); + } + + if (get_resource_type($this->resource) == 'oci8 connection' + || get_resource_type($this->resource) == 'oci8 persistent connection' + ) { + return "'" . addcslashes(str_replace("'", "''", $value), "\x00\n\r\"\x1a") . "'"; + } + } + trigger_error( 'Attempting to quote a value in ' . __CLASS__ . ' without extension/driver support ' - . 'can introduce security vulnerabilities in a production environment.' + . 'can introduce security vulnerabilities in a production environment.' ); - return '\'' . addcslashes(str_replace('\'', '\'\'', $value), "\x00\n\r\"\x1a") . '\''; + + return "'" . addcslashes(str_replace("'", "''", $value), "\x00\n\r\"\x1a") . "'"; } /** @@ -61,6 +124,6 @@ public function quoteValue($value) */ public function quoteTrustedValue($value) { - return '\'' . addcslashes(str_replace('\'', '\'\'', $value), "\x00\n\r\"\x1a") . '\''; + return "'" . addcslashes(str_replace('\'', '\'\'', $value), "\x00\n\r\"\x1a") . "'"; } } diff --git a/src/Sql/Platform/Oracle/SelectDecorator.php b/src/Sql/Platform/Oracle/SelectDecorator.php index 01d7fb7219..4911cdbb6e 100644 --- a/src/Sql/Platform/Oracle/SelectDecorator.php +++ b/src/Sql/Platform/Oracle/SelectDecorator.php @@ -51,24 +51,33 @@ protected function localizeVariables() * @param PlatformInterface $platform * @param DriverInterface $driver * @param ParameterContainer $parameterContainer - * @param $sqls - * @param $parameters + * @param array $sqls + * @param array $parameters * @return null */ - protected function processLimitOffset(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null, &$sqls, &$parameters) - { + protected function processLimitOffset( + PlatformInterface $platform, + DriverInterface $driver = null, + ParameterContainer $parameterContainer = null, + &$sqls = [], + &$parameters = [] + ) { if ($this->limit === null && $this->offset === null) { return; } $selectParameters = $parameters[self::SELECT]; - $starSuffix = $platform->getIdentifierSeparator() . self::SQL_STAR; + foreach ($selectParameters[0] as $i => $columnParameters) { - if ($columnParameters[0] == self::SQL_STAR || (isset($columnParameters[1]) && $columnParameters[1] == self::SQL_STAR) || strpos($columnParameters[0], $starSuffix)) { + if ($columnParameters[0] == self::SQL_STAR + || (isset($columnParameters[1]) && $columnParameters[1] == self::SQL_STAR) + || strpos($columnParameters[0], $starSuffix) + ) { $selectParameters[0] = [[self::SQL_STAR]]; break; } + if (isset($columnParameters[1])) { array_shift($columnParameters); $selectParameters[0][$i] = $columnParameters; @@ -80,38 +89,67 @@ protected function processLimitOffset(PlatformInterface $platform, DriverInterfa } // first, produce column list without compound names (using the AS portion only) - array_unshift($sqls, $this->createSqlFromSpecificationAndParameters( - ['SELECT %1$s FROM (SELECT b.%1$s, rownum b_rownum FROM (' => current($this->specifications[self::SELECT])], $selectParameters - )); + array_unshift($sqls, $this->createSqlFromSpecificationAndParameters([ + 'SELECT %1$s FROM (SELECT b.%1$s, rownum b_rownum FROM (' => current($this->specifications[self::SELECT]), + ], $selectParameters)); if ($parameterContainer) { + $number = $this->processInfo['subselectCount'] ? $this->processInfo['subselectCount'] : ''; + if ($this->limit === null) { - array_push($sqls, ') b ) WHERE b_rownum > (:offset)'); - $parameterContainer->offsetSet('offset', $this->offset, $parameterContainer::TYPE_INTEGER); + array_push( + $sqls, + ') b ) WHERE b_rownum > (:offset' . $number . ')' + ); + $parameterContainer->offsetSet( + 'offset' . $number, + $this->offset, + $parameterContainer::TYPE_INTEGER + ); } else { // create bottom part of query, with offset and limit using row_number - array_push($sqls, ') b WHERE rownum <= (:offset+:limit)) WHERE b_rownum >= (:offset + 1)'); - $parameterContainer->offsetSet('offset', $this->offset, $parameterContainer::TYPE_INTEGER); - $parameterContainer->offsetSet('limit', $this->limit, $parameterContainer::TYPE_INTEGER); + array_push( + $sqls, + ') b WHERE rownum <= (:offset' + . $number + . '+:limit' + . $number + . ')) WHERE b_rownum >= (:offset' + . $number + . ' + 1)' + ); + $parameterContainer->offsetSet( + 'offset' . $number, + $this->offset, + $parameterContainer::TYPE_INTEGER + ); + $parameterContainer->offsetSet( + 'limit' . $number, + $this->limit, + $parameterContainer::TYPE_INTEGER + ); } + $this->processInfo['subselectCount']++; } else { if ($this->limit === null) { - array_push($sqls, ') b ) WHERE b_rownum > ('. (int) $this->offset. ')' - ); + array_push($sqls, ') b ) WHERE b_rownum > (' . (int) $this->offset . ')'); } else { - array_push($sqls, ') b WHERE rownum <= (' - . (int) $this->offset - . '+' - . (int) $this->limit - . ')) WHERE b_rownum >= (' - . (int) $this->offset - . ' + 1)' + array_push( + $sqls, + ') b WHERE rownum <= (' + . (int) $this->offset + . '+' + . (int) $this->limit + . ')) WHERE b_rownum >= (' + . (int) $this->offset + . ' + 1)' ); } } $sqls[self::SELECT] = $this->createSqlFromSpecificationAndParameters( - $this->specifications[self::SELECT], $parameters[self::SELECT] + $this->specifications[self::SELECT], + $parameters[self::SELECT] ); } } diff --git a/test/Adapter/Driver/Oci8/Feature/RowCounterTest.php b/test/Adapter/Driver/Oci8/Feature/RowCounterTest.php new file mode 100644 index 0000000000..fe80d39e63 --- /dev/null +++ b/test/Adapter/Driver/Oci8/Feature/RowCounterTest.php @@ -0,0 +1,108 @@ +rowCounter = new RowCounter(); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Feature\RowCounter::getName + */ + public function testGetName() + { + $this->assertEquals('RowCounter', $this->rowCounter->getName()); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Feature\RowCounter::getCountForStatement + */ + public function testGetCountForStatement() + { + $statement = $this->getMockStatement('SELECT XXX', 5); + $statement->expects($this->once()) + ->method('prepare') + ->with($this->equalTo('SELECT COUNT(*) as "count" FROM (SELECT XXX)')); + $count = $this->rowCounter->getCountForStatement($statement); + $this->assertEquals(5, $count); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Feature\RowCounter::getCountForSql + */ + public function testGetCountForSql() + { + $this->rowCounter->setDriver($this->getMockDriver(5)); + $count = $this->rowCounter->getCountForSql('SELECT XXX'); + $this->assertEquals(5, $count); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Feature\RowCounter::getRowCountClosure + */ + public function testGetRowCountClosure() + { + $stmt = $this->getMockStatement('SELECT XXX', 5); + /** @var \Closure $closure */ + $closure = $this->rowCounter->getRowCountClosure($stmt); + $this->assertInstanceOf('Closure', $closure); + $this->assertEquals(5, $closure()); + } + + protected function getMockStatement($sql, $returnValue) + { + $statement = $this->getMock( + 'Zend\Db\Adapter\Driver\Oci8\Statement', + ['prepare', 'execute'], + [], + '', + false + ); + + // mock the result + $result = $this->getMock('stdClass', ['current']); + $result->expects($this->once()) + ->method('current') + ->will($this->returnValue(['count' => $returnValue])); + $statement->setSql($sql); + $statement->expects($this->once()) + ->method('execute') + ->will($this->returnValue($result)); + return $statement; + } + + protected function getMockDriver($returnValue) + { + $oci8Statement = $this->getMock('stdClass', ['current'], [], '', false); // stdClass can be used here + $oci8Statement->expects($this->once()) + ->method('current') + ->will($this->returnValue(['count' => $returnValue])); + $connection = $this->getMock('Zend\Db\Adapter\Driver\ConnectionInterface'); + $connection->expects($this->once()) + ->method('execute') + ->will($this->returnValue($oci8Statement)); + $driver = $this->getMock('Zend\Db\Adapter\Driver\Oci8\Oci8', ['getConnection'], [], '', false); + $driver->expects($this->once()) + ->method('getConnection') + ->will($this->returnValue($connection)); + return $driver; + } +} diff --git a/test/Adapter/Driver/Oci8/ResultTest.php b/test/Adapter/Driver/Oci8/ResultTest.php new file mode 100644 index 0000000000..78ddf59385 --- /dev/null +++ b/test/Adapter/Driver/Oci8/ResultTest.php @@ -0,0 +1,89 @@ +assertNull($result->getResource()); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Result::buffer + */ + public function testBuffer() + { + $result = new Result(); + $this->assertNull($result->buffer()); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Result::isBuffered + */ + public function testIsBuffered() + { + $result = new Result(); + $this->assertFalse($result->isBuffered()); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Result::getGeneratedValue + */ + public function testGetGeneratedValue() + { + $result = new Result(); + $this->assertNull($result->getGeneratedValue()); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Result::key + */ + public function testKey() + { + $result = new Result(); + $this->assertEquals(0, $result->key()); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Result::next + */ + public function testNext() + { + $mockResult = $this->getMockBuilder('Zend\Db\Adapter\Driver\Oci8\Result') + ->setMethods(['loadData']) + ->getMock(); + $mockResult->expects($this->any()) + ->method('loadData') + ->will($this->returnValue(true)); + $this->assertTrue($mockResult->next()); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Result::rewind + */ + public function testRewind() + { + $result = new Result(); + $this->assertNull($result->rewind()); + } +} diff --git a/test/Adapter/Driver/Oci8/StatementTest.php b/test/Adapter/Driver/Oci8/StatementTest.php index cce26b6c9c..127b7c5a8a 100644 --- a/test/Adapter/Driver/Oci8/StatementTest.php +++ b/test/Adapter/Driver/Oci8/StatementTest.php @@ -12,7 +12,11 @@ use Zend\Db\Adapter\Driver\Oci8\Statement; use Zend\Db\Adapter\Driver\Oci8\Oci8; use Zend\Db\Adapter\ParameterContainer; +use Zend\Db\Adapter\Profiler\Profiler; +/** + * @group integrationOracle + */ class StatementTest extends \PHPUnit_Framework_TestCase { /** @@ -45,6 +49,42 @@ public function testSetDriver() $this->assertEquals($this->statement, $this->statement->setDriver(new Oci8([]))); } + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Statement::setProfiler + */ + public function testSetProfiler() + { + $this->assertEquals($this->statement, $this->statement->setProfiler(new Profiler())); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Statement::getProfiler + */ + public function testGetProfiler() + { + $profiler = new Profiler(); + $this->statement->setProfiler($profiler); + $this->assertEquals($profiler, $this->statement->getProfiler()); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Statement::initialize + */ + public function testInitialize() + { + $oci8 = new Oci8([]); + $this->assertEquals($this->statement, $this->statement->initialize($oci8)); + } + + /** + * @covers Zend\Db\Adapter\Driver\Oci8\Statement::setSql + */ + public function testSetSql() + { + $this->assertEquals($this->statement, $this->statement->setSql('select * from table')); + $this->assertEquals('select * from table', $this->statement->getSql()); + } + /** * @covers Zend\Db\Adapter\Driver\Oci8\Statement::setParameterContainer */ @@ -72,19 +112,7 @@ public function testGetResource() { // Remove the following lines when you implement this test. $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } - - /** - * @covers Zend\Db\Adapter\Driver\Oci8\Statement::setSql - * @todo Implement testSetSql(). - */ - public function testSetSql() - { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' + 'This test has not been implemented yet.' ); } @@ -94,10 +122,8 @@ public function testSetSql() */ public function testGetSql() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->assertEquals($this->statement, $this->statement->setSql('select * from table')); + $this->assertEquals('select * from table', $this->statement->getSql()); } /** @@ -108,20 +134,16 @@ public function testPrepare() { // Remove the following lines when you implement this test. $this->markTestIncomplete( - 'This test has not been implemented yet.' + 'This test has not been implemented yet.' ); } /** * @covers Zend\Db\Adapter\Driver\Oci8\Statement::isPrepared - * @todo Implement testIsPrepared(). */ public function testIsPrepared() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->assertFalse($this->statement->isPrepared()); } /** @@ -132,7 +154,7 @@ public function testExecute() { // Remove the following lines when you implement this test. $this->markTestIncomplete( - 'This test has not been implemented yet.' + 'This test has not been implemented yet.' ); } } diff --git a/test/Adapter/Platform/OracleTest.php b/test/Adapter/Platform/OracleTest.php index c03e86a682..dd89313385 100644 --- a/test/Adapter/Platform/OracleTest.php +++ b/test/Adapter/Platform/OracleTest.php @@ -27,6 +27,72 @@ protected function setUp() $this->platform = new Oracle; } + /** + * @covers Zend\Db\Adapter\Platform\Oracle::__construct + */ + public function testContructWithOptions() + { + $this->assertEquals('"\'test\'.\'test\'"', $this->platform->quoteIdentifier('"test"."test"')); + $plataform1 = new Oracle(['quote_identifiers'=> false]); + $this->assertEquals('"test"."test"', $plataform1->quoteIdentifier('"test"."test"')); + $plataform2 = new Oracle(['quote_identifiers'=> 'false']); + $this->assertEquals('"test"."test"', $plataform2->quoteIdentifier('"test"."test"')); + } + + /** + * @covers Zend\Db\Adapter\Platform\Oracle::__construct + */ + public function testContructWithDriver() + { + $mockDriver = $this->getMockForAbstractClass( + 'Zend\Db\Adapter\Driver\Oci8\Oci8', + [[]], + '', + true, + true, + true, + [] + ); + $platform = new Oracle([], $mockDriver); + $this->assertEquals($mockDriver, $platform->getDriver()); + } + + /** + * @covers Zend\Db\Adapter\Platform\Oracle::setDriver + */ + public function testSetDriver() + { + $mockDriver = $this->getMockForAbstractClass( + 'Zend\Db\Adapter\Driver\Oci8\Oci8', + [[]], + '', + true, + true, + true, + [] + ); + $platform = $this->platform->setDriver($mockDriver); + $this->assertEquals($mockDriver, $platform->getDriver()); + } + + /** + * @covers Zend\Db\Adapter\Platform\Oracle::setDriver + * @expectedException Zend\Db\Adapter\Exception\InvalidArgumentException + * @expectedMessage $driver must be a Oci8 or Oracle PDO Zend\Db\Adapter\Driver, Oci8 instance or Oci PDO instance + */ + public function testSetDriverInvalid() + { + $this->platform->setDriver(null); + } + + /** + * @covers Zend\Db\Adapter\Platform\Oracle::getDriver + */ + public function testGetDriver() + { + $this->assertNull($this->platform->getDriver()); + } + /** * @covers Zend\Db\Adapter\Platform\Oracle::getName */ @@ -84,7 +150,8 @@ public function testQuoteValueRaisesNoticeWithoutPlatformSupport() { $this->setExpectedException( 'PHPUnit_Framework_Error_Notice', - 'Attempting to quote a value in Zend\Db\Adapter\Platform\Oracle without extension/driver support can introduce security vulnerabilities in a production environment' + 'Attempting to quote a value in Zend\Db\Adapter\Platform\Oracle without ' + . 'extension/driver support can introduce security vulnerabilities in a production environment' ); $this->platform->quoteValue('value'); } @@ -96,8 +163,14 @@ public function testQuoteValue() { $this->assertEquals("'value'", @$this->platform->quoteValue('value')); $this->assertEquals("'Foo O''Bar'", @$this->platform->quoteValue("Foo O'Bar")); - $this->assertEquals('\'\'\'; DELETE FROM some_table; -- \'', @$this->platform->quoteValue('\'; DELETE FROM some_table; -- ')); - $this->assertEquals("'\\''; DELETE FROM some_table; -- '", @$this->platform->quoteValue('\\\'; DELETE FROM some_table; -- ')); + $this->assertEquals( + '\'\'\'; DELETE FROM some_table; -- \'', + @$this->platform->quoteValue('\'; DELETE FROM some_table; -- ') + ); + $this->assertEquals( + "'\\''; DELETE FROM some_table; -- '", + @$this->platform->quoteValue('\\\'; DELETE FROM some_table; -- ') + ); } /** @@ -107,10 +180,16 @@ public function testQuoteTrustedValue() { $this->assertEquals("'value'", $this->platform->quoteTrustedValue('value')); $this->assertEquals("'Foo O''Bar'", $this->platform->quoteTrustedValue("Foo O'Bar")); - $this->assertEquals('\'\'\'; DELETE FROM some_table; -- \'', $this->platform->quoteTrustedValue('\'; DELETE FROM some_table; -- ')); + $this->assertEquals( + '\'\'\'; DELETE FROM some_table; -- \'', + $this->platform->quoteTrustedValue('\'; DELETE FROM some_table; -- ') + ); // '\\\'; DELETE FROM some_table; -- ' <- actual below - $this->assertEquals("'\\''; DELETE FROM some_table; -- '", $this->platform->quoteTrustedValue('\\\'; DELETE FROM some_table; -- ')); + $this->assertEquals( + "'\\''; DELETE FROM some_table; -- '", + $this->platform->quoteTrustedValue('\\\'; DELETE FROM some_table; -- ') + ); } /** @@ -120,7 +199,8 @@ public function testQuoteValueList() { $this->setExpectedException( 'PHPUnit_Framework_Error', - 'Attempting to quote a value in Zend\Db\Adapter\Platform\Oracle without extension/driver support can introduce security vulnerabilities in a production environment' + 'Attempting to quote a value in Zend\Db\Adapter\Platform\Oracle without ' + . 'extension/driver support can introduce security vulnerabilities in a production environment' ); $this->assertEquals("'Foo O''Bar'", $this->platform->quoteValueList("Foo O'Bar")); } @@ -146,18 +226,27 @@ public function testQuoteIdentifierInFragment() $this->assertEquals('foo as bar', $platform->quoteIdentifierInFragment('foo as bar')); // single char words - $this->assertEquals('("foo"."bar" = "boo"."baz")', $this->platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', ['(', ')', '='])); + $this->assertEquals( + '("foo"."bar" = "boo"."baz")', + $this->platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', ['(', ')', '=']) + ); // case insensitive safe words $this->assertEquals( '("foo"."bar" = "boo"."baz") AND ("foo"."baz" = "boo"."baz")', - $this->platform->quoteIdentifierInFragment('(foo.bar = boo.baz) AND (foo.baz = boo.baz)', ['(', ')', '=', 'and']) + $this->platform->quoteIdentifierInFragment( + '(foo.bar = boo.baz) AND (foo.baz = boo.baz)', + ['(', ')', '=', 'and'] + ) ); // case insensitive safe words in field $this->assertEquals( '("foo"."bar" = "boo".baz) AND ("foo".baz = "boo".baz)', - $this->platform->quoteIdentifierInFragment('(foo.bar = boo.baz) AND (foo.baz = boo.baz)', ['(', ')', '=', 'and', 'bAz']) + $this->platform->quoteIdentifierInFragment( + '(foo.bar = boo.baz) AND (foo.baz = boo.baz)', + ['(', ')', '=', 'and', 'bAz'] + ) ); } } diff --git a/test/Sql/Platform/Oracle/OracleTest.php b/test/Sql/Platform/Oracle/OracleTest.php new file mode 100644 index 0000000000..5e9979918a --- /dev/null +++ b/test/Sql/Platform/Oracle/OracleTest.php @@ -0,0 +1,29 @@ +getDecorators(); + + list($type, $decorator) = each($decorators); + $this->assertEquals('Zend\Db\Sql\Select', $type); + $this->assertInstanceOf('Zend\Db\Sql\Platform\Oracle\SelectDecorator', $decorator); + } +} diff --git a/test/Sql/Platform/Oracle/SelectDecoratorTest.php b/test/Sql/Platform/Oracle/SelectDecoratorTest.php index f044ee5141..5140236974 100644 --- a/test/Sql/Platform/Oracle/SelectDecoratorTest.php +++ b/test/Sql/Platform/Oracle/SelectDecoratorTest.php @@ -16,16 +16,25 @@ class SelectDecoratorTest extends \PHPUnit_Framework_TestCase { + //@codingStandardsIgnoreStart /** * @testdox integration test: Testing SelectDecorator will use Select to produce properly Oracle dialect prepared sql * @covers Zend\Db\Sql\Platform\SqlServer\SelectDecorator::prepareStatement * @covers Zend\Db\Sql\Platform\SqlServer\SelectDecorator::processLimitOffset * @dataProvider dataProvider */ - public function testPrepareStatement(Select $select, $expectedSql, $expectedParams, $notUsed, $expectedFormatParamCount) - { + //@codingStandardsIgnoreEnd + public function testPrepareStatement( + Select $select, + $expectedSql, + $expectedParams, + $notUsed, + $expectedFormatParamCount + ) { $driver = $this->getMock('Zend\Db\Adapter\Driver\DriverInterface'); - $driver->expects($this->exactly($expectedFormatParamCount))->method('formatParameterName')->will($this->returnValue('?')); + $driver->expects($this->exactly($expectedFormatParamCount)) + ->method('formatParameterName') + ->will($this->returnValue('?')); // test $adapter = $this->getMock( @@ -39,7 +48,9 @@ public function testPrepareStatement(Select $select, $expectedSql, $expectedPara $parameterContainer = new ParameterContainer; $statement = $this->getMock('Zend\Db\Adapter\Driver\StatementInterface'); - $statement->expects($this->any())->method('getParameterContainer')->will($this->returnValue($parameterContainer)); + $statement->expects($this->any()) + ->method('getParameterContainer') + ->will($this->returnValue($parameterContainer)); $statement->expects($this->once())->method('setSql')->with($expectedSql); @@ -50,16 +61,20 @@ public function testPrepareStatement(Select $select, $expectedSql, $expectedPara $this->assertEquals($expectedParams, $parameterContainer->getNamedArray()); } + // @codingStandardsIgnoreStart /** * @testdox integration test: Testing SelectDecorator will use Select to produce properly Oracle dialect sql statements * @covers Zend\Db\Sql\Platform\Oracle\SelectDecorator::getSqlString * @dataProvider dataProvider */ + // @codingStandardsIgnoreEnd public function testGetSqlString(Select $select, $ignored, $alsoIgnored, $expectedSql) { $parameterContainer = new ParameterContainer; $statement = $this->getMock('Zend\Db\Adapter\Driver\StatementInterface'); - $statement->expects($this->any())->method('getParameterContainer')->will($this->returnValue($parameterContainer)); + $statement->expects($this->any()) + ->method('getParameterContainer') + ->will($this->returnValue($parameterContainer)); $selectDecorator = new SelectDecorator; $selectDecorator->setSubject($select); @@ -84,9 +99,46 @@ public function dataProvider() $expectedSql1 = 'SELECT "b".* FROM (SELECT "a".* FROM (SELECT "test".* FROM "test") "a") "b"'; $expectedFormatParamCount1 = 0; + $select2a = new Select('test'); + $select2a->limit(2); + $select2b = new Select(['a' => $select2a]); + $select2 = new Select(['b' => $select2b]); + // @codingStandardsIgnoreStart + $expectedSql2_1 = 'SELECT "b".* FROM (SELECT "a".* FROM (SELECT * FROM (SELECT b.*, rownum b_rownum FROM ( SELECT "test".* FROM "test" ) b WHERE rownum <= (:offset2+:limit2)) WHERE b_rownum >= (:offset2 + 1)) "a") "b"'; + $expectedSql2_2 = 'SELECT "b".* FROM (SELECT "a".* FROM (SELECT * FROM (SELECT b.*, rownum b_rownum FROM ( SELECT "test".* FROM "test" ) b WHERE rownum <= (0+2)) WHERE b_rownum >= (0 + 1)) "a") "b"'; + // @codingStandardsIgnoreEnd + $expectedFormatParamCount2 = 0; + $expectedParams2 = ['offset2' => 0, 'limit2' => 2]; + + $select3a = new Select('test'); + $select3a->offset(2); + $select3b = new Select(['a' => $select3a]); + $select3 = new Select(['b' => $select3b]); + // @codingStandardsIgnoreStart + $expectedSql3_1 = 'SELECT "b".* FROM (SELECT "a".* FROM (SELECT * FROM (SELECT b.*, rownum b_rownum FROM ( SELECT "test".* FROM "test" ) b ) WHERE b_rownum > (:offset2)) "a") "b"'; + $expectedSql3_2 = 'SELECT "b".* FROM (SELECT "a".* FROM (SELECT * FROM (SELECT b.*, rownum b_rownum FROM ( SELECT "test".* FROM "test" ) b ) WHERE b_rownum > (2)) "a") "b"'; + // @codingStandardsIgnoreEnd + $expectedFormatParamCount3 = 0; + $expectedParams3 = ['offset2' => 2]; + + $select4a = new Select('test'); + $select4a->limit(2); + $select4a->offset(2); + $select4b = new Select(['a' => $select4a]); + $select4 = new Select(['b' => $select4b]); + // @codingStandardsIgnoreStart + $expectedSql4_1 = 'SELECT "b".* FROM (SELECT "a".* FROM (SELECT * FROM (SELECT b.*, rownum b_rownum FROM ( SELECT "test".* FROM "test" ) b WHERE rownum <= (:offset2+:limit2)) WHERE b_rownum >= (:offset2 + 1)) "a") "b"'; + $expectedSql4_2 = 'SELECT "b".* FROM (SELECT "a".* FROM (SELECT * FROM (SELECT b.*, rownum b_rownum FROM ( SELECT "test".* FROM "test" ) b WHERE rownum <= (2+2)) WHERE b_rownum >= (2 + 1)) "a") "b"'; + // @codingStandardsIgnoreEnd + $expectedFormatParamCount4 = 0; + $expectedParams4 = ['offset2' => 2, 'limit2' => 2]; + return [ [$select0, $expectedSql0, [], $expectedSql0, $expectedFormatParamCount0], [$select1, $expectedSql1, [], $expectedSql1, $expectedFormatParamCount1], + [$select2, $expectedSql2_1, $expectedParams2, $expectedSql2_2, $expectedFormatParamCount2], + [$select3, $expectedSql3_1, $expectedParams3, $expectedSql3_2, $expectedFormatParamCount3], + [$select4, $expectedSql4_1, $expectedParams4, $expectedSql4_2, $expectedFormatParamCount4], ]; } }