diff --git a/src/Schema/Grammar.php b/src/Schema/Grammar.php index 3744b09..dffd274 100644 --- a/src/Schema/Grammar.php +++ b/src/Schema/Grammar.php @@ -6,9 +6,11 @@ use Illuminate\Database\Connection; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Grammars\MySqlGrammar; +use Illuminate\Foundation\Application; use Illuminate\Support\Fluent; use Illuminate\Support\Str; use InvalidArgumentException; +use LogicException; use SingleStore\Laravel\Schema\Blueprint as SingleStoreBlueprint; use SingleStore\Laravel\Schema\Grammar\CompilesKeys; use SingleStore\Laravel\Schema\Grammar\ModifiesColumns; @@ -25,6 +27,67 @@ public function __construct() $this->addSingleStoreModifiers(); } + /** + * 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 compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + { + if (version_compare(Application::VERSION, '10.0', '<')) { + throw new LogicException('This database driver does not support modifying columns on Laravel < 10.0.'); + } + + $prefix = method_exists($blueprint, 'getPrefix') + ? $blueprint->getPrefix() + : (function () { + return $this->prefix; + })->call($blueprint); + + $isColumnstoreTable = $connection->scalar(sprintf( + "select exists (select 1 from information_schema.tables where table_schema = %s and table_name = %s and storage_type = 'COLUMNSTORE') as is_columnstore", + $this->quoteString($connection->getDatabaseName()), + $this->quoteString($prefix.$blueprint->getTable()) + )); + + if (! $isColumnstoreTable) { + return parent::compileChange($blueprint, $command, $connection); + } + + if (version_compare(Application::VERSION, '11.15', '<')) { + throw new LogicException('This database driver does not support modifying columns of a columnstore table on Laravel < 11.15.'); + } + + $tempCommand = clone $command; + $tempCommand->column = clone $command->column; + $tempCommand->column->change = false; + $tempCommand->column->name = '__temp__'.$command->column->name; + $tempCommand->column->after = is_null($command->column->after) && is_null($command->column->first) + ? $command->column->name + : $command->column->after; + + return [ + $this->compileAdd($blueprint, $tempCommand), + sprintf('update %s set %s = %s', + $this->wrapTable($blueprint), + $this->wrap($tempCommand->column), + $this->wrap($command->column) + ), + $this->compileDropColumn($blueprint, new Fluent([ + 'columns' => [$command->column->name], + ])), + $this->compileRenameColumn($blueprint, new Fluent([ + 'from' => $tempCommand->column->name, + 'to' => $command->column->name, + ]), $connection), + ]; + } + /** * Compile a primary key command. * diff --git a/tests/Hybrid/ChangeColumnTest.php b/tests/Hybrid/ChangeColumnTest.php new file mode 100644 index 0000000..1ffa1e8 --- /dev/null +++ b/tests/Hybrid/ChangeColumnTest.php @@ -0,0 +1,111 @@ +markTestSkipped('requires higher laravel version'); + } + + if ($this->runHybridIntegrations()) { + $cached = $this->mockDatabaseConnection; + + $this->mockDatabaseConnection = false; + + if (method_exists(Builder::class, 'useNativeSchemaOperationsIfPossible')) { + Schema::useNativeSchemaOperationsIfPossible(); + } + + $this->createTable(function (Blueprint $table) { + $table->rowstore(); + $table->id(); + $table->string('data'); + }); + + Schema::table('test', function (Blueprint $table) { + $table->text('data')->nullable()->change(); + }); + + $this->assertEquals(['id', 'data'], Schema::getColumnListing('test')); + + if (version_compare(Application::VERSION, '10.30', '>=')) { + $this->assertEquals('text', Schema::getColumnType('test', 'data')); + } + + $this->mockDatabaseConnection = $cached; + } + + $blueprint = new Blueprint('test'); + $blueprint->text('data')->nullable()->change(); + + $connection = $this->getConnection(); + $connection->shouldReceive('getDatabaseName')->andReturn('database'); + $connection->shouldReceive('scalar') + ->with("select exists (select 1 from information_schema.tables where table_schema = 'database' and table_name = 'test' and storage_type = 'COLUMNSTORE') as is_columnstore") + ->andReturn(0); + $connection->shouldReceive('usingNativeSchemaOperations')->andReturn(true); + $statements = $blueprint->toSql($connection, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertEquals('alter table `test` modify `data` text null', $statements[0]); + } + + /** @test */ + public function change_column_of_columnstore_table() + { + if (version_compare(Application::VERSION, '11.15', '<')) { + $this->markTestSkipped('requires higher laravel version'); + } + + if ($this->runHybridIntegrations()) { + $cached = $this->mockDatabaseConnection; + + $this->mockDatabaseConnection = false; + + $this->createTable(function (Blueprint $table) { + $table->id(); + $table->string('data'); + }); + + Schema::table('test', function (Blueprint $table) { + $table->text('data')->nullable()->change(); + }); + + $this->assertEquals(['id', 'data'], Schema::getColumnListing('test')); + $this->assertEquals('text', Schema::getColumnType('test', 'data')); + + $this->mockDatabaseConnection = $cached; + } + + $blueprint = new Blueprint('test'); + $blueprint->text('data')->nullable()->change(); + + $connection = $this->getConnection(); + $connection->shouldReceive('getDatabaseName')->andReturn('database'); + $connection->shouldReceive('scalar') + ->with("select exists (select 1 from information_schema.tables where table_schema = 'database' and table_name = 'test' and storage_type = 'COLUMNSTORE') as is_columnstore") + ->andReturn(1); + + $statements = $blueprint->toSql($connection, $this->getGrammar()); + + $this->assertCount(4, $statements); + $this->assertEquals([ + 'alter table `test` add `__temp__data` text null after `data`', + 'update `test` set `__temp__data` = `data`', + 'alter table `test` drop `data`', + 'alter table `test` change `__temp__data` `data`', + ], $statements); + } +}