[columnName1, columnName2] * @var array */ public array $tableColumns = []; public function setTableColumns(array $tableColumns = []): self { $this->tableColumns = $tableColumns; return $this; } public function setDebug(bool $debug): self { $this->debug = $debug; return $this; } private function printDebugMessage(string $msg): void { echo $msg . PHP_EOL; } public function getCustomTypes(): ?array { $res = DB::select(" SELECT pg_type.typname AS enum, pg_enum.enumlabel AS enumlabel FROM pg_type JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid; "); $res = collect($res); $res = $res->groupBy('enum')->toArray(); if(empty($res)) return null; $struct = []; foreach($res as $enumName => $values) { if(!isset($struct[$enumName])) $struct[$enumName] = []; foreach($values as $enumData) { $struct[$enumName][] = $enumData->enumlabel; } } return $struct; } public function hasType(string $typeName): bool { $existance = current(DB::select("SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = ?) AS exist", [$typeName])); return $existance->exist; } public function addType(string $typeName, array $values) { if(!$this->hasType($typeName)) return $this->createType($typeName, $values); $currentValues = self::getTypeDefinition($typeName); $toRemove = array_diff($currentValues, $values); $toAdd = array_diff($values, $currentValues); # If values removed from the current definition, then we need to perform to migrate the enum if(count($toRemove)) return $this->migrateType($typeName, $values); # Add New values to enum if($toAdd) return $this->addValuesToType($typeName, $values); } public function createType(string $typeName, array $values) { if($this->hasType($typeName)) return $this->debug ? $this->printDebugMessage(sprintf('Type %s already exists', $typeName)) : null; $placeholders = implode(",", array_fill(0, count($values), "'%s'")); $query = sprintf("CREATE TYPE %s AS ENUM(".$placeholders .")", $typeName, ...$values); if($this->debug) $this->printDebugMessage($query); DB::unprepared($query); if($this->debug) $this->printDebugMessage(sprintf('Type %s created with values: %s', $typeName, implode(',', $values))); } public function migrateType(string $typeName, array $values): void { # Alter affected tables/columns to be text values foreach($this->tableColumns as $table => $columns) { foreach($columns as $column) { $columnToText = sprintf("ALTER TABLE %s ALTER %s TYPE text", $table, $column); DB::unprepared($columnToText); if($this->debug) $this->printDebugMessage($columnToText); } } # Drop the enum type $dropTypeQuery = sprintf('DROP TYPE %s', $typeName); DB::unprepared($dropTypeQuery); if($this->debug) $this->printDebugMessage($dropTypeQuery); # Create again the enum type with the new values $this->createType($typeName, $values); # Alter the tables/columns to be the desired type foreach($this->tableColumns as $table => $columns) { foreach($columns as $column) { $columnToEnum = sprintf("ALTER TABLE %s ALTER %s TYPE %s USING %s::%s;", $table, $column, $typeName, $column, $typeName); DB::unprepared($columnToEnum); if($this->debug) $this->printDebugMessage($columnToEnum); } } } public function addValuesToType(string $typeName, array $toAdd) { $currentValues = self::getTypeDefinition($typeName); foreach($toAdd as $v) { if(in_array($v, $currentValues)) continue; DB::unprepared(sprintf('ALTER TYPE %s ADD VALUE \'%s\'', $typeName, $v)); } } public function getTypeDefinition(string $typeName): array { $typeData = current(DB::select("SELECT * FROM pg_type WHERE typname = ?", [$typeName])); if(empty($typeData)) return []; return array_map( fn($data) => $data->enumlabel, DB::select("SELECT * FROM pg_enum WHERE enumtypid = ?", [$typeData->oid]) ); } public function typeHasValue(string $typeName, string $value, array $enumValues = []): bool { return in_array($value, empty($enumValues) ? self::getTypeDefinition($typeName) : $enumValues); } public function dropType(string $typeName) { DB::unprepared(sprintf('DROP TYPE IF EXISTS %s', $typeName)); } }