繁体   English   中英

为什么 Perl 哈希键名称来自 mySQL 结果不是 UTF8?

[英]Why are Perl Hashkey names from mySQL Result are not UTF8?

我有点困惑,找不到答案。 所以我希望能在这里找到帮助。

我有一个小 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);

output 是:

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'
        };

首先,请忽略 Log4perl 警告,因为它只是演示我的问题的示例代码。 ;-)

我的问题是,我如何在示例中获得正确的编码哈希键而没有这种糟糕的黑客攻击,或者其他被问到为什么我在 hash 中获得的表格行不是 utf8 编码的?

我使用 perl 5.25、DBIx::Log4perl 0.26、DBI 1.642 和 mySQL 服务器 5.7.31

首先,让我们明确一点,这不是数据库本身的问题。 即使使用正确格式的表格也会出现问题,如下所示:

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 等)检索的数据。
  • 执行SET NAMES utf8或等效项(仅当mysql_enable_utf8设置为connect的一部分时)。

这意味着您可以:

  • 对发送到数据库的所有内容进行编码。 [1]
  • 解码从数据库接收到的任何其他字符串。

后者包括列名。 您可以使用以下内容对fetchrow_hashref返回的 hash 进行解码。

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}";
   }
}

Output:

$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. 由于模块中的错误,您通常可以在不编码这些内容的情况下逃脱。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM