土耳其人字符上的Postgres上部功能不会返回预期结果

时间:2012-10-23 11:52:37

标签: postgresql character-encoding turkish

看起来postgres upper/lower函数不处理土耳其语字符集中的选择字符。

select upper('Aaı'), lower('Aaİ') from mytable;

返回:

AAı, aaİ

而不是:

AAI, aai

请注意,正常的英文字符可以正确转换,但不能转换为土耳其语I(较低或较高)

Postgres版本:9.2 32 bit

数据库编码(其中任何一个都是相同的结果):UTF-8, WIN1254, C

客户端编码:

 UTF-8, WIN1254, C

操作系统:Windows 7 enterprise edition 64bit

SQL函数lowerupper在UTF-8编码数据库上为ı和İ返回以下相同的字节

\xc4b1    
\xc4b0   

以下是WIN1254(土耳其语)编码数据库

\xfd      
\xdd     

我希望我的调查是错误的,而且我错过了一些东西。

4 个答案:

答案 0 :(得分:9)

您的问题 100%Windows。(或者更确切地说,使用PostgreSQL构建的Microsoft Visual Studio更准确。)

对于记录,SQL UPPER最终通过几乎所有正确的参数调用Windows LCMapStringW(通过towupper通过str_toupper) (locale 1055土耳其语为UTF-8 - 编码的,Turkish_Turkey数据库),

<强>但

Visual Studio运行时(towupper)未在LCMapStringW dwMapFlags 中设置LCMAP_LINGUISTIC_CASING位。 (我可以确认设置它可以解决问题。)这不是Microsoft的错误;它是设计的,可能永远不会被“修复”(哦遗产的乐趣。)

你有三种方法:

  • 实现@ Sorrow的包装解决方案(或编写自己的本机函数替换(DLL)。)
  • 运行您的PostgreSQL实例,例如Ubuntu 展示了Turkic语言环境的正确行为(@Sorrow确认它对他有用);这可能是最简单,最干净的出路。
  • 在PostgreSQL MSVCR100.DLL目录中放入修补的32位bin (但UPPERLOWER可行,其他内容例如整理可能会继续失败 - 再次,在Windows级别.YMMV。)

为了完整(和怀旧的乐趣) ONLY ,以下是修补Windows系统的过程(但请记住,除非您将从摇篮中管理此PostgreSQL实例严重的,你可能会给你的继任者带来很大的麻烦;无论何时从头开始部署一个新的测试或备份系统,你或你的继任者都必须记得再次申请补丁 - 如果让我们说你一个一天升级到PostgreSQL 10,即使用MSVCR120.DLL代替MSVCR100.DLL,那么你也必须尝试修补新的DLL。)在测试系统上

  • 使用HxD打开C:\WINDOWS\SYSTEM32\MSVCR100.DLL
  • 在PostgreSQL bin目录下使用相同名称立即保存DLL(不要尝试使用资源管理器或命令行复制文件,它们可能会复制64位版本)
  • 文件仍在HxD中打开,请转到搜索&gt;替换,选择数据类型:Hexvalues ,然后
    • 搜索...... 4E 14 33 DB 3B CB 0F 84 41 12 00 00 B8 00 01 00 00
    • 替换为... 4E 14 33 DB 3B CB 0F 84 41 12 00 00 B8 00 01 00 01
    • ......然后再一次......
    • 搜索...... FC 51 6A 01 8D 4D 08 51 68 00 02 00 00 50 E8 E2
    • 替换为... FC 51 6A 01 8D 4D 08 51 68 00 02 00 01 50 E8 E2
  • ...并在PostgreSQL bin目录下重新保存,然后重新启动PostgreSQL并重新运行查询。
    • 如果您的查询仍无效(请确保您的数据库采用Turkish_Turkey LC_CTYPE进行UTF-8编码,LC_COLLATEpostgres.exe MSVCR100.DLL 32-bit Dependency Walker并确保它指示它从PostgreSQL bin目录中加载bin
    • 如果所有函数都将修补后的DLL复制到生产PostgreSQL citext目录并重新启动。

但请记住,当您将数据从Ubuntu系统移出或从修补的Windows系统移动到未修补的Windows系统时,您将再次遇到问题,如果Windows实例,您可能无法在Ubuntu上导回此数据在UPPER字段或基于LOWER / {{1}}的函数索引中引入了重复项。

答案 1 :(得分:3)

似乎给我您的问题与Windows有关。这就是Ubuntu(Postgres 8.4.14),数据库编码UTF-8:

的样子
test=# select upper('Aaı'), lower('Aaİ');
 upper | lower
-------+-------
 AAI   | aai
(1 row)

我的建议是 - 如果你必须使用Windows - 编写一个存储过程,为你做转换。使用内置replacereplace('abcdefabcdef', 'cd', 'XX')返回abXXefabXXef。可能有一个更优化的解决方案,我并不认为这种方法是正确的。

答案 2 :(得分:1)

上述问题的根源。似乎问题只发生在“I”到“ı”和“i”到“İ”的转换中。作为一种解决方法,只需在调用下层或上层函数之前直接替换这些字符,如下所示:

SELECT lower(replace('IİĞ', 'I', 'ı')) -> ıiğ
SELECT upper(replace('ıiğ', 'i', 'İ')) -> IİĞ

答案 3 :(得分:0)

这确实是PostgreSQL中的错误(即使在当前的git树中仍然没有修复)。 证明:https://github.com/postgres/postgres/blob/master/src/port/pgstrcasecmp.c

PostgreSQL开发人员甚至特别提到那里的土耳其语字符:

  

SQL99指定了Unicode感知的大小写规范化,我们还没有   拥有基础设施。相反,我们使用tolower()来提供   区域设置感知翻译。   但是,有一些区域设置也不对(例如,土耳其语可能会对'我'和'我'做一些奇怪的事情。)   我们目前的妥协是对字符使用tolower()   高位设置,并使用仅ASCII的7位下行   字符。

此文件中实现的

pg_upper()非常简单(作为其随附pg_tolower()):

unsigned char
pg_toupper(unsigned char ch)
{
    if (ch >= 'a' && ch <= 'z')
            ch += 'A' - 'a';
    else if (IS_HIGHBIT_SET(ch) && islower(ch))
            ch = toupper(ch);
    return ch;
}

正如您所看到的,此代码不会将其参数视为Unicode代码点,并且不能正确地100%正常工作,除非当前选择的区域设置恰好是我们关心的区域(如土耳其非unicode区域设置)和操作系统提供的非unicode toupper()正常工作。

这真的很难过,我只希望这将在即将发布的PostgreSQL版本中得到解决......