From 9ccf0031d1cb8669752bc95e85cdccad20706461 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 9 Jan 2024 22:51:21 +0330 Subject: [PATCH] [11.x] Remove Doctrine DBAL (#48864) * remove doctrine dbal from grammar * remove unused imports * fix tests * fix tests * remove mysql 5.7 tests * fix facade doctype * use native column modifying by default * fix tests * fix tests * wip * Revert "remove mysql 5.7 tests" This reverts commit ba31106361cb85493f02f9bb6043ec9aeda5e507. * support native column renaming on MySQL 5.7 * fix style * wip * wip * remove doctrine usage on DatabaseTruncation * wip * rename index on sqlite * remove doctrine/dbal from db commands * fix styles * wip * wip * support renaming columns on legacy MariaDB * remove redundant non-standard tests * add collation modifier to sqlite * support native column modifying on sqlite * fix styles * add user-defined types to db:show * wip * fix styles * fix dropForeign exception on SQLite * fix styles * include generated and hidden columns on sqlite * remove custom doctrine types * remove doctrine change column * styling * remove support for registering custom doctrine types * remove unused config methods and property * remove redundant semicolon * force re-run tests * remove doctrine related conflicts reverts #49438 and #49456 * remove doctrine/dbal from require-dev * Revert "remove doctrine/dbal from require-dev" This reverts commit fc4dd91060ee46f59f0015c216d758419a73f385. * revert unrelated changes * disable foreign key constraints when altering table on SQLite * fix styling * consider backticks when parsing collate on SQLite * update facade docblocks * remove doctrine connection * formatting * fix conflicts * formatting --------- Co-authored-by: Taylor Otwell --- composer.json | 4 - src/Illuminate/Database/Connection.php | 130 +-------- .../Console/DatabaseInspectionCommand.php | 204 ++------------ .../Database/Console/MonitorCommand.php | 6 +- .../Database/Console/ShowCommand.php | 122 ++++++--- .../Database/Console/ShowModelCommand.php | 83 ++---- .../Database/Console/TableCommand.php | 120 ++++---- .../Database/DBAL/TimestampType.php | 94 ------- src/Illuminate/Database/DatabaseManager.php | 53 ---- .../Database/Migrations/Migrator.php | 29 +- src/Illuminate/Database/MySqlConnection.php | 24 +- .../PDO/Concerns/ConnectsToDatabase.php | 28 -- src/Illuminate/Database/PDO/Connection.php | 186 ------------- src/Illuminate/Database/PDO/MySqlDriver.php | 19 -- .../Database/PDO/PostgresDriver.php | 19 -- src/Illuminate/Database/PDO/SQLiteDriver.php | 19 -- .../Database/PDO/SqlServerConnection.php | 152 ----------- .../Database/PDO/SqlServerDriver.php | 30 -- .../Database/PostgresConnection.php | 11 - .../Query/Processors/SQLiteProcessor.php | 13 +- src/Illuminate/Database/SQLiteConnection.php | 11 - src/Illuminate/Database/Schema/Blueprint.php | 21 +- src/Illuminate/Database/Schema/Builder.php | 26 -- .../Database/Schema/Grammars/ChangeColumn.php | 238 ---------------- .../Database/Schema/Grammars/Grammar.php | 38 ++- .../Database/Schema/Grammars/MySqlGrammar.php | 41 ++- .../Schema/Grammars/PostgresGrammar.php | 23 -- .../Database/Schema/Grammars/RenameColumn.php | 93 ------- .../Schema/Grammars/SQLiteGrammar.php | 245 +++++++++++------ .../Schema/Grammars/SqlServerGrammar.php | 14 +- .../Database/Schema/SQLiteBuilder.php | 16 ++ .../Database/SqlServerConnection.php | 11 - src/Illuminate/Database/composer.json | 5 - .../Foundation/Testing/DatabaseTruncation.php | 2 +- src/Illuminate/Support/Facades/DB.php | 7 +- src/Illuminate/Support/Facades/Schema.php | 1 - .../Database/DatabaseSchemaBlueprintTest.php | 50 +++- tests/Database/DatabaseSchemaBuilderTest.php | 17 +- .../ConfigureCustomDoctrineTypeTest.php | 114 -------- .../Database/DatabaseSchemaBlueprintTest.php | 257 +++++------------- .../Database/DatabaseSchemaBuilderTest.php | 8 +- .../Database/EloquentWhereHasMorphTest.php | 2 +- .../Database/Fixtures/TinyInteger.php | 38 --- ...SqlSchemaBuilderAlterTableWithEnumTest.php | 4 +- .../Database/SchemaBuilderTest.php | 73 ++--- .../Database/{DBAL => }/TimestampTypeTest.php | 51 ++-- 46 files changed, 651 insertions(+), 2101 deletions(-) delete mode 100644 src/Illuminate/Database/DBAL/TimestampType.php delete mode 100644 src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php delete mode 100644 src/Illuminate/Database/PDO/Connection.php delete mode 100644 src/Illuminate/Database/PDO/MySqlDriver.php delete mode 100644 src/Illuminate/Database/PDO/PostgresDriver.php delete mode 100644 src/Illuminate/Database/PDO/SQLiteDriver.php delete mode 100644 src/Illuminate/Database/PDO/SqlServerConnection.php delete mode 100644 src/Illuminate/Database/PDO/SqlServerDriver.php delete mode 100644 src/Illuminate/Database/Schema/Grammars/ChangeColumn.php delete mode 100644 src/Illuminate/Database/Schema/Grammars/RenameColumn.php mode change 100755 => 100644 src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php mode change 100755 => 100644 src/Illuminate/Support/Facades/DB.php mode change 100755 => 100644 tests/Database/DatabaseSchemaBuilderTest.php delete mode 100644 tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php delete mode 100644 tests/Integration/Database/Fixtures/TinyInteger.php rename tests/Integration/Database/{DBAL => }/TimestampTypeTest.php (55%) diff --git a/composer.json b/composer.json index ecab692045c4..f200b006f86e 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,6 @@ "ext-gmp": "*", "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.235.5", - "doctrine/dbal": "^4.0", "fakerphp/faker": "^1.23", "guzzlehttp/guzzle": "^7.8", "league/flysystem-aws-s3-v3": "^3.0", @@ -120,8 +119,6 @@ "psr/simple-cache-implementation": "1.0|2.0|3.0" }, "conflict": { - "carbonphp/carbon-doctrine-types": "<3.0.0|>=4.0", - "doctrine/dbal": "<4.0.0|>=5.0", "tightenco/collect": "<5.5.33" }, "autoload": { @@ -167,7 +164,6 @@ "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^4.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.8).", diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index a46448bb8974..502ca290e833 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -5,8 +5,6 @@ use Carbon\CarbonInterval; use Closure; use DateTimeInterface; -use Doctrine\DBAL\Connection as DoctrineConnection; -use Doctrine\DBAL\Types\Type; use Exception; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Events\QueryExecuted; @@ -189,20 +187,6 @@ class Connection implements ConnectionInterface */ protected $beforeExecutingCallbacks = []; - /** - * The instance of Doctrine connection. - * - * @var \Doctrine\DBAL\Connection - */ - protected $doctrineConnection; - - /** - * Type mappings that should be registered with new Doctrine connections. - * - * @var array - */ - protected $doctrineTypeMappings = []; - /** * The connection resolvers. * @@ -989,8 +973,6 @@ protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $ public function reconnect() { if (is_callable($this->reconnector)) { - $this->doctrineConnection = null; - return call_user_func($this->reconnector, $this); } @@ -1017,8 +999,6 @@ public function reconnectIfMissingConnection() public function disconnect() { $this->setPdo(null)->setReadPdo(null); - - $this->doctrineConnection = null; } /** @@ -1208,106 +1188,6 @@ public function useWriteConnectionWhenReading($value = true) return $this; } - /** - * Is Doctrine available? - * - * @return bool - */ - public function isDoctrineAvailable() - { - return class_exists('Doctrine\DBAL\Connection'); - } - - /** - * Indicates whether native alter operations will be used when dropping, renaming, or modifying columns, even if Doctrine DBAL is installed. - * - * @return bool - */ - public function usingNativeSchemaOperations() - { - return ! $this->isDoctrineAvailable() || SchemaBuilder::$alwaysUsesNativeSchemaOperationsIfPossible; - } - - /** - * Get a Doctrine Schema Column instance. - * - * @param string $table - * @param string $column - * @return \Doctrine\DBAL\Schema\Column - */ - public function getDoctrineColumn($table, $column) - { - $schema = $this->getDoctrineSchemaManager(); - - return $schema->introspectTable($table)->getColumn($column); - } - - /** - * Get the Doctrine DBAL schema manager for the connection. - * - * @return \Doctrine\DBAL\Schema\AbstractSchemaManager - */ - public function getDoctrineSchemaManager() - { - $connection = $this->getDoctrineConnection(); - - return $connection->createSchemaManager(); - } - - /** - * Get the Doctrine DBAL database connection instance. - * - * @return \Doctrine\DBAL\Connection - */ - public function getDoctrineConnection() - { - if (is_null($this->doctrineConnection)) { - $driver = $this->getDoctrineDriver(); - - $this->doctrineConnection = new DoctrineConnection(array_filter([ - 'pdo' => $this->getPdo(), - 'dbname' => $this->getDatabaseName(), - 'driver' => $driver->getName(), - 'serverVersion' => $this->getConfig('server_version'), - ]), $driver); - - foreach ($this->doctrineTypeMappings as $name => $type) { - $this->doctrineConnection - ->getDatabasePlatform() - ->registerDoctrineTypeMapping($type, $name); - } - } - - return $this->doctrineConnection; - } - - /** - * Register a custom Doctrine mapping type. - * - * @param Type|class-string $class - * @param string $name - * @param string $type - * @return void - * - * @throws \Doctrine\DBAL\Exception - * @throws \RuntimeException - */ - public function registerDoctrineType(Type|string $class, string $name, string $type): void - { - if (! $this->isDoctrineAvailable()) { - throw new RuntimeException( - 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' - ); - } - - if (! Type::hasType($name)) { - Type::getTypeRegistry() - ->register($name, is_string($class) ? new $class() : $class); - } - - $this->doctrineTypeMappings[$name] = $type; - } - /** * Get the current PDO connection. * @@ -1722,6 +1602,16 @@ public function withTablePrefix(Grammar $grammar) return $grammar; } + /** + * Get the server version for the connection. + * + * @return string + */ + public function getServerVersion(): string + { + return $this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); + } + /** * Register a connection resolver. * diff --git a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php index 42568fc2c02d..fbbe41bdd448 100644 --- a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php +++ b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php @@ -2,169 +2,35 @@ namespace Illuminate\Database\Console; -use Doctrine\DBAL\Platforms\AbstractPlatform; use Illuminate\Console\Command; use Illuminate\Database\ConnectionInterface; use Illuminate\Database\MySqlConnection; use Illuminate\Database\PostgresConnection; -use Illuminate\Database\QueryException; use Illuminate\Database\SQLiteConnection; use Illuminate\Database\SqlServerConnection; use Illuminate\Support\Arr; -use Illuminate\Support\Composer; -use Symfony\Component\Process\Exception\ProcessSignaledException; -use Symfony\Component\Process\Exception\RuntimeException; -use Symfony\Component\Process\Process; - -use function Laravel\Prompts\confirm; abstract class DatabaseInspectionCommand extends Command { /** - * A map of database column types. - * - * @var array - */ - protected $typeMappings = [ - 'bit' => 'string', - 'citext' => 'string', - 'enum' => 'string', - 'geometry' => 'string', - 'geomcollection' => 'string', - 'linestring' => 'string', - 'ltree' => 'string', - 'multilinestring' => 'string', - 'multipoint' => 'string', - 'multipolygon' => 'string', - 'point' => 'string', - 'polygon' => 'string', - 'sysname' => 'string', - ]; - - /** - * The Composer instance. - * - * @var \Illuminate\Support\Composer - */ - protected $composer; - - /** - * Create a new command instance. - * - * @param \Illuminate\Support\Composer|null $composer - * @return void - */ - public function __construct(Composer $composer = null) - { - parent::__construct(); - - $this->composer = $composer ?? $this->laravel->make(Composer::class); - } - - /** - * Register the custom Doctrine type mappings for inspection commands. - * - * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform - * @return void - */ - protected function registerTypeMappings(AbstractPlatform $platform) - { - foreach ($this->typeMappings as $type => $value) { - $platform->registerDoctrineTypeMapping($type, $value); - } - } - - /** - * Get a human-readable platform name for the given platform. + * Get a human-readable name for the given connection. * - * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * @param \Illuminate\Database\ConnectionInterface $connection * @param string $database * @return string */ - protected function getPlatformName(AbstractPlatform $platform, $database) - { - return match (class_basename($platform)) { - 'MySQLPlatform' => 'MySQL <= 5', - 'MySQL57Platform' => 'MySQL 5.7', - 'MySQL80Platform' => 'MySQL 8', - 'PostgreSQL100Platform', 'PostgreSQLPlatform' => 'Postgres', - 'SqlitePlatform' => 'SQLite', - 'SQLServerPlatform' => 'SQL Server', - 'SQLServer2012Platform' => 'SQL Server 2012', - default => $database, - }; - } - - /** - * Get the size of a table in bytes. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return int|null - */ - protected function getTableSize(ConnectionInterface $connection, string $table) + protected function getConnectionName(ConnectionInterface $connection, $database) { return match (true) { - $connection instanceof MySqlConnection => $this->getMySQLTableSize($connection, $table), - $connection instanceof PostgresConnection => $this->getPostgresTableSize($connection, $table), - $connection instanceof SQLiteConnection => $this->getSqliteTableSize($connection, $table), - default => null, + $connection instanceof MySqlConnection && $connection->isMaria() => 'MariaDB', + $connection instanceof MySqlConnection => 'MySQL', + $connection instanceof PostgresConnection => 'PostgreSQL', + $connection instanceof SQLiteConnection => 'SQLite', + $connection instanceof SqlServerConnection => 'SQL Server', + default => $database, }; } - /** - * Get the size of a MySQL table in bytes. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return mixed - */ - protected function getMySQLTableSize(ConnectionInterface $connection, string $table) - { - $result = $connection->selectOne('SELECT (data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = ? AND table_name = ?', [ - $connection->getDatabaseName(), - $table, - ]); - - return Arr::wrap((array) $result)['size']; - } - - /** - * Get the size of a Postgres table in bytes. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return mixed - */ - protected function getPostgresTableSize(ConnectionInterface $connection, string $table) - { - $result = $connection->selectOne('SELECT pg_total_relation_size(?) AS size;', [ - $table, - ]); - - return Arr::wrap((array) $result)['size']; - } - - /** - * Get the size of a SQLite table in bytes. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return mixed - */ - protected function getSqliteTableSize(ConnectionInterface $connection, string $table) - { - try { - $result = $connection->selectOne('SELECT SUM(pgsize) AS size FROM dbstat WHERE name=?', [ - $table, - ]); - - return Arr::wrap((array) $result)['size']; - } catch (QueryException) { - return null; - } - } - /** * Get the number of open connections for a database. * @@ -175,8 +41,8 @@ protected function getConnectionCount(ConnectionInterface $connection) { $result = match (true) { $connection instanceof MySqlConnection => $connection->selectOne('show status where variable_name = "threads_connected"'), - $connection instanceof PostgresConnection => $connection->selectOne('select count(*) AS "Value" from pg_stat_activity'), - $connection instanceof SqlServerConnection => $connection->selectOne('SELECT COUNT(*) Value FROM sys.dm_exec_sessions WHERE status = ?', ['running']), + $connection instanceof PostgresConnection => $connection->selectOne('select count(*) as "Value" from pg_stat_activity'), + $connection instanceof SqlServerConnection => $connection->selectOne('select count(*) Value from sys.dm_exec_sessions where status = ?', ['running']), default => null, }; @@ -201,48 +67,18 @@ protected function getConfigFromDatabase($database) } /** - * Ensure the dependencies for the database commands are available. - * - * @return bool - */ - protected function ensureDependenciesExist() - { - return tap(interface_exists('Doctrine\DBAL\Driver'), function ($dependenciesExist) { - if (! $dependenciesExist && confirm('Inspecting database information requires the Doctrine DBAL (doctrine/dbal) package. Would you like to install it?', default: false)) { - $this->installDependencies(); - } - }); - } - - /** - * Install the command's dependencies. - * - * @return void + * Remove the table prefix from a table name, if it exists. * - * @throws \Symfony\Component\Process\Exception\ProcessSignaledException + * @param \Illuminate\Database\ConnectionInterface $connection + * @param string $table + * @return string */ - protected function installDependencies() + protected function withoutTablePrefix(ConnectionInterface $connection, string $table) { - $command = collect($this->composer->findComposer()) - ->push('require doctrine/dbal:^3.5.1') - ->implode(' '); - - $process = Process::fromShellCommandline($command, null, null, null, null); + $prefix = $connection->getTablePrefix(); - if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { - try { - $process->setTty(true); - } catch (RuntimeException $e) { - $this->components->warn($e->getMessage()); - } - } - - try { - $process->run(fn ($type, $line) => $this->output->write($line)); - } catch (ProcessSignaledException $e) { - if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) { - throw $e; - } - } + return str_starts_with($table, $prefix) + ? substr($table, strlen($prefix)) + : $table; } } diff --git a/src/Illuminate/Database/Console/MonitorCommand.php b/src/Illuminate/Database/Console/MonitorCommand.php index 3dff3158268c..d87a441c015c 100644 --- a/src/Illuminate/Database/Console/MonitorCommand.php +++ b/src/Illuminate/Database/Console/MonitorCommand.php @@ -5,7 +5,6 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Events\DatabaseBusy; -use Illuminate\Support\Composer; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'db:monitor')] @@ -46,11 +45,10 @@ class MonitorCommand extends DatabaseInspectionCommand * * @param \Illuminate\Database\ConnectionResolverInterface $connection * @param \Illuminate\Contracts\Events\Dispatcher $events - * @param \Illuminate\Support\Composer $composer */ - public function __construct(ConnectionResolverInterface $connection, Dispatcher $events, Composer $composer) + public function __construct(ConnectionResolverInterface $connection, Dispatcher $events) { - parent::__construct($composer); + parent::__construct(); $this->connection = $connection; $this->events = $events; diff --git a/src/Illuminate/Database/Console/ShowCommand.php b/src/Illuminate/Database/Console/ShowCommand.php index c125c38820e4..d711a01bc685 100644 --- a/src/Illuminate/Database/Console/ShowCommand.php +++ b/src/Illuminate/Database/Console/ShowCommand.php @@ -2,12 +2,11 @@ namespace Illuminate\Database\Console; -use Doctrine\DBAL\Schema\AbstractSchemaManager; -use Doctrine\DBAL\Schema\Table; -use Doctrine\DBAL\Schema\View; use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionResolverInterface; +use Illuminate\Database\Schema\Builder; use Illuminate\Support\Arr; +use Illuminate\Support\Number; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'db:show')] @@ -20,8 +19,9 @@ class ShowCommand extends DatabaseInspectionCommand */ protected $signature = 'db:show {--database= : The database connection} {--json : Output the database information as JSON} - {--counts : Show the table row count Note: This can be slow on large databases }; - {--views : Show the database views Note: This can be slow on large databases }'; + {--counts : Show the table row count Note: This can be slow on large databases } + {--views : Show the database views Note: This can be slow on large databases } + {--types : Show the user defined types}'; /** * The console command description. @@ -38,28 +38,26 @@ class ShowCommand extends DatabaseInspectionCommand */ public function handle(ConnectionResolverInterface $connections) { - if (! $this->ensureDependenciesExist()) { - return 1; - } - $connection = $connections->connection($database = $this->input->getOption('database')); - $doctrineConnection = $connection->getDoctrineConnection(); - $schema = $connection->getDoctrineSchemaManager(); - - $this->registerTypeMappings($doctrineConnection->getDatabasePlatform()); + $schema = $connection->getSchemaBuilder(); $data = [ 'platform' => [ 'config' => $this->getConfigFromDatabase($database), - 'name' => $this->getPlatformName($doctrineConnection->getDatabasePlatform(), $database), + 'name' => $this->getConnectionName($connection, $database), + 'version' => $connection->getServerVersion(), 'open_connections' => $this->getConnectionCount($connection), ], 'tables' => $this->tables($connection, $schema), ]; if ($this->option('views')) { - $data['views'] = $this->collectViews($connection, $schema); + $data['views'] = $this->views($connection, $schema); + } + + if ($this->option('types')) { + $data['types'] = $this->types($connection, $schema); } $this->display($data); @@ -71,17 +69,19 @@ public function handle(ConnectionResolverInterface $connections) * Get information regarding the tables within the database. * * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @param \Illuminate\Database\Schema\Builder $schema * @return \Illuminate\Support\Collection */ - protected function tables(ConnectionInterface $connection, AbstractSchemaManager $schema) + protected function tables(ConnectionInterface $connection, Builder $schema) { - return collect($schema->listTables())->map(fn (Table $table, $index) => [ - 'table' => $table->getName(), - 'size' => $this->getTableSize($connection, $table->getName()), + return collect($schema->getTables())->map(fn ($table) => [ + 'table' => $table['name'], + 'schema' => $table['schema'], + 'size' => $table['size'], 'rows' => $this->option('counts') ? $connection->table($table->getName())->count() : null, - 'engine' => rescue(fn () => $table->getOption('engine'), null, false), - 'comment' => $table->getComment(), + 'engine' => $table['engine'], + 'collation' => $table['collation'], + 'comment' => $table['comment'], ]); } @@ -89,20 +89,38 @@ protected function tables(ConnectionInterface $connection, AbstractSchemaManager * Get information regarding the views within the database. * * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @param \Illuminate\Database\Schema\Builder $schema * @return \Illuminate\Support\Collection */ - protected function collectViews(ConnectionInterface $connection, AbstractSchemaManager $schema) + protected function views(ConnectionInterface $connection, Builder $schema) { - return collect($schema->listViews()) - ->reject(fn (View $view) => str($view->getName()) - ->startsWith(['pg_catalog', 'information_schema', 'spt_'])) - ->map(fn (View $view) => [ - 'view' => $view->getName(), + return collect($schema->getViews()) + ->reject(fn ($view) => str($view['name'])->startsWith(['pg_catalog', 'information_schema', 'spt_'])) + ->map(fn ($view) => [ + 'view' => $view['name'], + 'schema' => $view['schema'], 'rows' => $connection->table($view->getName())->count(), ]); } + /** + * Get information regarding the user-defined types within the database. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\Schema\Builder $schema + * @return \Illuminate\Support\Collection + */ + protected function types(ConnectionInterface $connection, Builder $schema) + { + return collect($schema->getTypes()) + ->map(fn ($type) => [ + 'name' => $type['name'], + 'schema' => $type['schema'], + 'type' => $type['type'], + 'category' => $type['category'], + ]); + } + /** * Render the database information. * @@ -136,10 +154,11 @@ protected function displayForCli(array $data) $platform = $data['platform']; $tables = $data['tables']; $views = $data['views'] ?? null; + $types = $data['types'] ?? null; $this->newLine(); - $this->components->twoColumnDetail(''.$platform['name'].''); + $this->components->twoColumnDetail(''.$platform['name'].'', $platform['version']); $this->components->twoColumnDetail('Database', Arr::get($platform['config'], 'database')); $this->components->twoColumnDetail('Host', Arr::get($platform['config'], 'host')); $this->components->twoColumnDetail('Port', Arr::get($platform['config'], 'port')); @@ -149,22 +168,27 @@ protected function displayForCli(array $data) $this->components->twoColumnDetail('Tables', $tables->count()); if ($tableSizeSum = $tables->sum('size')) { - $this->components->twoColumnDetail('Total Size', number_format($tableSizeSum / 1024 / 1024, 2).'MiB'); + $this->components->twoColumnDetail('Total Size', Number::fileSize($tableSizeSum, 2)); } $this->newLine(); if ($tables->isNotEmpty()) { - $this->components->twoColumnDetail('Table', 'Size (MiB)'.($this->option('counts') ? ' / Rows' : '')); + $hasSchema = ! is_null($tables->first()['schema']); + + $this->components->twoColumnDetail( + ($hasSchema ? 'Schema / ' : '').'Table', + 'Size'.($this->option('counts') ? ' / Rows' : '') + ); $tables->each(function ($table) { if ($tableSize = $table['size']) { - $tableSize = number_format($tableSize / 1024 / 1024, 2); + $tableSize = Number::fileSize($tableSize, 2); } $this->components->twoColumnDetail( - $table['table'].($this->output->isVerbose() ? ' '.$table['engine'].'' : null), - ($tableSize ? $tableSize : '—').($this->option('counts') ? ' / '.number_format($table['rows']).'' : '') + ($table['schema'] ? $table['schema'].' / ' : '').$table['table'].($this->output->isVerbose() ? ' '.$table['engine'].'' : null), + ($tableSize ?: '—').($this->option('counts') ? ' / '.Number::format($table['rows']).'' : '') ); if ($this->output->isVerbose()) { @@ -180,9 +204,33 @@ protected function displayForCli(array $data) } if ($views && $views->isNotEmpty()) { - $this->components->twoColumnDetail('View', 'Rows'); + $hasSchema = ! is_null($views->first()['schema']); + + $this->components->twoColumnDetail( + ($hasSchema ? 'Schema / ' : '').'View', + 'Rows' + ); + + $views->each(fn ($view) => $this->components->twoColumnDetail( + ($view['schema'] ? $view['schema'].' / ' : '').$view['view'], + Number::format($view['rows']) + )); + + $this->newLine(); + } + + if ($types && $types->isNotEmpty()) { + $hasSchema = ! is_null($types->first()['schema']); + + $this->components->twoColumnDetail( + ($hasSchema ? 'Schema / ' : '').'Type', + 'Type / Category' + ); - $views->each(fn ($view) => $this->components->twoColumnDetail($view['view'], number_format($view['rows']))); + $types->each(fn ($type) => $this->components->twoColumnDetail( + ($type['schema'] ? $type['schema'].' / ' : '').$type['name'], + $type['type'].' / '.$type['category'] + )); $this->newLine(); } diff --git a/src/Illuminate/Database/Console/ShowModelCommand.php b/src/Illuminate/Database/Console/ShowModelCommand.php index 3ef912004e8c..4ab262546c15 100644 --- a/src/Illuminate/Database/Console/ShowModelCommand.php +++ b/src/Illuminate/Database/Console/ShowModelCommand.php @@ -3,9 +3,6 @@ namespace Illuminate\Database\Console; use BackedEnum; -use Doctrine\DBAL\Schema\Column; -use Doctrine\DBAL\Schema\Index; -use Doctrine\DBAL\Types\DecimalType; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; @@ -70,10 +67,6 @@ class ShowModelCommand extends DatabaseInspectionCommand */ public function handle() { - if (! $this->ensureDependenciesExist()) { - return 1; - } - $class = $this->qualifyModel($this->argument('model')); try { @@ -81,7 +74,9 @@ public function handle() $class = get_class($model); } catch (BindingResolutionException $e) { - return $this->components->error($e->getMessage()); + $this->components->error($e->getMessage()); + + return 1; } if ($this->option('database')) { @@ -97,6 +92,8 @@ public function handle() $this->getRelations($model), $this->getObservers($model), ); + + return 0; } /** @@ -121,25 +118,23 @@ protected function getPolicy($model) protected function getAttributes($model) { $connection = $model->getConnection(); - $schema = $connection->getDoctrineSchemaManager(); - $this->registerTypeMappings($connection->getDoctrineConnection()->getDatabasePlatform()); - $table = $model->getConnection()->getTablePrefix().$model->getTable(); - $columns = $schema->listTableColumns($table); - $indexes = $schema->listTableIndexes($table); + $schema = $connection->getSchemaBuilder(); + $table = $model->getTable(); + $columns = $schema->getColumns($table); + $indexes = $schema->getIndexes($table); return collect($columns) - ->values() - ->map(fn (Column $column) => [ - 'name' => $column->getName(), - 'type' => $this->getColumnType($column), - 'increments' => $column->getAutoincrement(), - 'nullable' => ! $column->getNotnull(), + ->map(fn ($column) => [ + 'name' => $column['name'], + 'type' => $column['type'], + 'increments' => $column['auto_increment'], + 'nullable' => $column['nullable'], 'default' => $this->getColumnDefault($column, $model), - 'unique' => $this->columnIsUnique($column->getName(), $indexes), - 'fillable' => $model->isFillable($column->getName()), - 'hidden' => $this->attributeIsHidden($column->getName(), $model), + 'unique' => $this->columnIsUnique($column['name'], $indexes), + 'fillable' => $model->isFillable($column['name']), + 'hidden' => $this->attributeIsHidden($column['name'], $model), 'appended' => null, - 'cast' => $this->getCastType($column->getName(), $model), + 'cast' => $this->getCastType($column['name'], $model), ]) ->merge($this->getVirtualAttributes($model, $columns)); } @@ -148,7 +143,7 @@ protected function getAttributes($model) * Get the virtual (non-column) attributes for the given model. * * @param \Illuminate\Database\Eloquent\Model $model - * @param \Doctrine\DBAL\Schema\Column[] $columns + * @param array $columns * @return \Illuminate\Support\Collection */ protected function getVirtualAttributes($model, $columns) @@ -170,7 +165,7 @@ protected function getVirtualAttributes($model, $columns) return []; } }) - ->reject(fn ($cast, $name) => collect($columns)->has($name)) + ->reject(fn ($cast, $name) => collect($columns)->contains('name', $name)) ->map(fn ($cast, $name) => [ 'name' => $name, 'type' => null, @@ -428,45 +423,21 @@ protected function getCastsWithDates($model) ->merge($model->getCasts()); } - /** - * Get the type of the given column. - * - * @param \Doctrine\DBAL\Schema\Column $column - * @return string - */ - protected function getColumnType($column) - { - $name = $column->getType()->getName(); - - $unsigned = $column->getUnsigned() ? ' unsigned' : ''; - - $details = match (get_class($column->getType())) { - DecimalType::class => $column->getPrecision().','.$column->getScale(), - default => $column->getLength(), - }; - - if ($details) { - return sprintf('%s(%s)%s', $name, $details, $unsigned); - } - - return sprintf('%s%s', $name, $unsigned); - } - /** * Get the default value for the given column. * - * @param \Doctrine\DBAL\Schema\Column $column + * @param array $column * @param \Illuminate\Database\Eloquent\Model $model * @return mixed|null */ protected function getColumnDefault($column, $model) { - $attributeDefault = $model->getAttributes()[$column->getName()] ?? null; + $attributeDefault = $model->getAttributes()[$column['name']] ?? null; return match (true) { $attributeDefault instanceof BackedEnum => $attributeDefault->value, $attributeDefault instanceof UnitEnum => $attributeDefault->name, - default => $attributeDefault ?? $column->getDefault(), + default => $attributeDefault ?? $column['default'], }; } @@ -494,14 +465,14 @@ protected function attributeIsHidden($attribute, $model) * Determine if the given attribute is unique. * * @param string $column - * @param \Doctrine\DBAL\Schema\Index[] $indexes + * @param array $indexes * @return bool */ protected function columnIsUnique($column, $indexes) { - return collect($indexes) - ->filter(fn (Index $index) => count($index->getColumns()) === 1 && $index->getColumns()[0] === $column) - ->contains(fn (Index $index) => $index->isUnique()); + return collect($indexes)->contains( + fn ($index) => count($index['columns']) === 1 && $index['columns'][0] === $column && $index['unique'] + ); } /** diff --git a/src/Illuminate/Database/Console/TableCommand.php b/src/Illuminate/Database/Console/TableCommand.php index 499e1708f6a6..0bd67e1fdba1 100644 --- a/src/Illuminate/Database/Console/TableCommand.php +++ b/src/Illuminate/Database/Console/TableCommand.php @@ -2,12 +2,10 @@ namespace Illuminate\Database\Console; -use Doctrine\DBAL\Schema\Column; -use Doctrine\DBAL\Schema\ForeignKeyConstraint; -use Doctrine\DBAL\Schema\Index; -use Doctrine\DBAL\Schema\Table; use Illuminate\Database\ConnectionResolverInterface; -use Illuminate\Support\Str; +use Illuminate\Database\Schema\Builder; +use Illuminate\Support\Arr; +use Illuminate\Support\Number; use Symfony\Component\Console\Attribute\AsCommand; use function Laravel\Prompts\select; @@ -39,36 +37,34 @@ class TableCommand extends DatabaseInspectionCommand */ public function handle(ConnectionResolverInterface $connections) { - if (! $this->ensureDependenciesExist()) { - return 1; - } - $connection = $connections->connection($this->input->getOption('database')); + $schema = $connection->getSchemaBuilder(); + $tables = $schema->getTables(); - $schema = $connection->getDoctrineSchemaManager(); - - $this->registerTypeMappings($connection->getDoctrineConnection()->getDatabasePlatform()); - - $table = $this->argument('table') ?: select( + $tableName = $this->argument('table') ?: select( 'Which table would you like to inspect?', - collect($schema->listTables())->flatMap(fn (Table $table) => [$table->getName()])->toArray() + array_column($tables, 'name') ); - if (! $schema->tablesExist([$table])) { - return $this->components->warn("Table [{$table}] doesn't exist."); + $table = Arr::first($tables, fn ($table) => $table['name'] === $tableName); + + if (! $table) { + $this->components->warn("Table [{$table}] doesn't exist."); + + return 1; } - $table = $schema->introspectTable($table); + $tableName = $this->withoutTablePrefix($connection, $table['name']); - $columns = $this->columns($table); - $indexes = $this->indexes($table); - $foreignKeys = $this->foreignKeys($table); + $columns = $this->columns($schema, $tableName); + $indexes = $this->indexes($schema, $tableName); + $foreignKeys = $this->foreignKeys($schema, $tableName); $data = [ 'table' => [ - 'name' => $table->getName(), - 'columns' => $columns->count(), - 'size' => $this->getTableSize($connection, $table->getName()), + 'name' => $table['name'], + 'columns' => count($columns), + 'size' => $table['size'], ], 'columns' => $columns, 'indexes' => $indexes, @@ -83,46 +79,48 @@ public function handle(ConnectionResolverInterface $connections) /** * Get the information regarding the table's columns. * - * @param \Doctrine\DBAL\Schema\Table $table + * @param \Illuminate\Database\Schema\Builder $schema + * @param string $table * @return \Illuminate\Support\Collection */ - protected function columns(Table $table) + protected function columns(Builder $schema, string $table) { - return collect($table->getColumns())->map(fn (Column $column) => [ - 'column' => $column->getName(), + return collect($schema->getColumns($table))->map(fn ($column) => [ + 'column' => $column['name'], 'attributes' => $this->getAttributesForColumn($column), - 'default' => $column->getDefault(), - 'type' => $column->getType()->getName(), + 'default' => $column['default'], + 'type' => $column['type'], ]); } /** * Get the attributes for a table column. * - * @param \Doctrine\DBAL\Schema\Column $column + * @param array $column * @return \Illuminate\Support\Collection */ - protected function getAttributesForColumn(Column $column) + protected function getAttributesForColumn($column) { return collect([ - $column->getAutoincrement() ? 'autoincrement' : null, - 'type' => $column->getType()->getName(), - $column->getUnsigned() ? 'unsigned' : null, - ! $column->getNotNull() ? 'nullable' : null, + $column['type_name'], + $column['auto_increment'] ? 'autoincrement' : null, + $column['nullable'] ? 'nullable' : null, + $column['collation'], ])->filter(); } /** * Get the information regarding the table's indexes. * - * @param \Doctrine\DBAL\Schema\Table $table + * @param \Illuminate\Database\Schema\Builder $schema + * @param string $table * @return \Illuminate\Support\Collection */ - protected function indexes(Table $table) + protected function indexes(Builder $schema, string $table) { - return collect($table->getIndexes())->map(fn (Index $index) => [ - 'name' => $index->getName(), - 'columns' => collect($index->getColumns()), + return collect($schema->getIndexes($table))->map(fn ($index) => [ + 'name' => $index['name'], + 'columns' => collect($index['columns']), 'attributes' => $this->getAttributesForIndex($index), ]); } @@ -130,34 +128,36 @@ protected function indexes(Table $table) /** * Get the attributes for a table index. * - * @param \Doctrine\DBAL\Schema\Index $index + * @param array $index * @return \Illuminate\Support\Collection */ - protected function getAttributesForIndex(Index $index) + protected function getAttributesForIndex($index) { return collect([ - 'compound' => count($index->getColumns()) > 1, - 'unique' => $index->isUnique(), - 'primary' => $index->isPrimary(), - ])->filter()->keys()->map(fn ($attribute) => Str::lower($attribute)); + $index['type'], + count($index['columns']) > 1 ? 'compound' : null, + $index['unique'] && ! $index['primary'] ? 'unique' : null, + $index['primary'] ? 'primary' : null, + ])->filter(); } /** * Get the information regarding the table's foreign keys. * - * @param \Doctrine\DBAL\Schema\Table $table + * @param \Illuminate\Database\Schema\Builder $schema + * @param string $table * @return \Illuminate\Support\Collection */ - protected function foreignKeys(Table $table) + protected function foreignKeys(Builder $schema, string $table) { - return collect($table->getForeignKeys())->map(fn (ForeignKeyConstraint $foreignKey) => [ - 'name' => $foreignKey->getName(), - 'local_table' => $table->getName(), - 'local_columns' => collect($foreignKey->getLocalColumns()), - 'foreign_table' => $foreignKey->getForeignTableName(), - 'foreign_columns' => collect($foreignKey->getForeignColumns()), - 'on_update' => Str::lower(rescue(fn () => $foreignKey->getOption('onUpdate'), 'N/A')), - 'on_delete' => Str::lower(rescue(fn () => $foreignKey->getOption('onDelete'), 'N/A')), + return collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => [ + 'name' => $foreignKey['name'], + 'columns' => collect($foreignKey['columns']), + 'foreign_schema' => $foreignKey['foreign_schema'], + 'foreign_table' => $foreignKey['foreign_table'], + 'foreign_columns' => collect($foreignKey['foreign_columns']), + 'on_update' => $foreignKey['on_update'], + 'on_delete' => $foreignKey['on_delete'], ]); } @@ -201,7 +201,7 @@ protected function displayForCli(array $data) $this->components->twoColumnDetail('Columns', $table['columns']); if ($size = $table['size']) { - $this->components->twoColumnDetail('Size', number_format($size / 1024 / 1024, 2).'MiB'); + $this->components->twoColumnDetail('Size', Number::fileSize($size, 2)); } $this->newLine(); @@ -212,7 +212,7 @@ protected function displayForCli(array $data) $columns->each(function ($column) { $this->components->twoColumnDetail( $column['column'].' '.$column['attributes']->implode(', ').'', - (! is_null($column['default']) ? ''.$column['default'].' ' : '').''.$column['type'].'' + (! is_null($column['default']) ? ''.$column['default'].' ' : '').$column['type'] ); }); @@ -237,7 +237,7 @@ protected function displayForCli(array $data) $foreignKeys->each(function ($foreignKey) { $this->components->twoColumnDetail( - $foreignKey['name'].' '.$foreignKey['local_columns']->implode(', ').' references '.$foreignKey['foreign_columns']->implode(', ').' on '.$foreignKey['foreign_table'].'', + $foreignKey['name'].' '.$foreignKey['columns']->implode(', ').' references '.$foreignKey['foreign_columns']->implode(', ').' on '.$foreignKey['foreign_table'].'', $foreignKey['on_update'].' / '.$foreignKey['on_delete'], ); }); diff --git a/src/Illuminate/Database/DBAL/TimestampType.php b/src/Illuminate/Database/DBAL/TimestampType.php deleted file mode 100644 index aee4a2a0130b..000000000000 --- a/src/Illuminate/Database/DBAL/TimestampType.php +++ /dev/null @@ -1,94 +0,0 @@ - $this->getMySqlPlatformSQLDeclaration($column), - PostgreSQLPlatform::class => $this->getPostgresPlatformSQLDeclaration($column), - SQLServerPlatform::class => $this->getSqlServerPlatformSQLDeclaration($column), - SQLitePlatform::class => 'DATETIME', - default => throw NotSupported::new('TIMESTAMP'), - }; - } - - /** - * Get the SQL declaration for MySQL. - * - * @param array $column - * @return string - */ - protected function getMySqlPlatformSQLDeclaration(array $column): string - { - $columnType = 'TIMESTAMP'; - - if ($column['precision']) { - $columnType = 'TIMESTAMP('.min((int) $column['precision'], 6).')'; - } - - $notNull = $column['notnull'] ?? false; - - if (! $notNull) { - return $columnType.' NULL'; - } - - return $columnType; - } - - /** - * Get the SQL declaration for PostgreSQL. - * - * @param array $column - * @return string - */ - protected function getPostgresPlatformSQLDeclaration(array $column): string - { - return 'TIMESTAMP('.min((int) $column['precision'], 6).')'; - } - - /** - * Get the SQL declaration for SQL Server. - * - * @param array $column - * @return string - */ - protected function getSqlServerPlatformSQLDeclaration(array $column): string - { - return $column['precision'] ?? false - ? 'DATETIME2('.min((int) $column['precision'], 7).')' - : 'DATETIME'; - } - - /** - * {@inheritdoc} - * - * @return string - */ - public function getName() - { - return 'timestamp'; - } -} diff --git a/src/Illuminate/Database/DatabaseManager.php b/src/Illuminate/Database/DatabaseManager.php index daefffee9f88..76680ff2f2d4 100755 --- a/src/Illuminate/Database/DatabaseManager.php +++ b/src/Illuminate/Database/DatabaseManager.php @@ -2,7 +2,6 @@ namespace Illuminate\Database; -use Doctrine\DBAL\Types\Type; use Illuminate\Database\Connectors\ConnectionFactory; use Illuminate\Database\Events\ConnectionEstablished; use Illuminate\Support\Arr; @@ -57,13 +56,6 @@ class DatabaseManager implements ConnectionResolverInterface */ protected $reconnector; - /** - * The custom Doctrine column types. - * - * @var array - */ - protected $doctrineTypes = []; - /** * Create a new database manager instance. * @@ -227,8 +219,6 @@ protected function configure(Connection $connection, $type) // the connection, which will allow us to reconnect from the connections. $connection->setReconnector($this->reconnector); - $this->registerConfiguredDoctrineTypes($connection); - return $connection; } @@ -267,49 +257,6 @@ protected function setPdoForType(Connection $connection, $type = null) return $connection; } - /** - * Register custom Doctrine types with the connection. - * - * @param \Illuminate\Database\Connection $connection - * @return void - */ - protected function registerConfiguredDoctrineTypes(Connection $connection): void - { - foreach ($this->app['config']->get('database.dbal.types', []) as $name => $class) { - $this->registerDoctrineType($class, $name, $name); - } - - foreach ($this->doctrineTypes as $name => [$type, $class]) { - $connection->registerDoctrineType($class, $name, $type); - } - } - - /** - * Register a custom Doctrine type. - * - * @param string $class - * @param string $name - * @param string $type - * @return void - * - * @throws \Doctrine\DBAL\Exception - * @throws \RuntimeException - */ - public function registerDoctrineType(string $class, string $name, string $type): void - { - if (! class_exists('Doctrine\DBAL\Connection')) { - throw new RuntimeException( - 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' - ); - } - - if (! Type::hasType($name)) { - Type::addType($name, $class); - } - - $this->doctrineTypes[$name] = [$type, $class]; - } - /** * Disconnect from the given database and remove from local cache. * diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index 4f6ec5247a04..e650959419f9 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -2,9 +2,7 @@ namespace Illuminate\Database\Migrations; -use Doctrine\DBAL\Schema\SchemaException; use Illuminate\Console\View\Components\BulletList; -use Illuminate\Console\View\Components\Error; use Illuminate\Console\View\Components\Info; use Illuminate\Console\View\Components\Task; use Illuminate\Console\View\Components\TwoColumnDetail; @@ -428,28 +426,19 @@ protected function runMigration($migration, $method) */ protected function pretendToRun($migration, $method) { - try { - $name = get_class($migration); - - $reflectionClass = new ReflectionClass($migration); + $name = get_class($migration); - if ($reflectionClass->isAnonymous()) { - $name = $this->getMigrationName($reflectionClass->getFileName()); - } + $reflectionClass = new ReflectionClass($migration); - $this->write(TwoColumnDetail::class, $name); + if ($reflectionClass->isAnonymous()) { + $name = $this->getMigrationName($reflectionClass->getFileName()); + } - $this->write(BulletList::class, collect($this->getQueries($migration, $method))->map(function ($query) { - return $query['query']; - })); - } catch (SchemaException) { - $name = get_class($migration); + $this->write(TwoColumnDetail::class, $name); - $this->write(Error::class, sprintf( - '[%s] failed to dump queries. This may be due to changing database columns using Doctrine, which is not supported while pretending to run migrations.', - $name, - )); - } + $this->write(BulletList::class, collect($this->getQueries($migration, $method))->map(function ($query) { + return $query['query']; + })); } /** diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index 460a4fd375c1..00d212e9481d 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -3,13 +3,13 @@ namespace Illuminate\Database; use Exception; -use Illuminate\Database\PDO\MySqlDriver; use Illuminate\Database\Query\Grammars\MySqlGrammar as QueryGrammar; use Illuminate\Database\Query\Processors\MySqlProcessor; use Illuminate\Database\Schema\Grammars\MySqlGrammar as SchemaGrammar; use Illuminate\Database\Schema\MySqlBuilder; use Illuminate\Database\Schema\MySqlSchemaState; use Illuminate\Filesystem\Filesystem; +use Illuminate\Support\Str; use PDO; class MySqlConnection extends Connection @@ -48,6 +48,18 @@ 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. * @@ -107,14 +119,4 @@ protected function getDefaultPostProcessor() { return new MySqlProcessor; } - - /** - * Get the Doctrine DBAL driver. - * - * @return \Illuminate\Database\PDO\MySqlDriver - */ - protected function getDoctrineDriver() - { - return new MySqlDriver; - } } diff --git a/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php b/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php deleted file mode 100644 index a2354182bc2c..000000000000 --- a/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php +++ /dev/null @@ -1,28 +0,0 @@ -connection = $connection; - } - - /** - * Execute an SQL statement. - * - * @param string $statement - * @return int - */ - public function exec(string $statement): int - { - try { - $result = $this->connection->exec($statement); - - \assert($result !== false); - - return $result; - } catch (PDOException $exception) { - throw Exception::new($exception); - } - } - - /** - * Prepare a new SQL statement. - * - * @param string $sql - * @return \Doctrine\DBAL\Driver\Statement - * - * @throws \Doctrine\DBAL\Driver\PDO\Exception - */ - public function prepare(string $sql): StatementInterface - { - try { - return $this->createStatement( - $this->connection->prepare($sql) - ); - } catch (PDOException $exception) { - throw Exception::new($exception); - } - } - - /** - * Execute a new query against the connection. - * - * @param string $sql - * @return \Doctrine\DBAL\Driver\Result - */ - public function query(string $sql): ResultInterface - { - try { - $stmt = $this->connection->query($sql); - - \assert($stmt instanceof PDOStatement); - - return new Result($stmt); - } catch (PDOException $exception) { - throw Exception::new($exception); - } - } - - /** - * Get the last insert ID. - * - * @param string|null $name - * @return string|int - * - * @throws \Doctrine\DBAL\Driver\PDO\Exception - */ - public function lastInsertId($name = null): string|int - { - try { - if ($name === null) { - return $this->connection->lastInsertId(); - } - - return $this->connection->lastInsertId($name); - } catch (PDOException $exception) { - throw Exception::new($exception); - } - } - - /** - * Create a new statement instance. - * - * @param \PDOStatement $stmt - * @return \Doctrine\DBAL\Driver\PDO\Statement - */ - protected function createStatement(PDOStatement $stmt): Statement - { - return new Statement($stmt); - } - - /** - * Begin a new database transaction. - * - * @return void - */ - public function beginTransaction(): void - { - $this->connection->beginTransaction(); - } - - /** - * Commit a database transaction. - * - * @return void - */ - public function commit(): void - { - $this->connection->commit(); - } - - /** - * Rollback a database transaction. - * - * @return void - */ - public function rollBack(): void - { - $this->connection->rollBack(); - } - - /** - * Wrap quotes around the given input. - * - * @param string $input - * @param string $type - * @return string - */ - public function quote($input, $type = ParameterType::STRING): string - { - return $this->connection->quote($input, $type); - } - - /** - * Get the server version for the connection. - * - * @return string - */ - public function getServerVersion(): string - { - return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); - } - - /** - * Get the native PDO connection. - * - * @return \PDO - */ - public function getNativeConnection(): PDO - { - return $this->connection; - } -} diff --git a/src/Illuminate/Database/PDO/MySqlDriver.php b/src/Illuminate/Database/PDO/MySqlDriver.php deleted file mode 100644 index 54ac37536189..000000000000 --- a/src/Illuminate/Database/PDO/MySqlDriver.php +++ /dev/null @@ -1,19 +0,0 @@ -connection = $connection; - } - - /** - * Prepare a new SQL statement. - * - * @param string $sql - * @return \Doctrine\DBAL\Driver\Statement - */ - public function prepare(string $sql): StatementInterface - { - return new Statement( - $this->connection->prepare($sql) - ); - } - - /** - * Execute a new query against the connection. - * - * @param string $sql - * @return \Doctrine\DBAL\Driver\Result - */ - public function query(string $sql): Result - { - return $this->connection->query($sql); - } - - /** - * Execute an SQL statement. - * - * @param string $statement - * @return int - */ - public function exec(string $statement): int - { - return $this->connection->exec($statement); - } - - /** - * Get the last insert ID. - * - * @param string|null $name - * @return string|int - */ - public function lastInsertId($name = null): string|int - { - if ($name === null) { - return $this->connection->lastInsertId($name); - } - - return $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?') - ->execute([$name]) - ->fetchOne(); - } - - /** - * Begin a new database transaction. - * - * @return void - */ - public function beginTransaction(): void - { - $this->connection->beginTransaction(); - } - - /** - * Commit a database transaction. - * - * @return void - */ - public function commit(): void - { - $this->connection->commit(); - } - - /** - * Rollback a database transaction. - * - * @return void - */ - public function rollBack(): void - { - $this->connection->rollBack(); - } - - /** - * Wrap quotes around the given input. - * - * @param string $value - * @param int $type - * @return string - */ - public function quote($value, $type = ParameterType::STRING): string - { - $val = $this->connection->quote($value, $type); - - // Fix for a driver version terminating all values with null byte... - if (\is_string($val) && str_contains($val, "\0")) { - $val = \substr($val, 0, -1); - } - - return $val; - } - - /** - * Get the server version for the connection. - * - * @return string - */ - public function getServerVersion(): string - { - return $this->connection->getServerVersion(); - } - - /** - * Get the native PDO connection. - * - * @return \PDO - */ - public function getNativeConnection(): PDO - { - return $this->connection->getWrappedConnection(); - } -} diff --git a/src/Illuminate/Database/PDO/SqlServerDriver.php b/src/Illuminate/Database/PDO/SqlServerDriver.php deleted file mode 100644 index ac7b8a1aedef..000000000000 --- a/src/Illuminate/Database/PDO/SqlServerDriver.php +++ /dev/null @@ -1,30 +0,0 @@ -type); + $collation = preg_match( + '/\b'.preg_quote($result->name).'\b[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:default|check|as)\s*(?:\(.*?\))?[^,]*)*collate\s+["\'`]?(\w+)/i', + $sql, + $matches + ) === 1 ? strtolower($matches[1]) : null; + return [ 'name' => $result->name, 'type_name' => strtok($type, '(') ?: '', 'type' => $type, - 'collation' => null, + 'collation' => $collation, 'nullable' => (bool) $result->nullable, 'default' => $result->default, 'auto_increment' => $hasPrimaryKey && $result->primary && $type === 'integer', diff --git a/src/Illuminate/Database/SQLiteConnection.php b/src/Illuminate/Database/SQLiteConnection.php index ad7c1486d2d2..5d83c78cbbd2 100755 --- a/src/Illuminate/Database/SQLiteConnection.php +++ b/src/Illuminate/Database/SQLiteConnection.php @@ -3,7 +3,6 @@ namespace Illuminate\Database; use Exception; -use Illuminate\Database\PDO\SQLiteDriver; use Illuminate\Database\Query\Grammars\SQLiteGrammar as QueryGrammar; use Illuminate\Database\Query\Processors\SQLiteProcessor; use Illuminate\Database\Schema\Grammars\SQLiteGrammar as SchemaGrammar; @@ -122,16 +121,6 @@ protected function getDefaultPostProcessor() return new SQLiteProcessor; } - /** - * Get the Doctrine DBAL driver. - * - * @return \Illuminate\Database\PDO\SQLiteDriver - */ - protected function getDoctrineDriver() - { - return new SQLiteDriver; - } - /** * Get the database connection foreign key constraints configuration option. * diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index c8a534cafec9..98590946a93f 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -2,13 +2,11 @@ namespace Illuminate\Database\Schema; -use BadMethodCallException; use Closure; use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Concerns\HasUlids; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Grammars\Grammar; -use Illuminate\Database\SQLiteConnection; use Illuminate\Support\Fluent; use Illuminate\Support\Traits\Macroable; @@ -156,20 +154,7 @@ public function toSql(Connection $connection, Grammar $grammar) */ protected function ensureCommandsAreValid(Connection $connection) { - if ($connection instanceof SQLiteConnection) { - if ($this->commandsNamed(['dropColumn', 'renameColumn'])->count() > 1 - && ! $connection->usingNativeSchemaOperations()) { - throw new BadMethodCallException( - "SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification." - ); - } - - if ($this->commandsNamed(['dropForeign'])->count() > 0) { - throw new BadMethodCallException( - "SQLite doesn't support dropping foreign keys (you would need to re-create the table)." - ); - } - } + // } /** @@ -259,10 +244,6 @@ protected function addFluentIndexes() public function addFluentCommands(Connection $connection, Grammar $grammar) { foreach ($this->columns as $column) { - if ($column->change && ! $connection->usingNativeSchemaOperations()) { - continue; - } - foreach ($grammar->getFluentCommands() as $commandName) { $this->addCommand($commandName, compact('column')); } diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index c1a55cc49b50..8f1fab2901e8 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -48,13 +48,6 @@ class Builder */ public static $defaultMorphKeyType = 'int'; - /** - * Indicates whether Doctrine DBAL usage will be prevented if possible when dropping, renaming, and modifying columns. - * - * @var bool - */ - public static $alwaysUsesNativeSchemaOperationsIfPossible = false; - /** * Create a new database Schema manager. * @@ -115,17 +108,6 @@ public static function morphUsingUlids() return static::defaultMorphKeyType('ulid'); } - /** - * Attempt to use native schema operations for dropping, renaming, and modifying columns, even if Doctrine DBAL is installed. - * - * @param bool $value - * @return void - */ - public static function useNativeSchemaOperationsIfPossible(bool $value = true) - { - static::$alwaysUsesNativeSchemaOperationsIfPossible = $value; - } - /** * Create a database in the schema. * @@ -312,14 +294,6 @@ public function whenTableDoesntHaveColumn(string $table, string $column, Closure */ public function getColumnType($table, $column, $fullDefinition = false) { - if (! $this->connection->usingNativeSchemaOperations()) { - $table = $this->connection->getTablePrefix().$table; - - $type = $this->connection->getDoctrineColumn($table, $column)->getType(); - - return $type::lookupName($type); - } - $columns = $this->getColumns($table); foreach ($columns as $value) { diff --git a/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php b/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php deleted file mode 100644 index 7f429c0eccc5..000000000000 --- a/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php +++ /dev/null @@ -1,238 +0,0 @@ -isDoctrineAvailable()) { - throw new RuntimeException(sprintf( - 'Changing columns for table "%s" requires Doctrine DBAL. Please install the doctrine/dbal package.', - $blueprint->getTable() - )); - } - - $schema = $connection->getDoctrineSchemaManager(); - $databasePlatform = $connection->getDoctrineConnection()->getDatabasePlatform(); - $databasePlatform->registerDoctrineTypeMapping('enum', 'string'); - - $tableDiff = static::getChangedDiff( - $grammar, $blueprint, $schema - ); - - if (! $tableDiff->isEmpty()) { - return (array) $databasePlatform->getAlterTableSQL($tableDiff); - } - - return []; - } - - /** - * Get the Doctrine table difference for the given changes. - * - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema - * @return \Doctrine\DBAL\Schema\TableDiff - */ - protected static function getChangedDiff($grammar, Blueprint $blueprint, SchemaManager $schema) - { - $current = $schema->introspectTable($grammar->getTablePrefix().$blueprint->getTable()); - - return $schema->createComparator()->compareTables( - $current, static::getTableWithColumnChanges($blueprint, $current) - ); - } - - /** - * Get a copy of the given Doctrine table after making the column changes. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Doctrine\DBAL\Schema\Table $table - * @return \Doctrine\DBAL\Schema\Table - */ - protected static function getTableWithColumnChanges(Blueprint $blueprint, Table $table) - { - $table = clone $table; - - foreach ($blueprint->getChangedColumns() as $fluent) { - $column = static::getDoctrineColumn($table, $fluent); - - // Here we will spin through each fluent column definition and map it to the proper - // Doctrine column definitions - which is necessary because Laravel and Doctrine - // use some different terminology for various column attributes on the tables. - foreach ($fluent->getAttributes() as $key => $value) { - if (! is_null($option = static::mapFluentOptionToDoctrine($key))) { - if (method_exists($column, $method = 'set'.ucfirst($option))) { - $column->{$method}(static::mapFluentValueToDoctrine($option, $value)); - continue; - } - - $column->setPlatformOption($option, static::mapFluentValueToDoctrine($option, $value)); - } - } - } - - return $table; - } - - /** - * Get the Doctrine column instance for a column change. - * - * @param \Doctrine\DBAL\Schema\Table $table - * @param \Illuminate\Support\Fluent $fluent - * @return \Doctrine\DBAL\Schema\Column - */ - protected static function getDoctrineColumn(Table $table, Fluent $fluent) - { - return $table->modifyColumn( - $fluent['name'], static::getDoctrineColumnChangeOptions($fluent) - )->getColumn($fluent['name']); - } - - /** - * Get the Doctrine column change options. - * - * @param \Illuminate\Support\Fluent $fluent - * @return array - */ - protected static function getDoctrineColumnChangeOptions(Fluent $fluent) - { - $options = ['Type' => static::getDoctrineColumnType($fluent['type'])]; - - if (! in_array($fluent['type'], ['smallint', 'integer', 'bigint'])) { - $options['Autoincrement'] = false; - } - - if (in_array($fluent['type'], ['tinyText', 'text', 'mediumText', 'longText'])) { - $options['Length'] = static::calculateDoctrineTextLength($fluent['type']); - } - - if ($fluent['type'] === 'char') { - $options['Fixed'] = true; - } - - if (static::doesntNeedCharacterOptions($fluent['type'])) { - $options['PlatformOptions'] = [ - 'collation' => '', - 'charset' => '', - ]; - } - - return $options; - } - - /** - * Get the doctrine column type. - * - * @param string $type - * @return \Doctrine\DBAL\Types\Type - */ - protected static function getDoctrineColumnType($type) - { - $type = strtolower($type); - - return Type::getType(match ($type) { - 'biginteger' => 'bigint', - 'smallinteger' => 'smallint', - 'tinytext', 'mediumtext', 'longtext' => 'text', - 'binary' => 'blob', - 'uuid' => 'guid', - 'char' => 'string', - 'double' => 'float', - default => $type, - }); - } - - /** - * Calculate the proper column length to force the Doctrine text type. - * - * @param string $type - * @return int - */ - protected static function calculateDoctrineTextLength($type) - { - return match ($type) { - 'tinyText' => 1, - 'mediumText' => 65535 + 1, - 'longText' => 16777215 + 1, - default => 255 + 1, - }; - } - - /** - * Determine if the given type does not need character / collation options. - * - * @param string $type - * @return bool - */ - protected static function doesntNeedCharacterOptions($type) - { - return in_array($type, [ - 'bigInteger', - 'binary', - 'boolean', - 'date', - 'dateTime', - 'decimal', - 'double', - 'float', - 'integer', - 'json', - 'mediumInteger', - 'smallInteger', - 'time', - 'timestamp', - 'tinyInteger', - ]); - } - - /** - * Get the matching Doctrine option for a given Fluent attribute name. - * - * @param string $attribute - * @return string|null - */ - protected static function mapFluentOptionToDoctrine($attribute) - { - return match ($attribute) { - 'type', 'name' => null, - 'nullable' => 'notnull', - 'total' => 'precision', - 'places' => 'scale', - default => $attribute, - }; - } - - /** - * Get the matching Doctrine value for a given Fluent attribute. - * - * @param string $option - * @param mixed $value - * @return mixed - */ - protected static function mapFluentValueToDoctrine($option, $value) - { - return $option === 'notnull' ? ! $value : $value; - } -} diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index 9933a38be0fc..2d2de55bea69 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -3,8 +3,6 @@ namespace Illuminate\Database\Schema\Grammars; use BackedEnum; -use Doctrine\DBAL\Schema\AbstractSchemaManager as SchemaManager; -use Doctrine\DBAL\Schema\TableDiff; use Illuminate\Contracts\Database\Query\Expression; use Illuminate\Database\Concerns\CompilesJsonPaths; use Illuminate\Database\Connection; @@ -76,7 +74,11 @@ public function compileDropDatabaseIfExists($name) */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - return RenameColumn::compile($this, $blueprint, $command, $connection); + return sprintf('alter table %s rename column %s to %s', + $this->wrapTable($blueprint), + $this->wrap($command->from), + $this->wrap($command->to) + ); } /** @@ -91,7 +93,7 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - return ChangeColumn::compile($this, $blueprint, $command, $connection); + throw new LogicException('This database driver does not support modifying columns.'); } /** @@ -162,6 +164,18 @@ public function compileForeign(Blueprint $blueprint, Fluent $command) return $sql; } + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + throw new RuntimeException('This database driver does not support dropping foreign keys.'); + } + /** * Compile the blueprint's added column definitions. * @@ -319,22 +333,6 @@ protected function getDefaultValue($value) : "'".(string) $value."'"; } - /** - * Create an empty Doctrine DBAL TableDiff from the Blueprint. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema - * @return \Doctrine\DBAL\Schema\TableDiff - */ - public function getDoctrineTableDiff(Blueprint $blueprint, SchemaManager $schema) - { - $tableName = $this->getTablePrefix().$blueprint->getTable(); - - $table = $schema->introspectTable($tableName); - - return $schema->createComparator()->compareTables(oldTable: $table, newTable: $table); - } - /** * Get the fluent commands for the grammar. * diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 103901b6293f..07bd41291414 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -5,6 +5,7 @@ use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\ColumnDefinition; use Illuminate\Support\Fluent; use RuntimeException; @@ -356,13 +357,39 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - return $connection->usingNativeSchemaOperations() - ? sprintf('alter table %s rename column %s to %s', + $version = $connection->getServerVersion(); + + if (($connection->isMaria() && version_compare($version, '10.5.2', '<')) || + (! $connection->isMaria() && version_compare($version, '8.0.3', '<'))) { + $column = collect($connection->getSchemaBuilder()->getColumns($blueprint->getTable())) + ->firstWhere('name', $command->from); + + $modifiers = $this->addModifiers($column['type'], $blueprint, new ColumnDefinition([ + 'change' => true, + 'type' => match ($column['type_name']) { + 'bigint' => 'bigInteger', + 'int' => 'integer', + 'mediumint' => 'mediumInteger', + 'smallint' => 'smallInteger', + 'tinyint' => 'tinyInteger', + default => $column['type_name'], + }, + 'nullable' => $column['nullable'], + 'default' => $column['default'], + 'autoIncrement' => $column['auto_increment'], + 'collation' => $column['collation'], + 'comment' => $column['comment'], + ])); + + return sprintf('alter table %s change %s %s %s', $this->wrapTable($blueprint), $this->wrap($command->from), - $this->wrap($command->to) - ) - : parent::compileRenameColumn($blueprint, $command, $connection); + $this->wrap($command->to), + $modifiers + ); + } + + return parent::compileRenameColumn($blueprint, $command, $connection); } /** @@ -377,10 +404,6 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - if (! $connection->usingNativeSchemaOperations()) { - return parent::compileChange($blueprint, $command, $connection); - } - $columns = []; foreach ($blueprint->getChangedColumns() as $column) { diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 56a0bc510f88..e6b66bc0d01d 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -284,25 +284,6 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent } } - /** - * Compile a rename column command. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - */ - public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) - { - return $connection->usingNativeSchemaOperations() - ? sprintf('alter table %s rename column %s to %s', - $this->wrapTable($blueprint), - $this->wrap($command->from), - $this->wrap($command->to) - ) - : parent::compileRenameColumn($blueprint, $command, $connection); - } - /** * Compile a change column command into a series of SQL statements. * @@ -315,10 +296,6 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - if (! $connection->usingNativeSchemaOperations()) { - return parent::compileChange($blueprint, $command, $connection); - } - $columns = []; foreach ($blueprint->getChangedColumns() as $column) { diff --git a/src/Illuminate/Database/Schema/Grammars/RenameColumn.php b/src/Illuminate/Database/Schema/Grammars/RenameColumn.php deleted file mode 100644 index ff611c93160a..000000000000 --- a/src/Illuminate/Database/Schema/Grammars/RenameColumn.php +++ /dev/null @@ -1,93 +0,0 @@ -getDoctrineSchemaManager(); - $databasePlatform = $connection->getDoctrineConnection()->getDatabasePlatform(); - $databasePlatform->registerDoctrineTypeMapping('enum', 'string'); - - $column = $connection->getDoctrineColumn( - $grammar->getTablePrefix().$blueprint->getTable(), $command->from - ); - - return (array) $databasePlatform->getAlterTableSQL(static::getRenamedDiff( - $grammar, $blueprint, $command, $column, $schema - )); - } - - /** - * Get a new column instance with the new column name. - * - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Doctrine\DBAL\Schema\Column $column - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema - * @return \Doctrine\DBAL\Schema\TableDiff - */ - protected static function getRenamedDiff(Grammar $grammar, Blueprint $blueprint, Fluent $command, Column $column, SchemaManager $schema) - { - return static::setRenamedColumns( - $grammar->getDoctrineTableDiff($blueprint, $schema), $command, $column - ); - } - - /** - * Set the renamed columns on the table diff. - * - * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff - * @param \Illuminate\Support\Fluent $command - * @param \Doctrine\DBAL\Schema\Column $column - * @return \Doctrine\DBAL\Schema\TableDiff - */ - protected static function setRenamedColumns(TableDiff $tableDiff, Fluent $command, Column $column) - { - return new TableDiff( - $tableDiff->getOldTable(), - $tableDiff->getAddedColumns(), - $tableDiff->getModifiedColumns(), - $tableDiff->getDroppedColumns(), - [$command->from => new Column($command->to, $column->getType(), self::getWritableColumnOptions($column))], - $tableDiff->getAddedIndexes(), - $tableDiff->getModifiedIndexes(), - $tableDiff->getDroppedIndexes(), - $tableDiff->getRenamedIndexes(), - $tableDiff->getAddedForeignKeys(), - $tableDiff->getModifiedColumns(), - $tableDiff->getDroppedForeignKeys(), - ); - } - - /** - * Get the writable column options. - * - * @param \Doctrine\DBAL\Schema\Column $column - * @return array - */ - private static function getWritableColumnOptions(Column $column) - { - return array_filter($column->toArray(), function (string $name) use ($column) { - return method_exists($column, 'set'.$name); - }, ARRAY_FILTER_USE_KEY); - } -} diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php old mode 100755 new mode 100644 index 02766df8e84f..fcbaf0798685 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -2,11 +2,12 @@ namespace Illuminate\Database\Schema\Grammars; -use Doctrine\DBAL\Schema\Index; -use Doctrine\DBAL\Schema\TableDiff; use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\ColumnDefinition; +use Illuminate\Database\Schema\ForeignKeyDefinition; +use Illuminate\Database\Schema\IndexDefinition; use Illuminate\Support\Arr; use Illuminate\Support\Fluent; use RuntimeException; @@ -18,7 +19,7 @@ class SQLiteGrammar extends Grammar * * @var string[] */ - protected $modifiers = ['Increment', 'Nullable', 'Default', 'VirtualAs', 'StoredAs']; + protected $modifiers = ['Increment', 'Nullable', 'Default', 'Collate', 'VirtualAs', 'StoredAs']; /** * The columns available as serials. @@ -39,6 +40,21 @@ public function compileTableExists() return "select * from sqlite_master where type = 'table' and name = ?"; } + /** + * Compile the query to determine the SQL text that describes the given object. + * + * @param string $name + * @param string $type + * @return string + */ + public function compileSqlCreateStatement($name, $type = 'table') + { + return sprintf('select "sql" from sqlite_master where type = %s and name = %s', + $this->wrap($type), + $this->wrap(str_replace('.', '__', $name)) + ); + } + /** * Compile the query to determine if the dbstat table is available. * @@ -123,7 +139,7 @@ public function compileColumns($table) { return sprintf( 'select name, type, not "notnull" as "nullable", dflt_value as "default", pk as "primary" ' - .'from pragma_table_info(%s) order by cid asc', + .'from pragma_table_xinfo(%s) order by cid asc', $this->wrap(str_replace('.', '__', $table)) ); } @@ -177,39 +193,24 @@ public function compileCreate(Blueprint $blueprint, Fluent $command) $blueprint->temporary ? 'create temporary' : 'create', $this->wrapTable($blueprint), implode(', ', $this->getColumns($blueprint)), - (string) $this->addForeignKeys($blueprint), - (string) $this->addPrimaryKeys($blueprint) + $this->addForeignKeys($this->getCommandsByName($blueprint, 'foreign')), + $this->addPrimaryKeys($this->getCommandByName($blueprint, 'primary')) ); } /** * Get the foreign key syntax for a table creation statement. * - * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Database\Schema\ForeignKeyDefinition[] $foreignKeys * @return string|null */ - protected function addForeignKeys(Blueprint $blueprint) + protected function addForeignKeys($foreignKeys) { - $foreigns = $this->getCommandsByName($blueprint, 'foreign'); - - return collect($foreigns)->reduce(function ($sql, $foreign) { + return collect($foreignKeys)->reduce(function ($sql, $foreign) { // Once we have all the foreign key commands for the table creation statement // we'll loop through each of them and add them to the create table SQL we // are building, since SQLite needs foreign keys on the tables creation. - $sql .= $this->getForeignKey($foreign); - - if (! is_null($foreign->onDelete)) { - $sql .= " on delete {$foreign->onDelete}"; - } - - // If this foreign key specifies the action to be taken on update we will add - // that to the statement here. We'll append it to this SQL and then return - // the SQL so we can keep adding any other foreign constraints onto this. - if (! is_null($foreign->onUpdate)) { - $sql .= " on update {$foreign->onUpdate}"; - } - - return $sql; + return $sql.$this->getForeignKey($foreign); }, ''); } @@ -224,22 +225,35 @@ protected function getForeignKey($foreign) // We need to columnize the columns that the foreign key is being defined for // so that it is a properly formatted list. Once we have done this, we can // return the foreign key SQL declaration to the calling method for use. - return sprintf(', foreign key(%s) references %s(%s)', + $sql = sprintf(', foreign key(%s) references %s(%s)', $this->columnize($foreign->columns), $this->wrapTable($foreign->on), $this->columnize((array) $foreign->references) ); + + if (! is_null($foreign->onDelete)) { + $sql .= " on delete {$foreign->onDelete}"; + } + + // If this foreign key specifies the action to be taken on update we will add + // that to the statement here. We'll append it to this SQL and then return + // this SQL so we can keep adding any other foreign constraints to this. + if (! is_null($foreign->onUpdate)) { + $sql .= " on update {$foreign->onUpdate}"; + } + + return $sql; } /** * Get the primary key syntax for a table creation statement. * - * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent|null $primary * @return string|null */ - protected function addPrimaryKeys(Blueprint $blueprint) + protected function addPrimaryKeys($primary) { - if (! is_null($primary = $this->getCommandByName($blueprint, 'primary'))) { + if (! is_null($primary)) { return ", primary key ({$this->columnize($primary->columns)})"; } } @@ -263,22 +277,93 @@ public function compileAdd(Blueprint $blueprint, Fluent $command) } /** - * Compile a rename column command. + * Compile a change column command into a series of SQL statements. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string + * + * @throws \RuntimeException */ - public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) - { - return $connection->usingNativeSchemaOperations() - ? sprintf('alter table %s rename column %s to %s', - $this->wrapTable($blueprint), - $this->wrap($command->from), - $this->wrap($command->to) - ) - : parent::compileRenameColumn($blueprint, $command, $connection); + public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + { + $schema = $connection->getSchemaBuilder(); + $table = $blueprint->getTable(); + + $changedColumns = collect($blueprint->getChangedColumns()); + $columnNames = []; + $autoIncrementColumn = null; + + $columns = collect($schema->getColumns($table)) + ->map(function ($column) use ($blueprint, $changedColumns, &$columnNames, &$autoIncrementColumn) { + $column = $changedColumns->first(fn ($col) => $col->name === $column['name'], $column); + + if ($column instanceof Fluent) { + $name = $this->wrap($column); + $columnNames[] = $name; + $autoIncrementColumn = $column->autoIncrement ? $column->name : $autoIncrementColumn; + + return $this->addModifiers($name.' '.$this->getType($column), $blueprint, $column); + } else { + $name = $this->wrap($column['name']); + $columnNames[] = $name; + $autoIncrementColumn = $column['auto_increment'] ? $column['name'] : $autoIncrementColumn; + + return $this->addModifiers($name.' '.$column['type'], $blueprint, + new ColumnDefinition([ + 'change' => true, + 'type' => $column['type_name'], + 'nullable' => $column['nullable'], + 'default' => $column['default'], + 'autoIncrement' => $column['auto_increment'], + 'collation' => $column['collation'], + 'comment' => $column['comment'], + ]) + ); + } + })->all(); + + $foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ + 'columns' => $foreignKey['columns'], + 'on' => $foreignKey['foreign_table'], + 'references' => $foreignKey['foreign_columns'], + 'onUpdate' => $foreignKey['on_update'], + 'onDelete' => $foreignKey['on_delete'], + ]))->all(); + + [$primary, $indexes] = collect($schema->getIndexes($table))->map(fn ($index) => new IndexDefinition([ + 'name' => match (true) { + $index['primary'] => 'primary', + $index['unique'] => 'unique', + default => 'index', + }, + 'index' => $index['name'], + 'columns' => $index['columns'], + ]))->partition(fn ($index) => $index->name === 'primary'); + + $indexes = collect($indexes)->reject(fn ($index) => str_starts_with('sqlite_', $index->index))->map( + fn ($index) => $this->{'compile'.ucfirst($index->name)}($blueprint, $index) + )->all(); + + $tempTable = $this->wrap('__temp__'.$this->getTablePrefix().$table); + $table = $this->wrap($this->getTablePrefix().$table); + $columnNames = implode(', ', $columnNames); + + $foreignKeyConstraintsEnabled = $connection->scalar('pragma foreign_keys'); + + return array_filter(array_merge([ + $foreignKeyConstraintsEnabled ? $this->compileDisableForeignKeyConstraints() : null, + sprintf('create table %s (%s%s%s)', + $tempTable, + implode(', ', $columns), + $this->addForeignKeys($foreignKeys), + $autoIncrementColumn ? '' : $this->addPrimaryKeys($primary->first()) + ), + sprintf('insert into %s (%s) select %s from %s', $tempTable, $columnNames, $columnNames, $table), + sprintf('drop table %s', $table), + sprintf('alter table %s rename to %s', $tempTable, $table), + ], $indexes, [$foreignKeyConstraintsEnabled ? $this->compileEnableForeignKeyConstraints() : null])); } /** @@ -403,45 +488,11 @@ public function compileRebuild() */ public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - if ($connection->usingNativeSchemaOperations()) { - $table = $this->wrapTable($blueprint); - - $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); + $table = $this->wrapTable($blueprint); - return collect($columns)->map(fn ($column) => 'alter table '.$table.' '.$column - )->all(); - } else { - $tableDiff = $this->getDoctrineTableDiff( - $blueprint, $schema = $connection->getDoctrineSchemaManager() - ); - - $droppedColumns = []; - - foreach ($command->columns as $name) { - $droppedColumns[$name] = $connection->getDoctrineColumn( - $this->getTablePrefix().$blueprint->getTable(), $name - ); - } + $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); - $platform = $connection->getDoctrineConnection()->getDatabasePlatform(); - - return (array) $platform->getAlterTableSQL( - new TableDiff( - $tableDiff->getOldTable(), - $tableDiff->getAddedColumns(), - $tableDiff->getModifiedColumns(), - $droppedColumns, - $tableDiff->getRenamedColumns(), - $tableDiff->getAddedIndexes(), - $tableDiff->getModifiedIndexes(), - $tableDiff->getDroppedIndexes(), - $tableDiff->getRenamedIndexes(), - $tableDiff->getAddedForeignKeys(), - $tableDiff->getModifiedColumns(), - $tableDiff->getDroppedForeignKeys(), - ) - ); - } + return collect($columns)->map(fn ($column) => 'alter table '.$table.' '.$column)->all(); } /** @@ -512,26 +563,32 @@ public function compileRename(Blueprint $blueprint, Fluent $command) */ public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connection $connection) { - $schemaManager = $connection->getDoctrineSchemaManager(); - - $indexes = $schemaManager->listTableIndexes($this->getTablePrefix().$blueprint->getTable()); + $indexes = $connection->getSchemaBuilder()->getIndexes($blueprint->getTable()); - $index = Arr::get($indexes, $command->from); + $index = Arr::first($indexes, fn ($index) => $index['name'] === $command->from); if (! $index) { throw new RuntimeException("Index [{$command->from}] does not exist."); } - $newIndex = new Index( - $command->to, $index->getColumns(), $index->isUnique(), - $index->isPrimary(), $index->getFlags(), $index->getOptions() - ); + if ($index['primary']) { + throw new RuntimeException('SQLite does not support altering primary keys.'); + } - $platform = $connection->getDoctrineConnection()->getDatabasePlatform(); + if ($index['unique']) { + return [ + $this->compileDropUnique($blueprint, new IndexDefinition(['index' => $index['name']])), + $this->compileUnique($blueprint, + new IndexDefinition(['index' => $command->to, 'columns' => $index['columns']]) + ), + ]; + } return [ - $platform->getDropIndexSQL($command->from, $this->getTablePrefix().$blueprint->getTable()), - $platform->getCreateIndexSQL($newIndex, $this->getTablePrefix().$blueprint->getTable()), + $this->compileDropIndex($blueprint, new IndexDefinition(['index' => $index['name']])), + $this->compileIndex($blueprint, + new IndexDefinition(['index' => $command->to, 'columns' => $index['columns']]) + ), ]; } @@ -1111,6 +1168,20 @@ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) } } + /** + * Get the SQL for a collation column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyCollate(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->collation)) { + return " collate '{$column->collation}'"; + } + } + /** * Wrap the given JSON selector. * diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index d97ff4ce7847..72cb8c2ef975 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -258,12 +258,10 @@ public function compileAdd(Blueprint $blueprint, Fluent $command) */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - return $connection->usingNativeSchemaOperations() - ? sprintf("sp_rename '%s', %s, 'COLUMN'", - $this->wrap($blueprint->getTable().'.'.$command->from), - $this->wrap($command->to) - ) - : parent::compileRenameColumn($blueprint, $command, $connection); + return sprintf("sp_rename '%s', %s, 'COLUMN'", + $this->wrap($blueprint->getTable().'.'.$command->from), + $this->wrap($command->to) + ); } /** @@ -278,10 +276,6 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - if (! $connection->usingNativeSchemaOperations()) { - return parent::compileChange($blueprint, $command, $connection); - } - $changes = [$this->compileDropDefaultConstraint($blueprint, $command)]; foreach ($blueprint->getChangedColumns() as $column) { diff --git a/src/Illuminate/Database/Schema/SQLiteBuilder.php b/src/Illuminate/Database/Schema/SQLiteBuilder.php index 8ae272d767b6..f1a89a1e9478 100644 --- a/src/Illuminate/Database/Schema/SQLiteBuilder.php +++ b/src/Illuminate/Database/Schema/SQLiteBuilder.php @@ -51,6 +51,22 @@ public function getTables() ); } + /** + * Get the columns for a given table. + * + * @param string $table + * @return array + */ + public function getColumns($table) + { + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processColumns( + $this->connection->selectFromWriteConnection($this->grammar->compileColumns($table)), + $this->connection->scalar($this->grammar->compileSqlCreateStatement($table)) + ); + } + /** * Get all of the table names for the database. * diff --git a/src/Illuminate/Database/SqlServerConnection.php b/src/Illuminate/Database/SqlServerConnection.php index e376e6fa6c38..f977df57cc68 100755 --- a/src/Illuminate/Database/SqlServerConnection.php +++ b/src/Illuminate/Database/SqlServerConnection.php @@ -4,7 +4,6 @@ use Closure; use Exception; -use Illuminate\Database\PDO\SqlServerDriver; use Illuminate\Database\Query\Grammars\SqlServerGrammar as QueryGrammar; use Illuminate\Database\Query\Processors\SqlServerProcessor; use Illuminate\Database\Schema\Grammars\SqlServerGrammar as SchemaGrammar; @@ -139,14 +138,4 @@ protected function getDefaultPostProcessor() { return new SqlServerProcessor; } - - /** - * Get the Doctrine DBAL driver. - * - * @return \Illuminate\Database\PDO\SqlServerDriver - */ - protected function getDoctrineDriver() - { - return new SqlServerDriver; - } } diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index a4fbaf38280e..3a79f754e44e 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -34,13 +34,8 @@ "dev-master": "11.x-dev" } }, - "conflict": { - "carbonphp/carbon-doctrine-types": "<3.0.0|>=4.0", - "doctrine/dbal": "<4.0.0|>=5.0" - }, "suggest": { "ext-filter": "Required to use the Postgres database driver.", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^4.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.21).", "illuminate/console": "Required to use the database commands (^11.0).", "illuminate/events": "Required to use the observers with Eloquent (^11.0).", diff --git a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php index 6eb86d9b47a7..9a4d0341740b 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php @@ -83,7 +83,7 @@ protected function truncateTablesForConnection(ConnectionInterface $connection, $connection->unsetEventDispatcher(); - collect(static::$allTables[$name] ??= $connection->getDoctrineSchemaManager()->listTableNames()) + collect(static::$allTables[$name] ??= array_column($connection->getSchemaBuilder()->getTables(), 'name')) ->when( property_exists($this, 'tablesToTruncate'), fn ($tables) => $tables->intersect($this->tablesToTruncate), diff --git a/src/Illuminate/Support/Facades/DB.php b/src/Illuminate/Support/Facades/DB.php old mode 100755 new mode 100644 index e13bd509c9fb..f29f576e7731 --- a/src/Illuminate/Support/Facades/DB.php +++ b/src/Illuminate/Support/Facades/DB.php @@ -5,7 +5,6 @@ /** * @method static \Illuminate\Database\Connection connection(string|null $name = null) * @method static \Illuminate\Database\ConnectionInterface connectUsing(string $name, array $config, bool $force = false) - * @method static void registerDoctrineType(string $class, string $name, string $type) * @method static void purge(string|null $name = null) * @method static void disconnect(string|null $name = null) * @method static \Illuminate\Database\Connection reconnect(string|null $name = null) @@ -61,11 +60,6 @@ * @method static \Illuminate\Database\Connection setRecordModificationState(bool $value) * @method static void forgetRecordModificationState() * @method static \Illuminate\Database\Connection useWriteConnectionWhenReading(bool $value = true) - * @method static bool isDoctrineAvailable() - * @method static bool usingNativeSchemaOperations() - * @method static \Doctrine\DBAL\Schema\Column getDoctrineColumn(string $table, string $column) - * @method static \Doctrine\DBAL\Schema\AbstractSchemaManager getDoctrineSchemaManager() - * @method static \Doctrine\DBAL\Connection getDoctrineConnection() * @method static \PDO getPdo() * @method static \PDO|\Closure|null getRawPdo() * @method static \PDO getReadPdo() @@ -100,6 +94,7 @@ * @method static string getTablePrefix() * @method static \Illuminate\Database\Connection setTablePrefix(string $prefix) * @method static \Illuminate\Database\Grammar withTablePrefix(\Illuminate\Database\Grammar $grammar) + * @method static string getServerVersion() * @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/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 8aa0eb900a55..c4adbb5fe558 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -7,7 +7,6 @@ * @method static void defaultMorphKeyType(string $type) * @method static void morphUsingUuids() * @method static void morphUsingUlids() - * @method static void useNativeSchemaOperationsIfPossible(bool $value = true) * @method static bool createDatabase(string $name) * @method static bool dropDatabaseIfExists(string $name) * @method static bool hasTable(string $table) diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index b1bf206988ee..fe0f20d6f995 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -161,14 +161,15 @@ public function testRemoveColumn() $this->assertEquals(['alter table `users` add `foo` varchar(255) not null'], $blueprint->toSql($connection, new MySqlGrammar)); } - public function testRenameColumnWithoutDoctrine() + public function testRenameColumn() { $base = new Blueprint('users', function ($table) { $table->renameColumn('foo', 'bar'); }); $connection = m::mock(Connection::class); - $connection->shouldReceive('usingNativeSchemaOperations')->andReturn(true); + $connection->shouldReceive('getServerVersion')->andReturn('8.0.4'); + $connection->shouldReceive('isMaria')->andReturn(false); $blueprint = clone $base; $this->assertEquals(['alter table `users` rename column `foo` to `bar`'], $blueprint->toSql($connection, new MySqlGrammar)); @@ -183,14 +184,55 @@ public function testRenameColumnWithoutDoctrine() $this->assertEquals(['sp_rename \'"users"."foo"\', "bar", \'COLUMN\''], $blueprint->toSql($connection, new SqlServerGrammar)); } - public function testDropColumnWithoutDoctrine() + public function testNativeRenameColumnOnMysql57() + { + $blueprint = new Blueprint('users', function ($table) { + $table->renameColumn('name', 'title'); + $table->renameColumn('id', 'key'); + }); + + $connection = m::mock(Connection::class); + $connection->shouldReceive('isMaria')->andReturn(false); + $connection->shouldReceive('getServerVersion')->andReturn('5.7'); + $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], + ]); + + $this->assertEquals([ + "alter table `users` change `name` `title` varchar(255) collate 'utf8mb4_unicode_ci' null default 'foo'", + "alter table `users` change `id` `key` bigint unsigned not null auto_increment primary key comment 'lorem ipsum'", + ], $blueprint->toSql($connection, new MySqlGrammar)); + } + + public function testNativeRenameColumnOnLegacyMariaDB() + { + $blueprint = new Blueprint('users', function ($table) { + $table->renameColumn('name', 'title'); + $table->renameColumn('id', 'key'); + }); + + $connection = m::mock(Connection::class); + $connection->shouldReceive('isMaria')->andReturn(true); + $connection->shouldReceive('getServerVersion')->andReturn('10.1.35'); + $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], + ]); + + $this->assertEquals([ + "alter table `users` change `name` `title` varchar(255) collate 'utf8mb4_unicode_ci' null default 'foo'", + "alter table `users` change `id` `key` bigint unsigned not null auto_increment primary key comment 'lorem ipsum'", + ], $blueprint->toSql($connection, new MySqlGrammar)); + } + + public function testDropColumn() { $base = new Blueprint('users', function ($table) { $table->dropColumn('foo'); }); $connection = m::mock(Connection::class); - $connection->shouldReceive('usingNativeSchemaOperations')->andReturn(true); $blueprint = clone $base; $this->assertEquals(['alter table `users` drop `foo`'], $blueprint->toSql($connection, new MySqlGrammar)); diff --git a/tests/Database/DatabaseSchemaBuilderTest.php b/tests/Database/DatabaseSchemaBuilderTest.php old mode 100755 new mode 100644 index 4f62ef285de8..e5ffd52d3d4e --- a/tests/Database/DatabaseSchemaBuilderTest.php +++ b/tests/Database/DatabaseSchemaBuilderTest.php @@ -2,10 +2,8 @@ namespace Illuminate\Tests\Database; -use Doctrine\DBAL\Schema\Column; -use Doctrine\DBAL\Types\Type; use Illuminate\Database\Connection; -use Illuminate\Database\Query\Processors\PostgresProcessor; +use Illuminate\Database\Query\Processors\Processor; use Illuminate\Database\Schema\Builder; use Illuminate\Database\Schema\Grammars\Grammar; use LogicException; @@ -50,7 +48,7 @@ public function testHasTableCorrectlyCallsGrammar() { $connection = m::mock(Connection::class); $grammar = m::mock(stdClass::class); - $processor = m::mock(PostgresProcessor::class); + $processor = m::mock(Processor::class); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); $connection->shouldReceive('getPostProcessor')->andReturn($processor); $builder = new Builder($connection); @@ -77,16 +75,15 @@ public function testTableHasColumns() public function testGetColumnTypeAddsPrefix() { $connection = m::mock(Connection::class); - $column = m::mock(Column::class); - $type = m::mock(Type::class); $grammar = m::mock(Grammar::class); + $processor = m::mock(Processor::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->once()->andReturn([['name' => 'id', 'type_name' => 'integer']]); $builder = new Builder($connection); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); - $connection->shouldReceive('getDoctrineColumn')->once()->with('prefix_users', 'id')->andReturn($column); - $connection->shouldReceive('usingNativeSchemaOperations')->once()->andReturn(false); - $column->shouldReceive('getType')->once()->andReturn($type); - $type->shouldReceive('lookupName')->once()->andReturn('integer'); + $grammar->shouldReceive('compileColumns')->once()->with('prefix_users')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->once()->with('sql')->andReturn([['name' => 'id', 'type_name' => 'integer']]); $this->assertSame('integer', $builder->getColumnType('users', 'id')); } diff --git a/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php b/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php deleted file mode 100644 index fa177d48eb6b..000000000000 --- a/tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php +++ /dev/null @@ -1,114 +0,0 @@ - MySQLBitType::class, - 'xml' => PostgresXmlType::class, - ]; - } - - public function testRegisterCustomDoctrineTypesWithNonDefaultDatabaseConnections() - { - $this->assertTrue( - DB::connection() - ->getDoctrineConnection() - ->getDatabasePlatform() - ->hasDoctrineTypeMappingFor('xml') - ); - - // Custom type mappings are registered for a connection when it's created, - // this is not the default connection but it has the custom type mappings - $this->assertTrue( - DB::connection('sqlite') - ->getDoctrineConnection() - ->getDatabasePlatform() - ->hasDoctrineTypeMappingFor('xml') - ); - } - - public function testRenameConfiguredCustomDoctrineColumnTypeWithPostgres() - { - if ($this->driver !== 'pgsql') { - $this->markTestSkipped('Test requires a Postgres connection.'); - } - - Grammar::macro('typeXml', function () { - return 'xml'; - }); - - Schema::create('test', function (Blueprint $table) { - $table->addColumn('xml', 'test_column'); - }); - - Schema::table('test', function (Blueprint $table) { - $table->renameColumn('test_column', 'renamed_column'); - }); - - $this->assertFalse(Schema::hasColumn('test', 'test_column')); - $this->assertTrue(Schema::hasColumn('test', 'renamed_column')); - } - - public function testRenameConfiguredCustomDoctrineColumnTypeWithMysql() - { - if ($this->driver !== 'mysql') { - $this->markTestSkipped('Test requires a MySQL connection.'); - } - - Grammar::macro('typeBit', function () { - return 'bit'; - }); - - Schema::create('test', function (Blueprint $table) { - $table->addColumn('bit', 'test_column'); - }); - - Schema::table('test', function (Blueprint $table) { - $table->renameColumn('test_column', 'renamed_column'); - }); - - $this->assertFalse(Schema::hasColumn('test', 'test_column')); - $this->assertTrue(Schema::hasColumn('test', 'renamed_column')); - } -} - -class PostgresXmlType extends Type -{ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string - { - return 'xml'; - } - - public function getName() - { - return 'xml'; - } -} - -class MySQLBitType extends Type -{ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string - { - return 'bit'; - } - - public function getName() - { - return 'bit'; - } -} diff --git a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php index 3dee06143062..cc5355063e31 100644 --- a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php @@ -2,7 +2,6 @@ namespace Illuminate\Tests\Integration\Database; -use BadMethodCallException; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Grammars\MySqlGrammar; use Illuminate\Database\Schema\Grammars\PostgresGrammar; @@ -11,15 +10,12 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Orchestra\Testbench\TestCase; +use RuntimeException; class DatabaseSchemaBlueprintTest extends TestCase { protected function setUp(): void { - $this->beforeApplicationDestroyed(function () { - Schema::useNativeSchemaOperationsIfPossible(false); - }); - parent::setUp(); } @@ -44,47 +40,20 @@ public function testRenamingAndChangingColumnsWork() $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); - // Expect one of the following two query sequences to be present... $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR NOT NULL, age INTEGER NOT NULL)', - 'INSERT INTO users (name, age) SELECT name, age FROM __temp__users', - 'DROP TABLE __temp__users', - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (first_name VARCHAR NOT NULL, age VARCHAR NOT NULL)', - 'INSERT INTO users (first_name, age) SELECT name, age FROM __temp__users', - 'DROP TABLE __temp__users', - ], + 'create table "__temp__users" ("name" varchar not null, "age" integer not null)', + 'insert into "__temp__users" ("name", "age") select "name", "age" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', + 'alter table "users" rename column "name" to "first_name"', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); } - public function testRenamingColumnsWithoutDoctrineWorks() + public function testRenamingColumnsWorks() { - $connection = DB::connection(); - $schema = $connection->getSchemaBuilder(); - - $schema->useNativeSchemaOperationsIfPossible(); - - $base = new Blueprint('users', function ($table) { - $table->renameColumn('name', 'new_name'); - }); - - $blueprint = clone $base; - $this->assertEquals(['alter table `users` rename column `name` to `new_name`'], $blueprint->toSql($connection, new MySqlGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['alter table "users" rename column "name" to "new_name"'], $blueprint->toSql($connection, new PostgresGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['alter table "users" rename column "name" to "new_name"'], $blueprint->toSql($connection, new SQLiteGrammar)); - - $blueprint = clone $base; - $this->assertEquals(['sp_rename \'"users"."name"\', "new_name", \'COLUMN\''], $blueprint->toSql($connection, new SqlServerGrammar)); + $schema = DB::connection()->getSchemaBuilder(); $schema->create('test', function (Blueprint $table) { $table->string('foo'); @@ -101,13 +70,11 @@ public function testRenamingColumnsWithoutDoctrineWorks() $this->assertTrue($schema->hasColumns('test', ['bar', 'qux'])); } - public function testDroppingColumnsWithoutDoctrineWorks() + public function testDroppingColumnsWorks() { $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); - $schema->useNativeSchemaOperationsIfPossible(); - $blueprint = new Blueprint('users', function ($table) { $table->dropColumn('name'); }); @@ -120,8 +87,6 @@ public function testNativeColumnModifyingOnMySql() $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); - $schema->useNativeSchemaOperationsIfPossible(); - $blueprint = new Blueprint('users', function ($table) { $table->double('amount')->nullable()->invisible()->after('name')->change(); $table->timestamp('added_at', 4)->nullable(false)->useCurrent()->useCurrentOnUpdate()->change(); @@ -148,8 +113,6 @@ public function testNativeColumnModifyingOnPostgreSql() $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); - $schema->useNativeSchemaOperationsIfPossible(); - $blueprint = new Blueprint('users', function ($table) { $table->integer('code')->autoIncrement()->from(10)->comment('my comment')->change(); }); @@ -224,8 +187,6 @@ public function testNativeColumnModifyingOnSqlServer() $connection = DB::connection(); $schema = $connection->getSchemaBuilder(); - $schema->useNativeSchemaOperationsIfPossible(); - $blueprint = new Blueprint('users', function ($table) { $table->timestamp('added_at', 4)->nullable(false)->useCurrent()->change(); }); @@ -273,30 +234,24 @@ public function testChangingColumnWithCollationWorks() $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (age INTEGER NOT NULL COLLATE "RTRIM")', - 'INSERT INTO users (age) SELECT age FROM __temp__users', - 'DROP TABLE __temp__users', - ], + 'create table "__temp__users" ("age" integer not null collate \'RTRIM\')', + 'insert into "__temp__users" ("age") select "age" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); $queries = $blueprint2->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (age INTEGER NOT NULL COLLATE "NOCASE")', - 'INSERT INTO users (age) SELECT age FROM __temp__users', - 'DROP TABLE __temp__users', - ], + 'create table "__temp__users" ("age" integer not null collate \'NOCASE\')', + 'insert into "__temp__users" ("age") select "age" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); } public function testChangingCharColumnsWork() @@ -312,23 +267,13 @@ public function testChangingCharColumnsWork() $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name CHAR(50) NOT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', - ], - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name CHAR(50) NOT NULL COLLATE "BINARY")', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', - ], + 'create table "__temp__users" ("name" varchar not null)', + 'insert into "__temp__users" ("name") select "name" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); } public function testChangingPrimaryAutoincrementColumnsToNonAutoincrementColumnsWork() @@ -344,16 +289,13 @@ public function testChangingPrimaryAutoincrementColumnsToNonAutoincrementColumns $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT id FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (id BLOB NOT NULL, PRIMARY KEY(id))', - 'INSERT INTO users (id) SELECT id FROM __temp__users', - 'DROP TABLE __temp__users', - ], + 'create table "__temp__users" ("id" blob not null, primary key ("id"))', + 'insert into "__temp__users" ("id") select "id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); } public function testChangingDoubleColumnsWork() @@ -369,11 +311,10 @@ public function testChangingDoubleColumnsWork() $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - 'CREATE TEMPORARY TABLE __temp__products AS SELECT price FROM products', - 'DROP TABLE products', - 'CREATE TABLE products (price DOUBLE PRECISION NOT NULL)', - 'INSERT INTO products (price) SELECT price FROM __temp__products', - 'DROP TABLE __temp__products', + 'create table "__temp__products" ("price" double not null)', + 'insert into "__temp__products" ("price") select "price" from "products"', + 'drop table "products"', + 'alter table "__temp__products" rename to "products"', ]; $this->assertEquals($expected, $queries); @@ -397,8 +338,8 @@ public function testRenameIndexWorks() $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - 'DROP INDEX index1', - 'CREATE INDEX index2 ON users (name)', + 'drop index "index1"', + 'create index "index2" on "users" ("name")', ]; $this->assertEquals($expected, $queries); @@ -441,17 +382,11 @@ public function testAddUniqueIndexWithoutNameWorks() $queries = $blueprintMySql->toSql(DB::connection(), new MySqlGrammar); $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', - 'alter table `users` add unique `users_name_unique`(`name`)', - ], + 'alter table `users` modify `name` varchar(255) null', + 'alter table `users` add unique `users_name_unique`(`name`)', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); $blueprintPostgres = new Blueprint('users', function ($table) { $table->string('name')->nullable()->unique()->change(); @@ -460,18 +395,12 @@ public function testAddUniqueIndexWithoutNameWorks() $queries = $blueprintPostgres->toSql(DB::connection(), new PostgresGrammar); $expected = [ - [ - - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', - 'alter table "users" add constraint "users_name_unique" unique ("name")', - ], + 'alter table "users" alter column "name" type varchar(255), alter column "name" drop not null, alter column "name" drop default, alter column "name" drop identity if exists', + 'alter table "users" add constraint "users_name_unique" unique ("name")', + 'comment on column "users"."name" is NULL', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); $blueprintSQLite = new Blueprint('users', function ($table) { $table->string('name')->nullable()->unique()->change(); @@ -480,17 +409,14 @@ public function testAddUniqueIndexWithoutNameWorks() $queries = $blueprintSQLite->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', - 'create unique index "users_name_unique" on "users" ("name")', - ], + 'create table "__temp__users" ("name" varchar)', + 'insert into "__temp__users" ("name") select "name" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', + 'create unique index "users_name_unique" on "users" ("name")', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); $blueprintSqlServer = new Blueprint('users', function ($table) { $table->string('name')->nullable()->unique()->change(); @@ -499,17 +425,12 @@ public function testAddUniqueIndexWithoutNameWorks() $queries = $blueprintSqlServer->toSql(DB::connection(), new SqlServerGrammar); $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', - 'create unique index "users_name_unique" on "users" ("name")', - ], + "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE [dbo].[users] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID('[dbo].[users]') AND [name] in ('name') AND [default_object_id] <> 0;EXEC(@sql)", + 'alter table "users" alter column "name" nvarchar(255) null', + 'create unique index "users_name_unique" on "users" ("name")', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); } public function testAddUniqueIndexWithNameWorks() @@ -525,17 +446,11 @@ public function testAddUniqueIndexWithNameWorks() $queries = $blueprintMySql->toSql(DB::connection(), new MySqlGrammar); $expected = [ - [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', - 'alter table `users` add unique `index1`(`name`)', - ], + 'alter table `users` modify `name` varchar(255) null', + 'alter table `users` add unique `index1`(`name`)', ]; - $this->assertContains($queries, $expected); + $this->assertEquals($expected, $queries); $blueprintPostgres = new Blueprint('users', function ($table) { $table->unsignedInteger('name')->nullable()->unique('index1')->change(); @@ -544,12 +459,9 @@ public function testAddUniqueIndexWithNameWorks() $queries = $blueprintPostgres->toSql(DB::connection(), new PostgresGrammar); $expected = [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', + 'alter table "users" alter column "name" type integer, alter column "name" drop not null, alter column "name" drop default, alter column "name" drop identity if exists', 'alter table "users" add constraint "index1" unique ("name")', + 'comment on column "users"."name" is NULL', ]; $this->assertEquals($expected, $queries); @@ -561,11 +473,10 @@ public function testAddUniqueIndexWithNameWorks() $queries = $blueprintSQLite->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', + 'create table "__temp__users" ("name" integer)', + 'insert into "__temp__users" ("name") select "name" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', 'create unique index "index1" on "users" ("name")', ]; @@ -578,11 +489,8 @@ public function testAddUniqueIndexWithNameWorks() $queries = $blueprintSqlServer->toSql(DB::connection(), new SqlServerGrammar); $expected = [ - 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users', - 'DROP TABLE users', - 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)', - 'INSERT INTO users (name) SELECT name FROM __temp__users', - 'DROP TABLE __temp__users', + "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE [dbo].[users] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID('[dbo].[users]') AND [name] in ('name') AND [default_object_id] <> 0;EXEC(@sql)", + 'alter table "users" alter column "name" int null', 'create unique index "index1" on "users" ("name")', ]; @@ -634,39 +542,6 @@ public function testDropIndexOnColumnChangeWorks() ); } - public function testItEnsuresDroppingMultipleColumnsIsAvailable() - { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification."); - - Schema::table('users', function (Blueprint $table) { - $table->dropColumn('name'); - $table->dropColumn('email'); - }); - } - - public function testItEnsuresRenamingMultipleColumnsIsAvailable() - { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification."); - - Schema::table('users', function (Blueprint $table) { - $table->renameColumn('name', 'first_name'); - $table->renameColumn('name2', 'last_name'); - }); - } - - public function testItEnsuresRenamingAndDroppingMultipleColumnsIsAvailable() - { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification."); - - Schema::table('users', function (Blueprint $table) { - $table->dropColumn('name'); - $table->renameColumn('name2', 'last_name'); - }); - } - public function testItDoesNotSetPrecisionHigherThanSupportedWhenRenamingTimestamps() { Schema::create('users', function (Blueprint $table) { @@ -690,8 +565,8 @@ public function testItDoesNotSetPrecisionHigherThanSupportedWhenRenamingTimestam public function testItEnsuresDroppingForeignKeyIsAvailable() { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessage("SQLite doesn't support dropping foreign keys (you would need to re-create the table)."); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This database driver does not support dropping foreign keys.'); Schema::table('users', function (Blueprint $table) { $table->dropForeign('something'); diff --git a/tests/Integration/Database/DatabaseSchemaBuilderTest.php b/tests/Integration/Database/DatabaseSchemaBuilderTest.php index b016a0124eee..8a377d3616d7 100644 --- a/tests/Integration/Database/DatabaseSchemaBuilderTest.php +++ b/tests/Integration/Database/DatabaseSchemaBuilderTest.php @@ -59,7 +59,9 @@ public function testHasColumnAndIndexWithPrefixIndexDisabled() $table->string('name')->index(); }); - $this->assertArrayHasKey('table1_name_index', $connection->getDoctrineSchemaManager()->listTableIndexes('example_table1')); + $indexes = array_column($connection->getSchemaBuilder()->getIndexes('table1'), 'name'); + + $this->assertContains('table1_name_index', $indexes, 'name'); } public function testHasColumnAndIndexWithPrefixIndexEnabled() @@ -71,6 +73,8 @@ public function testHasColumnAndIndexWithPrefixIndexEnabled() $table->string('name')->index(); }); - $this->assertArrayHasKey('example_table1_name_index', $connection->getDoctrineSchemaManager()->listTableIndexes('example_table1')); + $indexes = array_column($connection->getSchemaBuilder()->getIndexes('table1'), 'name'); + + $this->assertContains('example_table1_name_index', $indexes); } } diff --git a/tests/Integration/Database/EloquentWhereHasMorphTest.php b/tests/Integration/Database/EloquentWhereHasMorphTest.php index d319317aaa63..15334b5788c2 100644 --- a/tests/Integration/Database/EloquentWhereHasMorphTest.php +++ b/tests/Integration/Database/EloquentWhereHasMorphTest.php @@ -153,7 +153,7 @@ public function testWhereHasMorphWithOwnerKey() }); Schema::table('comments', function (Blueprint $table) { - $table->string('commentable_id')->change(); + $table->string('commentable_id')->nullable()->change(); }); Post::where('id', 1)->update(['slug' => 'foo']); diff --git a/tests/Integration/Database/Fixtures/TinyInteger.php b/tests/Integration/Database/Fixtures/TinyInteger.php deleted file mode 100644 index 6b94dff3e869..000000000000 --- a/tests/Integration/Database/Fixtures/TinyInteger.php +++ /dev/null @@ -1,38 +0,0 @@ -unsignedInteger('age')->charset('')->change(); + $table->unsignedInteger('age')->change(); }); - $this->assertSame('integer', Schema::getColumnType('users', 'age')); + $this->assertSame('int', Schema::getColumnType('users', 'age')); } public function testGetAllTablesAndColumnListing() diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 206b2974b562..52ac4f7c17fc 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -2,12 +2,10 @@ namespace Illuminate\Tests\Integration\Database; -use Doctrine\DBAL\Types\Type; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Grammars\SQLiteGrammar; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -use Illuminate\Tests\Integration\Database\Fixtures\TinyInteger; class SchemaBuilderTest extends DatabaseTestCase { @@ -44,36 +42,12 @@ public function testDropAllViews() DB::statement('create view foo (id) as select 1'); } - public function testRegisterCustomDoctrineType() + public function testChangeToTinyInteger() { if ($this->driver !== 'sqlite') { $this->markTestSkipped('Test requires a SQLite connection.'); } - Schema::getConnection()->registerDoctrineType(TinyInteger::class, TinyInteger::NAME, 'TINYINT'); - - Schema::create('test', function (Blueprint $table) { - $table->string('test_column'); - }); - - $blueprint = new Blueprint('test', function (Blueprint $table) { - $table->tinyInteger('test_column')->change(); - }); - - $blueprint->build($this->getConnection(), new SQLiteGrammar); - - $this->assertArrayHasKey(TinyInteger::NAME, Type::getTypesMap()); - $this->assertSame('tinyinteger', Schema::getColumnType('test', 'test_column')); - } - - public function testRegisterCustomDoctrineTypeASecondTime() - { - if ($this->driver !== 'sqlite') { - $this->markTestSkipped('Test requires a SQLite connection.'); - } - - Schema::getConnection()->registerDoctrineType(TinyInteger::class, TinyInteger::NAME, 'TINYINT'); - Schema::create('test', function (Blueprint $table) { $table->string('test_column'); }); @@ -84,8 +58,7 @@ public function testRegisterCustomDoctrineTypeASecondTime() $blueprint->build($this->getConnection(), new SQLiteGrammar); - $this->assertArrayHasKey(TinyInteger::NAME, Type::getTypesMap()); - $this->assertSame('tinyinteger', Schema::getColumnType('test', 'test_column')); + $this->assertSame('integer', Schema::getColumnType('test', 'test_column')); } public function testChangeToTextColumn() @@ -105,9 +78,9 @@ public function testChangeToTextColumn() $queries = $blueprint->toSql($this->getConnection(), $this->getConnection()->getSchemaGrammar()); - $uppercase = strtoupper($type); + $uppercase = strtolower($type); - $expected = ["ALTER TABLE test CHANGE test_column test_column $uppercase NOT NULL"]; + $expected = ["alter table `test` modify `test_column` $uppercase not null"]; $this->assertEquals($expected, $queries); } @@ -130,12 +103,11 @@ public function testChangeTextColumnToTextColumn() $queries = $blueprint->toSql($this->getConnection(), $this->getConnection()->getSchemaGrammar()); - $uppercase = strtoupper($type); + $lowercase = strtolower($type); - $this->assertContains($queries, [ - ["ALTER TABLE test CHANGE test_column test_column $uppercase NOT NULL"], // MySQL - ["ALTER TABLE test CHANGE test_column test_column $uppercase NOT NULL COLLATE `utf8mb4_uca1400_ai_ci`"], // MariaDB - ]); + $expected = ["alter table `test` modify `test_column` $lowercase not null"]; + + $this->assertEquals($expected, $queries); } } @@ -369,6 +341,35 @@ public function testGetCompoundForeignKeys() )); } + public function testAlteringTableWithForeignKeyConstraintsEnabled() + { + Schema::enableForeignKeyConstraints(); + + Schema::create('parents', function (Blueprint $table) { + $table->id(); + $table->text('name'); + }); + + Schema::create('children', function (Blueprint $table) { + $table->foreignId('parent_id')->constrained(); + }); + + $id = DB::table('parents')->insertGetId(['name' => 'foo']); + DB::table('children')->insert(['parent_id' => $id]); + + Schema::table('parents', function (Blueprint $table) { + $table->string('name')->change(); + }); + + $foreignKeys = Schema::getForeignKeys('children'); + + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains( + fn ($foreign) => $foreign['columns'] === ['parent_id'] + && $foreign['foreign_table'] === 'parents' && $foreign['foreign_columns'] === ['id'] + )); + } + public function testSystemVersionedTables() { if ($this->driver !== 'mysql' || ! $this->getConnection()->isMaria()) { diff --git a/tests/Integration/Database/DBAL/TimestampTypeTest.php b/tests/Integration/Database/TimestampTypeTest.php similarity index 55% rename from tests/Integration/Database/DBAL/TimestampTypeTest.php rename to tests/Integration/Database/TimestampTypeTest.php index 5ecf225a52ec..05e57638c0e1 100644 --- a/tests/Integration/Database/DBAL/TimestampTypeTest.php +++ b/tests/Integration/Database/TimestampTypeTest.php @@ -1,33 +1,12 @@ TimestampType::class, - ]; - } - - public function testRegisterTimestampTypeOnConnection() - { - $this->assertTrue( - $this->app['db']->connection() - ->getDoctrineConnection() - ->getDatabasePlatform() - ->hasDoctrineTypeMappingFor('timestamp') - ); - } - public function testChangeDatetimeColumnToTimestampColumn() { Schema::create('test', function (Blueprint $table) { @@ -35,14 +14,18 @@ public function testChangeDatetimeColumnToTimestampColumn() }); Schema::table('test', function (Blueprint $table) { - $table->timestamp('datetime_to_timestamp')->nullable(true)->change(); + $table->timestamp('datetime_to_timestamp')->nullable()->change(); }); $this->assertTrue(Schema::hasColumn('test', 'datetime_to_timestamp')); // Only Postgres and MySQL actually have a timestamp type - in_array($this->driver, ['pgsql', 'mysql']) - ? $this->assertSame('timestamp', Schema::getColumnType('test', 'datetime_to_timestamp')) - : $this->assertSame('datetime', Schema::getColumnType('test', 'datetime_to_timestamp')); + $this->assertSame( + match ($this->driver) { + 'mysql', 'pgsql' => 'timestamp', + default => 'datetime', + }, + Schema::getColumnType('test', 'datetime_to_timestamp') + ); } public function testChangeTimestampColumnToDatetimeColumn() @@ -52,14 +35,18 @@ public function testChangeTimestampColumnToDatetimeColumn() }); Schema::table('test', function (Blueprint $table) { - $table->dateTime('timestamp_to_datetime')->nullable(true)->change(); + $table->dateTime('timestamp_to_datetime')->nullable()->change(); }); $this->assertTrue(Schema::hasColumn('test', 'timestamp_to_datetime')); // Postgres only has a timestamp type - $this->driver === 'pgsql' - ? $this->assertSame('timestamp', Schema::getColumnType('test', 'timestamp_to_datetime')) - : $this->assertSame('datetime', Schema::getColumnType('test', 'timestamp_to_datetime')); + $this->assertSame( + match ($this->driver) { + 'pgsql' => 'timestamp', + default => 'datetime', + }, + Schema::getColumnType('test', 'timestamp_to_datetime') + ); } public function testChangeStringColumnToTimestampColumn() @@ -73,12 +60,12 @@ public function testChangeStringColumnToTimestampColumn() }); $blueprint = new Blueprint('test', function ($table) { - $table->timestamp('string_to_timestamp')->nullable(true)->change(); + $table->timestamp('string_to_timestamp')->nullable()->change(); }); $queries = $blueprint->toSql($this->getConnection(), $this->getConnection()->getSchemaGrammar()); - $expected = ['ALTER TABLE test CHANGE string_to_timestamp string_to_timestamp TIMESTAMP NULL DEFAULT NULL']; + $expected = ['alter table `test` modify `string_to_timestamp` timestamp null']; $this->assertEquals($expected, $queries); }