From c32f15580b9a4bd8dfc4f9ca291ebec70b07bafb Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 17 Jan 2024 20:04:18 +0330 Subject: [PATCH 1/3] add a method to connection for driver and version comparison --- src/Illuminate/Database/Connection.php | 54 ++++++++++++- .../Console/DatabaseInspectionCommand.php | 2 +- src/Illuminate/Database/MySqlConnection.php | 22 ------ .../Database/Query/Grammars/MySqlGrammar.php | 5 +- .../Database/Query/Grammars/SQLiteGrammar.php | 5 +- .../Database/Schema/Grammars/MySqlGrammar.php | 5 +- .../Database/Schema/MySqlSchemaState.php | 2 +- src/Illuminate/Queue/DatabaseQueue.php | 23 ++---- src/Illuminate/Support/Facades/DB.php | 1 + tests/Database/DatabaseConnectionTest.php | 78 +++++++++++++++++++ .../Database/DatabaseSchemaBlueprintTest.php | 9 +-- .../Database/SchemaBuilderTest.php | 2 +- 12 files changed, 148 insertions(+), 60 deletions(-) diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index 502ca290e833..33f597456f07 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -20,7 +20,9 @@ use Illuminate\Database\Schema\Builder as SchemaBuilder; use Illuminate\Support\Arr; use Illuminate\Support\InteractsWithTime; +use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; +use InvalidArgumentException; use PDO; use PDOStatement; use RuntimeException; @@ -1318,7 +1320,7 @@ public function getConfig($option = null) } /** - * Get the PDO driver name. + * Get the driver name from the configuration options. * * @return string */ @@ -1612,6 +1614,56 @@ public function getServerVersion(): string return $this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); } + /** + * Determine if the connection matches the given driver name and version. + * + * @template TValue + * + * @param string|array|\Closure(string,string): TValue $driver + * @param string|null $operator + * @param string|null $version + * @return bool|TValue + */ + public function is(string|array|Closure $driver, ?string $operator = null, ?string $version = null) + { + if (func_num_args() === 2 || (! is_string($driver) && func_num_args() === 3)) { + throw new InvalidArgumentException('Illegal arguments combination.'); + } + + $actualDriver = $this->getDriverName() ?? $this->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME); + $actualVersion = $this->getConfig('version') ?? $this->getServerVersion(); + + [$actualDriver, $actualVersion] = match (true) { + Str::contains($actualVersion, 'MariaDB') => ['mariadb', Str::between($actualVersion, '5.5.5-', '-MariaDB')], + Str::contains($actualVersion, ['vitess', 'PlanetScale']) => ['vitess', Str::before($actualVersion, '-')], + default => [strtolower($actualDriver), $actualVersion], + }; + + if ($driver instanceof Closure) { + return $driver($actualDriver, $actualVersion); + } + + if (is_array($driver)) { + foreach ($driver as $key => $value) { + [$name, $operator, $version] = match (true) { + is_string($key) => [$key, $value[0], $value[1]], + is_array($value) => $value, + default => [$value, null, null], + }; + + if (strtolower($name) === $actualDriver + && (! $operator || ! $version || version_compare($actualVersion, $version, $operator))) { + return true; + } + } + + return false; + } + + return strtolower($driver) === $actualDriver + && (! $operator || ! $version || version_compare($actualVersion, $version, $operator)); + } + /** * Register a connection resolver. * diff --git a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php index fbbe41bdd448..dfadba6531c2 100644 --- a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php +++ b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php @@ -22,7 +22,7 @@ abstract class DatabaseInspectionCommand extends Command protected function getConnectionName(ConnectionInterface $connection, $database) { return match (true) { - $connection instanceof MySqlConnection && $connection->isMaria() => 'MariaDB', + $connection instanceof MySqlConnection && $connection->is('mariadb') => 'MariaDB', $connection instanceof MySqlConnection => 'MySQL', $connection instanceof PostgresConnection => 'PostgreSQL', $connection instanceof SQLiteConnection => 'SQLite', diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index 00d212e9481d..67401341d319 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -38,28 +38,6 @@ protected function isUniqueConstraintError(Exception $exception) return boolval(preg_match('#Integrity constraint violation: 1062#i', $exception->getMessage())); } - /** - * Determine if the connected database is a MariaDB database. - * - * @return bool - */ - public function isMaria() - { - return str_contains($this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), 'MariaDB'); - } - - /** - * Get the server version for the connection. - * - * @return string - */ - public function getServerVersion(): string - { - return str_contains($version = parent::getServerVersion(), 'MariaDB') - ? Str::between($version, '5.5.5-', '-MariaDB') - : $version; - } - /** * Get the default query grammar instance. * diff --git a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php index 1396e46ad17f..add14ec2f563 100755 --- a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php @@ -4,7 +4,6 @@ use Illuminate\Database\Query\Builder; use Illuminate\Support\Str; -use PDO; class MySqlGrammar extends Grammar { @@ -116,9 +115,7 @@ protected function compileGroupLimit(Builder $query) */ public function useLegacyGroupLimit(Builder $query) { - $version = $query->getConnection()->getReadPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); - - return ! $query->getConnection()->isMaria() && version_compare($version, '8.0.11') < 0; + return $query->getConnection()->is('mysql', '<', '8.0.11'); } /** diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index 72b591ea5715..75fe10fdcfbe 100755 --- a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -5,7 +5,6 @@ use Illuminate\Database\Query\Builder; use Illuminate\Support\Arr; use Illuminate\Support\Str; -use PDO; class SQLiteGrammar extends Grammar { @@ -193,9 +192,7 @@ protected function compileJsonContainsKey($column) */ protected function compileGroupLimit(Builder $query) { - $version = $query->getConnection()->getReadPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); - - if (version_compare($version, '3.25.0') >= 0) { + if ($query->getConnection()->is('sqlite', '>=', '3.25.0')) { return parent::compileGroupLimit($query); } diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 07bd41291414..e3e9489be4ee 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -357,10 +357,7 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - $version = $connection->getServerVersion(); - - if (($connection->isMaria() && version_compare($version, '10.5.2', '<')) || - (! $connection->isMaria() && version_compare($version, '8.0.3', '<'))) { + if ($connection->is([['mariadb', '<', '10.5.2'], ['mysql', '<', '8.0.3']])) { $column = collect($connection->getSchemaBuilder()->getColumns($blueprint->getTable())) ->firstWhere('name', $command->from); diff --git a/src/Illuminate/Database/Schema/MySqlSchemaState.php b/src/Illuminate/Database/Schema/MySqlSchemaState.php index 2514c18bd6c1..f80a39531ed6 100644 --- a/src/Illuminate/Database/Schema/MySqlSchemaState.php +++ b/src/Illuminate/Database/Schema/MySqlSchemaState.php @@ -87,7 +87,7 @@ protected function baseDumpCommand() { $command = 'mysqldump '.$this->connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0'; - if (! $this->connection->isMaria()) { + if ($this->connection->is('mariadb')) { $command .= ' --set-gtid-purged=OFF'; } diff --git a/src/Illuminate/Queue/DatabaseQueue.php b/src/Illuminate/Queue/DatabaseQueue.php index d57f18795d1f..de2a19897d4d 100644 --- a/src/Illuminate/Queue/DatabaseQueue.php +++ b/src/Illuminate/Queue/DatabaseQueue.php @@ -255,25 +255,16 @@ protected function getNextAvailableJob($queue) */ protected function getLockForPopping() { - $databaseEngine = $this->database->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME); - $databaseVersion = $this->database->getConfig('version') ?? $this->database->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); - - if (Str::of($databaseVersion)->contains('MariaDB')) { - $databaseEngine = 'mariadb'; - $databaseVersion = Str::before(Str::after($databaseVersion, '5.5.5-'), '-'); - } elseif (Str::of($databaseVersion)->contains(['vitess', 'PlanetScale'])) { - $databaseEngine = 'vitess'; - $databaseVersion = Str::before($databaseVersion, '-'); - } - - if (($databaseEngine === 'mysql' && version_compare($databaseVersion, '8.0.1', '>=')) || - ($databaseEngine === 'mariadb' && version_compare($databaseVersion, '10.6.0', '>=')) || - ($databaseEngine === 'pgsql' && version_compare($databaseVersion, '9.5', '>=')) || - ($databaseEngine === 'vitess' && version_compare($databaseVersion, '19.0', '>='))) { + if ($this->database->is([ + ['mysql', '>=', '8.0.1'], + ['mariadb', '>=', '10.6.0'], + ['pgsql', '>=', '9.5'], + ['vitess', '>=', '19.0'], + ])) { return 'FOR UPDATE SKIP LOCKED'; } - if ($databaseEngine === 'sqlsrv') { + if ($this->database->is('sqlsrv')) { return 'with(rowlock,updlock,readpast)'; } diff --git a/src/Illuminate/Support/Facades/DB.php b/src/Illuminate/Support/Facades/DB.php index f29f576e7731..3ffa265bea28 100644 --- a/src/Illuminate/Support/Facades/DB.php +++ b/src/Illuminate/Support/Facades/DB.php @@ -95,6 +95,7 @@ * @method static \Illuminate\Database\Connection setTablePrefix(string $prefix) * @method static \Illuminate\Database\Grammar withTablePrefix(\Illuminate\Database\Grammar $grammar) * @method static string getServerVersion() + * @method static bool is(string|array|\Closure $driver, string|null $operator = null, string|null $version = null) * @method static void resolverFor(string $driver, \Closure $callback) * @method static mixed getResolver(string $driver) * @method static mixed transaction(\Closure $callback, int $attempts = 1) diff --git a/tests/Database/DatabaseConnectionTest.php b/tests/Database/DatabaseConnectionTest.php index 8709e339c226..004e4ff2b9e4 100755 --- a/tests/Database/DatabaseConnectionTest.php +++ b/tests/Database/DatabaseConnectionTest.php @@ -528,6 +528,84 @@ public function testGetRawQueryLog() $this->assertEquals(1.23, $log[0]['time']); } + public function testIsWithConfig() + { + $connection = new Connection(m::mock(PDO::class), config: ['driver' => 'mysql', 'version' => '5.7.2']); + + $this->assertTrue($connection->is('mysql')); + $this->assertTrue($connection->is('mysql', '<', '8.0.11')); + $this->assertTrue($connection->is([['mysql', '<', '8.0.11'], ['sqlite', '>=', '3.5.0']])); + $this->assertTrue($connection->is(['mysql' => ['<', '8.0.11'], 'sqlite' => ['>=', '3.5.0']])); + $this->assertTrue($connection->is(['mysql', 'sqlite'])); + $this->assertTrue($connection->is( + fn ($driver, $version) => $driver === 'mysql' && version_compare($version, '8.0.11', '<') + )); + $this->assertEquals(['mysql', '5.7.2'], $connection->is(fn ($driver, $version) => [$driver, $version])); + + $this->assertFalse($connection->is('mariadb')); + $this->assertFalse($connection->is('mysql', '>=', '8.0.11')); + $this->assertFalse($connection->is([['mysql', '>', '8.0.11'], ['sqlite', '>=', '3.5.0']])); + $this->assertFalse($connection->is(['sqlsrv' => ['<', '8.0.11'], 'vitess' => ['>=', '3.5.0']])); + $this->assertFalse($connection->is(['vitess', 'sqlite'])); + $this->assertFalse($connection->is( + fn ($driver, $version) => $driver === 'pgsql' && version_compare($version, '8.0.11', '<') + )); + } + + public function testIsWithPDO() + { + $pdo = m::mock(PDO::class); + $pdo->shouldReceive('getAttribute')->with(PDO::ATTR_DRIVER_NAME)->andReturn('mysql'); + $pdo->shouldReceive('getAttribute')->with(PDO::ATTR_SERVER_VERSION)->andReturn('5.5.5-10.5.2-MariaDB'); + $connection = new Connection($pdo, config: []); + + $this->assertTrue($connection->is('mariadb')); + $this->assertTrue($connection->is('mariadb', '<', '10.10.10')); + $this->assertTrue($connection->is([['mysql', '>', '8.0.11'], ['mariadb', '>=', '9.5.0']])); + $this->assertTrue($connection->is(['mysql' => ['>', '8.0.11'], 'mariadb' => ['>=', '9.5.0']])); + $this->assertTrue($connection->is(['mariadb', 'sqlite'])); + $this->assertTrue($connection->is( + fn ($driver, $version) => $driver === 'mariadb' && version_compare($version, '10.10.10', '<') + )); + $this->assertEquals(['mariadb', '10.5.2'], $connection->is(fn ($driver, $version) => [$driver, $version])); + + $this->assertFalse($connection->is('mysql')); + $this->assertFalse($connection->is('mysql', '>=', '8.0.11')); + $this->assertFalse($connection->is([['mysql', '>', '8.0.11'], ['sqlite', '>=', '3.5.0']])); + $this->assertFalse($connection->is(['sqlsrv' => ['<', '8.0.11'], 'vitess' => ['>=', '3.5.0']])); + $this->assertFalse($connection->is(['vitess', 'sqlite'])); + $this->assertFalse($connection->is( + fn ($driver, $version) => $driver === 'pgsql' && version_compare($version, '8.0.11', '<') + )); + } + + public function testIsWithStringException() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = new Connection(m::mock(PDO::class)); + + $connection->is('mysql', '8.0.11'); + } + + public function testIsWithArrayException() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = new Connection(m::mock(PDO::class)); + + $connection->is(['mysql'], '<', '8.0.11'); + } + + public function testIsWithClosureException() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = new Connection(m::mock(PDO::class)); + + $connection->is(fn () => 'mysql', '<', '8.0.11'); + } + protected function getMockConnection($methods = [], $pdo = null) { $pdo = $pdo ?: new DatabaseConnectionTestMockPDO; diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index fe0f20d6f995..b8d527a6ad07 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -168,8 +168,7 @@ public function testRenameColumn() }); $connection = m::mock(Connection::class); - $connection->shouldReceive('getServerVersion')->andReturn('8.0.4'); - $connection->shouldReceive('isMaria')->andReturn(false); + $connection->shouldReceive('is')->with([['mariadb', '<', '10.5.2'], ['mysql', '<', '8.0.3']])->andReturn(false); $blueprint = clone $base; $this->assertEquals(['alter table `users` rename column `foo` to `bar`'], $blueprint->toSql($connection, new MySqlGrammar)); @@ -192,8 +191,7 @@ public function testNativeRenameColumnOnMysql57() }); $connection = m::mock(Connection::class); - $connection->shouldReceive('isMaria')->andReturn(false); - $connection->shouldReceive('getServerVersion')->andReturn('5.7'); + $connection->shouldReceive('is')->with([['mariadb', '<', '10.5.2'], ['mysql', '<', '8.0.3']])->andReturn(true); $connection->shouldReceive('getSchemaBuilder->getColumns')->andReturn([ ['name' => 'name', 'type' => 'varchar(255)', 'type_name' => 'varchar', 'nullable' => true, 'collation' => 'utf8mb4_unicode_ci', 'default' => 'foo', 'comment' => null, 'auto_increment' => false], ['name' => 'id', 'type' => 'bigint unsigned', 'type_name' => 'bigint', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => 'lorem ipsum', 'auto_increment' => true], @@ -213,8 +211,7 @@ public function testNativeRenameColumnOnLegacyMariaDB() }); $connection = m::mock(Connection::class); - $connection->shouldReceive('isMaria')->andReturn(true); - $connection->shouldReceive('getServerVersion')->andReturn('10.1.35'); + $connection->shouldReceive('is')->with([['mariadb', '<', '10.5.2'], ['mysql', '<', '8.0.3']])->andReturn(true); $connection->shouldReceive('getSchemaBuilder->getColumns')->andReturn([ ['name' => 'name', 'type' => 'varchar(255)', 'type_name' => 'varchar', 'nullable' => true, 'collation' => 'utf8mb4_unicode_ci', 'default' => 'foo', 'comment' => null, 'auto_increment' => false], ['name' => 'id', 'type' => 'bigint unsigned', 'type_name' => 'bigint', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => 'lorem ipsum', 'auto_increment' => true], diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index e1fb45a26267..25099ad2abac 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -372,7 +372,7 @@ public function testAlteringTableWithForeignKeyConstraintsEnabled() public function testSystemVersionedTables() { - if ($this->driver !== 'mysql' || ! $this->getConnection()->isMaria()) { + if (! $this->getConnection()->is('mariadb')) { $this->markTestSkipped('Test requires a MariaDB connection.'); } From 3c1fdecbd59406bcf09025b5eeacf87cf1c50338 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 17 Jan 2024 21:08:13 +0330 Subject: [PATCH 2/3] formatting --- src/Illuminate/Database/MySqlConnection.php | 2 -- src/Illuminate/Queue/DatabaseQueue.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index 67401341d319..ebc605639fba 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -9,8 +9,6 @@ use Illuminate\Database\Schema\MySqlBuilder; use Illuminate\Database\Schema\MySqlSchemaState; use Illuminate\Filesystem\Filesystem; -use Illuminate\Support\Str; -use PDO; class MySqlConnection extends Connection { diff --git a/src/Illuminate/Queue/DatabaseQueue.php b/src/Illuminate/Queue/DatabaseQueue.php index de2a19897d4d..3ab3b917c291 100644 --- a/src/Illuminate/Queue/DatabaseQueue.php +++ b/src/Illuminate/Queue/DatabaseQueue.php @@ -8,8 +8,6 @@ use Illuminate\Queue\Jobs\DatabaseJob; use Illuminate\Queue\Jobs\DatabaseJobRecord; use Illuminate\Support\Carbon; -use Illuminate\Support\Str; -use PDO; class DatabaseQueue extends Queue implements QueueContract, ClearableQueue { From 57fdc08aea7928fc17902e7c3b60fd5769fc2926 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 17 Jan 2024 21:34:01 +0330 Subject: [PATCH 3/3] fix a typo --- src/Illuminate/Database/Schema/MySqlSchemaState.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Schema/MySqlSchemaState.php b/src/Illuminate/Database/Schema/MySqlSchemaState.php index f80a39531ed6..25b949d0e0cb 100644 --- a/src/Illuminate/Database/Schema/MySqlSchemaState.php +++ b/src/Illuminate/Database/Schema/MySqlSchemaState.php @@ -87,7 +87,7 @@ protected function baseDumpCommand() { $command = 'mysqldump '.$this->connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0'; - if ($this->connection->is('mariadb')) { + if (! $this->connection->is('mariadb')) { $command .= ' --set-gtid-purged=OFF'; }