Почему имена Perl Hashkey из mySQL Result не соответствуют UTF8? - PullRequest
1 голос
/ 06 августа 2020

Я немного запутался и не нашел ответа. Так что я надеюсь найти здесь помощь.

У меня есть небольшая mysql таблица:

CREATE TABLE `datatable` (
  `Artikelnummer` varchar(10) NOT NULL,
  `Bezeichnung` varchar(25) NOT NULL,
  `Länge` mediumint(6) DEFAULT NULL,
  `Breite` mediumint(6) DEFAULT NULL,
  `Höhe` mediumint(6) DEFAULT NULL,
  `Vö-Datum` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `datatable` 
(`Artikelnummer`, `Bezeichnung`, `Länge`, `Breite`, `Höhe`, `Vö-Datum`)
VALUES ('123456', 'Hängematte', 12, 20, 35, '2020-08-31');

ALTER TABLE `datatable`
  ADD PRIMARY KEY (`Artikelnummer`);

и мой тестовый сценарий perl:

#!/usr/bin/perl

use warnings;
use strict;
use utf8;

use CGI qw (:standard);
use DBI;
use Data::Dumper;
use DBIx::Log4perl;

binmode(STDOUT, ':utf8');

my $dbh = '';

if ($dbh = DBIx::Log4perl->connect("DBI:mysql:test","user","password",{
            RaiseError => 1,
            PrintError => 1,
            mysql_enable_utf8 => 1
        }))
{
    $dbh->do('SET NAMES utf8');
    $dbh->do('SET CHARSET utf8');

    my $sql_query
        = 'SELECT * FROM datatable WHERE Artikelnummer = ?';
    my $out = $dbh->prepare($sql_query);
    $out->execute( "123456" )
        or die 'Select-Fehler: '.$dbh->errstr();
    my $Content = $out->fetchrow_hashref();
    $out->finish();

    # Test 1
    if ($Content->{'Bezeichnung'} eq 'Hängematte') {
        print "Test 1: Content Hängematte found!\n";
    }

    # Test 2
    if (defined $Content->{'Höhe'}) {
        print "Test 2: Key Höhe found!\n";
    } else {
        print "Test 2: Key Höhe not found!\n";
    }
    
    # Hack: Hardcore BadHack!!!
    $Content->{'Höhe'} = $Content->{'Höhe'};
    $Content->{'Länge'} = $Content->{'Länge'};
    $Content->{'Vö-Datum'} = $Content->{'Vö-Datum'};

    # Test 3
    if (defined $Content->{'Höhe'}) {
        print "Test 3: Key Höhe found!\n";
    } else {
        print "Test 3: Key Höhe not found!\n";
    }

    print Dumper($Content);
    
} else {
    print "Verbindungsfehler: " . $dbh->errstr(); 
}
exit(0);

Результат:

Log4perl: Seems like no initialization happened. Forgot to call init()?
Test 1: Content Hängematte found!
Test 2: Key Höhe not found!
Test 3: Key Höhe found!
$VAR1 = {
          'Vö-Datum' => '2020-08-31',
          'Länge' => 12,
          'Höhe' => 35,
          'Höhe' => 35,
          'Breite' => 20,
          'Länge' => 12,
          'Bezeichnung' => "H\x{e4}ngematte",
          'Vö-Datum' => '2020-08-31',
          'Artikelnummer' => '123456'
        };

Во-первых, проигнорируйте предупреждение Log4 perl, потому что это всего лишь пример кода, демонстрирующий мою проблему. ; -)

Мой вопрос в том, как мне получить правильные закодированные ha sh -ключи без этого плохого взлома в примере или другой вопрос, почему я получаю строки таблицы не в кодировке utf8 в моем ha sh ?

Я использую perl 5.25, DBIx :: Log4 perl 0.26, DBI 1.642 и mySQL server 5.7.31

1 Ответ

4 голосов
/ 06 августа 2020

Для начала давайте проясним, что это не проблема самой базы данных. Проблема возникает даже с правильно сформированными таблицами, такими как:

mysql> DESCRIBE MyTable;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| Höhe  | varchar(10) | NO   |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
1 row in set (0.00 sec)

Проблема полностью на принимающей стороне.

По умолчанию DBD :: mysql возвращает каждую строку, закодированную согласно действующему набору символов клиента (SET NAMES).

Можно использовать mysql_enable_utf8, чтобы до некоторой степени переопределить это. В частности, он выполняет следующие действия:

  • Декодирует данные, полученные из типа текстового столбца (char, varchar, et c).
  • Выполняет SET NAMES utf8 или эквивалент (только когда mysql_enable_utf8 устанавливается как часть connect).

Это означает, что вам решать:

  • Кодировать все, что отправляется в базу данных. [ 1]
  • Декодировать любые другие строки, полученные из баз данных.

Последняя включает имена столбцов. Вы можете использовать следующее, чтобы декодировать ha sh, возвращаемый fetchrow_hashref.

sub _d { my ($s) = @_; utf8::decode($s); $s }

sub decode_keys {
   my ($hash) = @_;
   return { map { _d($_) => $hash->{$_} } keys(%$hash) };
}

Воспроизведение проблемы и демонстрация исправления:

use 5.014;
use warnings;

use utf8;                             # Source saved as UTF-8
use open ':std', ':encoding(UTF-8)';  # Terminal expects UTF-8

use Data::Dumper qw( Dumper );
use DBI          qw( );

sub _e { my ($s) = @_; utf8::encode($s); $s }
sub _d { my ($s) = @_; utf8::decode($s); $s }

sub decode_keys { { map { _d($_) => $_ } keys(%{ $_[0] }) } }

my $host     = ...;
my $db       = ...;
my $user     = ...;
my $password = ...;

my $dbh = DBI->connect(
   "dbi:mysql:$db;host=$host",
   $user, $password,
   {
      RaiseError => 1,
      PrintError => 0,
      PrintWarn  => 1,
      mysql_enable_utf8 => 1,  # Or mysql_enable_utf8mb4 => 1
   },
);

$dbh->do(_e('
   CREATE TEMPORARY TABLE `MyTable` (
      `Höhe` VARCHAR(10) NOT NULL
   ) ENGINE=MyISAM DEFAULT CHARSET=utf8
'));

$dbh->do(_e('
   INSERT INTO `MyTable`
      SET `Höhe`="Höhe"
'));

my $rows = $dbh->selectall_arrayref(
   _e('SELECT * FROM `MyTable`'),
   { Slice => {} },
);

{
   local $Data::Dumper::Useqq = 1;
   print(Dumper($rows));
}

for my $row (@$rows) {
   for my $col_name (keys(%$row)) {
      say "$col_name: $row->{$col_name}";
   }
}

$_ = decode_keys($_) for @$rows;

{
   local $Data::Dumper::Useqq = 1;
   print(Dumper($rows));
}

for my $row (@$rows) {
   for my $col_name (keys(%$row)) {
      say "$col_name: $row->{$col_name}";
   }
}

Вывод:

$VAR1 = [
          {
            "H\303\266he" => "H\x{f6}he"
          }
        ];
Höhe: Höhe
$VAR1 = [
          {
            "H\x{f6}he" => "H\x{f6}he"
          }
        ];
Höhe: Höhe
  1. Из-за ошибки в модуле вы часто можете обойтись без их кодирования.
...