diff --git a/CHANGELOG.md b/CHANGELOG.md index 082c6754..7cdf774c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ # v6.2.0 (Not Released Yet) Added -- `json` `mediumText` `longText` support for `Schema\Builder` (#155) +- `json` `mediumText` `longText` `char` support for `Schema\Builder` (#155) (#158) - `Schema\Grammar::compileDropForeign` to allow dropping foreign key constraints (#163) +- `Schema\Builder::dropAllTables` works properly, dropping foreign keys, indexes, then tables in order of interleaving (#161) Changed - `Query\Builder::lock()` no longer throw an error and will be ignored instead (#156) +- `Schema\Builder::getIndexListing()` `Schema\Grammar::compileIndexListing()` converted to `getIndexes()` and `compileIndexes()` to align with standard Laravel methods (#161) Fixed - `Schema\Grammar::compileAdd()` `Schema\Grammar::compileChange()` `Schema\Grammar::compileChange()` now create separate statements (#159) diff --git a/composer.json b/composer.json index 268654f1..99d4d42f 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "php": "^8.1", "ext-grpc": "*", "ext-json": "*", - "laravel/framework": "^10.34.2", + "laravel/framework": "^10.37.0", "google/cloud-spanner": "^1.58.4", "grpc/grpc": "^1.42", "symfony/cache": "~6", diff --git a/phpunit.xml b/phpunit.xml index 274c1c37..8426cc50 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -27,6 +27,7 @@ stopOnFailure="true"> ./tests + ./tests diff --git a/src/Query/Processor.php b/src/Query/Processor.php index ec9106ec..589c95ca 100644 --- a/src/Query/Processor.php +++ b/src/Query/Processor.php @@ -78,13 +78,34 @@ public function processColumns($results) } /** + * @deprecated Use processIndexes($results) instead. * @param array $results * @return array */ public function processIndexListing($results) + { + return self::processIndexes($results); + } + + /** + * @param array $results + * @return array + */ + public function processIndexes($results) { return array_map(function ($result) { return ((object) $result)->index_name; }, $results); } + + /** + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + return array_map(function ($result) { + return ((object) $result)->key_name; + }, $results); + } } diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php index 1e922692..cc40b360 100644 --- a/src/Schema/Builder.php +++ b/src/Schema/Builder.php @@ -19,7 +19,9 @@ use Closure; use Colopl\Spanner\Query\Processor; +use Colopl\Spanner\Connection; use Illuminate\Database\Schema\Builder as BaseBuilder; +use Illuminate\Support\Fluent; /** * @property Grammar $grammar @@ -47,21 +49,26 @@ public function getAllTables() } /** + * @inheritDoc Adds a parent key, for tracking interleaving + * + * @return list + */ + public function getTables() + { + return $this->connection->select( + $this->grammar->compileTables() + ); + } + + /** + * @deprecated Use getIndexes($table) instead + * * @param string $table * @return string[] */ public function getIndexListing($table) { - $table = $this->connection->getTablePrefix().$table; - - $results = $this->connection->select( - $this->grammar->compileIndexListing(), [$table] - ); - - /** @var Processor $processor */ - $processor = $this->connection->getPostProcessor(); - - return $processor->processIndexListing($results); + return parent::getIndexes($table); } /** @@ -83,7 +90,7 @@ public function dropIndex($table, $name) */ public function dropIndexIfExist($table, $name) { - if(in_array($name, $this->getIndexListing($table))) { + if(in_array($name, $this->getIndexes($table))) { $blueprint = $this->createBlueprint($table); $blueprint->dropIndex($name); $this->build($blueprint); @@ -99,4 +106,65 @@ protected function createBlueprint($table, Closure $callback = null) ? ($this->resolver)($table, $callback) : new Blueprint($table, $callback); } + + /** + * Drop all tables from the database. + * + * @return void + */ + public function dropAllTables() + { + /** @var Connection */ + $connection = $this->connection; + $tables = $this->getTables(); + $sortedTables = []; + + // add parents counter + foreach ($tables as $table) { + $sortedTables[$table['name']] = ['parents' => 0, ...$table]; + } + + // loop through all tables and count how many parents they have + foreach ($sortedTables as $key => $table) { + if(!$table['parent']) continue; + + $current = $table; + while($current['parent']) { + $table['parents'] += 1; + $current = $sortedTables[$current['parent']]; + } + $sortedTables[$key] = $table; + } + + // sort tables desc based on parent count + usort($sortedTables, fn($a, $b) => $b['parents'] <=> $a['parents']); + + // drop foreign keys first (otherwise index queries will include them) + $queries = []; + foreach ($sortedTables as $tableData) { + $tableName = $tableData['name']; + $foreigns = $this->getForeignKeys($tableName); + $blueprint = $this->createBlueprint($tableName); + foreach ($foreigns as $foreign) { + $blueprint->dropForeign($foreign); + } + array_push($queries, ...$blueprint->toSql($connection, $this->grammar)); + } + $connection->runDdlBatch($queries); + + // drop indexes and tables + $queries = []; + foreach ($sortedTables as $tableData) { + $tableName = $tableData['name']; + $indexes = $this->getIndexes($tableName); + $blueprint = $this->createBlueprint($tableName); + foreach ($indexes as $index) { + if($index == 'PRIMARY_KEY') continue; + $blueprint->dropIndex($index); + } + $blueprint->drop(); + array_push($queries, ...$blueprint->toSql($connection, $this->grammar)); + } + $connection->runDdlBatch($queries); + } } diff --git a/src/Schema/Grammar.php b/src/Schema/Grammar.php index 6a926061..f1567e0f 100644 --- a/src/Schema/Grammar.php +++ b/src/Schema/Grammar.php @@ -58,7 +58,7 @@ public function compileTableExists() */ public function compileTables() { - return 'select `table_name` as name from information_schema.tables where table_schema = \'\' and table_type = \'BASE TABLE\''; + 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\''; } /** @@ -86,6 +86,8 @@ public function compileColumnListing() /** * Compile the query to determine the list of indexes. * + * @deprecated Use compileIndexes($table) instead. + * * @return string */ public function compileIndexListing() @@ -93,6 +95,34 @@ public function compileIndexListing() return 'select index_name as `index_name` from information_schema.indexes where table_schema = \'\' and table_name = ?'; } + /** + * Compile the query to determine the list of indexes. + * + * @param string $table + * @return string + */ + public function compileIndexes($table) + { + return sprintf( + 'select index_name as `index_name` from information_schema.indexes where table_schema = \'\' and table_name = %s', + $this->quoteString($table) + ); + } + + /** + * Compile the query to determine the list of foreign keys. + * + * @param string $table + * @return string + */ + public function compileForeignKeys($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. * diff --git a/tests/Schema/BuilderTest.php b/tests/Schema/BuilderTestLast.php similarity index 83% rename from tests/Schema/BuilderTest.php rename to tests/Schema/BuilderTestLast.php index 73b91d3f..ceaebe77 100644 --- a/tests/Schema/BuilderTest.php +++ b/tests/Schema/BuilderTestLast.php @@ -23,7 +23,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Str; -class BuilderTest extends TestCase +class BuilderTestLast extends TestCase { private const TABLE_NAME_CREATED = 'schema_builder_test_table'; private const TABLE_NAME_RELATION_PARENT = 'users'; @@ -243,11 +243,53 @@ public function test_getAllTables(): void /** @var array{ name: string, type: string } $row */ $row = Arr::first( - $sb->getAllTables(), + $sb->getTables(), static fn (array $row): bool => $row['name'] === $table, ); $this->assertSame($table, $row['name']); $this->assertSame('BASE TABLE', $row['type']); } + + public function test_dropAllTables(): void + { + $conn = $this->getDefaultConnection(); + $sb = $conn->getSchemaBuilder(); + $table1 = $this->generateTableName(class_basename(__CLASS__)); + $sb->create($table1, function (Blueprint $table) { + $table->uuid('id')->primary(); + $table->uuid('something'); + $table->index('something'); + }); + + $table2 = $this->generateTableName(class_basename(__CLASS__)); + $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); + }); + + $table3 = $this->generateTableName(class_basename(__CLASS__)); + $sb->create($table3, function (Blueprint $table) use ($table2) { + $table->uuid('table2_id'); + $table->uuid('table3_id'); + $table->primary(['table2_id', 'table3_id']); + $table->interleaveInParent($table2); + }); + + $table4 = $this->generateTableName(class_basename(__CLASS__)); + $sb->create($table4, function (Blueprint $table) use ($table3) { + $table->uuid('table2_id'); + $table->uuid('table3_id'); + $table->uuid('table4_id'); + $table->primary(['table2_id', 'table3_id', 'table4_id']); + $table->interleaveInParent($table3); + }); + + $sb->dropAllTables(); + + $tables = $sb->getTables(); + $this->assertEmpty($tables); + } }