前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(51)变长类型实现(valena.c)

Postgresql源码(51)变长类型实现(valena.c)

作者头像
mingjie
发布2022-05-31 10:04:33
5520
发布2022-05-31 10:04:33
举报
文章被收录于专栏:Postgresql源码分析

总结(可以先跳过)

varattrib_4b总结:

  • 所有变长类型的判断 都 使用第一个字节:
    • 0x00 (0bxxxx xxx0):is 4B(未压缩数据varattrib_4b,最大1G)
    • 0x00 (0bxxxx xx00):is 4B_U(未压缩数据varattrib_4b,第二个二进制如果没用使用且长度满足,可以把4B转换为1B)
    • 0x00 (0bxxxx xx10):is 4B_C(压缩数据varattrib_4b,最大1G,用于判断是否压缩)
    • 0x00 (0bxxxx xxx1):is 1B(未压缩数据,1B断类型,最大126b)
    • 0x00 (0b0000 0001):is 1B_E(TOAST专用)
  • 4B头共32个二进制位,低2位存控制数据,长度保存在高30位中(注意len永远包含header的4字节,所以能保存的数据是1G-4B)
    • 长度保存:va_header = (uint32) (len) << 2
      • 30个二进制位len范围:0 - 1073741824 B (2^30) == 1024MB
    • 长度读取:(va_header >> 2) & 0x3FFFFFFF
      • 0x3FFFFFFF == 0b0011 1111 1111...右移两位后只需取低30位
  • 1B的头共8个二进制位,低1位存控制数据,长度保存在高7位中(注意len永远包含header的1字节,所以能保存的数据是128B-1B)
    • 长度保存:va_header = (uint8) (len) << 1) | 0x01
      • 7个二进制len范围:0 - 128 B (2^7) ,控制位顺便加个1表示当前是varattrib_1b
    • 长度读取:(va_header >> 1) & 0x7F
      • 0x7F == 0b0111 1111 右移一位后只需取低7位
  • 4B到1B的转换
    • 4B的头是4字节,1B的头是1字节,转换也提供了几个宏,简单的说就是判断数据len能不能被1B装得下:VARATT_CAN_MAKE_SHORT

text支持大量字符换操作函数,匹配C++标准库,字符串操作函数一般都不需要自己写,用时先查询varlena.c

text

postgresql中经常可以看到text类型,直接调试时可以看到这样的结果:

代码语言:javascript
复制
(gdb) p *t
$6 = {vl_len_ = "<\000\000", vl_dat = 0x1bd49a4 "hello world~\020"}

原因是text的类型的定义,数据是用char表示的,所以打印出来默认都是字符类型。这里char并不是字节的含义,头部的四个字节是按二进制位拆分使用的。

代码语言:javascript
复制
struct varlena
{
	char		vl_len_[4];		/* Do not touch this field directly! */
	char		vl_dat[FLEXIBLE_ARRAY_MEMBER];	/* Data content is here */
};

#define VARHDRSZ		((int32) sizeof(int32))
typedef struct varlena text;

text真身

变长字符较常用的是保存在下面几类结构之中

(其实还有其他很多数据结构,形如header+data的格式例如ArrayType,header是固定的格式4B,data随意)

代码语言:javascript
复制
typedef union
{
	struct						/* Normal varlena (4-byte length) */
	{
		uint32		va_header;
		char		va_data[FLEXIBLE_ARRAY_MEMBER];
	}			va_4byte;
	struct						/* Compressed-in-line format */
	{
		uint32		va_header;
		uint32		va_tcinfo;	/* Original data size (excludes header) and
								 * compression method; see va_extinfo */
		char		va_data[FLEXIBLE_ARRAY_MEMBER]; /* Compressed data */
	}			va_compressed;
} varattrib_4b;

typedef struct
{
	uint8		va_header;
	char		va_data[FLEXIBLE_ARRAY_MEMBER]; /* Data begins here */
} varattrib_1b;

