Skip to content

[BUGFIX] Add custom CockroachSchemaManager and override selectTableColumns() / fixes #35 #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/CockroachDbConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Grammar as BaseGrammar;
use Illuminate\Database\PDO\PostgresDriver;
use Illuminate\Database\PostgresConnection;
use Illuminate\Filesystem\Filesystem;
use YlsIdeas\CockroachDb\Builder\CockroachDbBuilder as DbBuilder;
use YlsIdeas\CockroachDb\Driver\CockroachDbDriver;
use YlsIdeas\CockroachDb\Processor\CockroachDbProcessor as DbProcessor;
use YlsIdeas\CockroachDb\Query\CockroachGrammar as QueryGrammar;
use YlsIdeas\CockroachDb\Schema\CockroachGrammar as SchemaGrammar;
Expand Down Expand Up @@ -76,7 +76,7 @@ protected function getDefaultPostProcessor(): DbProcessor
*/
protected function getDoctrineDriver()
{
return new PostgresDriver();
return new CockroachDbDriver();
}

/**
Expand Down
17 changes: 17 additions & 0 deletions src/Driver/CockroachDbDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace YlsIdeas\CockroachDb\Driver;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Illuminate\Database\PDO\PostgresDriver;
use YlsIdeas\CockroachDb\Schema\CockroachSchemaManager;

class CockroachDbDriver extends PostgresDriver
{
public function getSchemaManager(Connection $conn, AbstractPlatform $platform): AbstractSchemaManager
{
return new CockroachSchemaManager($conn, $platform);
}
}
16 changes: 16 additions & 0 deletions src/Schema/CockroachGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,20 @@ public function compileDropUnique(Blueprint $blueprint, Fluent $command)

return "drop index {$this->wrapTable($blueprint)}@{$index} cascade";
}

public function compileColumns($database, $schema, $table): string
{
return sprintf(
'select a.attname as name, t.typname as type_name, format_type(a.atttypid, a.atttypmod) as type, '
.'(select tc.collcollate from pg_catalog.pg_collation tc where tc.oid = a.attcollation) as collation, '
.'not a.attnotnull as nullable, '
.'(select pg_get_expr(adbin, adrelid) from pg_attrdef where c.oid = pg_attrdef.adrelid and pg_attrdef.adnum = a.attnum) as default, '
.'col_description(c.oid, a.attnum) as comment '
.'from pg_attribute a, pg_class c, pg_type t, pg_namespace n '
.'where c.relname = %s and n.nspname = %s and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid and n.oid = c.relnamespace and a.attisdropped = false '
.'order by a.attnum',
$this->quoteString($table),
$this->quoteString($schema)
);
}
}
93 changes: 93 additions & 0 deletions src/Schema/CockroachSchemaManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace YlsIdeas\CockroachDb\Schema;

use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\Identifier;
use Doctrine\DBAL\Schema\PostgreSQLSchemaManager;

class CockroachSchemaManager extends PostgreSQLSchemaManager
{
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
{
$sql = 'SELECT';

if ($tableName === null) {
$sql .= ' c.relname AS table_name, n.nspname AS schema_name,';
}

$sql .= <<<'SQL'
a.attnum,
quote_ident(a.attname) AS field,
t.typname AS type,
format_type(a.atttypid, a.atttypmod) AS complete_type,
(SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,
(SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
(SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
a.attnotnull AS isnotnull,
(SELECT 't'
FROM pg_index
WHERE c.oid = pg_index.indrelid
AND pg_index.indkey[0] = a.attnum
AND pg_index.indisprimary = 't'
) AS pri,
(SELECT pg_get_expr(adbin, adrelid)
FROM pg_attrdef
WHERE c.oid = pg_attrdef.adrelid
AND pg_attrdef.adnum=a.attnum
) AS default,
(SELECT pg_description.description
FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
) AS comment
FROM pg_attribute a
INNER JOIN pg_class c
ON c.oid = a.attrelid
INNER JOIN pg_type t
ON t.oid = a.atttypid
INNER JOIN pg_namespace n
ON n.oid = c.relnamespace
LEFT JOIN pg_depend d
ON d.objid = c.oid
AND d.deptype = 'e'
AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class')
SQL;

$conditions = array_merge([
'a.attnum > 0',
"c.relkind = 'r'",
'd.refobjid IS NULL',
'a.attisdropped = false',
], $this->buildQueryConditions($tableName));

$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum';

return $this->_conn->executeQuery($sql);
}

/**
* @param string|null $tableName
*
* @return list<string>
*/
private function buildQueryConditions($tableName): array
{
$conditions = [];

if ($tableName !== null) {
if (strpos($tableName, '.') !== false) {
[$schemaName, $tableName] = explode('.', $tableName);
$conditions[] = 'n.nspname = ' . $this->_platform->quoteStringLiteral($schemaName);
} else {
$conditions[] = 'n.nspname = ANY(current_schemas(false))';
}

$identifier = new Identifier($tableName);
$conditions[] = 'c.relname = ' . $this->_platform->quoteStringLiteral($identifier->getName());
}

$conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')";

return $conditions;
}
}
67 changes: 67 additions & 0 deletions tests/Integration/Database/MigrationRenamingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace YlsIdeas\CockroachDb\Tests\Integration\Database;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class MigrationRenamingTest extends DatabaseTestCase
{
private const TEST_TABLE = 'test_table';

protected function setUp(): void
{
parent::setUp();

Schema::create(self::TEST_TABLE, function (Blueprint $table) {
$table->id();
$table->string('column_y');
$table->string('column_x');
});
}

public function test_rename_column_to_previously_deleted_one(): void
{
Schema::table(self::TEST_TABLE, function (Blueprint $table) {
$table->dropColumn('column_y');
});

Schema::table(self::TEST_TABLE, function (Blueprint $table) {
$table->renameColumn('column_x', 'column_y');
});

$tableColumns = Schema::getColumnListing(self::TEST_TABLE);
$this->assertCount(2, $tableColumns);
$this->assertEquals('id', $tableColumns[0]);
$this->assertEquals('column_y', $tableColumns[1]);
}

public function test_rename_column_with_any_previously_deleted_one(): void
{
Schema::table(self::TEST_TABLE, function (Blueprint $table) {
$table->dropColumn('column_y');
});

Schema::table(self::TEST_TABLE, function (Blueprint $table) {
$table->renameColumn('column_x', 'column_z');
});

$tableColumns = Schema::getColumnListing(self::TEST_TABLE);
$this->assertCount(2, $tableColumns);
$this->assertEquals('id', $tableColumns[0]);
$this->assertEquals('column_z', $tableColumns[1]);
}

public function test_rename_column_without_deleted_ones(): void
{
Schema::table(self::TEST_TABLE, function (Blueprint $table) {
$table->renameColumn('column_x', 'column_z');
});

$tableColumns = Schema::getColumnListing(self::TEST_TABLE);
$this->assertCount(3, $tableColumns);
$this->assertEquals('id', $tableColumns[0]);
$this->assertEquals('column_y', $tableColumns[1]);
$this->assertEquals('column_z', $tableColumns[2]);
}
}
54 changes: 54 additions & 0 deletions tests/Integration/Database/SchemaGetColumnsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace YlsIdeas\CockroachDb\Tests\Integration\Database;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use YlsIdeas\CockroachDb\Builder\CockroachDbBuilder;

