Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Aktualizacja Laravel 5.4, konwersja do utf4mb z utf8

W porządku, napisałem migrację, aby osiągnąć to w moim własnym systemie.

  • Pozwala opcjonalnie określić nazwę połączenia, aby odwoływać się do połączenia innego niż domyślne.

  • Pobiera listę tabel z bazy danych połączenia za pomocą polecenia SHOW TABLES zapytanie.

  • Następnie przechodzi przez każdą tabelę w pętli i aktualizuje wszystkie kolumny typu ciąg/znak do nowego zestawu znaków i sortowania.

  • Zrobiłem to tak, że musi być dostarczone wywołanie zwrotne, aby określić, czy kolumna powinna mieć zmienioną długość na podaną nową długość. W mojej implementacji VARCHAR i CHAR kolumny o długości większej niż 191 są aktualizowane do długości 191 podczas migracji w górę i VARCHAR i CHAR kolumny o długości dokładnie 191 są aktualizowane do długości 255 podczas migracji wstecznej/w dół.

  • Po zaktualizowaniu wszystkich kolumn napisów/znaków, kilka zapytań zostanie uruchomionych w celu zmiany zestawu znaków i kolacji tabeli, konwersji wszelkich pozostałych kolacji na nowe, a następnie zmiany domyślnego zestawu znaków i kolacji tabeli.

  • Na koniec domyślny zestaw znaków i sortowanie bazy danych zostaną zmienione.

Notatki

  • Początkowo próbowałem po prostu przekonwertować tabele na nowe kodowanie, ale napotkałem problemy z długościami kolumn. 191 znaków to maksymalna długość znaków w utf8mb4 podczas używania InnoDB w mojej wersji MySQL/MariaDB i zmiana sortowania tabeli spowodowała błąd.

  • Na początku chciałem tylko zaktualizować długości do nowej długości, ale chciałem również zapewnić funkcję wycofywania, więc nie było to możliwe, ponieważ w odwrotnej metodzie ustawiałbym długości kolumn, które byłyby utf8mb4 do 255, co byłoby zbyt długie, więc zdecydowałem się również na zmianę sortowania.

  • Następnie próbowałem po prostu zmienić długość, zestaw znaków i sortowanie varchar i char kolumny, które były zbyt długie, ale w moim systemie powodowało to błędy, gdy miałem indeksy wielokolumnowe, które zawierały takie kolumny. Najwyraźniej indeksy wielokolumnowe muszą używać tego samego sortowania.

  • Ważna uwaga na tym polega to, że migracja odwrotna/w dół nie będzie w 100% idealna dla wszystkich. Nie sądzę, że byłoby to możliwe bez przechowywania dodatkowych informacji o oryginalnych kolumnach podczas migracji. Tak więc moja obecna implementacja migracji wstecznej/w dół zakłada, że ​​kolumny o długości 191 miały pierwotnie 255.

  • Podobnie ważna uwaga na tym polega to, że spowoduje to ślepą zmianę sortowania wszystkich kolumn ciągów/znaków na nowe sortowanie, niezależnie od pierwotnego sortowania, więc jeśli istnieją kolumny z różnymi sortowaniami, wszystkie zostaną przekonwertowane na nowe i zrobi to na odwrót to samo, oryginały nie zostaną zachowane.

<?php

use Illuminate\Database\Migrations\Migration;

