Skip to content

Commit

Permalink
Fix column modifying on columnstore tables (#88)
Browse files Browse the repository at this point in the history
* fix column modifying on columnstore tables
  • Loading branch information
hafezdivandari authored Jul 24, 2024
1 parent d15a998 commit 10756db
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 0 deletions.
63 changes: 63 additions & 0 deletions src/Schema/Grammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*
Expand Down
111 changes: 111 additions & 0 deletions tests/Hybrid/ChangeColumnTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace SingleStore\Laravel\Tests\Hybrid;

use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Schema;
use SingleStore\Laravel\Schema\Blueprint;
use SingleStore\Laravel\Schema\Builder;
use SingleStore\Laravel\Tests\BaseTest;

class ChangeColumnTest extends BaseTest
{
use HybridTestHelpers;

/** @test */
public function change_column_on_rowstore_table()
{
if (version_compare(Application::VERSION, '10.0', '<')) {
$this->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);
}
}

0 comments on commit 10756db

Please sign in to comment.