diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index c45ae4ae57ee..ed329af88049 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -45,6 +45,54 @@ public function processColumnListing($results) }, $results); } + /** + * Process the results of a types query. + * + * @param array $results + * @return array + */ + public function processTypes($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => $result->name, + 'schema' => $result->schema, + 'implicit' => (bool) $result->implicit, + 'type' => match (strtolower($result->type)) { + 'b' => 'base', + 'c' => 'composite', + 'd' => 'domain', + 'e' => 'enum', + 'p' => 'pseudo', + 'r' => 'range', + 'm' => 'multirange', + default => null, + }, + 'category' => match (strtolower($result->category)) { + 'a' => 'array', + 'b' => 'boolean', + 'c' => 'composite', + 'd' => 'date_time', + 'e' => 'enum', + 'g' => 'geometric', + 'i' => 'network_address', + 'n' => 'numeric', + 'p' => 'pseudo', + 'r' => 'range', + 's' => 'string', + 't' => 'timespan', + 'u' => 'user_defined', + 'v' => 'bit_string', + 'x' => 'unknown', + 'z' => 'internal_use', + default => null, + }, + ]; + }, $results); + } + /** * Process the results of a columns query. * diff --git a/src/Illuminate/Database/Query/Processors/Processor.php b/src/Illuminate/Database/Query/Processors/Processor.php index 7abf156ad662..9f0e3764957f 100755 --- a/src/Illuminate/Database/Query/Processors/Processor.php +++ b/src/Illuminate/Database/Query/Processors/Processor.php @@ -77,6 +77,17 @@ public function processViews($results) }, $results); } + /** + * Process the results of a types query. + * + * @param array $results + * @return array + */ + public function processTypes($results) + { + return $results; + } + /** * Process the results of a columns query. * diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 07698e7fc206..f7bf535a5d92 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -211,6 +211,16 @@ public function getViews() ); } + /** + * Get the user-defined types that belong to the database. + * + * @return array + */ + public function getTypes() + { + throw new LogicException('This database driver does not support user-defined types.'); + } + /** * Get all of the table names for the database. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 2995e339d0d5..c0a5f7d2a73e 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -101,6 +101,23 @@ public function compileViews() return 'select viewname as name, schemaname as schema, definition from pg_views order by viewname'; } + /** + * Compile the query to determine the user-defined types. + * + * @return string + */ + public function compileTypes() + { + return 'select t.typname as name, n.nspname as schema, t.typtype as type, t.typcategory as category, ' + ."((t.typinput = 'array_in'::regproc and t.typoutput = 'array_out'::regproc) or t.typtype = 'm') as implicit " + .'from pg_type t join pg_namespace n on n.oid = t.typnamespace ' + .'left join pg_class c on c.oid = t.typrelid ' + .'left join pg_type el on el.oid = t.typelem ' + .'left join pg_class ce on ce.oid = el.typrelid ' + ."where ((t.typrelid = 0 and (ce.relkind = 'c' or ce.relkind is null)) or c.relkind = 'c') " + ."and n.nspname not in ('pg_catalog', 'information_schema')"; + } + /** * Compile the SQL needed to retrieve all table names. * @@ -473,9 +490,22 @@ public function compileDropAllTypes($types) return 'drop type '.implode(',', $this->escapeNames($types)).' cascade'; } + /** + * Compile the SQL needed to drop all domains. + * + * @param array $domains + * @return string + */ + public function compileDropAllDomains($domains) + { + return 'drop domain '.implode(',', $this->escapeNames($domains)).' cascade'; + } + /** * Compile the SQL needed to retrieve all type names. * + * @deprecated Will be removed in a future Laravel version. + * * @return string */ public function compileGetAllTypes() diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index 0efe5dc62161..20d23817537a 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -53,6 +53,18 @@ public function hasTable($table) )) > 0; } + /** + * Get the user-defined types that belong to the database. + * + * @return array + */ + public function getTypes() + { + return $this->connection->getPostProcessor()->processTypes( + $this->connection->selectFromWriteConnection($this->grammar->compileTypes()) + ); + } + /** * Get all of the table names for the database. * @@ -151,6 +163,8 @@ public function dropAllViews() /** * Get all of the type names for the database. * + * @deprecated Will be removed in a future Laravel version. + * * @return array */ public function getAllTypes() @@ -168,20 +182,27 @@ public function getAllTypes() public function dropAllTypes() { $types = []; + $domains = []; - foreach ($this->getAllTypes() as $row) { - $row = (array) $row; + $schemas = $this->grammar->escapeNames($this->getSchemas()); - $types[] = reset($row); + foreach ($this->getTypes() as $type) { + if (! $type['implicit'] && in_array($this->grammar->escapeNames([$type['schema']])[0], $schemas)) { + if ($type['type'] === 'domain') { + $domains[] = $type['schema'].'.'.$type['name']; + } else { + $types[] = $type['schema'].'.'.$type['name']; + } + } } - if (empty($types)) { - return; + if (! empty($types)) { + $this->connection->statement($this->grammar->compileDropAllTypes($types)); } - $this->connection->statement( - $this->grammar->compileDropAllTypes($types) - ); + if (! empty($domains)) { + $this->connection->statement($this->grammar->compileDropAllDomains($domains)); + } } /** diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 1bf87ba9a151..4fed4dd8166b 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -14,6 +14,7 @@ * @method static bool hasView(string $view) * @method static array getTables() * @method static array getViews() + * @method static array getTypes() * @method static bool hasColumn(string $table, string $column) * @method static bool hasColumns(string $table, array $columns) * @method static void whenTableHasColumn(string $table, string $column, \Closure $callback) diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 2e79303b0574..3bd0fc313c14 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -182,6 +182,33 @@ public function testGetViews() $this->assertEmpty(array_diff(['foo', 'bar', 'baz'], array_column($views, 'name'))); } + public function testGetAndDropTypes() + { + if ($this->driver !== 'pgsql') { + $this->markTestSkipped('Test requires a PostgreSQL connection.'); + } + + DB::statement('create type pseudo_foo'); + DB::statement('create type comp_foo as (f1 int, f2 text)'); + DB::statement("create type enum_foo as enum ('new', 'open', 'closed')"); + DB::statement('create type range_foo as range (subtype = float8)'); + DB::statement('create domain domain_foo as text'); + + $types = Schema::getTypes(); + + $this->assertCount(11, $types); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'pseudo_foo' && $type['type'] === 'pseudo' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'comp_foo' && $type['type'] === 'composite' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'enum_foo' && $type['type'] === 'enum' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'range_foo' && $type['type'] === 'range' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'domain_foo' && $type['type'] === 'domain' && ! $type['implicit'])); + + Schema::dropAllTypes(); + $types = Schema::getTypes(); + + $this->assertEmpty($types); + } + public function testGetIndexes() { Schema::create('foo', function (Blueprint $table) {