получение длины строки utf8mb4 с помощью Perl из MySql

Я написал небольшую функцию на Perl, которая берет строку и проверяет ее длину без пробелов. основной код выглядит следующим образом:

sub foo
{
   use utf8;
   my @wordsArray = split(/ /, $_[0]));
   my $result = length(join('', @wordsArray));
   return $result;
}

Когда я предоставляю этой функции строку, содержащую специальные символы (например, буквы иврита), она отлично работает. проблема начинается, когда я использую значение из столбца MySql с набором символов utf8mb4: в таком случае вычисляемое значение выше, чем значение в предыдущем примере.

Я догадываюсь, почему происходит такое поведение: спецсимволы в таблице записаны в 4-байтовом формате, и, таким образом, каждая буква считается как два символа в кодировке utf8.

Кто-нибудь знает, как можно решить вышеуказанное, чтобы я получил правильное количество символов из строки, исходящей из таблицы БД, определенной как utf8mb4?

ИЗМЕНИТЬ:

Еще немного информации о приведенном выше коде:

Столбец БД, используемый в качестве аргумента функции, имеет тип VARCHAR(1000) с сопоставлением utf8mb4_unicode_ci. Я извлекаю строку через соединение MySql, настроенное следующим образом:

$mySql = DBI->connect(
  "DBI:mysql:$db_info{'database'}:$db_info{'hostname'};mysql_multi_statements=1;",
  "$db_info{'user'}",
  "$db_info{'password'}",
  {'RaiseError' => 1,'AutoCommit' => 0});
...
$mySql->do("set names utf8mb4");

пример значения данных будет "שלום עולם" (что на иврите означает "Hello World").

1) При вызове foo($request->{VALUE}); (где ЗНАЧЕНИЕ — данные столбца из БД) результат равен 16 (где каждый символ иврита считается за два символа, а один пробел между ними не учитывается). Самосвал в данном случае это:

$VAR1 = "\327\251\327\234\327\225\327\235 \327\242\327\225\327\234\327\235";

