Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ RUN pecl_mt_install() { \
&& apk add --no-cache bash gmp libxml2 libstdc++ \
&& apk add --no-cache --virtual=.build-deps autoconf curl-dev gcc gmp-dev g++ libxml2-dev linux-headers make pcre-dev tzdata \
&& docker-php-ext-install -j$(nproc) bcmath gmp \
&& pecl_mt_install protobuf \
&& pecl_mt_install protobuf-4.30.2 \
&& pecl_mt_install grpc \
&& pecl_mt_install pcov \
&& docker-php-ext-enable grpc opcache protobuf \
Expand Down
11 changes: 11 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ parameters:
- message: "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface\\:\\:recordsHaveBeenModified\\(\\)\\.$#"
count: 1
path: src/Query/Processor.php
-
message: '#^Closure invoked with 2 parameters, 3 required\.$#'
identifier: arguments.count
count: 1
path: src/Schema/Builder.php

-
message: '#^Parameter \#2 of closure expects Closure, Closure\|null given\.$#'
identifier: argument.type
count: 1
path: src/Schema/Builder.php
Comment on lines +9 to +19
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error will be fixed with this PR. #279

72 changes: 46 additions & 26 deletions src/Query/Processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,41 +100,53 @@ protected function processColumn(mixed $value): mixed
}

/**
* Process the results of a columns query.
*
* {@inheritDoc}
* @param array<array-key, array<array-key, mixed>> $results
* @return array<array-key, array{
* name: string,
* type_name: string,
* type: string,
* collation: null,
* nullable: bool,
* default: scalar,
* auto_increment: false,
* comment: null
* }>
* @inheritDoc
*/
public function processTables($results)
{
return array_map(function ($result) {
$result = (object) $result;

return [
'name' => $result->name,
'schema' => $result->schema !== '' ? $result->schema : null,
'schema_qualified_name' => $result->schema !== ''
? $result->schema . '.' . $result->name
: $result->name,
'parent' => $result->parent,
'size' => null,
'comment' => null,
'collation' => null,
'engine' => null,
];
}, $results);
}

/**
* @inheritDoc
*/
public function processColumns($results)
{
return array_map(static function (array $result) {
$result = (object) $result;

return [
'name' => $result['COLUMN_NAME'],
'type_name' => preg_replace("/\([^)]+\)/", "", $result['SPANNER_TYPE']),
'type' => $result['SPANNER_TYPE'],
'name' => $result->name,
'type_name' => (string) preg_replace("/\([^)]+\)/", "", $result->type),
'type' => $result->type,
'collation' => null,
'nullable' => $result['IS_NULLABLE'] !== 'NO',
'default' => $result['COLUMN_DEFAULT'],
'nullable' => $result->nullable === 'YES',
'default' => $result->default,
// TODO check IS_IDENTITY and set auto_increment accordingly
'auto_increment' => false,
'comment' => null,
'generation' => null,
];
}, $results);
}

/**
* {@inheritDoc}
* @param list<array<string, mixed>> $results
* @return list<array{name: string, columns: list<string>, type: string, unique: bool, primary: bool}>
* @inheritDoc
*/
public function processIndexes($results)
{
Expand All @@ -152,14 +164,22 @@ public function processIndexes($results)
}

/**
* {@inheritDoc}
* @param array{key_name: string}&array<string, mixed> $results
* @return array<array-key, string>
* @inheritDoc
*/
public function processForeignKeys($results)
{
return array_map(function ($result) {
return ((object) $result)->key_name;
$result = (object) $result;

return [
'name' => $result->name,
'columns' => explode(',', $result->columns),
'foreign_schema' => $result->foreign_schema,
'foreign_table' => $result->foreign_table,
'foreign_columns' => explode(',', $result->foreign_columns),
'on_update' => strtolower($result->on_update),
'on_delete' => strtolower($result->on_delete),
];
}, $results);
}
}
30 changes: 13 additions & 17 deletions src/Schema/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,6 @@ class Builder extends BaseBuilder
*/
public static $defaultMorphKeyType = 'uuid';

