Работа вокруг ошибки 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 столкнуться с той же проблемой с ним, за счет необходимости проделать дополнительную работу с двоичными значениями.
Вы можете детерминированно справиться с этим в
При подключении включите
Это должно надежно работать с данными, считываемыми из базы данных, поскольку 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 больше не был полностью совместим с книгой, а также не полностью совместим с правильной моделью. За последние годы ситуация улучшилась, но на это потребовалось много времени, и многие жертвы остались на дороге.
Во-вторых, многие справочные страницы, такие как
И, наконец, 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");
...