class UpgradeDatabaseToUtf8mb4 extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $this->changeDatabaseCharacterSetAndCollation('utf8mb4', 'utf8mb4_unicode_ci', 191, function ($column) {
            return $this->isStringTypeWithLength($column) && $column['type_brackets'] > 191;
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        $this->changeDatabaseCharacterSetAndCollation('utf8', 'utf8_unicode_ci', 255, function ($column) {
            return $this->isStringTypeWithLength($column) && $column['type_brackets'] == 191;
        });
    }

    /**
     * Change the database referred to by the connection (null is the default connection) to the provided character set
     * (e.g. utf8mb4) and collation (e.g. utf8mb4_unicode_ci). It may be necessary to change the length of some fixed
     * length columns such as char and varchar to work with the new encoding. In which case the new length of such
     * columns and a callback to determine whether or not that particular column should be altered may be provided. If a
     * connection other than the default connection is to be changed, the string referring to the connection may be
     * provided as the last parameter (This string will be passed to DB::connection(...) to retrieve an instance of that
     * connection).
     *
     * @param string       $charset
     * @param string       $collation
     * @param null|int     $newColumnLength
     * @param Closure|null $columnLengthCallback
     * @param string|null  $connection
     */
    protected function changeDatabaseCharacterSetAndCollation($charset, $collation, $newColumnLength = null, $columnLengthCallback = null, $connection = null)
    {
        $tables = $this->getTables($connection);

        foreach ($tables as $table) {
            $this->updateColumnsInTable($table, $charset, $collation, $newColumnLength, $columnLengthCallback, $connection);
            $this->convertTableCharacterSetAndCollation($table, $charset, $collation, $connection);
        }

        $this->alterDatabaseCharacterSetAndCollation($charset, $collation, $connection);
    }

    /**
     * Get an instance of the database connection provided with an optional string referring to the connection. This
     * should be null if referring to the default connection.
     *
     * @param string|null $connection
     *
     * @return \Illuminate\Database\Connection
     */
    protected function getDatabaseConnection($connection = null)
    {
        return DB::connection($connection);
    }

    /**
     * Get a list of tables on the provided connection.
     *
     * @param null $connection
     *
     * @return array
     */
    protected function getTables($connection = null)
    {
        $tables = [];

        $results = $this->getDatabaseConnection($connection)->select('SHOW TABLES');
        foreach ($results as $result) {
            foreach ($result as $key => $value) {
                $tables[] = $value;
                break;
            }
        }

        return $tables;
    }

    /**
     * Given a stdClass representing the column, extract the required information in a more accessible format. The array
     * returned will contain the field name, the type of field (Without the length), the length where applicable (or
     * null), true/false indicating the column allowing null values and the default value.
     *
     * @param stdClass $column
     *
     * @return array
     */
    protected function extractInformationFromColumn($column)
    {
        $type = $column->Type;
        $typeBrackets = null;
        $typeEnd = null;

        if (preg_match('/^([a-z]+)(?:\\(([^\\)]+?)\\))?(.*)/i', $type, $matches)) {
            $type = strtolower(trim($matches[1]));

            if (isset($matches[2])) {
                $typeBrackets = trim($matches[2]);
            }

            if (isset($matches[3])) {
                $typeEnd = trim($matches[3]);
            }
        }

        return [
            'field' => $column->Field,
            'type' => $type,
            'type_brackets' => $typeBrackets,
            'type_end' => $typeEnd,
            'null' => strtolower($column->Null) == 'yes',
            'default' => $column->Default,
            'charset' => is_string($column->Collation) && ($pos = strpos($column->Collation, '_')) !== false ? substr($column->Collation, 0, $pos) : null,
            'collation' => $column->Collation
        ];
    }

    /**
     * Tell if the provided column is a string/character type and needs to have it's charset/collation changed.
     *
     * @param string $column
     *
     * @return bool
     */
    protected function isStringType($column)
    {
        return in_array(strtolower($column['type']), ['char', 'varchar', 'tinytext', 'text', 'mediumtext', 'longtext', 'enum', 'set']);
    }

    /**
     * Tell if the provided column is a string/character type with a length.
     *
     * @param string $column
     *
     * @return bool
     */
    protected function isStringTypeWithLength($column)
    {
        return in_array(strtolower($column['type']), ['char', 'varchar']);
    }

    /**
     * Update all of the string/character columns in the database to be the new collation. Additionally, modify the
     * lengths of those columns that have them to be the newLength provided, when the shouldUpdateLength callback passed
     * returns true.
     *
     * @param string        $table
     * @param string        $charset
     * @param string        $collation
     * @param int|null      $newLength
     * @param Closure|null  $shouldUpdateLength
     * @param string|null   $connection
     */
    protected function updateColumnsInTable($table, $charset, $collation, $newLength = null, Closure $shouldUpdateLength = null, $connection = null)
    {
        $columnsToChange = [];

        foreach ($this->getColumnsFromTable($table, $connection) as $column) {
            $column = $this->extractInformationFromColumn($column);

            if ($this->isStringType($column)) {
                $sql = "CHANGE `%field%` `%field%` %type%%brackets% CHARACTER SET %charset% COLLATE %collation% %null% %default%";
                $search = ['%field%', '%type%', '%brackets%', '%charset%', '%collation%', '%null%', '%default%'];
                $replace = [
                    $column['field'],
                    $column['type'],
                    $column['type_brackets'] ? '(' . $column['type_brackets'] . ')' : '',
                    $charset,
                    $collation,
                    $column['null'] ? 'NULL' : 'NOT NULL',
                    is_null($column['default']) ? ($column['null'] ? 'DEFAULT NULL' : '') : 'DEFAULT \'' . $column['default'] . '\''
                ];

                if ($this->isStringTypeWithLength($column) && $shouldUpdateLength($column) && is_int($newLength) && $newLength > 0) {
                    $replace[2] = '(' . $newLength . ')';
                }

                $columnsToChange[] = trim(str_replace($search, $replace, $sql));
            }
        }

        if (count($columnsToChange) > 0) {
            $query = "ALTER TABLE `{$table}` " . implode(', ', $columnsToChange);

            $this->getDatabaseConnection($connection)->update($query);
        }
    }

    /**
     * Get a list of all the columns for the provided table. Returns an array of stdClass objects.
     *
     * @param string      $table
     * @param string|null $connection
     *
     * @return array
     */
    protected function getColumnsFromTable($table, $connection = null)
    {
        return $this->getDatabaseConnection($connection)->select('SHOW FULL COLUMNS FROM ' . $table);
    }

    /**
     * Convert a table's character set and collation.
     *
     * @param string      $table
     * @param string      $charset
     * @param string      $collation
     * @param string|null $connection
     */
    protected function convertTableCharacterSetAndCollation($table, $charset, $collation, $connection = null)
    {
        $query = "ALTER TABLE {$table} CONVERT TO CHARACTER SET {$charset} COLLATE {$collation}";
        $this->getDatabaseConnection($connection)->update($query);

        $query = "ALTER TABLE {$table} DEFAULT CHARACTER SET {$charset} COLLATE {$collation}";
        $this->getDatabaseConnection($connection)->update($query);
    }

    /**
     * Change the entire database's (The database represented by the connection) character set and collation.
     *
     * # Note: This must be done with the unprepared method, as PDO complains that the ALTER DATABASE command is not yet
     *         supported as a prepared statement.
     *
     * @param string      $charset
     * @param string      $collation
     * @param string|null $connection
     */
    protected function alterDatabaseCharacterSetAndCollation($charset, $collation, $connection = null)
    {
        $database = $this->getDatabaseConnection($connection)->getDatabaseName();

        $query = "ALTER DATABASE {$database} CHARACTER SET {$charset} COLLATE {$collation}";

        $this->getDatabaseConnection($connection)->unprepared($query);
    }
}

Proszę, proszę, utwórz kopię zapasową bazy danych przed uruchomieniem tego . Używaj na własne ryzyko!



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak importować i eksportować bazę danych MySQL

  2. jak wybrać licznik z głównego zapytania do podzapytania

  3. Kod błędu 1292 - Obcięta niepoprawna PODWÓJNA wartość - Mysql

  4. Doktryna 2 z wieloma indeksami

  5. Jak wygenerować 5 liczb losowych w procedurze składowanej mysql