/**
* @param null $schema
* @inheritDoc Adds a parent key, for tracking interleaving
*
* @return list<array{ name: string, type: string, parent: string }>
*/
public function getTables($schema = null)
{
/** @var list<array{ name: string, type: string, parent: string }> */
return $this->connection->select(
$this->grammar->compileTables(null),
);
}

/**
* @param string $table
* @param string $name
Expand All @@ -73,7 +59,7 @@ public function dropIndex($table, $name)
*/
public function dropIndexIfExist($table, $name)
{
if (in_array($name, $this->getIndexes($table), true)) {
if (in_array($name, $this->getIndexListing($table), true)) {
$blueprint = $this->createBlueprint($table);
$blueprint->dropIndex($name);
$this->build($blueprint);
Expand All @@ -85,7 +71,6 @@ public function dropIndexIfExist($table, $name)
*/
protected function createBlueprint($table, ?Closure $callback = null)
{
/** @phpstan-ignore isset.property */
return isset($this->resolver)
? ($this->resolver)($table, $callback)
: new Blueprint($this->connection, $table, $callback);
Expand All @@ -97,6 +82,17 @@ protected function createBlueprint($table, ?Closure $callback = null)
public function dropAllTables()
{
$connection = $this->connection;
/** @var list<array{
* name: string,
* schema: string|null,
* schema_qualified_name: string,
* size: int|null,
* comment: string|null,
* collation: string|null,
* engine: string|null,
* parent: string|null
* }> $tables
*/
Comment on lines +85 to +95
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adds parent: string|null to shape.

$tables = $this->getTables();

if (count($tables) === 0) {
Expand Down Expand Up @@ -134,7 +130,7 @@ public function dropAllTables()
$foreigns = $this->getForeignKeys($tableName);
$blueprint = $this->createBlueprint($tableName);
foreach ($foreigns as $foreign) {
$blueprint->dropForeign($foreign);
$blueprint->dropForeign($foreign['name']);
}
array_push($queries, ...$blueprint->toSql());
}
Expand Down
70 changes: 50 additions & 20 deletions src/Schema/Grammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,38 @@ class Grammar extends BaseGrammar
*/
public function compileTables($schema)
{
return 'select `table_name` as name, `table_type` as type, `parent_table_name` as parent from information_schema.tables where table_schema = \'\' and table_type = \'BASE TABLE\'';
return implode(' ', [
'select',
implode(', ', [
'table_name as name',
'table_schema as `schema`',
'parent_table_name as parent',
]),
'from information_schema.tables',
'where table_type = \'BASE TABLE\'',
'and table_schema = \'\'',
]);
}

/**
* Compile the query to determine the columns.
*
* @param string $table
* @return string
*/
public function compileColumns($schema, $table)
{
return implode(' ', [
'select',
implode(', ', [
'column_name as `name`',
'spanner_type as `type`',
'is_nullable as `nullable`',
'column_default as `default`',
]),
'from information_schema.columns',
'where table_name = ' . $this->quoteString($table),
]);
}

/**
Expand Down Expand Up @@ -84,25 +115,24 @@ public function compileIndexes($schema, $table)
*/
public function compileForeignKeys($schema, $table)
{
return sprintf(
'select constraint_name as `key_name` from information_schema.table_constraints where constraint_type = "FOREIGN KEY" and table_schema = \'\' and table_name = %s',
$this->quoteString($table),
);
}

/**
* Compile the query to determine the columns.
*
* @param string|null $schema
* @param $table
* @return string
*/
public function compileColumns($schema, $table)
{
return sprintf(
'select * from information_schema.columns where table_schema = \'\' and table_name = %s',
$this->quoteString($table),
);
return implode(' ', [
'select',
implode(', ', [
'kc.constraint_name as `name`',
'string_agg(kc.column_name) as `columns`',
'cc.table_schema as `foreign_schema`',
'cc.table_name as `foreign_table`',
'string_agg(cc.column_name) as `foreign_columns`',
'rc.update_rule as `on_update`',
'rc.delete_rule as `on_delete`',
]),
'from information_schema.key_column_usage kc',
'join information_schema.referential_constraints rc on kc.constraint_name = rc.constraint_name',
'join information_schema.constraint_column_usage cc on kc.constraint_name = cc.constraint_name',
'where kc.table_schema = ""',
'and kc.table_name = ' . $this->quoteString($table),
'group by kc.constraint_name, cc.table_schema, cc.table_name, rc.update_rule, rc.delete_rule'
]);
}

/**
Expand Down
79 changes: 68 additions & 11 deletions tests/Schema/BuilderTestLast.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,35 +213,62 @@ public function test_getTables(): void
$table->primary('id');
});

/** @var array{ name: string, type: string } $row */
$row = Arr::first(
$sb->getTables(),
static fn(array $row): bool => $row['name'] === $table,
);
$row = Arr::first($sb->getTables(), fn ($row) => $row['name'] === $table);

$this->assertSame($table, $row['name']);
$this->assertSame([
'name' => $table,
'schema' => null,
'parent' => null,
'size' => null,
'comment' => null,
'collation' => null,
'engine' => null,
], $row);
}

public function test_getColumns(): void
public function test_getColumns_with_nullable(): void
{
$conn = $this->getDefaultConnection();
$sb = $conn->getSchemaBuilder();
$table = $this->generateTableName(class_basename(__CLASS__));

$sb->create($table, function (Blueprint $table) {
$table->uuid('id');
$table->primary('id');
$table->integer('id')->nullable()->primary();
});

$this->assertSame([
'name' => 'id',
'type_name' => 'INT64',
'type' => 'INT64',
'collation' => null,
'nullable' => true,
'default' => null,
'auto_increment' => false,
'comment' => null,
'generation' => null,
], Arr::first($sb->getColumns($table)));
}

public function test_getColumns_with_default(): void
{
$conn = $this->getDefaultConnection();
$sb = $conn->getSchemaBuilder();
$table = $this->generateTableName(class_basename(__CLASS__));

$sb->create($table, function (Blueprint $table) {
$table->string('id', 1)->default('a')->primary();
});

$this->assertSame([
'name' => 'id',
'type_name' => 'STRING',
'type' => 'STRING(36)',
'type' => 'STRING(1)',
'collation' => null,
'nullable' => false,
'default' => null,
'default' => '"a"',
'auto_increment' => false,
'comment' => null,
'generation' => null,
], Arr::first($sb->getColumns($table)));
}

Expand Down Expand Up @@ -305,6 +332,36 @@ public function test_getIndexListing(): void
], $sb->getIndexListing($table));
}

public function test_getForeignKeys(): void
{
$conn = $this->getDefaultConnection();
$sb = $conn->getSchemaBuilder();
$table1 = $this->generateTableName(class_basename(__CLASS__). '_1');
$sb->create($table1, function (Blueprint $table) {
$table->uuid('id')->primary();
$table->uuid('something');
$table->index('something');
});

$table2 = $this->generateTableName(class_basename(__CLASS__). '_2');
$sb->create($table2, function (Blueprint $table) use ($table1) {
$table->uuid('table2_id')->primary();
$table->uuid('other_id');
$table->index('other_id');
$table->foreign('other_id')->references('id')->on($table1);
});

$this->assertSame([[
'name' => strtolower($table2) . '_other_id_foreign',
'columns' => ['other_id'],
'foreign_schema' => '',
'foreign_table' => $table1,
'foreign_columns' => ['id'],
'on_update' => "no action",
'on_delete' => "no action",
]], $sb->getForeignKeys($table2));
}

public function test_dropAllTables(): void
{
$conn = $this->getDefaultConnection();
Expand Down
Loading