From 828fc25dd00d81e3b605df401311413efe96e387 Mon Sep 17 00:00:00 2001 From: Travis Tegen Date: Mon, 5 Aug 2024 09:59:45 -0400 Subject: [PATCH 1/2] update for Laravel 11 --- .github/workflows/tests.yml | 12 +- composer.json | 12 +- readme.md | 15 +-- scripts/oci81.sh | 28 ----- src/Jfelder/OracleDB/OracleConnection.php | 39 ++++-- src/Jfelder/OracleDB/PDO/OracleDriver.php | 19 --- .../Query/Processors/OracleProcessor.php | 1 + .../Schema/Grammars/OracleGrammar.php | 24 ++-- tests/OracleDBConnectorTest.php | 5 +- tests/OracleDBQueryBuilderTest.php | 85 ++++--------- tests/OracleDBSchemaGrammarTest.php | 116 ++++-------------- 11 files changed, 106 insertions(+), 250 deletions(-) delete mode 100755 scripts/oci81.sh delete mode 100644 src/Jfelder/OracleDB/PDO/OracleDriver.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 39bea9e..2b13dfb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,9 +25,9 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2, 8.3] + php: [8.2, 8.3] stability: [prefer-lowest, prefer-stable] - + name: PHP ${{ matrix.php }} - ${{ matrix.stability }} steps: @@ -43,18 +43,14 @@ jobs: tools: composer:v2, pecl coverage: none - - name: Setup OCI8 for PHP 8.1 - run: ./scripts/oci81.sh - if: matrix.php == 8.1 - - name: Setup OCI8 for PHP 8.2 run: ./scripts/oci82.sh if: matrix.php == 8.2 - + - name: Setup OCI8 for PHP 8.3 run: ./scripts/oci83.sh if: matrix.php == 8.3 - + - name: Install Composer dependencies run: composer install --prefer-dist --no-interaction --no-progress diff --git a/composer.json b/composer.json index 60e2244..e7806cf 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "jfelder/oracledb", "description": "Oracle DB driver for Laravel", - "keywords": ["oracle", "laravel", "laravel 10", "pdo_oci", "oci8"], + "keywords": ["oracle", "laravel", "laravel 11", "pdo_oci", "oci8"], "license": "MIT", "authors": [ { @@ -10,13 +10,13 @@ } ], "require": { - "php": "^8.1", - "illuminate/database": "^10.0", - "illuminate/pagination": "^10.0" + "php": "^8.2", + "illuminate/database": "^11.0", + "illuminate/pagination": "^11.0" }, "require-dev": { - "mockery/mockery": "^1.5.1", - "phpunit/phpunit": "^10.0.7", + "mockery/mockery": "^1.6", + "phpunit/phpunit": "^11.0.1", "laravel/pint": "^1.13" }, "autoload": { diff --git a/readme.md b/readme.md index 68303af..f705b8d 100644 --- a/readme.md +++ b/readme.md @@ -1,17 +1,14 @@ ## Laravel Oracle Database Package -### OracleDB (updated for Laravel 10) +### OracleDB (updated for Laravel 11) Build Status Total Downloads Latest Stable Version License - 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 the [OCI8 Functions](https://www.php.net/manual/en/ref.oci8.php) wrapped into the PDO namespace. -> **Note:** This package is designed to run in PHP 8.1, and has not been tested in PHP 8.0 - **Please report any bugs you may find.** - [Installation](#installation) @@ -129,15 +126,7 @@ features not already listed. - create a column to hold IP addresses `$blueprint->ipAddress('foo')` (would be implemented as varchar2 45) - create a column to hold MAC addresses `$blueprint->macAddress('foo')` (would be implemented as varchar2 17) - create a geometry column `$blueprint->geometry('coordinates')` -- create a geometric point column `$blueprint->point('coordinates')` -- create a geometric point column specifying srid `$blueprint->point('coordinates', 4326)` -- create a linestring column `$blueprint->linestring('coordinates')` -- create a polygon column `$blueprint->polygon('coordinates')` -- create a geometry collection column `$blueprint->geometrycollection('coordinates')` -- create a multipoint column `$blueprint->multipoint('coordinates')` -- create a multilinestring column `$blueprint->multilinestring('coordinates')` -- create a multipolygon column `$blueprint->multipolygon('coordinates')` -- create a double column without specifying second or third parameters `$blueprint->double('foo')` (but `$blueprint->double('foo', 5, 2)` is supported) +- create a geography column `$blueprint->geography('coordinates')` - create a timestamp column with `useCurrent` modifier `$blueprint->timestamp('created_at')->useCurrent()` ### License diff --git a/scripts/oci81.sh b/scripts/oci81.sh deleted file mode 100755 index 5d647d6..0000000 --- a/scripts/oci81.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -# install deps -sudo apt-get update -qq -sudo apt-get -y install -qq build-essential unzip wget libaio1 - -# install oci8 libs & extension -sudo mkdir -p /opt/oracle - -wget https://github.com/bumpx/oracle-instantclient/raw/master/instantclient-basic-linux.x64-12.1.0.2.0.zip -wget https://github.com/bumpx/oracle-instantclient/raw/master/instantclient-sdk-linux.x64-12.1.0.2.0.zip - -sudo unzip -o ./instantclient-basic-linux.x64-12.1.0.2.0.zip -d /opt/oracle -sudo unzip -o ./instantclient-sdk-linux.x64-12.1.0.2.0.zip -d /opt/oracle - -sudo ln -s /opt/oracle/instantclient/sqlplus /usr/bin/sqlplus -sudo ln -s /opt/oracle/instantclient_12_1 /opt/oracle/instantclient -sudo ln -s /opt/oracle/instantclient/libclntsh.so.12.1 /opt/oracle/instantclient/libclntsh.so -sudo ln -s /opt/oracle/instantclient/libocci.so.12.1 /opt/oracle/instantclient/libocci.so - -sudo sh -c "echo 'instantclient,/opt/oracle/instantclient' | pecl install oci8-3.2.1" - -# setup ld library path -sudo sh -c "echo '/opt/oracle/instantclient' >> /etc/ld.so.conf" -sudo ldconfig - -# enable oci8 extension -sudo sh -c "echo 'extension=oci8.so' >> /etc/php/8.1/cli/php.ini" \ No newline at end of file diff --git a/src/Jfelder/OracleDB/OracleConnection.php b/src/Jfelder/OracleDB/OracleConnection.php index 0aaa686..a56268f 100644 --- a/src/Jfelder/OracleDB/OracleConnection.php +++ b/src/Jfelder/OracleDB/OracleConnection.php @@ -4,7 +4,6 @@ use Exception; use Illuminate\Database\Connection; -use Jfelder\OracleDB\PDO\OracleDriver; use Jfelder\OracleDB\Query\Grammars\OracleGrammar as QueryGrammar; use Jfelder\OracleDB\Query\OracleBuilder as OracleQueryBuilder; use Jfelder\OracleDB\Query\Processors\OracleProcessor; @@ -14,6 +13,24 @@ class OracleConnection extends Connection { + /** + * {@inheritdoc} + */ + public function getDriverTitle() + { + return 'Oracle'; + } + + /** + * Get the server version for the connection. + * + * @return string + */ + public function getServerVersion(): string + { + return 'Run SELECT * FROM V$VERSION; to get the Oracle server version.'; + } + /** * Get a schema builder instance for the connection. * @@ -74,16 +91,6 @@ protected function getDefaultPostProcessor() return new OracleProcessor; } - /** - * Get the Doctrine DBAL driver. - * - * @return \Doctrine\DBAL\Driver\OCI8\Driver - */ - protected function getDoctrineDriver() - { - return new OracleDriver; - } - /** * Bind values to their parameters in the given statement. * @@ -144,4 +151,14 @@ protected function isUniqueConstraintError(Exception $exception) { return boolval(preg_match('#ORA-00001: unique constraint#i', $exception->getMessage())); } + + /** + * Get the schema state for the connection. + * + * @throws \RuntimeException + */ + public function getSchemaState(string $dummyArg1 = null, string $dummyArg2 = null) + { + throw new RuntimeException('Schema dumping is not supported when using Oracle.'); + } } diff --git a/src/Jfelder/OracleDB/PDO/OracleDriver.php b/src/Jfelder/OracleDB/PDO/OracleDriver.php deleted file mode 100644 index ab8e1b2..0000000 --- a/src/Jfelder/OracleDB/PDO/OracleDriver.php +++ /dev/null @@ -1,19 +0,0 @@ -getConnection()->oracleInsertGetId($sql, $values); } + // todo remove this because it was removed in Laravel 11. add a processColumns like the specific processors have. /** * Process the results of a column listing query. * diff --git a/src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php b/src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php index 787f103..e3c422a 100644 --- a/src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php +++ b/src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php @@ -132,7 +132,7 @@ public function compileCreate(Blueprint $blueprint, Fluent $command) } /** - * Compile a create table command. + * Compile a column addition table command. * * @param Illuminate\Database\Schema\Blueprint $blueprint * @param Illuminate\Support\Fluent $command @@ -140,11 +140,15 @@ public function compileCreate(Blueprint $blueprint, Fluent $command) */ public function compileAdd(Blueprint $blueprint, Fluent $command) { - $columns = implode(', ', $this->getColumns($blueprint)); + $column = $this->getColumn($blueprint, $command->column); - $sql = 'alter table '.$this->wrapTable($blueprint)." add ( $columns"; + $sql = 'alter table '.$this->wrapTable($blueprint)." add ( $column"; - $sql .= (string) $this->addPrimaryKeys($blueprint); + $primary = $this->getCommandByName($blueprint, 'primary'); + + if (! is_null($primary) && in_array($command->column->name, $primary->columns)) { + $sql .= ", constraint {$primary->index} primary key ( {$command->column->name} )"; + } return $sql .= ' )'; } @@ -470,7 +474,11 @@ protected function typeTinyInteger(Fluent $column) */ protected function typeFloat(Fluent $column) { - return "number({$column->total}, {$column->places})"; + if ($column->precision) { + return "float({$column->precision})"; + } + + return 'float'; } /** @@ -480,11 +488,7 @@ protected function typeFloat(Fluent $column) */ protected function typeDouble(Fluent $column) { - if (is_null($column->total) || is_null($column->places)) { - throw new RuntimeException('This database engine requires specifying both precision and scale for a "double" column.'); - } - - return "number({$column->total}, {$column->places})"; + return 'double precision'; } /** diff --git a/tests/OracleDBConnectorTest.php b/tests/OracleDBConnectorTest.php index 712812a..513912b 100644 --- a/tests/OracleDBConnectorTest.php +++ b/tests/OracleDBConnectorTest.php @@ -4,6 +4,7 @@ use Jfelder\OracleDB\Connectors\OracleConnector; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class OracleDBConnectorTest extends TestCase @@ -13,9 +14,7 @@ public function tearDown(): void m::close(); } - /** - * @dataProvider oracleConnectProvider - */ + #[DataProvider('oracleConnectProvider')] public function testOracleConnectCallsCreateConnectionWithProperArguments($dsn, $config) { $connector = $this->getMockBuilder(OracleConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock(); diff --git a/tests/OracleDBQueryBuilderTest.php b/tests/OracleDBQueryBuilderTest.php index 7d6c388..f83dd40 100644 --- a/tests/OracleDBQueryBuilderTest.php +++ b/tests/OracleDBQueryBuilderTest.php @@ -21,6 +21,7 @@ use Jfelder\OracleDB\Query\OracleBuilder as OracleQueryBuilder; use Jfelder\OracleDB\Query\Processors\OracleProcessor; use Mockery as m; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\TestCase; use RuntimeException; use stdClass; @@ -70,9 +71,7 @@ public function testBasicSelectWithGetColumns() $this->assertNull($builder->columns); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testBasicSelectUseWritePdo() { $builder = $this->getOracleBuilderWithProcessor(); @@ -794,9 +793,7 @@ public function testOracleUnionLimitsAndOffsets() $this->assertEquals('select t2.* from ( select rownum AS "rn", t1.* from ((select * from "users") union (select * from "dogs")) t1 ) t2 where t2."rn" between 6 and 15', $builder->toSql()); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testUnionAggregate() { $expected = 'select count(*) as aggregate from ((select * from "posts") union (select * from "videos")) as "temp_table"'; @@ -806,9 +803,7 @@ public function testUnionAggregate() $builder->from('posts')->union($this->getOracleBuilder()->from('videos'))->count(); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testHavingAggregate() { $expected = 'select count(*) as aggregate from (select (select "count(*)" from "videos" where "posts"."id" = "videos"."post_id") as "videos_count" from "posts" having "videos_count" > ?) as "temp_table"'; @@ -2220,9 +2215,7 @@ public function testDeleteWithJoinMethod() $builder->from('users')->join('contacts', 'users.id', '=', 'contacts.id')->where('users.email', '=', 'foo')->orderBy('users.id')->limit(1)->delete(); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testTruncateMethod() { $builder = $this->getOracleBuilder(); @@ -2259,9 +2252,7 @@ public function testPreservedAreAppliedByToSql() $this->assertEquals(['bar'], $builder->getBindings()); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testPreservedAreAppliedByInsert() { $builder = $this->getOracleBuilder(); @@ -2272,9 +2263,7 @@ public function testPreservedAreAppliedByInsert() $builder->insert(['email' => 'foo']); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testPreservedAreAppliedByInsertGetId() { $this->called = false; @@ -2286,9 +2275,7 @@ public function testPreservedAreAppliedByInsertGetId() $builder->insertGetId(['email' => 'foo'], 'id'); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testPreservedAreAppliedByInsertUsing() { $builder = $this->getOracleBuilder(); @@ -2301,9 +2288,7 @@ public function testPreservedAreAppliedByInsertUsing() // NOTE: testPreservedAreAppliedByUpsert omitted since ->upsert is on the "not implemented" list - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testPreservedAreAppliedByUpdate() { $builder = $this->getOracleBuilder(); @@ -2314,9 +2299,7 @@ public function testPreservedAreAppliedByUpdate() $builder->update(['email' => 'foo']); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testPreservedAreAppliedByDelete() { $builder = $this->getOracleBuilder(); @@ -2327,9 +2310,7 @@ public function testPreservedAreAppliedByDelete() $builder->delete(); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testPreservedAreAppliedByTruncate() { $builder = $this->getOracleBuilder(); @@ -2340,9 +2321,7 @@ public function testPreservedAreAppliedByTruncate() $builder->truncate(); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testPreservedAreAppliedByExists() { $builder = $this->getOracleBuilder(); @@ -2513,9 +2492,7 @@ public function testDynamicWhere() $this->assertEquals($builder, $builder->dynamicWhere($method, $parameters)); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testDynamicWhereIsNotGreedy() { $method = 'whereIosVersionAndAndroidVersionOrOrientation'; @@ -2574,9 +2551,7 @@ public function testOracleLock() $this->assertEquals(['baz'], $builder->getBindings()); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testSelectWithLockUsesWritePdo() { $builder = $this->getOracleBuilderWithProcessor(); @@ -2700,9 +2675,7 @@ public function testLowercaseLeadingBooleansAreRemoved() $this->assertSame('select * from "users" where "name" = ?', $builder->toSql()); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testChunkWithLastChunkComplete() { $builder = $this->getMockQueryBuilder(); @@ -2726,9 +2699,7 @@ public function testChunkWithLastChunkComplete() }); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testChunkWithLastChunkPartial() { $builder = $this->getMockQueryBuilder(); @@ -2749,9 +2720,7 @@ public function testChunkWithLastChunkPartial() }); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testChunkCanBeStoppedByReturningFalse() { $builder = $this->getMockQueryBuilder(); @@ -2774,9 +2743,7 @@ public function testChunkCanBeStoppedByReturningFalse() }); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testChunkWithCountZero() { $builder = $this->getMockQueryBuilder(); @@ -2794,9 +2761,7 @@ public function testChunkWithCountZero() }); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testChunkPaginatesUsingIdWithLastChunkComplete() { $builder = $this->getMockQueryBuilder(); @@ -2820,9 +2785,7 @@ public function testChunkPaginatesUsingIdWithLastChunkComplete() }, 'someIdField'); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testChunkPaginatesUsingIdWithLastChunkPartial() { $builder = $this->getMockQueryBuilder(); @@ -2843,9 +2806,7 @@ public function testChunkPaginatesUsingIdWithLastChunkPartial() }, 'someIdField'); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testChunkPaginatesUsingIdWithCountZero() { $builder = $this->getMockQueryBuilder(); @@ -2863,9 +2824,7 @@ public function testChunkPaginatesUsingIdWithCountZero() }, 'someIdField'); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testChunkPaginatesUsingIdWithAlias() { $builder = $this->getMockQueryBuilder(); diff --git a/tests/OracleDBSchemaGrammarTest.php b/tests/OracleDBSchemaGrammarTest.php index c1963e0..fda774d 100644 --- a/tests/OracleDBSchemaGrammarTest.php +++ b/tests/OracleDBSchemaGrammarTest.php @@ -59,18 +59,6 @@ public function testBasicCreateTable() $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() @@ -175,23 +163,6 @@ 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'); @@ -199,25 +170,15 @@ public function testBasicAlterTable() $blueprint->string('email'); $conn = $this->getConnection(); + $conn->shouldNotReceive('getConfig'); $statements = $blueprint->toSql($conn, $this->getGrammar()); - $this->assertEquals(1, count($statements)); - $this->assertEquals('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 testBasicAlterTableWithPrimary() - { - $blueprint = new Blueprint('users'); - $blueprint->increments('id'); - $blueprint->string('email'); - - $conn = $this->getConnection(); - - $statements = $blueprint->toSql($conn, $this->getGrammar()); - - $this->assertEquals(1, count($statements)); - $this->assertEquals('alter table users add ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table users add ( id number(10,0) not null, constraint users_id_primary primary key ( id ) )', + 'alter table users add ( email varchar2(255) not null )', + ], $statements); } public function testBasicAlterTableWithPrefix() @@ -232,24 +193,13 @@ public function testBasicAlterTableWithPrefix() $statements = $blueprint->toSql($conn, $grammar); - $this->assertEquals(1, count($statements)); - $this->assertEquals('alter table prefix_users add ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); - } - - public function testBasicAlterTableWithPrefixAndPrimary() - { - $blueprint = new Blueprint('users'); - $blueprint->increments('id'); - $blueprint->string('email'); - $grammar = $this->getGrammar(); - $grammar->setTablePrefix('prefix_'); - - $conn = $this->getConnection(); - - $statements = $blueprint->toSql($conn, $grammar); + // todo fix OracleGrammar.php code to name the constraint prefix_users_id_primary - $this->assertEquals(1, count($statements)); - $this->assertEquals('alter table prefix_users add ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table prefix_users add ( id number(10,0) not null, constraint users_id_primary primary key ( id ) )', + 'alter table prefix_users add ( email varchar2(255) not null )', + ], $statements); } public function testDropTable() @@ -514,11 +464,16 @@ public function testAddingForeignID() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); + $this->assertCount(9, $statements); $this->assertSame([ - 'alter table users add ( foo number(19,0) not null, company_id number(19,0) not null, laravel_idea_id number(19,0) not null, team_id number(19,0) not null, team_column_id number(19,0) not null )', + 'alter table users add ( foo number(19,0) not null )', + 'alter table users add ( company_id number(19,0) not null )', 'alter table users add constraint users_company_id_foreign foreign key ( company_id ) references companies ( id )', + 'alter table users add ( laravel_idea_id number(19,0) not null )', 'alter table users add constraint users_laravel_idea_id_foreign foreign key ( laravel_idea_id ) references laravel_ideas ( id )', + 'alter table users add ( team_id number(19,0) not null )', 'alter table users add constraint users_team_id_foreign foreign key ( team_id ) references teams ( id )', + 'alter table users add ( team_column_id number(19,0) not null )', 'alter table users add constraint users_team_column_id_foreign foreign key ( team_column_id ) references teams ( id )', ], $statements); } @@ -673,41 +628,21 @@ public function testAddingTinyInteger() public function testAddingFloat() { $blueprint = new Blueprint('users'); - $blueprint->float('foo', 5, 2); + $blueprint->float('foo', 5); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertEquals('alter table users add ( foo number(5, 2) not null )', $statements[0]); + $this->assertEquals('alter table users add ( foo float(5) not null )', $statements[0]); } public function testAddingDouble() { - $blueprint = new Blueprint('users'); - $blueprint->double('foo', 5, 2); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - - $this->assertEquals(1, count($statements)); - $this->assertEquals('alter table users add ( foo number(5, 2) not null )', $statements[0]); - } - - public function testAddingDoubleWithoutSecondParameter() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('requires specifying both precision and scale'); - $blueprint = new Blueprint('users'); $blueprint->double('foo'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - } - - public function testAddingDoubleWithoutThirdParameter() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('requires specifying both precision and scale'); - $blueprint = new Blueprint('users'); - $blueprint->double('foo', 15); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table users add ( foo double precision not null )', $statements[0]); } public function testAddingDecimal() @@ -795,8 +730,11 @@ public function testAddingTimeStamps() $blueprint->timestamps(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertEquals(1, count($statements)); - $this->assertEquals('alter table users add ( created_at timestamp null, updated_at timestamp null )', $statements[0]); + $this->assertEquals(2, count($statements)); + $this->assertSame([ + 'alter table users add ( created_at timestamp null )', + 'alter table users add ( updated_at timestamp null )', + ], $statements); } public function testAddingBinary() From acf30e107d3a9c4c05d38e0ce36c866018376b46 Mon Sep 17 00:00:00 2001 From: Travis Tegen Date: Tue, 6 Aug 2024 14:33:01 -0400 Subject: [PATCH 2/2] cover a few more Laravel changes --- readme.md | 6 + .../OracleDB/Query/Grammars/OracleGrammar.php | 51 +++++++ .../Query/Processors/OracleProcessor.php | 18 --- src/Jfelder/OracleDB/Schema/OracleBuilder.php | 12 +- tests/OracleDBOCIProcessorTest.php | 17 +-- tests/OracleDBQueryBuilderTest.php | 124 ++++++++++++++++++ 6 files changed, 187 insertions(+), 41 deletions(-) diff --git a/readme.md b/readme.md index f705b8d..88bf90e 100644 --- a/readme.md +++ b/readme.md @@ -95,6 +95,7 @@ features not already listed. #### Query Builder +- group limiting via a groupLimit clause `$query->groupLimit($value, $column);` note: this was only added to Laravel so Eloquent can limit the number of eagerly loaded results per parent - 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');` @@ -103,6 +104,11 @@ features not already listed. - json operations `DB::from('users')->where('items->sku', '=', 'foo-bar')->get();` - whereFulltext `DB::table('users')->whereFulltext('description', 'Hello World');` +#### Eloquent + +- setting $guarded on an Eloquent model as anything other than an empty array. your models must either not define $guarded at all, or set it to an empty array. If not, Eloquent may attempt to run a column listing sql query resulting in an exception. +- limiting the number of eagerly loaded results per parent, ie get only 3 posts per user `User::with(['posts' => fn ($query) => $query->limit(3)])->paginate();` + #### Schema Builder - drop a table if it exists `Schema::dropIfExists('some_table');` diff --git a/src/Jfelder/OracleDB/Query/Grammars/OracleGrammar.php b/src/Jfelder/OracleDB/Query/Grammars/OracleGrammar.php index d5b7c6a..ba56fb7 100644 --- a/src/Jfelder/OracleDB/Query/Grammars/OracleGrammar.php +++ b/src/Jfelder/OracleDB/Query/Grammars/OracleGrammar.php @@ -70,6 +70,24 @@ public function compileSelect(Builder $query) return $sql; } + /** + * Compile a "where like" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereLike(Builder $query, $where) + { + if (! $where['caseSensitive']) { + throw new RuntimeException('This database engine does not support case insensitive like operations. The sql "UPPER(some_column) like ?" can accomplish insensitivity.'); + } + + $where['operator'] = $where['not'] ? 'not like' : 'like'; + + return $this->whereBasic($query, $where); + } + /** * Compile an insert statement into SQL. * @@ -284,6 +302,29 @@ protected function compileLimit(Builder $query, $limit) return ''; } + /** + * Compile a group limit clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileGroupLimit(Builder $query) + { + throw new RuntimeException('This database engine does not support group limit operations.'); + } + + /** + * Compile a row number clause. + * + * @param string $partition + * @param string $orders + * @return string + */ + protected function compileRowNumber($partition, $orders) + { + throw new RuntimeException('This database engine does not support row number operations.'); + } + /** * Compile the "offset" portions of the query. * @@ -295,6 +336,16 @@ protected function compileOffset(Builder $query, $offset) return ''; } + /** + * Compile a query to get the number of open connections for a database. + * + * @throws RuntimeException + */ + public function compileThreadCount() + { + throw new RuntimeException('This database engine does not support getting the number of open connections.'); + } + /** * Wrap a single string in keyword identifiers. * diff --git a/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php b/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php index aac5697..45f12c5 100644 --- a/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php +++ b/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php @@ -19,22 +19,4 @@ public function processInsertGetId(Builder $query, $sql, $values, $sequence = nu { return $query->getConnection()->oracleInsertGetId($sql, $values); } - - // todo remove this because it was removed in Laravel 11. add a processColumns like the specific processors have. - /** - * Process the results of a column listing query. - * - * @param array $results - * @return array - */ - public function processColumnListing($results) - { - $mapping = function ($r) { - $r = (object) $r; - - return $r->column_name; - }; - - return array_map($mapping, $results); - } } diff --git a/src/Jfelder/OracleDB/Schema/OracleBuilder.php b/src/Jfelder/OracleDB/Schema/OracleBuilder.php index 9c17b1c..aa031b7 100644 --- a/src/Jfelder/OracleDB/Schema/OracleBuilder.php +++ b/src/Jfelder/OracleDB/Schema/OracleBuilder.php @@ -2,6 +2,8 @@ namespace Jfelder\OracleDB\Schema; +use RuntimeException; + class OracleBuilder extends \Illuminate\Database\Schema\Builder { /** @@ -29,14 +31,6 @@ public function hasTable($table) */ public function getColumnListing($table) { - $sql = $this->grammar->compileColumnExists($table); - - $database = $this->connection->getDatabaseName(); - - $table = $this->connection->getTablePrefix().$table; - - $results = $this->connection->select($sql, [$database, $table]); - - return $this->connection->getPostProcessor()->processColumnListing($results); + throw new RuntimeException('This database engine does not support column listing operations. Eloquent models must set $guarded to [] or not define it at all.'); } } diff --git a/tests/OracleDBOCIProcessorTest.php b/tests/OracleDBOCIProcessorTest.php index 5ec1a4e..ee6a4a1 100644 --- a/tests/OracleDBOCIProcessorTest.php +++ b/tests/OracleDBOCIProcessorTest.php @@ -37,18 +37,7 @@ public function testInsertGetIdProcessing() $this->assertSame(1234, $result); } - public function testProcessColumnListing() - { - $processor = new OracleProcessor; - $listing = [['column_name' => 'id'], ['column_name' => 'name'], ['column_name' => 'email']]; - $expected = ['id', 'name', 'email']; - $this->assertEquals($expected, $processor->processColumnListing($listing)); - - // convert listing to objects to simulate PDO::FETCH_CLASS - foreach ($listing as &$row) { - $row = (object) $row; - } - - $this->assertEquals($expected, $processor->processColumnListing($listing)); - } + // Laravel's DatabaseMySqlProcessorTest, DatabasePostgresProcessorTest, etc have a test named + // testProcessColumns, but $processor->processColumns is only used by Schema Builder class, and we + // are planning to remove Schema Builder entirely (todo) from this package. } diff --git a/tests/OracleDBQueryBuilderTest.php b/tests/OracleDBQueryBuilderTest.php index f83dd40..1c539fc 100644 --- a/tests/OracleDBQueryBuilderTest.php +++ b/tests/OracleDBQueryBuilderTest.php @@ -22,6 +22,7 @@ use Jfelder\OracleDB\Query\Processors\OracleProcessor; use Mockery as m; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use RuntimeException; use stdClass; @@ -489,6 +490,53 @@ public function testWhereLikeOracle() $this->assertEquals([0 => '1'], $builder->getBindings()); } + #[TestWith(['whereLike: 3rd arg missing'])] + #[TestWith(['whereLike: 3rd arg false'])] + #[TestWith(['whereLike: 3rd arg true'])] + #[TestWith(['whereNotLike: 3rd arg missing'])] + #[TestWith(['whereNotLike: 3rd arg false'])] + #[TestWith(['whereNotLike: 3rd arg true'])] + public function testWhereLikeClauseOracle($case) + { + if ($case === 'whereLike: 3rd arg missing') { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This database engine does not support case insensitive like operations. The sql "UPPER(some_column) like ?" can accomplish insensitivity.'); + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereLike('id', '1'); + $builder->toSql(); + } elseif ($case === 'whereLike: 3rd arg false') { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This database engine does not support case insensitive like operations. The sql "UPPER(some_column) like ?" can accomplish insensitivity.'); + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereLike('id', '1', false); + $builder->toSql(); + } elseif ($case === 'whereLike: 3rd arg true') { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereLike('id', '1', true); + $this->assertSame('select * from "users" where "id" like ?', $builder->toSql()); + $this->assertEquals([0 => '1'], $builder->getBindings()); + } elseif ($case === 'whereNotLike: 3rd arg missing') { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This database engine does not support case insensitive like operations. The sql "UPPER(some_column) like ?" can accomplish insensitivity.'); + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereNotLike('id', '1'); + $builder->toSql(); + } elseif ($case === 'whereNotLike: 3rd arg false') { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This database engine does not support case insensitive like operations. The sql "UPPER(some_column) like ?" can accomplish insensitivity.'); + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereNotLike('id', '1', false); + $builder->toSql(); + } elseif ($case === 'whereNotLike: 3rd arg true') { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereNotLike('id', '1', true); + $this->assertSame('select * from "users" where "id" not like ?', $builder->toSql()); + $this->assertEquals([0 => '1'], $builder->getBindings()); + } else { + throw new \Exception("Unknown case number [$case]"); + } + } + public function testWhereBetweens() { $builder = $this->getOracleBuilder(); @@ -717,6 +765,73 @@ public function testArrayWhereColumn() $this->assertEquals([], $builder->getBindings()); } + public function testWhereAny() + { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereAny(['last_name', 'email'], 'like', '%Otwell%'); + $this->assertSame('select * from "users" where ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereAny(['last_name', 'email'], '%Otwell%'); + $this->assertSame('select * from "users" where ("last_name" = ? or "email" = ?)', $builder->toSql()); + $this->assertEquals(['%Otwell%', '%Otwell%'], $builder->getBindings()); + } + + public function testOrWhereAny() + { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->orWhereAny(['last_name', 'email'], 'like', '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->whereAny(['last_name', 'email'], 'like', '%Otwell%', 'or'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->orWhereAny(['last_name', 'email'], '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" = ? or "email" = ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + } + + public function testWhereNone() + { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereNone(['last_name', 'email'], 'like', '%Otwell%'); + $this->assertSame('select * from "users" where not ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->whereNone(['last_name', 'email'], 'Otwell'); + $this->assertSame('select * from "users" where not ("last_name" = ? or "email" = ?)', $builder->toSql()); + $this->assertEquals(['Otwell', 'Otwell'], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->whereNone(['last_name', 'email'], 'like', '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? and not ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + } + + public function testOrWhereNone() + { + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->orWhereNone(['last_name', 'email'], 'like', '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? or not ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->whereNone(['last_name', 'email'], 'like', '%Otwell%', 'or'); + $this->assertSame('select * from "users" where "first_name" like ? or not ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getOracleBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->orWhereNone(['last_name', 'email'], '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? or not ("last_name" = ? or "email" = ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + } + public function testUnions() { $builder = $this->getOracleBuilder(); @@ -2141,6 +2256,15 @@ public function testUpdateMethodRespectsRaw() $this->assertEquals(1, $result); } + public function testUpdateMethodWorksWithQueryAsValue() + { + $builder = $this->getOracleBuilder(); + $builder->getConnection()->shouldReceive('update')->once()->with('update "users" set "credits" = (select sum(credits) from "transactions" where "transactions"."user_id" = "users"."id" and "type" = ?) where "id" = ?', ['foo', 1])->andReturn(1); + $result = $builder->from('users')->where('id', '=', 1)->update(['credits' => $this->getOracleBuilder()->from('transactions')->selectRaw('sum(credits)')->whereColumn('transactions.user_id', 'users.id')->where('type', 'foo')]); + + $this->assertEquals(1, $result); + } + public function testUpdateOrInsertMethod() { $builder = m::mock(OracleQueryBuilder::class.'[where,exists,insert]', [