/* TOAST pointers are a subset of varattrib_1b with an identifying tag byte */
typedef struct
{
	uint8		va_header;		/* Always 0x80 or 0x01 */
	uint8		va_tag;			/* Type of datum */
	char		va_data[FLEXIBLE_ARRAY_MEMBER]; /* Type-specific data */
} varattrib_1b_e;

具体存放时会按用途选一个结构,其中varattrib_4b是可以用varlena来表示的,另外两种一般用char*直接使用

代码语言:javascript
复制
varattrib_4b  ---使用--->  varlena、bytea(varlena)、text(varlena)、BpChar(varlena)、VarChar(varlena)
varattrib_1b  ---使用--->  Pointer(char*)、Datum(uintptr_t)、char*
varattrib_1b_e

设计思路比较简单,都是结构体头部放一个header按位存一些控制信息,后面跟数据。

PG提供了一系列宏来可以很便捷的操作这些变长类型,而且如果你的自定义结构体也符合header+data的结构,也可以直接使用这些宏操作你的结构体。

例如ArrayType也是4字节header,后面放数据。就可以使用SET_VARSIZE宏把数据长度放入header中记录了。后面也可以用其他宏把长度取出。

代码语言:javascript
复制
typedef struct ArrayType
{
	int32		vl_len_;		/* varlena header (do not touch directly!) */
	int			ndim;			/* # of dimensions */
	int32		dataoffset;		/* offset to data, or 0 if no bitmap */
	Oid			elemtype;		/* element type OID */
} ArrayType;

// 使用
ArrayType  *aresult = (ArrayType *) result;
...
SET_VARSIZE(aresult, allocated_size);

这里面最常用的还是4B头的varattrib_4b,其他类型使用大同小异,后面重点分析varattrib_4b的原理和使用方法。

varattrib_4b

varattrib_4b用union提供了两套用法:存一般数据;存压缩数据。

代码语言:javascript
复制
typedef union
{
	struct						/* Normal varlena (4-byte length) */
	{
		uint32		va_header;
		char		va_data[FLEXIBLE_ARRAY_MEMBER];
	}			va_4byte;
	struct						/* Compressed-in-line format */
	{
		uint32		va_header;
		uint32		va_tcinfo;	/* Original data size (excludes header) and
								 * compression method; see va_extinfo */
		char		va_data[FLEXIBLE_ARRAY_MEMBER]; /* Compressed data */
	}			va_compressed;
} varattrib_4b;

PG默认提供了两套宏,分别是大端序和小端序。Linux默认使用小端序:

代码语言:javascript
复制
/*
xxxxxx00 4-byte length word, aligned, uncompressed data (up to 1G)
xxxxxx10 4-byte length word, aligned, *compressed* data (up to 1G)

The "xxx" bits are the length field (which includes itself in all cases). In the big-endian case we mask to extract the length, in the little-endian case we shift.  Note that in both cases the flag bits are in the physically first byte.  Also, it is not possible for a 1-byte length word to be zero; this lets us disambiguate alignment padding bytes from the start of an unaligned datum.  (We now *require* pad bytes to be filled with zero!)

“xxx”位是长度字段(在所有情况下都包括它自己)。在 big-endian 的情况下,我们使用 mask 来提取长度,在 little-endian 的情况下,我们进行移位。请注意,在这两种情况下,标志位都在物理上的第一个字节中。此外,一个 1 字节长度的字不可能为零。这让我们可以从未对齐数据的开头消除对齐填充字节的歧义。 (我们现在*要求*填充字节用零填充!)

*/

// 判断类型
#define VARATT_IS_4B(PTR) \
	((((varattrib_1b *) (PTR))->va_header & 0x01) == 0x00)
#define VARATT_IS_4B_U(PTR) \
	((((varattrib_1b *) (PTR))->va_header & 0x03) == 0x00)
#define VARATT_IS_4B_C(PTR) \
	((((varattrib_1b *) (PTR))->va_header & 0x03) == 0x02)