2) При вызове foo("שלום עולם");:

  • при объявлении use utf8; результатом будет 8 (поскольку в этой строке 8 видимых символов). Дампер (Useqq=1) в этом случае:

    $VAR1 = "\x{5e9}\x{5dc}\x{5d5}\x{5dd} \x{5e2}\x{5d5}\x{5dc}\x{5dd}";

  • если не объявлять `use utf8;', результат равен 16 и аналогичен случаю отправки значения из БД:

    $VAR1 = "\327\251\327\234\327\225\327\235 \327\242\327\225\327\234\327\235";

Похоже, мне нужно найти способ преобразования полученного значения в UTF8, прежде чем начать с ним работать.


person chikko    schedule 17.05.2015    source источник
comment
Лучше писать как sub foo { $_[0] =~ tr/ //c; }   -  person Borodin    schedule 17.05.2015
comment
спасибо, но это не очень помогает мне сейчас, не так ли? :)   -  person chikko    schedule 17.05.2015
comment
Вот почему это комментарий, а не решение   -  person Borodin    schedule 17.05.2015
comment
Подобные умные комментарии только уменьшат ваши шансы на получение полезной помощи. Здесь никому не платят за написание решения для вас.   -  person Borodin    schedule 17.05.2015


Ответы (1)


То, что MySQL называет utf8, является ограниченным подмножеством UTF-8, которое допускает только три байта на символ и охватывает кодовые точки до 0xFFFF. Даже utf8mb4 не охватывает весь диапазон UTF-8, который поддерживает закодированные символы длиной до 6 байт.

Следствием этого является то, что любые данные из столбца utf8 или utf8mb4 представляют собой просто строку UTF-8 в Perl, и не должно быть никакой разницы между двумя кодировками базы данных.

Я предполагаю, что вы не включили UTF-8 для своего дескриптора DBI, поэтому все рассматривается как просто последовательность байтов. Вы должны включить mysql_enable_utf8 при вызове connect, который должен выглядеть примерно так:

my $dbh = DBI->connect($dsn, $user, $password, { mysql_enable_utf8 => 1 });

С дополнительными данными я вижу, что строка, которую вы извлекаете из базы данных, действительно имеет кодировку שלום עולם UTF-8.

Однако, если я его декодирую, то, прежде всего, я получаю 8 непробельных символов как из вашей подпрограммы foo, так и из моей собственной, а не 9; а также вы должны получать символы из базы данных, а не байты

Я подозреваю, что вы, возможно, записали закодированную строку в базу данных в первую очередь. Вот короткая программа, которая создает таблицу MySQL, записывает в нее две записи (одну строку символов и одну закодированную строку) и извлекает то, что она написала. Вы увидите, что Единственное, что имеет значение, это установка mysql_enable_utf8. Поведение одинаково независимо от того, закодирована исходная строка или нет, с SET NAMES utf8mb4 или без него.

Дальнейшие эксперименты показали, что либо mysql_enable_utf8 или SET NAMES utf8mb4 заставит DBI правильно записывать данные, но последнее не влияет на чтение< /эм>

Я предлагаю использовать ТОЛЬКО mysql_enable_utf8 при чтении или записи

Вы также должны use utf8 только в начале всех ваших программ. Отсутствие этого означает, что вы не можете использовать символы, отличные от ASCII, в своем коде.

use utf8;
use strict;
use warnings;

use DBI;
use open qw/ :std :encoding(utf-8) /;

STDOUT->autoflush;

my $VAR1 = "\327\251\327\234\327\225\327\235 \327\242\327\225\327\234\327\235";

my $dbh = DBI->connect(
    qw/ DBI:mysql:database=temp admin admin /, {
        RaiseError => 1,
        PrintError => 0,
        mysql_enable_utf8 => 1,
    }
) or die DBI::errstr;

$dbh->do('SET NAMES utf8mb4');

$dbh->do('DROP TABLE IF EXISTS temp');
$dbh->do('CREATE TABLE temp (value VARCHAR(64) CHARACTER SET utf8mb4)');

my $insert = $dbh->prepare('INSERT INTO temp (value) VALUES (?)');
$insert->execute('שלום עולם');
$insert->execute($VAR1);

my $values = $dbh->selectcol_arrayref('SELECT value FROM temp');
printf "string: %s  foo: %d\n", $_, foo($_) for @$values;

sub foo2 {
  $_[0] =~ tr/ //c;
}

sub foo {
  length join '', split / /, $_[0];
}

вывод с mysql_enable_utf8 => 1

string: שלום עולם  foo: 8
string: שלום עולם  foo: 8

вывод с mysql_enable_utf8 => 0

string: ש××× ×¢×××  foo: 16
string: ש××× ×¢×××  foo: 16
person Borodin    schedule 17.05.2015
comment
Спасибо за ответ, Бородин! хотя мой код подключения не включает utf8, как вы предложили, после того, как соединение было установлено, он выполняет следующее: я уверен, что добавил эту часть кода после того, как столкнулся с некоторыми проблемами в прошлом. будет ли включение utf8 конфликтовать с именами наборов, которые я добавил? - person chikko; 17.05.2015
comment
@chikko: Все, что делает use utf8, это сообщает компилятору, что исходный код программы находится в UTF-8. На самом деле это должно быть прямо в верхней части файла и не будет иметь никакого эффекта в этой подпрограмме. - person Borodin; 17.05.2015
comment
Ах, вы имеете в виду вариант mysql_enable_utf8. Я не уверен, и в документации нет ясности по этому поводу. Я бы попробовал включить эту опцию и удалить set names, чтобы посмотреть, работает ли это. - person Borodin; 17.05.2015
comment
да, только позже я это понял. (мой код, очевидно, делает что-то большее, чем то, что я указал выше - это был просто пример). я попробую добавить атрибут mysql_enable_utf8 в строку подключения и обновить. еще раз спасибо - person chikko; 17.05.2015
comment
я попытался добавить атрибут mysql_enable_utf8 с удалением части имен набора и без него - к сожалению, оба они дали тот же результат, что и раньше. эти специальные символы должны иметь другое представление в utf8mb4... кстати - в php, например, у них есть специальная обработка для подсчета таких символов кодировки: функция mb_strlen. - person chikko; 17.05.2015
comment
@chikko: mb_strlen - это просто функция длины, которая также позволяет указать параметр кодирования. Теоретически, как только вы настроите его правильно, строка, которую вы извлекаете из базы данных, уже должна быть декодирована. Я предлагаю вам использовать Data::Dump (или Data::Dumper с $Data::Dumper::Useqq равным 1) для просмотра того, что вы на самом деле получаете. Я мог бы настроить тестовую базу данных MySQL, если у меня есть время. Можете ли вы дать мне некоторое представление о ваших данных? - person Borodin; 18.05.2015
comment
С 2003 года не было никаких 5- или 6-байтовых последовательностей. - person ikegami; 18.05.2015
comment
@Borodin: похоже на изменение значения из БД с помощью decode('utf-8', $_[0]); в самом начале у меня все слетает, так как значение из БД вернулось без кодировки UTF8. Несмотря на то, что я не использовал ваш последний ответ, ваша помощь и поддержка привели меня к решению - большое вам спасибо за это! я помечаю ваш ответ как мой ответ. - person chikko; 19.05.2015
comment
@Chikko: Нет, ты не должен этого делать. Ваш запрос к базе данных должен возвращать декодированную строку, а ее ручное декодирование — это хак, чтобы компенсировать предыдущую ошибку в вашем коде. Я бы давно это предложил, если бы это было правильным решением - person Borodin; 19.05.2015
comment
@Borodin: В настоящее время производственная среда работает по назначению - но, конечно, вы правы - это решение - всего лишь обходной путь. я создам тестовый код, который будет содержать флаг mysql_enable_utf8, и обновлю производство, как только смогу. еще раз спасибо :) - person chikko; 19.05.2015