首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >截断文本编码安全

截断文本编码安全
EN

Database Administration用户
提问于 2019-12-20 16:06:02
回答 3查看 484关注 0票数 4

在Postgres中,以保留编码的方式将text字段截断到最大字节数的最有效方法是什么?例如,如何在保持有效的UTF8字符串表示的同时保留最多N个字节。

例如,假设UTF8,如果一个字段包含abc€,这是5个字节的4个字符,如果我想将该字段截断为最多4个字节,但保留一个有效的UTF8字符串,则实际上只需要保留前3个字节。

我觉得我需要以下内容,但我不想使用'escape',而是忽略无效的字节,只保留abc

代码语言:javascript
复制
select encode(substring('abc€'::bytea, 0, 5), 'escape');
 encode  
---------
 abc\342

实现这一目标的最佳方法是什么?

EN

回答 3

Database Administration用户

发布于 2019-12-20 17:12:29

使用此功能:

代码语言:javascript
复制
CREATE OR REPLACE FUNCTION get_prefix (
   string text,
   max_bytes bigint
) RETURNS text
   LANGUAGE sql STRICT AS
$SELECT p
FROM (SELECT p.p, octet_length(p.p) AS len
      FROM generate_series(0, length($1)) AS len
         CROSS JOIN LATERAL substr($1, 1, len.len) AS p) AS q
WHERE len <= $2
ORDER BY len DESC
LIMIT 1$;

它的工作方式如下:

代码语言:javascript
复制
SELECT get_prefix('abc€', 4);
 get_prefix 
------------
 abc
(1 row)
票数 1
EN

Database Administration用户

发布于 2019-12-20 17:22:10

下面是另一个写的PL/pgSQL

代码语言:javascript
复制
Create or Replace function max_bytea_length(pchars text, bytea_length int)
RETURNS bytea
LANGUAGE plpgsql

COST 100
VOLATILE 
AS $BODY$

declare 
_i int;
_length_of_chars int;
_newchars text;
_testchars text;
begin

if octet_length(pchars::bytea) <= bytea_length then
   return pchars::bytea;
end if;
_i = least( octet_length(pchars)-4, bytea_length-4);
_length_of_chars =  char_length(pchars);
loop 
   _newchars= left(pchars, _i);
    _testchars = left(pchars, _i+1); 
  if octet_length(_testchars::bytea) > bytea_length or _i = _length_of_chars  then
     return _newchars::bytea;
  end if ;
  _i = _i+1;
end loop ;

end;
$BODY$

-

代码语言:javascript
复制
select max_bytea_length( 'abc€', 4 )::char(10)
'abc'

-

代码语言:javascript
复制
select max_bytea_length('i ♥ u function changed it to be faster', 56)::char(60)
票数 1
EN

Database Administration用户

发布于 2019-12-21 17:45:17

这里有一个UTF-8的解决方案,它基于检查UTF-8表示中的非前导字节。

该实现是纯SQL (非过程),以受益于内联,并使用用例时.构造来实现if-然后-否则的逻辑。

查询实现的思想是:首先,检查八进制长度是否已经小于限制,如果是,则立即返回原始字符串。否则,检查候选截断点右侧的字节是否为序列的一部分,如果不是,则截断,否则将截断点向左移动,直到它不中断序列为止。

有效字符串中的UTF-8字节属于以下三种类型之一:

  • 单字节字符(位字符串0xxxxxxxbyte & 128 = 0可测试)
  • 多字节序列的第一个字节(位字符串11xxxxxxbyte & 192 = 192测试)
  • 多字节序列的第二个或第三个或第四个字节(位字符串10xxxxxxbyte & 192 = 128测试)

如果假定分界点右侧的字节属于第一类或第二类,我们可以在该字节之前安全地截断,但不包括它。否则,它属于第三类,我们需要向后测试最多3个字节,直到找到一个不属于第三类的字节,或者字符串太短,结果是空字符串。

守则:

代码语言:javascript
复制
create function utf8_truncate(str text, len int) returns text
as $
select case when octet_length(str) <= len then str
else (
   with bstr(s) as (select convert_to(str, 'UTF-8'))
   select
   case
   when len>=1 and (get_byte(s, len) & 192) <> 128
   then convert_from(substring(s, 1, len), 'UTF-8')
   else
     case
     when len>=2 and (get_byte(s, len-1) & 192) <> 128
     then convert_from(substring(s, 1, len-1), 'UTF-8')
     else
       case
       when len>=3 and (get_byte(s, len-2) & 192) <> 128
       then convert_from(substring(s, 1, len-2), 'UTF-8')
       else
         case
         when len>=4 and (get_byte(s, len-3) & 192) <> 128
         then convert_from(substring(s, 1, len-3), 'UTF-8')
         else ''
         end
       end
     end
   end
 from bstr)
   end;
$ language sql strict immutable parallel safe;

一些注意事项:

  • get_byte(bytea, int)中,偏移值从0开始,而在substring(bytea, int, int)中,偏移值从1开始。
  • 问题中的示例将text转换为bytea以获取字节表示,但这是不正确的:如果文本包含反斜杠,则它们将被解释为旁路转义格式的转义序列。应该使用convert_to()
票数 0
EN
页面原文内容由Database Administration提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://dba.stackexchange.com/questions/256049

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档