9

看起来 postgresupper/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

4 回答 4

10

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

作为记录,SQLUPPER最终使用几乎所有正确的参数(区域设置 1055 土耳其语用于 -编码的数据库)调用 Windows LCMapStringW(通过towuppervia ),str_toupperUTF-8Turkish_Turkey

Visual Studio Runtime( towupper) 没有设置dwMapFlags中的LCMAP_LINGUISTIC_CASING位。(我可以确认设置它可以解决问题。) 这不是微软的错误;它是设计使然,可能永远不会被“修复”(哦,遗产的乐趣。)LCMapStringW

你有三种方法可以解决这个问题:

  • 实现@Sorrow 的包装器解决方案(或编写您自己的本机函数替换(DLL)。)
  • 在例如 Ubuntu 上运行您的 PostgreSQL 实例,该实例对突厥语语言环境表现出正确的行为(@Sorrow 确认它适用于他);这可能是最简单、最干净的出路。
  • 在您的 PostgreSQL目录中放入一个已修补的 32 位MSVCR100.DLLbin(但是虽然UPPER并且LOWER会起作用,但其他诸如排序规则之类的事情可能会继续失败——同样是在 Windows 级别。YMMV。)

出于完整性(和怀旧的乐趣),这里是修补 Windows 系统的过程(但请记住,除非您从摇篮到坟墓管理这个 PostgreSQL 实例,否则您可能会给您的继任者带来很多悲伤;无论何时从头开始部署一个新的测试或备份系统,你或你的继任者必须记住再次应用补丁——如果你有一天升级到 PostgreSQL 10,它说使用MSVCR120.DLL而不是MSVCR100.DLL,那么你将拥有也可以尝试修补新的 DLL。) 在测试系统上

  • 使用HxD打开C:\WINDOWS\SYSTEM32\MSVCR100.DLL
  • 立即将 DLL 以相同的名称保存在您的 PostgreSQLbin目录下(不要尝试使用资源管理器或命令行复制文件,它们可能会复制 64 位版本)
  • 文件仍然在 HxD 中打开,转到Search > Replace,选择Datatype: 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 并重新运行您的查询。
    • 如果您的查询仍然不起作用(确保您的数据库是 UTF-8 编码Turkish_TurkeyLC_CTYPELC_COLLATEpostgres.exe32 位 Dependency Walker中打开并确保它表明它是MSVCR100.DLL从 PostgreSQLbin目录加载的。
    • 如果所有功能都很好,将修补的 DLL 复制到生产 PostgreSQLbin目录并重新启动。

但是请记住,当您将数据从 Ubuntu 系统或已修补的 Windows 系统移到未修补的 Windows 系统时,您将再次遇到问题,如果 Windows 实例在citext字段或基于UPPER/LOWER的函数索引。

于 2012-11-04T10:03:49.350 回答
4

在我看来,您的问题与 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。可能有一个更优化的解决方案,我并不认为这种方法是正确的。

于 2012-11-01T06:58:44.710 回答
1

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

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

SQL99 指定了 Unicode 感知大小写规范化,我们还没有基础设施。相反,我们使用 tolower() 来提供区域感知翻译。但是,在某些语言环境中这也是不正确的(例如,土耳其语可能会用 'i' 和 'I' 做一些奇怪的事情)。 我们当前的折衷方案是对设置了高位的字符使用 tolower(),对 7 位字符使用仅 ASCII 的小写。

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 语言环境)并且操作系统提供非 unicodetoupper()工作正常。

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

于 2012-11-01T07:44:26.067 回答
1

上面解释了问题的根源。似乎只有将“I”转换为“ı”和“i”转换为“İ”时才会出现问题。作为一种解决方法,只需在调用 lower 或 upper 函数之前直接替换这些字符,如下所示:

SELECT lower(replace('IİĞ', 'I', 'ı')) -> ıiğ
SELECT upper(replace('ıiğ', 'i', 'İ')) -> IİĞ
于 2021-03-03T14:44:27.533 回答