From 6f3575b7a0d61895577e30af865f35ff3986a616 Mon Sep 17 00:00:00 2001 From: Travis Tegen Date: Tue, 9 Feb 2021 15:49:07 -0500 Subject: [PATCH] update for compatibility with Laravel 8 --- .gitignore | 3 +- .travis.yml | 1 - composer.json | 21 ++- readme.md | 63 ++++--- src/Jfelder/OracleDB/OCI_PDO/OCI.php | 10 -- src/Jfelder/OracleDB/OCI_PDO/OCIStatement.php | 5 +- src/Jfelder/OracleDB/OracleConnection.php | 19 +- .../OracleDB/OracleDBServiceProvider.php | 8 +- src/Jfelder/OracleDB/Query/OracleBuilder.php | 28 +++ .../Query/Processors/OracleProcessor.php | 3 +- .../Schema/Grammars/OracleGrammar.php | 8 + tests/OracleDBConnectorTest.php | 13 +- tests/OracleDBOCIProcessorTest.php | 6 +- tests/OracleDBOCIStatementTest.php | 9 +- tests/OracleDBOCITest.php | 20 +-- tests/OracleDBPDOProcessorTest.php | 11 +- tests/OracleDBQueryBuilderTest.php | 167 +++++++++++++++--- tests/OracleDBSchemaGrammarTest.php | 81 ++++++++- tests/mocks/OCIFunctions.php | 15 +- 19 files changed, 371 insertions(+), 120 deletions(-) create mode 100644 src/Jfelder/OracleDB/Query/OracleBuilder.php diff --git a/.gitignore b/.gitignore index 2649e99..7cfa9b7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,4 @@ composer.lock atlassian-ide-plugin.xml /vagrant Vagrantfile - - +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml index 952e142..bbb7d1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 7.2 - 7.3 - 7.4 diff --git a/composer.json b/composer.json index d79255e..f817a7c 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "jfelder/oracledb", "description": "Oracle DB driver for Laravel", - "keywords": ["oracle", "laravel", "laravel 7", "pdo_oci", "oci8"], + "keywords": ["oracle", "laravel", "laravel 8", "pdo_oci", "oci8"], "license": "MIT", "authors": [ { @@ -10,19 +10,26 @@ } ], "require": { - "php": "^7.2.5", - "illuminate/support": "^7.0", - "illuminate/database": "^7.0", - "illuminate/pagination": "^7.0" + "php": "^7.3.0", + "illuminate/support": "^8.0", + "illuminate/database": "^8.24.0", + "illuminate/pagination": "^8.0" }, "require-dev": { - "mockery/mockery": "^1.3.1", - "phpunit/phpunit": "^8.4|^9.0" + "mockery/mockery": "^1.4.2", + "phpunit/phpunit": "^9.3.3" }, "autoload": { "psr-4": { "Jfelder\\": "src/Jfelder" } }, + "extra": { + "laravel": { + "providers": [ + "Jfelder\\OracleDB\\OracleDBServiceProvider" + ] + } + }, "minimum-stability": "stable" } diff --git a/readme.md b/readme.md index 51c1739..3e175fe 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,13 @@ ## Laravel Oracle Database Package -### OracleDB (updated for Laravel 7.x) +### OracleDB (updated for Laravel 8.x) [![Latest Stable Version](https://poser.pugx.org/jfelder/oracledb/v/stable.png)](https://packagist.org/packages/jfelder/oracledb) [![Total Downloads](https://poser.pugx.org/jfelder/oracledb/downloads.png)](https://packagist.org/packages/jfelder/oracledb) [![Build Status](https://travis-ci.org/jfelder/Laravel-OracleDB.png)](https://travis-ci.org/jfelder/Laravel-OracleDB) -OracleDB is an Oracle Database Driver package for [Laravel Framework](https://laravel.com) - thanks [@taylorotwell](https://github.com/taylorotwell). OracleDB is an extension of [Illuminate/Database](https://github.com/illuminate/database) that uses either the [PDO_OCI] (https://www.php.net/manual/en/ref.pdo-oci.php) extension or the [OCI8 Functions](https://www.php.net/manual/en/ref.oci8.php) wrapped into the PDO namespace. +OracleDB is an Oracle Database Driver package for [Laravel Framework](https://laravel.com) - thanks [@taylorotwell](https://github.com/taylorotwell). OracleDB is an extension of [Illuminate/Database](https://github.com/illuminate/database) that uses either the [PDO_OCI](https://www.php.net/manual/en/ref.pdo-oci.php) extension or the [OCI8 Functions](https://www.php.net/manual/en/ref.oci8.php) wrapped into the PDO namespace. + +_NOTE: This package has not been tested in PHP 8._ **Please report any bugs you may find.** @@ -16,48 +18,51 @@ OracleDB is an Oracle Database Driver package for [Laravel Framework](https://la ### Installation -Add `jfelder/oracledb` as a requirement to composer.json: +With [Composer](https://getcomposer.org): -```json -{ - "require": { - "jfelder/oracledb": "7.*" - } -} +```sh +composer require jfelder/oracledb ``` -And then run `composer update` -Once Composer has installed or updated your packages you need to register OracleDB. Open up `config/app.php` and find -the `providers` key and add: +During this command, Laravel's "Auto-Discovery" feature should automatically register OracleDB's service +provider. -```php -Jfelder\OracleDB\OracleDBServiceProvider::class, +Next, publish OracleDB's configuration file using the vendor:publish Artisan command. This will copy OracleDB's +configuration file to `config/oracledb.php` in your project. + +```sh +php artisan vendor:publish --tag=oracledb-config ``` -Finally you need to publish a configuration file by running the following Artisan command. +To finish the installation, set your environment variables (typically in your .env file) to the corresponding +env variables used in `config/oracledb.php`: such as `DB_HOST`, `DB_USERNAME`, etc. -```terminal -$ php artisan vendor:publish -``` -This will copy the configuration file to config/oracledb.php +Additionally, it may be necessary for your app to configure the NLS_DATE_FORMAT of the database connection session, +before any queries are executed. One way to accomplish this is to run a statement in your `AppServiceProvider`'s `boot` +method, for example: +```php +if (config('database.default') === 'oracle') { + DB::statement("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'"); +} +``` ### Basic Usage -The configuration file for this package is located at 'config/oracledb.php'. -In this file you define all of your oracle database connections. If you need to make more than one connection, just +The configuration file for this package is located at `config/oracledb.php`. +In this file, you define all of your oracle database connections. If you need to make more than one connection, just copy the example one. If you want to make one of these connections the default connection, enter the name you gave the -connection into the "Default Database Connection Name" section in 'config/database.php'. +connection into the "Default Database Connection Name" section in `config/database.php`. -Once you have configured the OracleDB database connection(s), you may run queries using the 'DB' class as normal. +Once you have configured the OracleDB database connection(s), you may run queries using the `DB` facade as normal. -#### NEW: The oci8 library in now the default library. If you want to use the pdo library, enter "pdo" as the driver and the code will automatically use the pdo library instead of the oci8 library. Any other value will result in the oci8 library being used. +_NOTE: OCI8 is the default driver. If you want to use the PDO_OCI driver, change the `driver` value to `'pdo'` in the `config/oracledb.php` file for whichever connections you wish to have utilize PDO_OCI. Setting the driver to `'pdo'` will make OracleDB use the [PDO_OCI](https://www.php.net/manual/en/ref.pdo-oci.php) extension. Given any other `driver` value, OracleDB will use the [OCI8 Functions](https://www.php.net/manual/en/ref.oci8.php)._ ```php $results = DB::select('select * from users where id = ?', [1]); ``` The above statement assumes you have set the default connection to be the oracle connection you setup in -config/database.php file and will always return an 'array' of results. +config/database.php file and will always return an `array` of results. ```php $results = DB::connection('oracle')->select('select * from users where id = ?', [1]); @@ -77,16 +82,19 @@ in config/oracledb.php file. > **Note:** When using the insertGetId method, you can specify the auto-incrementing column name as the second parameter in insertGetId function. It will default to "id" if not specified. -See [Laravel Database Basic Docs](https://laravel.com/docs/7.x/database) for more information. +See [Laravel Database Basic Docs](https://laravel.com/docs/8.x/database) for more information. ### Unimplemented Features -Some of the features available in the first-party Laravel database drivers are not implemented in this package. Pull requests are welcome for implementing any of these features, or for expanding this list if you find any unimplemented features not already listed. +Some of the features available in the first-party Laravel database drivers are not implemented in this package. Pull +requests are welcome for implementing any of these features, or for expanding this list if you find any unimplemented +features not already listed. #### Query Builder - insertOrIgnore `DB::from('users')->insertOrIgnore(['email' => 'foo']);` - insertGetId with empty values `DB::from('users')->insertGetId([]);` (but calling with non-empty values is supported) +- upserts `DB::from('users')->upsert([['email' => 'foo', 'name' => 'bar'], ['name' => 'bar2', 'email' => 'foo2']], 'email');` - deleting with a join `DB::from('users')->join('contacts', 'users.id', '=', 'contacts.id')->where('users.email', '=', 'foo')->delete();` - deleting with a limit `DB::from('users')->where('email', '=', 'foo')->orderBy('id')->take(1)->delete();` - json operations `DB::from('users')->where('items->sku', '=', 'foo-bar')->get();` @@ -99,6 +107,7 @@ Some of the features available in the first-party Laravel database drivers are n - set collation on a column `$blueprint->string('some_column')->collation('BINARY_CI')` - set comments on a table `$blueprint->comment("This table is great.")` - set comments on a column `$blueprint->string('foo')->comment("Some helpful info about the foo column")` +- set the starting value of an auto-incrementing column `$blueprint->increments('id')->startingValue(1000)` - create a private temporary table `$blueprint->temporary()` - rename an index `$blueprint->renameIndex('foo', 'bar')` - specify an algorithm when creating an index via the third argument `$blueprint->index(['foo', 'bar'], 'baz', 'hash')` diff --git a/src/Jfelder/OracleDB/OCI_PDO/OCI.php b/src/Jfelder/OracleDB/OCI_PDO/OCI.php index bbbe855..76e2619 100644 --- a/src/Jfelder/OracleDB/OCI_PDO/OCI.php +++ b/src/Jfelder/OracleDB/OCI_PDO/OCI.php @@ -212,16 +212,6 @@ public function getAttribute($attribute) return; } - /** - * Return an array of available PDO drivers. - * - * @return array Array of PDO driver names. - */ - public static function getAvailableDrivers() - { - return parent::getAvailableDrivers(); - } - /** * Checks if inside a transaction. * diff --git a/src/Jfelder/OracleDB/OCI_PDO/OCIStatement.php b/src/Jfelder/OracleDB/OCI_PDO/OCIStatement.php index a843155..dafdc82 100644 --- a/src/Jfelder/OracleDB/OCI_PDO/OCIStatement.php +++ b/src/Jfelder/OracleDB/OCI_PDO/OCIStatement.php @@ -34,7 +34,8 @@ class OCIStatement extends \PDOStatement */ protected $datatypes = [ \PDO::PARAM_BOOL => \SQLT_INT, - \PDO::PARAM_NULL => \SQLT_INT, + // there is no SQLT_NULL, but oracle will insert a null value if it receives an empty string + \PDO::PARAM_NULL => \SQLT_CHR, \PDO::PARAM_INT => \SQLT_INT, \PDO::PARAM_STR => \SQLT_CHR, \PDO::PARAM_INPUT_OUTPUT => \SQLT_CHR, @@ -195,6 +196,8 @@ public function bindValue($parameter, $value, $data_type = \PDO::PARAM_STR) /** * Closes the cursor, enabling the statement to be executed again. + * + * Todo implement this method instead of always returning true * * @return bool Returns TRUE on success or FALSE on failure. */ diff --git a/src/Jfelder/OracleDB/OracleConnection.php b/src/Jfelder/OracleDB/OracleConnection.php index 99beebb..b42fd7f 100644 --- a/src/Jfelder/OracleDB/OracleConnection.php +++ b/src/Jfelder/OracleDB/OracleConnection.php @@ -3,10 +3,11 @@ namespace Jfelder\OracleDB; use Illuminate\Database\Connection; -use Jfelder\OracleDB\Schema\OracleBuilder; +use Jfelder\OracleDB\Schema\OracleBuilder as OracleSchemaBuilder; use Jfelder\OracleDB\Query\Processors\OracleProcessor; use Doctrine\DBAL\Driver\OCI8\Driver as DoctrineDriver; use Jfelder\OracleDB\Query\Grammars\OracleGrammar as QueryGrammer; +use Jfelder\OracleDB\Query\OracleBuilder as OracleQueryBuilder; use Jfelder\OracleDB\Schema\Grammars\OracleGrammar as SchemaGrammer; use PDO; @@ -15,7 +16,7 @@ class OracleConnection extends Connection /** * Get a schema builder instance for the connection. * - * @return \Illuminate\Database\Schema\OracleBuilder + * @return \Jfelder\OracleDB\Schema\OracleBuilder */ public function getSchemaBuilder() { @@ -23,7 +24,19 @@ public function getSchemaBuilder() $this->useDefaultSchemaGrammar(); } - return new OracleBuilder($this); + return new OracleSchemaBuilder($this); + } + + /** + * Get a new query builder instance. + * + * @return \Jfelder\OracleDB\Query\OracleBuilder + */ + public function query() + { + return new OracleQueryBuilder( + $this, $this->getQueryGrammar(), $this->getPostProcessor() + ); } /** diff --git a/src/Jfelder/OracleDB/OracleDBServiceProvider.php b/src/Jfelder/OracleDB/OracleDBServiceProvider.php index 079ab6a..9d3d396 100644 --- a/src/Jfelder/OracleDB/OracleDBServiceProvider.php +++ b/src/Jfelder/OracleDB/OracleDBServiceProvider.php @@ -14,11 +14,9 @@ class OracleDBServiceProvider extends ServiceProvider */ public function boot() { - $this->publishes( - [ - __DIR__.'/../../config/oracledb.php' => config_path('oracledb.php'), - ] - ); + $this->publishes([ + __DIR__.'/../../config/oracledb.php' => config_path('oracledb.php'), + ], 'oracledb-config'); } /** diff --git a/src/Jfelder/OracleDB/Query/OracleBuilder.php b/src/Jfelder/OracleDB/Query/OracleBuilder.php new file mode 100644 index 0000000..886c02f --- /dev/null +++ b/src/Jfelder/OracleDB/Query/OracleBuilder.php @@ -0,0 +1,28 @@ +createSub($query); + + $expression = '('.$query.') '.$this->grammar->wrapTable($as); + + $this->addBinding($bindings, 'join'); + + $this->joins[] = $this->newJoinClause($this, 'cross', new Expression($expression)); + + return $this; + } +} diff --git a/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php b/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php index b60584b..836441b 100644 --- a/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php +++ b/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php @@ -4,6 +4,7 @@ use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Processors\Processor as Processor; +use Jfelder\OracleDB\OCI_PDO\OCI; class OracleProcessor extends Processor { @@ -29,7 +30,7 @@ public function processInsertGetId(Builder $query, $sql, $values, $sequence = nu // PDO driver params are 1-based so ++ has to be before bindValue // OCI driver params are 0-based so no ++ before bindValue - if (get_class($pdo) != 'Jfelder\OracleDB\OCI_PDO\OCI') { + if (get_class($pdo) != OCI::class) { $counter++; } diff --git a/src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php b/src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php index f51d7cf..83a2206 100644 --- a/src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php +++ b/src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php @@ -99,6 +99,10 @@ protected function addForeignKeys(Blueprint $blueprint) if (! is_null($foreign->onDelete)) { $sql .= " on delete {$foreign->onDelete}"; } + + if (! is_null($foreign->onUpdate)) { + $sql .= " on update {$foreign->onUpdate}"; + } } return $sql; @@ -201,6 +205,10 @@ public function compileForeign(Blueprint $blueprint, Fluent $command) $sql .= " on delete {$command->onDelete}"; } + if (! is_null($command->onUpdate)) { + $sql .= " on update {$command->onUpdate}"; + } + return $sql; } } diff --git a/tests/OracleDBConnectorTest.php b/tests/OracleDBConnectorTest.php index 49bd6d6..f8cc3f2 100644 --- a/tests/OracleDBConnectorTest.php +++ b/tests/OracleDBConnectorTest.php @@ -1,5 +1,7 @@ setDefaultOptions([0 => 'foo', 1 => 'bar']); $this->assertEquals([0 => 'baz', 1 => 'bar', 2 => 'boom'], $connector->getOptions(['options' => [0 => 'baz', 2 => 'boom']])); } @@ -22,11 +24,10 @@ public function testOptionResolution() */ public function testOracleConnectCallsCreateConnectionWithProperArguments($dsn, $config) { - $connection = m::mock('stdClass'); - $connector = $this->getMockBuilder('Jfelder\OracleDB\Connectors\OracleConnector')->setMethods(['createConnection', 'getOptions'])->getMock(); - $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->will($this->returnValue(['options'])); - $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->will($this->returnValue($connection)); - + $connector = $this->getMockBuilder(OracleConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock(); + $connection = m::mock(\stdClass::class); + $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']); + $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection); $result = $connector->connect($config); $this->assertSame($result, $connection); diff --git a/tests/OracleDBOCIProcessorTest.php b/tests/OracleDBOCIProcessorTest.php index 71c9c71..da8d834 100644 --- a/tests/OracleDBOCIProcessorTest.php +++ b/tests/OracleDBOCIProcessorTest.php @@ -1,5 +1,7 @@ shouldReceive('prepare')->once()->with('sql')->andReturn($stmt); - $connection = m::mock('Illuminate\Database\Connection'); + $connection = m::mock(OracleConnection::class); $connection->shouldReceive('getPdo')->once()->andReturn($pdo); - $builder = m::mock('Illuminate\Database\Query\Builder'); + $builder = m::mock(OracleBuilder::class); $builder->shouldReceive('getConnection')->once()->andReturn($connection); $processor = new Jfelder\OracleDB\Query\Processors\OracleProcessor; diff --git a/tests/OracleDBOCIStatementTest.php b/tests/OracleDBOCIStatementTest.php index 54d6d13..e783090 100644 --- a/tests/OracleDBOCIStatementTest.php +++ b/tests/OracleDBOCIStatementTest.php @@ -169,13 +169,20 @@ public function testBindValueWithValidDataType() $this->assertTrue($this->stmt->bindValue('param', 'hello')); } + public function testBindValueWithNullDataType() + { + global $OCIBindByNameTypeReceived; + $this->assertTrue($this->stmt->bindValue('param', null, \PDO::PARAM_NULL)); + $this->assertSame(\SQLT_CHR, $OCIBindByNameTypeReceived); + } + public function testBindValueWithInvalidDataType() { $this->expectException(InvalidArgumentException::class); $this->stmt->bindValue(0, 'hello', 8); } - // method not yet implemented + // todo update this test once this method has been implemented public function testCloseCursor() { $this->assertTrue($this->stmt->closeCursor()); diff --git a/tests/OracleDBOCITest.php b/tests/OracleDBOCITest.php index 3236ead..f534100 100644 --- a/tests/OracleDBOCITest.php +++ b/tests/OracleDBOCITest.php @@ -2,6 +2,7 @@ use Jfelder\OracleDB\OCI_PDO\OCI; use Jfelder\OracleDB\OCI_PDO\OCIException; +use Jfelder\OracleDB\OCI_PDO\OCIStatement; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -37,14 +38,14 @@ public function tearDown(): void public function testConstructorSuccessWithPersistentConnection() { $oci = new OCI('dsn', null, null, [\PDO::ATTR_PERSISTENT => 1]); - $this->assertInstanceOf('Jfelder\OracleDB\OCI_PDO\OCI', $oci); + $this->assertInstanceOf(OCI::class, $oci); $this->assertEquals(1, $oci->getAttribute(\PDO::ATTR_PERSISTENT)); } public function testConstructorSuccessWithoutPersistentConnection() { $oci = new OCI('dsn', null, null, [\PDO::ATTR_PERSISTENT => 0]); - $this->assertInstanceOf('Jfelder\OracleDB\OCI_PDO\OCI', $oci); + $this->assertInstanceOf(OCI::class, $oci); $this->assertEquals(0, $oci->getAttribute(\PDO::ATTR_PERSISTENT)); } @@ -154,7 +155,7 @@ public function testExec() $property = $reflection->getProperty('stmt'); $property->setAccessible(true); $oci_stmt = $property->getValue($oci); - $this->assertInstanceOf('Jfelder\OracleDB\OCI_PDO\OCIStatement', $oci_stmt); + $this->assertInstanceOf(OCIStatement::class, $oci_stmt); // use reflection to test values of protected properties of OCIStatement object $reflection = new \ReflectionClass($oci_stmt); @@ -189,11 +190,6 @@ public function testGetAttributeForInvalidAttribute() $this->assertEquals(null, $this->oci->getAttribute('doesnotexist')); } -// public function testGetAvailableDrivers () -// { -// $this->assertArrayHasKey(0, $this->oci->getAvailableDrivers()); -// } - public function testInTransactionWhileNotInTransaction() { $this->assertFalse($this->oci->inTransaction()); @@ -222,7 +218,7 @@ public function testPrepareWithNonParameterQuery() $sql = 'select * from table'; $oci = new \TestOCIStub(); $stmt = $oci->prepare($sql); - $this->assertInstanceOf('Jfelder\OracleDB\OCI_PDO\OCIStatement', $stmt); + $this->assertInstanceOf(OCIStatement::class, $stmt); // use reflection to test values of protected properties $reflection = new \ReflectionClass($stmt); @@ -248,7 +244,7 @@ public function testPrepareWithParameterQuery() $sql = 'select * from table where id = ? and date = ?'; $oci = new \TestOCIStub(); $stmt = $oci->prepare($sql); - $this->assertInstanceOf('Jfelder\OracleDB\OCI_PDO\OCIStatement', $stmt); + $this->assertInstanceOf(OCIStatement::class, $stmt); // use reflection to test values of protected properties $reflection = new \ReflectionClass($stmt); @@ -284,7 +280,7 @@ public function testQuery() $sql = 'select * from table'; $oci = new \TestOCIStub(); $stmt = $oci->query($sql); - $this->assertInstanceOf('Jfelder\OracleDB\OCI_PDO\OCIStatement', $stmt); + $this->assertInstanceOf(OCIStatement::class, $stmt); // use reflection to test values of protected properties $reflection = new \ReflectionClass($stmt); @@ -310,7 +306,7 @@ public function testQueryWithModeParams() $sql = 'select * from table'; $oci = new \TestOCIStub(); $stmt = $oci->query($sql, \PDO::FETCH_CLASS, 'stdClass', []); - $this->assertInstanceOf('Jfelder\OracleDB\OCI_PDO\OCIStatement', $stmt); + $this->assertInstanceOf(OCIStatement::class, $stmt); // use reflection to test values of protected properties $reflection = new \ReflectionClass($stmt); diff --git a/tests/OracleDBPDOProcessorTest.php b/tests/OracleDBPDOProcessorTest.php index 3d53034..0a55374 100644 --- a/tests/OracleDBPDOProcessorTest.php +++ b/tests/OracleDBPDOProcessorTest.php @@ -1,5 +1,8 @@ getMockBuilder('ProcessorTestPDOStub')->getMock(); - $pdo->expects($this->once())->method('lastInsertId')->with($this->equalTo('id'))->will($this->returnValue('1')); + $pdo->expects($this->once())->method('lastInsertId')->with($this->equalTo('id'))->willReturn('1'); - $connection = m::mock('Illuminate\Database\Connection'); + $connection = m::mock(Connection::class); $connection->shouldReceive('insert')->once()->with('sql', ['foo']); $connection->shouldReceive('getPdo')->andReturn($pdo); - $builder = m::mock('Illuminate\Database\Query\Builder'); + $builder = m::mock(Builder::class); $builder->shouldReceive('getConnection')->times(2)->andReturn($connection); - $processor = new Illuminate\Database\Query\Processors\Processor; + $processor = new Processor; $result = $processor->processInsertGetId($builder, 'sql', ['foo'], 'id'); $this->assertSame(1, $result); diff --git a/tests/OracleDBQueryBuilderTest.php b/tests/OracleDBQueryBuilderTest.php index 12fa6fd..b3dde00 100644 --- a/tests/OracleDBQueryBuilderTest.php +++ b/tests/OracleDBQueryBuilderTest.php @@ -9,6 +9,8 @@ use Illuminate\Database\Query\Processors\Processor; use Illuminate\Pagination\AbstractPaginator as Paginator; use Illuminate\Pagination\LengthAwarePaginator; +use Jfelder\OracleDB\Query\OracleBuilder as OracleQueryBuilder; +use Jfelder\OracleDB\Query\Processors\OracleProcessor; use PHPUnit\Framework\TestCase; include 'mocks/PDOMocks.php'; @@ -187,13 +189,13 @@ public function testWhenCallbackWithReturn() public function testWhenCallbackWithDefault() { $callback = function ($query, $condition) { - $this->assertEquals($condition, 'truthy'); + $this->assertEquals('truthy', $condition); $query->where('id', '=', 1); }; $default = function ($query, $condition) { - $this->assertEquals($condition, 0); + $this->assertEquals(0, $condition); $query->where('id', '=', 2); }; @@ -206,7 +208,7 @@ public function testWhenCallbackWithDefault() $builder = $this->getOracleBuilder(); $builder->select('*')->from('users')->when(0, $callback, $default)->where('email', 'foo'); $this->assertSame('select * from "users" where "id" = ? and "email" = ?', $builder->toSql()); - $this->assertEquals([0 => 2, 1 => 'foo'], $builder->getBindings()); + $this->assertEquals([0 => 2, 1 => 'foo'], $builder->getBindings()); } public function testUnlessCallback() @@ -246,13 +248,13 @@ public function testUnlessCallbackWithReturn() public function testUnlessCallbackWithDefault() { $callback = function ($query, $condition) { - $this->assertEquals($condition, 0); + $this->assertEquals(0, $condition); $query->where('id', '=', 1); }; $default = function ($query, $condition) { - $this->assertEquals($condition, 'truthy'); + $this->assertEquals('truthy', $condition); $query->where('id', '=', 2); }; @@ -290,24 +292,29 @@ public function testBasicWheres() public function testWheresWithArrayValue() { $builder = $this->getOracleBuilder(); - $builder->select('*')->from('users')->where('id', [12, 30]); + $builder->select('*')->from('users')->where('id', [12]); $this->assertSame('select * from "users" where "id" = ?', $builder->toSql()); - $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings()); + $this->assertEquals([0 => 12], $builder->getBindings()); $builder = $this->getOracleBuilder(); $builder->select('*')->from('users')->where('id', '=', [12, 30]); $this->assertSame('select * from "users" where "id" = ?', $builder->toSql()); - $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings()); + $this->assertEquals([0 => 12], $builder->getBindings()); $builder = $this->getOracleBuilder(); $builder->select('*')->from('users')->where('id', '!=', [12, 30]); $this->assertSame('select * from "users" where "id" != ?', $builder->toSql()); - $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings()); + $this->assertEquals([0 => 12], $builder->getBindings()); $builder = $this->getOracleBuilder(); $builder->select('*')->from('users')->where('id', '<>', [12, 30]); $this->assertSame('select * from "users" where "id" <> ?', $builder->toSql()); - $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings()); + $this->assertEquals([0 => 12], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('id', '=', [[12, 30]]); + $this->assertSame('select * from "users" where "id" = ?', $builder->toSql()); + $this->assertEquals([0 => 12], $builder->getBindings()); } public function testDateBasedWheresAcceptsTwoArguments() @@ -465,12 +472,22 @@ public function testWhereBetweens() { $builder = $this->getOracleBuilder(); $builder->select('*')->from('users')->whereBetween('id', [1, 2]); - $this->assertEquals('select * from "users" where "id" between ? and ?', $builder->toSql()); + $this->assertSame('select * from "users" where "id" between ? and ?', $builder->toSql()); + $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereBetween('id', [[1, 2, 3]]); + $this->assertSame('select * from "users" where "id" between ? and ?', $builder->toSql()); + $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereBetween('id', [[1], [2, 3]]); + $this->assertSame('select * from "users" where "id" between ? and ?', $builder->toSql()); $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); $builder = $this->getOracleBuilder(); $builder->select('*')->from('users')->whereNotBetween('id', [1, 2]); - $this->assertEquals('select * from "users" where "id" not between ? and ?', $builder->toSql()); + $this->assertSame('select * from "users" where "id" not between ? and ?', $builder->toSql()); $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); $builder = $this->getOracleBuilder(); @@ -479,6 +496,24 @@ public function testWhereBetweens() $this->assertEquals([], $builder->getBindings()); } + public function testWhereBetweenColumns() + { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereBetweenColumns('id', ['users.created_at', 'users.updated_at']); + $this->assertSame('select * from "users" where "id" between "users"."created_at" and "users"."updated_at"', $builder->toSql()); + $this->assertEquals([], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereNotBetweenColumns('id', ['created_at', 'updated_at']); + $this->assertSame('select * from "users" where "id" not between "created_at" and "updated_at"', $builder->toSql()); + $this->assertEquals([], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereBetweenColumns('id', [new Raw(1), new Raw(2)]); + $this->assertSame('select * from "users" where "id" between 1 and 2', $builder->toSql()); + $this->assertEquals([], $builder->getBindings()); + } + public function testBasicOrWheres() { $builder = $this->getOracleBuilder(); @@ -963,11 +998,20 @@ public function testHavings() $builder = $this->getOracleBuilder(); $builder->select(['category', new Raw('count(*) as "total"')])->from('item')->where('department', '=', 'popular')->groupBy('category')->having('total', '>', 3); $this->assertSame('select "category", count(*) as "total" from "item" where "department" = ? group by "category" having "total" > ?', $builder->toSql()); + } + public function testHavingBetweens() + { $builder = $this->getOracleBuilder(); - $builder->select('*')->from('users')->havingBetween('last_login_date', ['2018-11-16', '2018-12-16']); - $this->assertSame('select * from "users" having "last_login_date" between ? and ?', $builder->toSql()); - } + $builder->select('*')->from('users')->havingBetween('id', [1, 2, 3]); + $this->assertSame('select * from "users" having "id" between ? and ?', $builder->toSql()); + $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->havingBetween('id', [[1, 2], [3, 4]]); + $this->assertSame('select * from "users" having "id" between ? and ?', $builder->toSql()); + $this->assertEquals([0 => 1, 1 => 2], $builder->getBindings()); + } public function testHavingShortcut() { @@ -1221,6 +1265,13 @@ public function testCrossJoins() $this->assertSame('select * from "tableB" cross join "tableA" on "tableA"."column1" = "tableB"."column2"', $builder->toSql()); } + public function testCrossJoinSubs() + { + $builder = $this->getOracleBuilder(); + $builder->selectRaw('(sale / "overall".sales) * 100 AS percent_of_total')->from('sales')->crossJoinSub($this->getOracleBuilder()->selectRaw('SUM(sale) AS sales')->from('sales'), 'overall'); + $this->assertSame('select (sale / "overall".sales) * 100 AS percent_of_total from "sales" cross join (select SUM(sale) AS sales from "sales") "overall"', $builder->toSql()); + } + public function testComplexJoin() { $builder = $this->getOracleBuilder(); @@ -1835,6 +1886,22 @@ public function testUpdateMethod() $this->assertEquals(1, $result); } + public function testUpsertMethod() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('does not support'); + $builder = $this->getOracleBuilder(); + $builder->from('users')->upsert([['email' => 'foo', 'name' => 'bar'], ['name' => 'bar2', 'email' => 'foo2']], 'email'); + } + + public function testUpsertMethodWithUpdateColumns() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('does not support'); + $builder = $this->getOracleBuilder(); + $builder->from('users')->upsert([['email' => 'foo', 'name' => 'bar'], ['name' => 'bar2', 'email' => 'foo2']], 'email', ['name']); + } + public function testUpdateMethodWithJoins() { $builder = $this->getOracleBuilder(); @@ -1861,7 +1928,7 @@ public function testUpdateMethodRespectsRaw() public function testUpdateOrInsertMethod() { - $builder = m::mock(Builder::class.'[where,exists,insert]', [ + $builder = m::mock(OracleQueryBuilder::class.'[where,exists,insert]', [ m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class), @@ -1873,7 +1940,7 @@ public function testUpdateOrInsertMethod() $this->assertTrue($builder->updateOrInsert(['email' => 'foo'], ['name' => 'bar'])); - $builder = m::mock(Builder::class.'[where,exists,update]', [ + $builder = m::mock(OracleQueryBuilder::class.'[where,exists,update]', [ m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class), @@ -1889,7 +1956,7 @@ public function testUpdateOrInsertMethod() public function testUpdateOrInsertMethodWorksWithEmptyUpdateValues() { - $builder = m::spy(Builder::class.'[where,exists,update]', [ + $builder = m::spy(OracleQueryBuilder::class.'[where,exists,update]', [ m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class), @@ -2083,7 +2150,7 @@ public function testDynamicWhere() { $method = 'whereFooBarAndBazOrQux'; $parameters = ['corge', 'waldo', 'fred']; - $builder = m::mock('Illuminate\Database\Query\Builder')->makePartial(); + $builder = m::mock(OracleQueryBuilder::class)->makePartial(); $builder->shouldReceive('where')->with('foo_bar', '=', $parameters[0], 'and')->once()->andReturn($builder); $builder->shouldReceive('where')->with('baz', '=', $parameters[1], 'and')->once()->andReturn($builder); @@ -2099,7 +2166,7 @@ public function testDynamicWhereIsNotGreedy() { $method = 'whereIosVersionAndAndroidVersionOrOrientation'; $parameters = ['6.1', '4.2', 'Vertical']; - $builder = m::mock('Illuminate\Database\Query\Builder')->makePartial(); + $builder = m::mock(OracleQueryBuilder::class)->makePartial(); $builder->shouldReceive('where')->with('ios_version', '=', '6.1', 'and')->once()->andReturn($builder); $builder->shouldReceive('where')->with('android_version', '=', '4.2', 'and')->once()->andReturn($builder); @@ -2127,14 +2194,14 @@ public function testBuilderThrowsExpectedExceptionWithUndefinedMethod() public function setupCacheTestQuery($cache, $driver) { - $connection = m::mock('Illuminate\Database\ConnectionInterface'); + $connection = m::mock(ConnectionInterface::class); $connection->shouldReceive('getName')->andReturn('connection_name'); $connection->shouldReceive('getCacheManager')->once()->andReturn($cache); $cache->shouldReceive('driver')->once()->andReturn($driver); $grammar = new Illuminate\Database\Query\Grammars\Grammar; - $processor = m::mock('Illuminate\Database\Query\Processors\Processor'); + $processor = m::mock(OracleProcessor::class); - $builder = $this->getMock('Illuminate\Database\Query\Builder', ['getFresh'], [$connection, $grammar, $processor]); + $builder = $this->getMock(OracleQueryBuilder::class, ['getFresh'], [$connection, $grammar, $processor]); $builder->expects($this->once())->method('getFresh')->with($this->equalTo(['*']))->will($this->returnValue(['results'])); return $builder->select('*')->from('users')->where('email', 'foo@bar.com'); @@ -2250,12 +2317,12 @@ public function testSubSelectResetBindings() $query->from('two')->select('baz')->where('subkey', '=', 'subval'); }, 'sub'); - $this->assertEquals('select (select "baz" from "two" where "subkey" = ?) as "sub" from "one"', $builder->toSql()); + $this->assertSame('select (select "baz" from "two" where "subkey" = ?) as "sub" from "one"', $builder->toSql()); $this->assertEquals(['subval'], $builder->getBindings()); $builder->select('*'); - $this->assertEquals('select * from "one"', $builder->toSql()); + $this->assertSame('select * from "one"', $builder->toSql()); $this->assertEquals([], $builder->getBindings()); } @@ -2749,15 +2816,57 @@ public function testLimitsAndOffsetsNotUsingQuotes() $this->assertEquals('select t2.* from ( select rownum AS "rn", t1.* from (select * from users) t1 ) t2 where t2."rn" between 16 and 30', $builder->toSql()); } + public function testClone() + { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users'); + $clone = $builder->clone()->where('email', 'foo'); + + $this->assertNotSame($builder, $clone); + $this->assertSame('select * from "users"', $builder->toSql()); + $this->assertSame('select * from "users" where "email" = ?', $clone->toSql()); + } + + public function testCloneWithout() + { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('email', 'foo')->orderBy('email'); + $clone = $builder->cloneWithout(['orders']); + + $this->assertSame('select * from "users" where "email" = ? order by "email" asc', $builder->toSql()); + $this->assertSame('select * from "users" where "email" = ?', $clone->toSql()); + } + + public function testCloneWithoutBindings() + { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('email', 'foo')->orderBy('email'); + $clone = $builder->cloneWithout(['wheres'])->cloneWithoutBindings(['where']); + + $this->assertSame('select * from "users" where "email" = ? order by "email" asc', $builder->toSql()); + $this->assertEquals([0 => 'foo'], $builder->getBindings()); + + $this->assertSame('select * from "users" order by "email" asc', $clone->toSql()); + $this->assertEquals([], $clone->getBindings()); + } + + protected function getConnection() + { + $connection = m::mock(ConnectionInterface::class); + $connection->shouldReceive('getDatabaseName')->andReturn('database'); + + return $connection; + } + protected function getOracleBuilder($quote = true) { global $ConfigReturnValue; $ConfigReturnValue = $quote; $grammar = new Jfelder\OracleDB\Query\Grammars\OracleGrammar; - $processor = m::mock('Jfelder\OracleDB\Query\Processors\OracleProcessor'); + $processor = m::mock(OracleProcessor::class); - return new Builder(m::mock('Illuminate\Database\ConnectionInterface'), $grammar, $processor); + return new OracleQueryBuilder($this->getConnection(), $grammar, $processor); } protected function getOracleBuilderWithProcessor() @@ -2765,7 +2874,7 @@ protected function getOracleBuilderWithProcessor() $grammar = new Jfelder\OracleDB\Query\Grammars\OracleGrammar; $processor = new Jfelder\OracleDB\Query\Processors\OracleProcessor; - return new Builder(m::mock('Illuminate\Database\ConnectionInterface'), $grammar, $processor); + return new OracleQueryBuilder($this->getConnection(), $grammar, $processor); } /** @@ -2773,7 +2882,7 @@ protected function getOracleBuilderWithProcessor() */ protected function getMockQueryBuilder() { - return m::mock(Builder::class, [ + return m::mock(OracleQueryBuilder::class, [ m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class), diff --git a/tests/OracleDBSchemaGrammarTest.php b/tests/OracleDBSchemaGrammarTest.php index ee96b38..cc12e74 100644 --- a/tests/OracleDBSchemaGrammarTest.php +++ b/tests/OracleDBSchemaGrammarTest.php @@ -3,6 +3,8 @@ use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\ForeignIdColumnDefinition; +use Jfelder\OracleDB\OracleConnection; +use Jfelder\OracleDB\Schema\Grammars\OracleGrammar; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -15,6 +17,26 @@ public function tearDown(): void m::close(); } + public function testCreateDatabase() + { + $grammar = new class extends OracleGrammar {}; + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('This database driver does not support creating databases.'); + + $grammar->compileCreateDatabase('foo', m::mock(OracleConnection::class)); + } + + public function testDropDatabaseIfExists() + { + $grammar = new class extends OracleGrammar {}; + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('This database driver does not support dropping databases.'); + + $grammar->compileDropDatabaseIfExists('foo'); + } + public function testBasicCreateTable() { $blueprint = new Blueprint('users'); @@ -27,8 +49,20 @@ public function testBasicCreateTable() $statements = $blueprint->toSql($conn, $this->getGrammar()); - $this->assertEquals(1, count($statements)); - $this->assertEquals('create table users ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); + $this->assertCount(1, $statements); + $this->assertSame('create table users ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->increments('id'); + $blueprint->string('email'); + + $conn = $this->getConnection(); + $conn->shouldNotReceive('getConfig'); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table users add ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); } public function testBasicCreateTableWithPrimary() @@ -133,6 +167,23 @@ public function testBasicCreateTableWithPrefixPrimaryAndForeignKeysWithCascadeDe $this->assertEquals('create table prefix_users ( id number(10,0) not null, email varchar2(255) not null, foo_id number(10,0) not null, constraint users_foo_id_foreign foreign key ( foo_id ) references prefix_orders ( id ) on delete cascade, constraint users_id_primary primary key ( id ) )', $statements[0]); } + public function testAutoIncrementStartingValue() + { + // calling ->startingValue() should have no effect on the generated sql because it hasn't been implemented + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->increments('id')->startingValue(1000); + $blueprint->string('email'); + + $conn = $this->getConnection(); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('create table users ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); + } + public function testBasicAlterTable() { $blueprint = new Blueprint('users'); @@ -387,6 +438,13 @@ public function testAddingForeignKey() $this->assertCount(1, $statements); $this->assertSame('alter table users add constraint users_foo_id_foreign foreign key ( foo_id ) references orders ( id ) on delete cascade', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnUpdate(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table users add constraint users_foo_id_foreign foreign key ( foo_id ) references orders ( id ) on update cascade', $statements[0]); } public function testAddingForeignKeyWithCascadeDelete() @@ -562,6 +620,18 @@ public function testAddingInteger() $this->assertEquals('alter table users add ( foo number(10,0) not null, constraint users_foo_primary primary key ( foo ) )', $statements[0]); } + public function testAddingIncrementsWithStartingValues() + { + // calling ->startingValue() should have no effect on the generated sql because it hasn't been implemented + + $blueprint = new Blueprint('users'); + $blueprint->id()->startingValue(1000); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table users add ( id number(19,0) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); + } + public function testAddingMediumInteger() { $blueprint = new Blueprint('users'); @@ -731,8 +801,10 @@ public function testAddingBinary() $this->assertEquals('alter table users add ( foo blob not null )', $statements[0]); } - public function testAddingCommentDoesNothing() + public function testAddingComment() { + // calling ->comment() on a column should have no effect on the generated sql because it hasn't been implemented + $blueprint = new Blueprint('users'); $blueprint->string('foo')->comment("Escape ' when using words like it's"); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); @@ -773,7 +845,6 @@ public function testBasicSelectNotUsingQuotes() public function testGrammarsAreMacroable() { - // compileReplace macro. $this->getGrammar()::macro('compileReplace', function () { return true; }); @@ -785,7 +856,7 @@ public function testGrammarsAreMacroable() protected function getConnection() { - return m::mock('Illuminate\Database\Connection'); + return m::mock(OracleConnection::class); } public function getGrammar($quote = false) diff --git a/tests/mocks/OCIFunctions.php b/tests/mocks/OCIFunctions.php index c956a2f..3953082 100644 --- a/tests/mocks/OCIFunctions.php +++ b/tests/mocks/OCIFunctions.php @@ -7,18 +7,22 @@ $OCIExecuteStatus = true; $OCIFetchStatus = true; $OCIBindChangeStatus = false; + $OCIBindByNameTypeReceived = null; } namespace Jfelder\OracleDB\OCI_PDO { - // Generic + // OCI specific if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_error")) { function oci_error($a = '') { - return ['code' => 0,'message' => '', 'sqltext' => '']; + global $OCIExecuteStatus, $OCIFetchStatus, $OCITransactionStatus; + + return ($OCIExecuteStatus && $OCIFetchStatus && $OCITransactionStatus) + ? false + : ['code' => 0,'message' => '', 'sqltext' => '']; } } - // OCI specific if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_connect")) { function oci_connect($a = '') { @@ -79,7 +83,10 @@ function get_resource_type($a = '') if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_bind_by_name")) { function oci_bind_by_name($a = '', $b = '', &$c, $d = '', $e = '') { - global $OCIStatementStatus, $OCIBindChangeStatus; + global $OCIStatementStatus, $OCIBindChangeStatus, $OCIBindByNameTypeReceived; + + $OCIBindByNameTypeReceived = $e; + if ($OCIBindChangeStatus) { $c = 'oci_bind_by_name'; }