#define VARATT_IS_1B(PTR) \
	((((varattrib_1b *) (PTR))->va_header & 0x01) == 0x01)
#define VARATT_IS_1B_E(PTR) \
	((((varattrib_1b *) (PTR))->va_header) == 0x01)

// 长度操作
#define VARSIZE_4B(PTR) \
	((((varattrib_4b *) (PTR))->va_4byte.va_header >> 2) & 0x3FFFFFFF)

#define SET_VARSIZE_4B(PTR,len) \
	(((varattrib_4b *) (PTR))->va_4byte.va_header = (((uint32) (len)) << 2))
#define SET_VARSIZE_4B_C(PTR,len) \
	(((varattrib_4b *) (PTR))->va_4byte.va_header = (((uint32) (len)) << 2) | 0x02)

// 获取数据
#define VARDATA_4B(PTR)		(((varattrib_4b *) (PTR))->va_4byte.va_data)
#define VARDATA_4B_C(PTR)	(((varattrib_4b *) (PTR))->va_compressed.va_data)

// 压缩用
#define VARHDRSZ_COMPRESSED		offsetof(varattrib_4b, va_compressed.va_data)
#define VARDATA_COMPRESSED_GET_EXTSIZE(PTR) \
	(((varattrib_4b *) (PTR))->va_compressed.va_tcinfo & VARLENA_EXTSIZE_MASK)
#define VARDATA_COMPRESSED_GET_COMPRESS_METHOD(PTR) \
	(((varattrib_4b *) (PTR))->va_compressed.va_tcinfo >> VARLENA_EXTSIZE_BITS)

varattrib_4b总结:

  • 所有变长类型的判断 都 使用第一个字节:
    • 0x00 (0bxxxx xxx0):is 4B(未压缩数据varattrib_4b,最大1G)
    • 0x00 (0bxxxx xx00):is 4B_U(未压缩数据varattrib_4b,第二个二进制如果没用使用且长度满足,可以把4B转换为1B)
    • 0x00 (0bxxxx xx10):is 4B_C(压缩数据varattrib_4b,最大1G,用于判断是否压缩)
    • 0x00 (0bxxxx xxx1):is 1B(未压缩数据,1B断类型,最大126b)
    • 0x00 (0b0000 0001):is 1B_E(TOAST专用)
  • 4B头共32个二进制位,低2位存控制数据,长度保存在高30位中(注意len永远包含header的4字节,所以能保存的数据是1G-4B)
    • 长度保存:va_header = (uint32) (len) << 2
      • 30个二进制位len范围:0 - 1073741824 B (2^30) == 1024MB
    • 长度读取:(va_header >> 2) & 0x3FFFFFFF
      • 0x3FFFFFFF == 0b0011 1111 1111...右移两位后只需取低30位
  • 1B的头共8个二进制位,低1位存控制数据,长度保存在高7位中(注意len永远包含header的1字节,所以能保存的数据是128B-1B)
    • 长度保存:va_header = (uint8) (len) << 1) | 0x01
      • 7个二进制len范围:0 - 128 B (2^7) ,控制位顺便加个1表示当前是varattrib_1b
    • 长度读取:(va_header >> 1) & 0x7F
      • 0x7F == 0b0111 1111 右移一位后只需取低7位
  • 4B到1B的转换
    • 4B的头是4字节,1B的头是1字节,转换也提供了几个宏,简单的说就是判断数据len能不能被1B装得下:VARATT_CAN_MAKE_SHORT

varlena.c函数

text支持大量字符换操作函数,匹配C++标准库,这里记录一部分便于查询使用

1 text和cstring的相互转换

  • cstring转换到text:默认用4B
  • text转cstring:不知道text是哪种类型,需要先判断,在转换。VARSIZE_ANY_EXHDR宏内包含了控制位的判断逻辑。
  • textin:调用cstring_to_text
  • textout:调用text_to_cstring
代码语言:javascript
复制
text *
cstring_to_text(const char *s)
{
	return cstring_to_text_with_len(s, strlen(s));
}

