Skip to content

Commit 10756db

Browse files
Fix column modifying on columnstore tables (#88)
* fix column modifying on columnstore tables
1 parent d15a998 commit 10756db

File tree

2 files changed

+174
-0
lines changed

2 files changed

+174
-0
lines changed

src/Schema/Grammar.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
use Illuminate\Database\Connection;
77
use Illuminate\Database\Schema\Blueprint;
88
use Illuminate\Database\Schema\Grammars\MySqlGrammar;
9+
use Illuminate\Foundation\Application;
910
use Illuminate\Support\Fluent;
1011
use Illuminate\Support\Str;
1112
use InvalidArgumentException;
13+
use LogicException;
1214
use SingleStore\Laravel\Schema\Blueprint as SingleStoreBlueprint;
1315
use SingleStore\Laravel\Schema\Grammar\CompilesKeys;
1416
use SingleStore\Laravel\Schema\Grammar\ModifiesColumns;
@@ -25,6 +27,67 @@ public function __construct()
2527
$this->addSingleStoreModifiers();
2628
}
2729

30+
/**
31+
* Compile a change column command into a series of SQL statements.
32+
*
33+
* @param \Illuminate\Database\Schema\Blueprint $blueprint
34+
* @param \Illuminate\Support\Fluent $command
35+
* @param \Illuminate\Database\Connection $connection
36+
* @return array|string
37+
*
38+
* @throws \RuntimeException
39+
*/
40+
public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection)
41+
{
42+
if (version_compare(Application::VERSION, '10.0', '<')) {
43+
throw new LogicException('This database driver does not support modifying columns on Laravel < 10.0.');
44+
}
45+
46+
$prefix = method_exists($blueprint, 'getPrefix')
47+
? $blueprint->getPrefix()
48+
: (function () {
49+
return $this->prefix;
50+
})->call($blueprint);
51+
52+
$isColumnstoreTable = $connection->scalar(sprintf(
53+
"select exists (select 1 from information_schema.tables where table_schema = %s and table_name = %s and storage_type = 'COLUMNSTORE') as is_columnstore",
54+
$this->quoteString($connection->getDatabaseName()),
55+
$this->quoteString($prefix.$blueprint->getTable())
56+
));
57+
58+
if (! $isColumnstoreTable) {
59+
return parent::compileChange($blueprint, $command, $connection);
60+
}
61+
62+
if (version_compare(Application::VERSION, '11.15', '<')) {
63+
throw new LogicException('This database driver does not support modifying columns of a columnstore table on Laravel < 11.15.');
64+
}
65+
66+
$tempCommand = clone $command;
67+
$tempCommand->column = clone $command->column;
68+
$tempCommand->column->change = false;
69+
$tempCommand->column->name = '__temp__'.$command->column->name;
70+
$tempCommand->column->after = is_null($command->column->after) && is_null($command->column->first)
71+
? $command->column->name
72+
: $command->column->after;
73+
74+
return [
75+
$this->compileAdd($blueprint, $tempCommand),
76+
sprintf('update %s set %s = %s',
77+
$this->wrapTable($blueprint),
78+
$this->wrap($tempCommand->column),
79+
$this->wrap($command->column)
80+
),
81+
$this->compileDropColumn($blueprint, new Fluent([
82+
'columns' => [$command->column->name],
83+
])),
84+
$this->compileRenameColumn($blueprint, new Fluent([
85+
'from' => $tempCommand->column->name,
86+
'to' => $command->column->name,
87+
]), $connection),
88+
];
89+
}
90+
2891
/**
2992
* Compile a primary key command.
3093
*

tests/Hybrid/ChangeColumnTest.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
namespace SingleStore\Laravel\Tests\Hybrid;
4+
5+
use Illuminate\Foundation\Application;
6+
use Illuminate\Support\Facades\Schema;
7+
use SingleStore\Laravel\Schema\Blueprint;
8+
use SingleStore\Laravel\Schema\Builder;
9+
use SingleStore\Laravel\Tests\BaseTest;
10+
11+
class ChangeColumnTest extends BaseTest
12+
{
13+
use HybridTestHelpers;
14+
15+
/** @test */
16+
public function change_column_on_rowstore_table()
17+
{
18+
if (version_compare(Application::VERSION, '10.0', '<')) {
19+
$this->markTestSkipped('requires higher laravel version');
20+
}
21+
22+
if ($this->runHybridIntegrations()) {
23+
$cached = $this->mockDatabaseConnection;
24+
25+
$this->mockDatabaseConnection = false;
26+
27+
if (method_exists(Builder::class, 'useNativeSchemaOperationsIfPossible')) {
28+
Schema::useNativeSchemaOperationsIfPossible();
29+
}
30+
31+
$this->createTable(function (Blueprint $table) {
32+
$table->rowstore();
33+
$table->id();
34+
$table->string('data');
35+
});
36+
37+
Schema::table('test', function (Blueprint $table) {
38+
$table->text('data')->nullable()->change();
39+
});
40+
41+
$this->assertEquals(['id', 'data'], Schema::getColumnListing('test'));
42+
43+
if (version_compare(Application::VERSION, '10.30', '>=')) {
44+
$this->assertEquals('text', Schema::getColumnType('test', 'data'));
45+
}
46+
47+
$this->mockDatabaseConnection = $cached;
48+
}
49+
50+
$blueprint = new Blueprint('test');
51+
$blueprint->text('data')->nullable()->change();
52+
53+
$connection = $this->getConnection();
54+
$connection->shouldReceive('getDatabaseName')->andReturn('database');
55+
$connection->shouldReceive('scalar')
56+
->with("select exists (select 1 from information_schema.tables where table_schema = 'database' and table_name = 'test' and storage_type = 'COLUMNSTORE') as is_columnstore")
57+
->andReturn(0);
58+
$connection->shouldReceive('usingNativeSchemaOperations')->andReturn(true);
59+
$statements = $blueprint->toSql($connection, $this->getGrammar());
60+
61+
$this->assertCount(1, $statements);
62+
$this->assertEquals('alter table `test` modify `data` text null', $statements[0]);
63+
}
64+
65+
/** @test */
66+
public function change_column_of_columnstore_table()
67+
{
68+
if (version_compare(Application::VERSION, '11.15', '<')) {
69+
$this->markTestSkipped('requires higher laravel version');
70+
}
71+
72+
if ($this->runHybridIntegrations()) {
73+
$cached = $this->mockDatabaseConnection;
74+
75+
$this->mockDatabaseConnection = false;
76+
77+
$this->createTable(function (Blueprint $table) {
78+
$table->id();
79+
$table->string('data');
80+
});
81+
82+
Schema::table('test', function (Blueprint $table) {
83+
$table->text('data')->nullable()->change();
84+
});
85+
86+
$this->assertEquals(['id', 'data'], Schema::getColumnListing('test'));
87+
$this->assertEquals('text', Schema::getColumnType('test', 'data'));
88+
89+
$this->mockDatabaseConnection = $cached;
90+
}
91+
92+
$blueprint = new Blueprint('test');
93+
$blueprint->text('data')->nullable()->change();
94+
95+
$connection = $this->getConnection();
96+
$connection->shouldReceive('getDatabaseName')->andReturn('database');
97+
$connection->shouldReceive('scalar')
98+
->with("select exists (select 1 from information_schema.tables where table_schema = 'database' and table_name = 'test' and storage_type = 'COLUMNSTORE') as is_columnstore")
99+
->andReturn(1);
100+
101+
$statements = $blueprint->toSql($connection, $this->getGrammar());
102+
103+
$this->assertCount(4, $statements);
104+
$this->assertEquals([
105+
'alter table `test` add `__temp__data` text null after `data`',
106+
'update `test` set `__temp__data` = `data`',
107+
'alter table `test` drop `data`',
108+
'alter table `test` change `__temp__data` `data`',
109+
], $statements);
110+
}
111+
}

0 commit comments

Comments
 (0)