暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

[译] PostgreSQL 中的文本不太相等 - 以及如何以更精细的方式比较文本

原创 王语嫣 2022-06-27
1608

原文地址:Not so equal texts in PostgreSQL - and how to compare texts in a more elaborate way
原文作者:Andreas ‘ads’ Scherbaum

Christophe Pettus在这里发布了一个有趣的挑战。表面上看起来相等的两个字符串,但如果你让 PostgreSQL 比较它们,它们是不相等的。

现在让我从一个注释开始:Twitter 完全把这个挑战搞砸了。

怎么会这样?虽然这两个字符串在原始中是不同的,但在将其发布到 Twitter 时,字符串是相等的。这其中的乐趣在哪里?

我向 Christophe 询问了原始查询:

INSERT INTO t VALUES (E'Zo\u0065\u0301', E'Zo\u00e9');

您最终会在表格中看到以下文本:

SELECT * FROM t;
  a  |  b  
-----+-----
 Zoé | Zoé
(1 row)

如果将 UTF-8 字符串转换为十六进制,则会得到“ 0x5a 0x6f 0x65 0xcc 0x81 ”和“ 0x5a 0x6f 0xc3 0xa9 ”。显然它们是不同的。

但是,如果您转换推文中的两个字符串,您会得到“ 0x5a 0x6f 0xc3 0xa9 ”和“ 0x5a 0x6f 0xc3 0xa9 ”。相同的字符串。可怜的推特。

当我发现这个挑战时,检查十六进制值实际上是我的第一个想法。但是,根据我在“PostgreSQL 中的数据类型”和“ PostgreSQL中的高级数据类型”讲座中的经验,我认为即使字符串实际上相等,也应该有可能“解决”这个难题。

我们将深入探讨 PostgreSQL 的真正可扩展性!

在我上一章的“ PostgreSQL 中的高级数据类型”演讲中,我解释了可以使用您自己的数据类型以及相应的函数、运算符和运算符类来扩展 PostgreSQL。让我们好好利用这些知识,让相等的字符串不相等。

查看查询似乎很容易,我需要的是:

  • 一种新的数据类型(类似于字符串)
  • “=”的新运算符,实际上使字符串不相等

准备工作

为了保存我的常规数据库并且不会意外地搞砸目录,我创建了一个新数据库,其中进行了以下所有测试:

\set ON_ERROR_STOP false
\c postgres
DROP DATABASE IF EXISTS "Zoé";
CREATE DATABASE "Zoé";
\c "Zoé"

\set ON_ERROR_STOP true
\set ECHO queries

CREATE SCHEMA "Zoé";
SET search_path TO "Zoé";

我将新数据库命名为“ Zoé ”——因为我可以。为了让它看起来更漂亮,我还创建了一个“ Zoé ”模式。因为为什么不。这也有助于显示我在哪里使用我自己的函数和对象,因为它们总是以“ Zoé ”进行模式限定。

新数据类型

PostgreSQL 中的一种新数据类型需要几个支持函数,输入和输出,接收和发送。这是最低限度的。

幸运的是,我正在模仿TEXT类型,因此我可以为此重用所有文本函数。

CREATE FUNCTION "Zoé".varchar2_in(cstring)
       RETURNS varchar2
            AS 'textin'
      LANGUAGE internal STRICT;

CREATE FUNCTION "Zoé".varchar2_out(varchar2)
       RETURNS cstring
            AS 'textout'
      LANGUAGE internal STRICT;

CREATE FUNCTION "Zoé".varchar2_recv(internal)
       RETURNS varchar2
            AS 'textrecv'
      LANGUAGE internal STRICT;

CREATE FUNCTION "Zoé".varchar2_send(varchar2)
       RETURNS bytea
            AS 'textsend'
      LANGUAGE internal STRICT;

是时候创建新类型了。如果你认识这个名字,请告诉我…

CREATE TYPE "Zoé".varchar2 (
    INTERNALLENGTH = variable,
    INPUT = "Zoé".varchar2_in,
    OUTPUT = "Zoé".varchar2_out,
    RECEIVE = "Zoé".varchar2_recv,
    SEND = "Zoé".varchar2_send,
    COLLATABLE = true,
    CATEGORY = 'S',
    PREFERRED = true,
    ALIGNMENT = int4,
    STORAGE = extended
);

