Skip to content
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

[10.x] Get user-defined types on PostgreSQL #49303

Merged
merged 6 commits into from
Dec 12, 2023
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
48 changes: 48 additions & 0 deletions src/Illuminate/Database/Query/Processors/PostgresProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
11 changes: 11 additions & 0 deletions src/Illuminate/Database/Query/Processors/Processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
10 changes: 10 additions & 0 deletions src/Illuminate/Database/Schema/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
30 changes: 30 additions & 0 deletions src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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()
Expand Down
37 changes: 29 additions & 8 deletions src/Illuminate/Database/Schema/PostgresBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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()
Expand All @@ -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));
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/Illuminate/Support/Facades/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 27 additions & 0 deletions tests/Integration/Database/SchemaBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down