Skip to content
Merged
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
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
80 changes: 69 additions & 11 deletions tests/Schema/BuilderTestLast.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,35 +213,63 @@ 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,
'schema_qualified_name' => $table,
'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 +333,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