diff --git a/.ci/oracle_fixtures.sh b/.ci/oracle_fixtures.sh new file mode 100644 index 0000000000..82f692d98c --- /dev/null +++ b/.ci/oracle_fixtures.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +echo "Configure Oracle test database" + +"CREATE DATABASE test DATAFILE 'zenddb_test' SIZE 10M" diff --git a/.travis.yml b/.travis.yml index e7b538f18c..dc3e958b94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ matrix: - LEGACY_DEPS="phpunit/phpunit zendframework/zend-hydrator" - TEST_INTEGRATION=true - TESTS_ZEND_DB_ADAPTER_DRIVER_MYSQL=true + - TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8=true - php: 5.6 env: - DEPS=latest @@ -36,6 +37,14 @@ matrix: - LEGACY_DEPS="phpunit/phpunit zendframework/zend-hydrator" - TEST_INTEGRATION=true - TESTS_ZEND_DB_ADAPTER_DRIVER_MYSQL=true + - php: 7.0 + services: + - oci8 + env: + - DEPS=locked + - LEGACY_DEPS="phpunit/phpunit zendframework/zend-hydrator" + - TEST_INTEGRATION=true + - TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8=true - php: 7.0 env: - DEPS=latest @@ -52,6 +61,7 @@ matrix: - TEST_COVERAGE=true - TEST_INTEGRATION=true - TESTS_ZEND_DB_ADAPTER_DRIVER_MYSQL=true + - TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8=true - php: 7.1 env: - DEPS=latest @@ -65,6 +75,7 @@ matrix: - DEPS=locked - TEST_INTEGRATION=true - TESTS_ZEND_DB_ADAPTER_DRIVER_MYSQL=true + - TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8=true - php: 7.2 services: - postgresql @@ -74,6 +85,7 @@ matrix: - DEPS=locked - TEST_INTEGRATION=true - TESTS_ZEND_DB_ADAPTER_DRIVER_PGSQL=true + - TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8=true - php: 7.2 env: - DEPS=latest diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e58cf9e02c..5816ec07a0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -43,11 +43,12 @@ - - + + + diff --git a/src/Adapter/Driver/Oci8/Statement.php b/src/Adapter/Driver/Oci8/Statement.php index 2d8ca1eef8..e7a8e6323a 100644 --- a/src/Adapter/Driver/Oci8/Statement.php +++ b/src/Adapter/Driver/Oci8/Statement.php @@ -289,11 +289,18 @@ protected function bindParametersFromContainer() $type = SQLT_BIN; break; case ParameterContainer::TYPE_LOB: + case ParameterContainer::TYPE_CLOB: $type = OCI_B_CLOB; $clob = oci_new_descriptor($this->driver->getConnection()->getResource(), OCI_DTYPE_LOB); $clob->writetemporary($value, OCI_TEMP_CLOB); $value = $clob; break; + case ParameterContainer::TYPE_BLOB: + $type = OCI_B_BLOB; + $blob = oci_new_descriptor($this->driver->getConnection()->getResource(), OCI_DTYPE_LOB); + $blob->writetemporary($value, OCI_TEMP_BLOB); + $value = $blob; + break; case ParameterContainer::TYPE_STRING: default: $type = SQLT_CHR; diff --git a/src/Adapter/ParameterContainer.php b/src/Adapter/ParameterContainer.php index c630837752..acb9e9b9e0 100644 --- a/src/Adapter/ParameterContainer.php +++ b/src/Adapter/ParameterContainer.php @@ -22,6 +22,8 @@ class ParameterContainer implements Iterator, ArrayAccess, Countable const TYPE_BINARY = 'binary'; const TYPE_STRING = 'string'; const TYPE_LOB = 'lob'; + const TYPE_BLOB = 'blob'; + const TYPE_CLOB = 'clob'; /** * Data diff --git a/test/integration/Adapter/Driver/Oci8/ConnectionTest.php b/test/integration/Adapter/Driver/Oci8/ConnectionTest.php new file mode 100644 index 0000000000..0ac403f2c6 --- /dev/null +++ b/test/integration/Adapter/Driver/Oci8/ConnectionTest.php @@ -0,0 +1,29 @@ +variables); + $connection->connect(); + + self::assertTrue($connection->isConnected()); + $connection->disconnect(); + } +} diff --git a/test/integration/Adapter/Driver/Oci8/TableGatewayTest.php b/test/integration/Adapter/Driver/Oci8/TableGatewayTest.php new file mode 100644 index 0000000000..0cf45d7d15 --- /dev/null +++ b/test/integration/Adapter/Driver/Oci8/TableGatewayTest.php @@ -0,0 +1,155 @@ + 'OCI8', + 'connection_string' => $this->variables['connectionstring'], + 'username' => $this->variables['username'], + 'password' => $this->variables['password'], + 'character_set' => $this->variables['charset'], + 'options' => ['buffer_results' => true] + ]); + $tableGateway = new TableGateway('TEST', $adapter); + $rowset = $tableGateway->select('ID = 0'); + + $this->assertNull($rowset->current()); + } + + /** + * @see https://github.com/zendframework/zend-db/issues/330 + */ + public function testSelectWithEmptyCurrentWithoutBufferResult() + { + $adapter = new Adapter([ + 'driver' => 'OCI8', + 'connection_string' => $this->variables['connectionstring'], + 'username' => $this->variables['username'], + 'password' => $this->variables['password'], + 'character_set' => $this->variables['charset'], + 'options' => ['buffer_results' => false] + ]); + $tableGateway = new TableGateway('TEST', $adapter); + $rowset = $tableGateway->select('ID = 0'); + + $this->assertNull($rowset->current()); + } + + /** + * @see https://github.com/zendframework/zend-db/pull/396 + */ + public function testBlobWithOci8() + { + $adapter = new Adapter([ + 'driver' => 'OCI8', + 'connection_string' => $this->variables['connectionstring'], + 'username' => $this->variables['username'], + 'password' => $this->variables['password'], + 'character_set' => $this->variables['charset'], + 'options' => ['buffer_results' => false] + ]); + $tableGateway = new TableGateway('TEST', $adapter); + + $blob = 'very long sentence that is in fact not very long that tests blob'; + + $data = new ParameterContainer(); + $data->setFromArray(['CONTENT_BLOB' => $blob]); + $data->offsetSetErrata('CONTENT_BLOB', ParameterContainer::TYPE_BLOB); + + $sql = 'UPDATE TEST SET CONTENT_BLOB = :CONTENT_BLOB WHERE ID = 1'; + $stm = $tableGateway->getAdapter()->getDriver()->createStatement($sql); + $stm->setParameterContainer($data); + $stm->execute(); + + $rowset = $tableGateway->select('ID = 1')->current(); + + $this->assertInstanceOf('OCI-Lob', $rowset['CONTENT_BLOB']); + $value = $rowset['CONTENT_BLOB']->read($rowset['CONTENT_BLOB']->size()); + $this->assertEquals($blob, $value); + } + + /** + * @see https://github.com/zendframework/zend-db/pull/396 + */ + public function testClobWithOci8() + { + $adapter = new Adapter([ + 'driver' => 'OCI8', + 'connection_string' => $this->variables['connectionstring'], + 'username' => $this->variables['username'], + 'password' => $this->variables['password'], + 'character_set' => $this->variables['charset'], + 'options' => ['buffer_results' => false] + ]); + $tableGateway = new TableGateway('TEST', $adapter); + + $clob = 'very long sentence that is in fact not very long that tests clob'; + + $data = new ParameterContainer(); + $data->setFromArray(['CONTENT_CLOB' => $clob]); + $data->offsetSetErrata('CONTENT_CLOB', ParameterContainer::TYPE_CLOB); + + $sql = 'UPDATE TEST SET CONTENT_CLOB = :CONTENT_CLOB WHERE ID = 1'; + $stm = $tableGateway->getAdapter()->getDriver()->createStatement($sql); + $stm->setParameterContainer($data); + $stm->execute(); + + $rowset = $tableGateway->select('ID = 1')->current(); + + $this->assertInstanceOf('OCI-Lob', $rowset['CONTENT_CLOB']); + $value = $rowset['CONTENT_CLOB']->read($rowset['CONTENT_CLOB']->size()); + $this->assertEquals($clob, $value); + } + + /** + * @see https://github.com/zendframework/zend-db/pull/396 + */ + public function testLobWithOci8() + { + $adapter = new Adapter([ + 'driver' => 'OCI8', + 'connection_string' => $this->variables['connectionstring'], + 'username' => $this->variables['username'], + 'password' => $this->variables['password'], + 'character_set' => $this->variables['charset'], + 'options' => ['buffer_results' => false] + ]); + $tableGateway = new TableGateway('TEST', $adapter); + + $clob = 'very long sentence that is in fact not very long that tests lob'; + + $data = new ParameterContainer(); + $data->setFromArray(['CONTENT_CLOB' => $clob]); + $data->offsetSetErrata('CONTENT_CLOB', ParameterContainer::TYPE_LOB); + + $sql = 'UPDATE TEST SET CONTENT_CLOB = :CONTENT_CLOB WHERE ID = 2'; + $stm = $tableGateway->getAdapter()->getDriver()->createStatement($sql); + $stm->setParameterContainer($data); + $stm->execute(); + + $rowset = $tableGateway->select('ID = 2')->current(); + + $this->assertInstanceOf('OCI-Lob', $rowset['CONTENT_CLOB']); + $value = $rowset['CONTENT_CLOB']->read($rowset['CONTENT_CLOB']->size()); + $this->assertEquals($clob, $value); + } +} diff --git a/test/integration/Adapter/Driver/Oci8/TraitSetup.php b/test/integration/Adapter/Driver/Oci8/TraitSetup.php new file mode 100644 index 0000000000..f9aabed9b9 --- /dev/null +++ b/test/integration/Adapter/Driver/Oci8/TraitSetup.php @@ -0,0 +1,44 @@ + 'TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_CONNECTIONSTRING', + 'username' => 'TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_USERNAME', + 'password' => 'TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_PASSWORD', + 'charset' => 'TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_CHARSET', + 'database' => 'TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_DATABASE', + ]; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + if (!getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8')) { + $this->markTestSkipped('Oci8 integration test disabled'); + } + + if (!extension_loaded('oci8')) { + $this->fail('The phpunit group integration-oci8 was enabled, but the extension is not loaded.'); + } + + foreach ($this->variables as $name => $value) { + if (!getenv($value)) { + $this->markTestSkipped(sprintf( + 'Missing required variable %s from phpunit.xml for this integration test', + $value + )); + } + $this->variables[$name] = getenv($value); + } + } +} diff --git a/test/integration/Adapter/Platform/Oci8Test.php b/test/integration/Adapter/Platform/Oci8Test.php new file mode 100644 index 0000000000..9b56b0e1f4 --- /dev/null +++ b/test/integration/Adapter/Platform/Oci8Test.php @@ -0,0 +1,77 @@ +markTestSkipped(__CLASS__ . ' integration tests are not enabled!'); + } + if (extension_loaded('oci8')) { + $this->adapters['oci8'] = oci_connect( + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_USERNAME'), + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_PASSWORD'), + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_CONNECTIONSTRING'), + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_CHARSET'), + null + ); + } + if (extension_loaded('pdo_oci')) { + $this->adapters['pdo_oci'] = new \PDO( + 'oci:dbname=' . getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_CONNECTIONSTRING'), + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_USERNAME'), + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_PASSWORD') + ); + } + } + + public function testQuoteValueWithOci8() + { + if (!isset($this->adapters['oci8']) + || !$this->adapters['oci8'] instanceof \Oracle + ) { + $this->markTestSkipped('Oracle (oci8) not configured in unit test configuration file'); + } + $oracle = new Oracle($this->adapters['oci8']); + $value = $oracle->quoteValue('value'); + self::assertEquals('\'value\'', $value); + + $oracle = new Oracle(new Oci8\Oci8(new Oci8\Connection($this->adapters['oci8']))); + $value = $oracle->quoteValue('value'); + self::assertEquals('\'value\'', $value); + } + + public function testQuoteValueWithPdoOci() + { + if (!isset($this->adapters['pdo_oci']) + || !$this->adapters['pdo_oci'] instanceof \PDO + ) { + $this->markTestSkipped('Oracle (pdo_oci) not configured in unit test configuration file'); + } + $oracle = new Pdo($this->adapters['pdo_oci']); + $value = $oracle->quoteValue('value'); + self::assertEquals('\'value\'', $value); + + $oracle = new Pdo(new Pdo\Pdo(new Pdo\Connection($this->adapters['pdo_oci']))); + $value = $oracle->quoteValue('value'); + self::assertEquals('\'value\'', $value); + } +} diff --git a/test/integration/IntegrationTestListener.php b/test/integration/IntegrationTestListener.php index 9bb4dcaa31..9908f8120b 100644 --- a/test/integration/IntegrationTestListener.php +++ b/test/integration/IntegrationTestListener.php @@ -9,9 +9,7 @@ namespace ZendIntegrationTest\Db; -use Exception; use PDO; -use PDOException; use PHPUnit\Framework\BaseTestListener; use PHPUnit_Framework_TestSuite as TestSuite; use ZendIntegrationTest\Db\Platform\FixtureLoader; @@ -40,6 +38,9 @@ public function startTestSuite(TestSuite $suite) if (getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_PGSQL')) { $this->fixtureLoader = new \ZendIntegrationTest\Db\Platform\PgsqlFixtureLoader(); } + if (getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8')) { + $this->fixtureLoader = new \ZendIntegrationTest\Db\Platform\Oci8FixtureLoader(); + } if (! isset($this->fixtureLoader)) { return; diff --git a/test/integration/Platform/Oci8FixtureLoader.php b/test/integration/Platform/Oci8FixtureLoader.php new file mode 100644 index 0000000000..43aa666db7 --- /dev/null +++ b/test/integration/Platform/Oci8FixtureLoader.php @@ -0,0 +1,79 @@ +oci8 = oci_pconnect( + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_USERNAME'), + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_PASSWORD'), + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_CONNECTIONSTRING'), + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_CHARSET'), + null + ); + + $this->dropDatabase(); + + $sql = file_get_contents($this->fixtureFile); + $sql2 = <<oci8, $sql2); + $ret = oci_execute($resource); + if (false === $ret) { + throw new RuntimeException(sprintf( + 'I cannot create the table for database %s. Check the %s file. %s', + getenv('TESTS_ZEND_DB_ADAPTER_DRIVER_OCI8_DATABASE'), + $this->fixtureFile, + print_r(oci_error($this->oci8), true) + )); + } + } + + public function dropDatabase() + { + if (!$this->initialRun) { + // Not possible to drop database in Oracle. + + return; + } + $this->initialRun = false; + + $resource = oci_parse($this->oci8, 'DROP TABLE TEST CASCADE CONSTRAINTS'); + oci_execute($resource); + + $resource = oci_parse($this->oci8, 'DROP TABLE TEST_CHARSET CASCADE CONSTRAINTS'); + oci_execute($resource); + + $resource = oci_parse($this->oci8, 'DROP SEQUENCE test_sequence'); + oci_execute($resource); + + $resource = oci_parse($this->oci8, 'DROP SEQUENCE testcharset_sequence'); + oci_execute($resource); + + $resource = oci_parse($this->oci8, 'DROP TRIGGER TEST_ON_INSERT'); + oci_execute($resource); + + $resource = oci_parse($this->oci8, 'DROP TRIGGER TESTCHARSET_ON_INSERT'); + oci_execute($resource); + } +} diff --git a/test/integration/TestFixtures/oci8.sql b/test/integration/TestFixtures/oci8.sql new file mode 100644 index 0000000000..fb10007efa --- /dev/null +++ b/test/integration/TestFixtures/oci8.sql @@ -0,0 +1,55 @@ +CREATE TABLE TEST +( + ID NUMBER(3, 0) NOT NULL ENABLE, + NAME VARCHAR2(255 CHAR) NOT NULL ENABLE, + VALUE VARCHAR2(255 CHAR) NOT NULL ENABLE, + CONTENT_BLOB BLOB, + CONTENT_CLOB CLOB, + CONSTRAINT TEST_PK PRIMARY KEY (ID) +); + +CREATE SEQUENCE test_sequence; + +CREATE OR REPLACE TRIGGER TEST_ON_INSERT + BEFORE INSERT + ON test + FOR EACH ROW + BEGIN + SELECT test_sequence.nextval + INTO :new.id + FROM dual; + END; +/ + +ALTER TRIGGER TEST_ON_INSERT ENABLE; + +INSERT INTO TEST (NAME, VALUE) VALUES ('foo', 'bar'); +INSERT INTO TEST (NAME, VALUE) VALUES ('bar', 'baz'); + +CREATE TABLE TEST_CHARSET +( + ID NUMBER(3, 0) NOT NULL ENABLE, + FIELD$ VARCHAR2(255 CHAR) NOT NULL ENABLE, + FIELD_ VARCHAR2(255 CHAR) NOT NULL ENABLE, + CONSTRAINT TESTCHARSET_PK PRIMARY KEY (ID) +); + +CREATE SEQUENCE testcharset_sequence; + +CREATE OR REPLACE TRIGGER TESTCHARSET_ON_INSERT + BEFORE INSERT + ON TEST_CHARSET + FOR EACH ROW + BEGIN + SELECT testcharset_sequence.nextval + INTO :new.id + FROM dual; + END; +/ + +ALTER TRIGGER TESTCHARSET_ON_INSERT ENABLE; + +INSERT INTO TEST_CHARSET (FIELD$, FIELD_) VALUES ('foo', 'bar'); +INSERT INTO TEST_CHARSET (FIELD$, FIELD_) VALUES ('bar', 'baz'); + +COMMIT;