如您所见,这使用了新创建的支持功能,我将其放入我的专用模式中。

此时我已经可以使用这种类型,创建表,插入数据:

CREATE TABLE "Zoé".t (a "Zoé".varchar2,
                      b "Zoé".varchar2
                     );

INSERT INTO "Zoé".t VALUES ('Zoé', 'Zoé');

检查内容:

SELECT * FROM "Zoé".t;
  a  |  b  
-----+-----
 Zoé | Zoé
(1 row)

操作员

但是我还不能比较这两列:

SELECT * FROM "Zoé".t WHERE a = b;
SELECT * FROM "Zoé".t WHERE a = b;
ERROR:  operator does not exist: varchar2 = varchar2
LINE 1: SELECT * FROM "Zoé".t WHERE a = b;
                                      ^
HINT:  No operator matches the given name and argument types. You might need to add 

PostgreSQL 不知道如何比较我的新类型的两个字符串。我需要为我的新类型创建“ = ”运算符。

事实上,我不仅需要创建“ = ”运算符,还需要创建“ <> ”运算符,因为“ = ”运算符需要一个否定符。

为了创建这两个运算符,我还需要两个函数来比较我的两个varchar2值,并告诉 PostgreSQL 它们是否相等。这就是变化发生的地方。

TEXT的原始功能:

CREATE FUNCTION pg_catalog.texteq (TEXT, TEXT)
        RETURNS boolean
             AS 'texteq'
       LANGUAGE internal STRICT;

CREATE FUNCTION pg_catalog.textne (TEXT, TEXT)
        RETURNS boolean
             AS 'textne'
       LANGUAGE internal STRICT;

我的新数据类型的功能:

CREATE FUNCTION "Zoé".texteq ("Zoé".varchar2, "Zoé".varchar2)
        RETURNS boolean
             AS 'textne'
       LANGUAGE internal STRICT;

CREATE FUNCTION "Zoé".textne ("Zoé".varchar2, "Zoé".varchar2)
        RETURNS boolean
             AS 'texteq'
       LANGUAGE internal STRICT;

你看出区别了吗?

现在我有了支持函数,我可以创建运算符:

CREATE OPERATOR "Zoé".<> (
    FUNCTION = textne,
    LEFTARG = "Zoé".varchar2,
    RIGHTARG = "Zoé".varchar2,
    COMMUTATOR = OPERATOR("Zoé".<>),
    NEGATOR = OPERATOR("Zoé".=),
    RESTRICT = neqsel,
    JOIN = neqjoinsel
);

CREATE OPERATOR "Zoé".= (
    FUNCTION = texteq,
    LEFTARG = "Zoé".varchar2,
    RIGHTARG = "Zoé".varchar2,
    COMMUTATOR = OPERATOR("Zoé".=),
    NEGATOR = OPERATOR("Zoé".<>),
    MERGES,
    HASHES,
    RESTRICT = eqsel,
    JOIN = eqjoinsel
);

测试一下

SELECT * FROM "Zoé".t WHERE a = b;
 a | b 
---+---
(0 rows)

由于我刚刚切换了运算符,我可以将查询转过来询问字符串是否不相等:

SELECT * FROM "Zoé".t WHERE a != b;
  a  |  b  
-----+-----
 Zoé | Zoé
(1 row)

最后一句

为我的新类型切换 texteq和 textne函数会告诉 PostgreSQL 字符串不相等,而实际上它们是相等的。另一个同样“很好”的选项是我使用“正确”的内部函数创建texteq和nextne函数,但是在创建运算符时切换这些函数。这将给出相同的结果。

PostgreSQL 的可扩展性让我可以自由地设计我想看到的结果,切换真假,让你相信字符串不相等,而实际上它们是不相等的。

当然,这只是为应对挑战而开发的一个快速示例。然而,它表明 PostgreSQL 确实可以扩展以处理您对应用程序的任何类型的需求。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论