功能概述
IvorySQL提供兼容Oracle内置函数 ,用于返回参数在内部存储表示中所占用的字节数,
即返回参数的“存储大小”。对于字符类型数据,返回其字节长度(不含变长头);对于定长类型(如 NUMBER、
BOOLEAN、DATE、TIMESTAMP 等),返回该类型的存储宽度;参数为 NULL 时返回 NULL。VSIZE('parameter')
1. 实现原理
VSIZE 需要接受任意数据类型的入参(字符、数值、布尔、日期时间等),并根据其存储方式(变长 varlena 类型
或定长类型)分别计算字节数,这类与具体类型存储细节相关的逻辑无法用简单的 SQL 包装实现,因此本次开发
使用 C 语言编写扩展函数 ora_vsize,注册为:
sys.vsize(anycompatible) RETURNS int4
使用 anycompatible 伪类型作为参数类型,使得该函数可以接受任意数据类型的入参,未显式指定类型的字符串
字面量(如 'abc')会按照 PostgreSQL 的默认规则解析为 text 类型,与 Oracle 中 VSIZE('abc') 的行为一致。
函数声明为 STRICT,因此入参为 NULL 时直接返回 NULL,无需在函数体中额外处理。
函数实现位于 contrib/ivorysql_ora/src/builtin_functions/misc_functions.c 中的 ora_vsize:
-
首次调用时,通过
get_fn_expr_argtype()取得实参的真实类型 OID,并调用get_typlen()获取该类型的typlen(存储长度),缓存到fcinfo→flinfo→fn_extra,避免同一查询中重复查目录;后续调用直接从fn_extra中读取缓存值。 -
typlen == -1:表示变长(varlena)类型,如 text、varchar2、numeric 等。此时调用toast_raw_datum_size()获取该值的逻辑(解压缩后)大小 —— 该函数会统一处理 1 字节/4 字节头、压缩存储 以及 TOAST 外部存储等各种情况,返回值统一按 4 字节头换算,因此再减去VARHDRSZ即可得到不含头部的 有效数据字节数。这与octet_length()计算字节长度所采用的方式一致,因此VSIZE('abc') = LENGTHB('abc')。 -
typlen == -2:表示 cstring 类型,返回其字符串长度加 1(含结尾\0)。 -
其余情况:为定长类型,直接返回该类型的
typlen作为存储宽度(例如 int4 为 4,int8/float8/date/ timestamp/timestamptz 均为 8,boolean 为 1)。
具体函数注册在 builtin_functions—1.0.sql 中完成:
/* VSIZE */
/*
* VSIZE: Oracle-compatible function returning the number of bytes in the
* internal representation of the argument. Returns NULL for NULL input.
* For varlena types the logical (decompressed) data size, excluding the
* varlena header, is returned; for fixed-width types the storage width is
* returned.
*
* The anycompatible pseudo-type accepts a value of any data type, and an
* untyped string literal is resolved to text, so VSIZE('abc') works just
* like in Oracle.
*/
CREATE FUNCTION sys.vsize(anycompatible)
RETURNS int4
AS 'MODULE_PATHNAME', 'ora_vsize'
LANGUAGE C
STRICT
IMMUTABLE;
/* End - VSIZE */
2. VSIZE 典型用例
用例语句 |
返回值 |
SELECT vsize('abc'); |
3 |
SELECT vsize(CAST('abc' AS VARCHAR2)); |
3 |
SELECT vsize('abc'::varchar); |
3 |
SELECT vsize('abc'::char(10)); |
10 |
SELECT vsize('你好'::text); |
6 |
SELECT vsize(0::number); |
2 |
SELECT vsize(1::number); |
4 |
SELECT vsize(123::number); |
4 |
SELECT vsize(1.23::number); |
6 |
SELECT vsize(123::int4); |
4 |
SELECT vsize(123::int8); |
8 |
SELECT vsize(1.23::float8); |
8 |
SELECT vsize('NaN'::float8); |
8 |
SELECT vsize(true); |
1 |
SELECT vsize('2024-01-01'::date); |
8 |
SELECT vsize('2024-01-01 10:00:00'::timestamp); |
8 |
SELECT vsize('2024-01-01 10:00:00+08'::timestamptz); |
8 |
SELECT vsize(NULL::text); |
NULL |
SELECT vsize(repeat('a', 100000)); |
100000 |
对于同一字符串,VSIZE 与 LENGTHB 的结果一致:
SELECT vsize('abc') = lengthb('abc') AS same_as_lengthb;
same_as_lengthb
-----------------
t
即使数据经过压缩存储或 TOAST 到行外,VSIZE 仍然返回其未压缩的逻辑字节数:
CREATE TABLE vsize_big(a text);
INSERT INTO vsize_big SELECT repeat('b', 200000) FROM generate_series(1, 10);
SELECT bool_and(vsize(a) = lengthb(a)) AS toasted_matches_lengthb, min(vsize(a)) AS min_size FROM vsize_big;
toasted_matches_lengthb | min_size
-------------------------+----------
t | 200000