Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2951468
  • 博文数量: 199
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 4126
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-06 19:06
个人简介

半个PostgreSQL DBA,热衷于数据库相关的技术。我的ppt分享https://pan.baidu.com/s/1eRQsdAa https://github.com/chenhuajun https://chenhuajun.github.io

文章分类

全部博文(199)

文章存档

2020年(5)

2019年(1)

2018年(12)

2017年(23)

2016年(43)

2015年(51)

2014年(27)

2013年(21)

2011年(1)

2010年(4)

2009年(5)

2008年(6)

分类: Mysql/postgreSQL

2014-12-08 12:52:51

LC_CTYPE代表了区域中的字符分类,比如哪些字符是字母,哪些是数字,大小写等。PostgreSQL支持区域相关的行为,其底层实现是调用了操作系统提供的相关接口,比如判断字符大小写的isupper()。因此PostgreSQL中字符分类相关的行为和OS一致,但是实测发现,还是有一些差别的。

根据PostgreSQL的手册,受字符分类(LC_CTYPE)影响的几个功能,有以下几个。
  • upper, lower, initcap
  • 大小写不敏感的模式匹配
  • 使用了字符分类的正则表达式匹配

下面做一些测试。测试环境为CentOS 6.5 + PostgreSQL 9.3。
PostgreSQL的LC_CTYPE值可以在initdb或createdb时指定,也可以通过collate(实际是LC_COLLATE+LC_CTYPE的组合)在建表或SQL的表达式中指定。下面的测试,使用表达式指定LC_CTYPE。

LC_CTYPE为C时,不能识别全角英文字母。

  1. postgres=# select upper('a' collate "C" );
  2.  a

  3. postgres=# select lower('A' collate "C" );
  4.  A

  5. postgres=# select initcap('aaa' collate "C" );
  6.  aaa

  7. postgres=# select 'a' ilike 'A' collate "C";
  8.  f

LC_CTYPE为zh_CN时,可以识别全角英文字母 。

  1. postgres=# select upper('a' collate "zh_CN");
  2.  A

  3. postgres=# select lower('A' collate "zh_CN");
  4.  a

  5. postgres=# select initcap('aaa' collate "zh_CN");
  6.  Aaa

  7. postgres=# select 'a' ilike 'A' collate "zh_CN";
  8.  t

然而对正则表达式的字符分类,不论区域是什么都不识别全角英文字母。

  1. postgres=# select 'A' collate "C" ~ '[[:upper:]]';
  2.  f

  3. postgres=# select '1' collate "C" ~ '[[:alnum:]]';
  4.  f

  5. postgres=# select 'A' collate "zh_CN" ~ '[[:upper:]]';
  6.  f

  7. postgres=# select '1' collate "zh_CN" ~ '[[:alnum:]]';
  8.  f

但是OS是支持的。

  1. [chenhj@hanode1 ~]$ export LC_ALL=C
  2. [chenhj@hanode1 ~]$ echo 'A' |grep '[[:upper:]]';
  3. [chenhj@hanode1 ~]$ echo '1' |grep '[[:alnum:]]';
  4. [chenhj@hanode1 ~]$ export LC_ALL=zh_CN.utf8
  5. [chenhj@hanode1 ~]$ echo 'A' |grep '[[:upper:]]';

  6. [chenhj@hanode1 ~]$ echo '1' |grep '[[:alnum:]]';

为什么会这样?
查看了PostgreSQL中正则表达式实现的代码。原来PostgreSQL中为了确保性能预先把字符分类属性都计算好了缓存起来的。而缓存的字符有限,最多也就缓存pg_wchar值(UTF编码时pg_wchar值相当于unicode代码点)是0~0x7FF的字符,其他的字符都认为不匹配。

src/backend/regex/regc_pg_locale.c

  1. pg_ctype_get_cache(pg_wc_probefunc probefunc)
  2. {
  3.         case PG_REGEX_LOCALE_WIDE:
  4.         case PG_REGEX_LOCALE_WIDE_L:
  5.             max_chr = (pg_wchar) 0x7FF;
  6. ...
  7.     for (cur_chr = 0; cur_chr <= max_chr; cur_chr++)
  8.     {
  9.         if ((*probefunc) (cur_chr))
  10.             nmatches++;
  11.         else if (nmatches > 0)
  12.         {
  13.             if (!store_match(pcc, cur_chr - nmatches, nmatches))
  14.                 goto out_of_memory;
  15.             nmatches = 0;
  16.         }
  17.     }
  18. ...
  19. }

src/backend/utils/mb/wchar.c
  1. /*
  2.  * convert UTF8 string to pg_wchar (UCS-4)
  3.  * caller must allocate enough space for "to", including a trailing
  4.  * len: length of from.
  5.  * "from" not necessarily null terminated.
  6.  */
  7. static int
  8. pg_utf2wchar_with_len(const unsigned char *from, pg_wchar *to, int len)
  9. {
  10.     int            cnt = 0;
  11.     uint32        c1,
  12.                 c2,
  13.                 c3,
  14.                 c4;

  15.     while (len > 0 && *from)
  16.     {
  17.         if ((*from & 0x80) == 0)
  18.         {
  19.             *to = *from++;
  20.             len--;
  21.         }
  22.         else if ((*from & 0xe0) == 0xc0)
  23.         {
  24.             if (len < 2)
  25.                 break;            /* drop trailing incomplete char */
  26.             c1 = *from++ & 0x1f;
  27.             c2 = *from++ & 0x3f;
  28.             *to = (c1 << 6) | c2;
  29.             len -= 2;
  30.         }
  31.         else if ((*from & 0xf0) == 0xe0)
  32.         {
  33.             if (len < 3)
  34.                 break;            /* drop trailing incomplete char */
  35.             c1 = *from++ & 0x0f;
  36.             c2 = *from++ & 0x3f;
  37.             c3 = *from++ & 0x3f;
  38.             *to = (c1 << 12) | (c2 << 6) | c3;
  39.             len -= 3;
  40.         }
  41.         else if ((*from & 0xf8) == 0xf0)
  42.         {
  43.             if (len < 4)
  44.                 break;            /* drop trailing incomplete char */
  45.             c1 = *from++ & 0x07;
  46.             c2 = *from++ & 0x3f;
  47.             c3 = *from++ & 0x3f;
  48.             c4 = *from++ & 0x3f;
  49.             *to = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
  50.             len -= 4;
  51.         }
  52.         else
  53.         {
  54.             /* treat a bogus char as length 1; not ours to raise error */
  55.             *to = *from++;
  56.             len--;
  57.         }
  58.         to++;
  59.         cnt++;
  60.     }
  61.     *to = 0;
  62.     return cnt;
  63. }


而全角英文的unicode代码点是超过0x7FF的。
/usr/share/i18n/locales/i18n

  1. ...
  2. upper /
  3. ...
  4. % HALFWIDTH AND FULLWIDTH FORMS/
  5.    <UFF21>..<UFF3A>

再试一下0x7FF以内的某个字符,发现确实是支持的。
比如下面的'?'(0xc5)


话说这些看上去很古怪的字符,会有人在中文里用吗?




阅读(6469) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~