Работа вокруг ошибки perl DBD::mysql UTF-8

У нас есть программное обеспечение, написанное на Perl, которое извлекает данные из базы данных MySQL. Для этого мы используем интерфейс DBD::mysql

Мы можем получить все данные правильно, база данных - это UTF8MB4, а приложение perl использует UTF-8.

Код для получения результата sql:

use utf8;
use encoding 'utf8';

...
my $dsn = "DBI:mysql:database=mydatabase;mysql_enable_utf8=1";
my $dbh = DBI->connect($dsn, $userid, $password, { mysql_enable_utf8 => 1 } ) or die $DBI::errstr;

...

my $sth = $dbh->prepare("SELECT addressid, 
                            company, firstname, lastname, 
                            address, zip, city, country,
                            phone, mobile, home,
                            speeddial_phone, speeddial_mobile, speeddial_home,
                            fax, email
                        FROM address
                        WHERE (firstname like ? or lastname like ? or company like ?)
                        LIMIT $sizeLimit
                        ");
$sth->execute(  $searchExpression, $searchExpression, $searchExpression) or die $DBI::errstr;

Пока $searchExpression содержит нормальные символы, он работает нормально. Но как только мы запрашиваем специальные символы, не входящие в ASCII, например, é ö ä ü и подобные, мы не получаем пустой набор результатов обратно.

Согласно этому посту, это связано с ошибкой в ​​драйверах dbd::mysql до версии 4.041_01

http://blogs.perl.org/users/mike_b/2016/12/dbdmysql-all-your-utf-8-bugs-are-belong-to-us.html

Я проверял разные вещи, но безрезультатно.

Я включил ведение журнала запросов на сервере mysql, и там я вижу, что параметры со специальными символами вводятся в неправильной кодировке.

Вот вывод лога mysql в файл:

Time                 Id Command    Argument
180905  9:17:06   403 Connect   inno-ldap-db@localhost on phonebook_innovaphone
                  403 Query     SELECT addressid,
                                company, firstname, lastname,
                                address, zip, city, country,
                                phone, mobile, home,
                                speeddial_phone, speeddial_mobile, speeddial_home,
                                fax, email
                            FROM address
                            WHERE companyid='1' and (firstname like 'andré%' or lastname like 'andré%' or company like 'andré%' )
                            LIMIT 25
                  403 Quit

Поскольку в настоящее время мы не можем обновить систему (это Debian 7, который включает в себя только более старые пакеты, такие как 4.021-1+deb7u3), мне нужно будет решить эту проблему.

Или какое-то волшебство для предварительного кодирования / декодирования параметров, или драйвер odbc, возможно, не столкнется с этой ошибкой?

3 ответа

Чтобы уточнить детали: DBD::mysql не может знать, какую кодировку ожидает сервер для параметров привязки. Вдобавок у него та же ошибка Unicode, что и у многих модулей CPAN: он игнорирует кодировку хранилища, которую использует Perl, и просто смотрит на внутренние биты и байты, поэтому одна и та же строка в Perl будет выводиться правильно, иногда ошибочно, что является корнем о путанице, которую испытывают многие люди: результаты не имеют смысла, потому что внутреннее кодирование строк не является чем-то, что обычно раскрывается на уровне Perl.

Пожалуй, правильный способ справиться с этим - попросить пользователя пометить каждый аргумент как двоичный (для столбцов blob) и что-то еще - это то, что делает - он предполагает, что все является текстом, если вы его не переопределите, и, следовательно, вы выиграли. t столкнуться с той же проблемой с ним, за счет необходимости проделать дополнительную работу с двоичными значениями.

Вы можете детерминированно справиться с этим в а также вот как:

При подключении включите флаг / attr. Вы не должны делать это позже, но последовательность, указанная в вопросе, также должна работать. Вы также должны использовать кодировку для вашей базы данных / таблиц / текстовых столбцов.

Это должно надежно работать с данными, считываемыми из базы данных, поскольку mysql/mariadb будет правильно помечать текст как текст (включая его кодировку), а двоичный файл как двоичный.

Остается проблема, что делать при отправке данных. Это просто: при отправке текста убедитесь, что он находится в юникоде (что означает, что он не закодирован в utf-8, поскольку Perl может напрямую представлять символы юникода). Затем вы можете либо обновить (не меняет значение строки в perl, либо кодировать строку в utf8 (что меняет значение в perl):

      # when you have a unicode text string
utf8::upgrade $text_column; # do this
utf8::encode $text_column; # OR that

Любой из них гарантирует, что внутреннее представление строки Perl совместимо с utf-8, чего и ожидает база данных. Первый вариант, вероятно, предпочтительнее, поскольку он продолжает работать, если драйвер базы данных когда-либо будет исправлен (или когда вы переключитесь на )

Альтернативный вариант, если ваши данные уже закодированы в utf-8, - это перейти на более раннюю версию:

      # when you have an utf-8 encoded binary string
utf8::downgrade $text_column;

Для BLOB / бинарного столбцов, вы должны убедиться , что внутреннее представление Perl является не в UTF-8. Вы можете убедиться в этом, используя :

      # when you have BLOB data
utf8::downgrade $blob_column;

Это также не меняет значения строки в Perl, но поскольку DBD::mysql не заботится о том, что думает Perl, он поступит правильно и отправит данные в двоичном формате.

К сожалению, эта версия не совместима с будущим - если вы переключитесь на другой исправный драйвер базы данных, вам, возможно, придется работать с данными BLOB по-другому. Один из таких способов - пометить параметр как и выполните возврат к предыдущей версии, которая должна работать с правильными драйверами.

Теперь немного истории.

Путаница в модулях Perl и CPAN, по моему личному мнению, коренится в нескольких разных вещах. Во-первых, при первой реализации юникода в Perl люди осознали, что модель юникода, которую они впервые представили для Perl, не работает должным образом, и захотели ее изменить. К сожалению, книга Camel для документирования Perl уже была обновлена, поэтому они не изменили ее, чтобы она оставалась совместимой с книгой, с некоторыми вялыми исправлениями. В результате perl больше не был полностью совместим с книгой, а также не полностью совместим с правильной моделью. За последние годы ситуация улучшилась, но на это потребовалось много времени, и многие жертвы остались на дороге.

Во-вторых, многие справочные страницы, такие как , являются совершенно неправильными и увековечивают неверные предположения, что perl знает о кодировке своих символьных строк, или что так называемый «utf8-flag» имеет какое-то отношение к тому, что строка находится в Unicode и / или utf-8. Ни то, ни другое не верно.

И, наконец, XS API был изменен несовместимо: чтобы получить символьную строку sina в pre-unicode perls, вы должны вызвать вызываемую функцию, которая просто возвращает указатель на символы, все из которых могут быть сохранены в одном байте. время, т.е. двоичное

Вместо того, чтобы сохранять это таким образом, более новые версии perl продолжали возвращать внутреннее байтовое представление строки, поэтому модули, которые не были обновлены, будут получать необработанные данные, не имея возможности правильно их интерпретировать, потому что не возвращает достаточно информации для декодирования данных.

Таким образом, за ночь все модули Perl, работающие со строковыми данными, приобрели Perl Unicode Bug(tm), и не все из них были исправлены. И их исправление сейчас может привести к нарушению работы программ, которые каким-то образом помогли решить эти проблемы.

Оказалось, что строка (полученная через Net::LDAP::Server) уже была в какой-то кодировке utf8, а затем драйвер mysql снова ее кодировал.

Решил проблему, добавив этот код

use Encode qw( decode );
my $decoded = eval { decode('UTF-8', $encoded, Encode::FB_CROAK) }

Код взят из этого поста: Правильный способ обнаружения кодирования в Perl

Спасибо за подсказку о двойном кодировании в мосвы

Чтобы Perl работал со страницами utf8 и сопоставлением mysql, вам нужно установить utf8mb4 в полях mysql, установить атрибут mysql_enable_utf8mb4 в соединении DBI и выполнить sql-запрос «SET NAMES utf8mb4» после соединения с базой данных.

      #!/usr/bin/perl
print "Content-type: text/html; charset=UTF-8\n\n";

#use utf8;
#use open ':utf8';
#binmode STDOUT, ":utf8";
#binmode STDIN , ":utf8";
#use encoding 'utf8';

our $dbh = DBI->connect("DBI:mysql:database=$database;host=$servername;port=$port",$username,$password, {PrintWarn => 0, PrintError => 0, mysql_enable_utf8mb4 => 1}) || die;
$dbh->do("SET NAMES utf8mb4");
...
Другие вопросы по тегам