class SchemaGetColumnsTest extends DatabaseTestCase
{
private const TEST_TABLE = 'test_table';

/**
* @before
*/
public function onlyIfGetColumnsExists(): void
{
if (! method_exists(CockroachDbBuilder::class, 'getColumns')) {
$this->markTestSkipped("The Schema::getColumns() function is only available in a later Laravel version.");
}
}

protected function setUp(): void
{
parent::setUp();

Schema::create(self::TEST_TABLE, function (Blueprint $table) {
$table->id();
$table->string('column_y');
$table->string('column_x');
});
}

public function test_schema_get_columns_with_dropped_columns(): void
{
Schema::table(self::TEST_TABLE, function (Blueprint $table) {
$table->dropColumn('column_y');
});

$tableColumns = Schema::getColumns(self::TEST_TABLE);
$this->assertCount(2, $tableColumns);
$this->assertSame('id', $tableColumns[0]['name']);
$this->assertSame('column_x', $tableColumns[1]['name']);
}

public function test_schema_get_columns_without_dropped_columns(): void
{
$tableColumns = Schema::getColumns(self::TEST_TABLE);
$this->assertCount(3, $tableColumns);
$this->assertSame('id', $tableColumns[0]['name']);
$this->assertSame('column_y', $tableColumns[1]['name']);
$this->assertSame('column_x', $tableColumns[2]['name']);
}
}