text *
cstring_to_text_with_len(const char *s, int len)
{
	text	   *result = (text *) palloc(len + VARHDRSZ);

	SET_VARSIZE(result, len + VARHDRSZ);
	memcpy(VARDATA(result), s, len);

	return result;
}

char *
text_to_cstring(const text *t)
{
	/* must cast away the const, unfortunately */
	text	   *tunpacked = pg_detoast_datum_packed(unconstify(text *, t));
	int			len = VARSIZE_ANY_EXHDR(tunpacked);
	char	   *result;

	result = (char *) palloc(len + 1);
	memcpy(result, VARDATA_ANY(tunpacked), len);
	result[len] = '\0';

	if (tunpacked != t)
		pfree(tunpacked);

	return result;
}

/*
 *		textin			- converts "..." to internal representation
 */
Datum
textin(PG_FUNCTION_ARGS)
{
	char	   *inputText = PG_GETARG_CSTRING(0);

	PG_RETURN_TEXT_P(cstring_to_text(inputText));
}

/*
 *		textout			- converts internal representation to "..."
 */
Datum
textout(PG_FUNCTION_ARGS)
{
	Datum		txt = PG_GETARG_DATUM(0);

	PG_RETURN_CSTRING(TextDatumGetCString(txt));
}

2 textcat追加

  • 算好了两个长度和,新建一个text,SET_VARSIZE
  • 数据拷贝到新建text中
代码语言:javascript
复制
Datum
textcat(PG_FUNCTION_ARGS)
{
	text	   *t1 = PG_GETARG_TEXT_PP(0);
	text	   *t2 = PG_GETARG_TEXT_PP(1);

	PG_RETURN_TEXT_P(text_catenate(t1, t2));
}
/*
 * text_catenate
 *	Guts of textcat(), broken out so it can be used by other functions
 *
 * Arguments can be in short-header form, but not compressed or out-of-line
 */
static text *
text_catenate(text *t1, text *t2)
{
	text	   *result;
	int			len1,
				len2,
				len;
	char	   *ptr;

	len1 = VARSIZE_ANY_EXHDR(t1);
	len2 = VARSIZE_ANY_EXHDR(t2);

	/* paranoia ... probably should throw error instead? */
	if (len1 < 0)
		len1 = 0;
	if (len2 < 0)
		len2 = 0;

	len = len1 + len2 + VARHDRSZ;
	result = (text *) palloc(len);

	/* Set size of result string... */
	SET_VARSIZE(result, len);

	/* Fill data field of result string... */
	ptr = VARDATA(result);
	if (len1 > 0)
		memcpy(ptr, VARDATA_ANY(t1), len1);
	if (len2 > 0)
		memcpy(ptr + len1, VARDATA_ANY(t2), len2);

	return result;
}

3 text_substring子串

代码语言:javascript
复制
static text *
text_substring(Datum str, int32 start, int32 length, bool length_not_specified)

4 text_substring重叠子串替换

代码语言:javascript
复制
static text *
text_overlay(text *t1, text *t2, int sp, int sl)

5 text_position子串位置

代码语言:javascript
复制
static int
text_position(text *t1, text *t2, Oid collid)

6 varstr_cmp字典序比较

代码语言:javascript
复制
int
varstr_cmp(const char *arg1, int len1, const char *arg2, int len2, Oid collid)
  
// texteq text_lt text_le text_gt text_ge
// 

7 text_starts_with

代码语言:javascript
复制
Datum
text_starts_with(PG_FUNCTION_ARGS)

8 太多了详见varlena.c

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结(可以先跳过)
  • text
  • text真身
  • varattrib_4b
  • varlena.c函数
    • 1 text和cstring的相互转换
      • 2 textcat追加
        • 3 text_substring子串
          • 4 text_substring重叠子串替换
            • 5 text_position子串位置
              • 6 varstr_cmp字典序比较
                • 7 text_starts_with
                  • 8 太多了详见varlena.c
                  相关产品与服务
                  文件存储
                  文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档