diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index b62fe4a5..68f93003 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -116,6 +116,7 @@ **** xref:master/oracle_builtin_functions/rawtohex.adoc[rawtohex] **** xref:master/oracle_builtin_functions/stragg.adoc[stragg] **** xref:master/oracle_builtin_functions/dbtimezone_impl.adoc[dbtimezone] +**** xref:master/oracle_builtin_functions/vsize.adoc[vsize] *** xref:master/gb18030.adoc[国标GB18030] * 参考指南 ** xref:master/tools_reference.adoc[工具参考] diff --git a/CN/modules/ROOT/pages/master/oracle_builtin_functions/vsize.adoc b/CN/modules/ROOT/pages/master/oracle_builtin_functions/vsize.adoc new file mode 100644 index 00000000..0bac6d1b --- /dev/null +++ b/CN/modules/ROOT/pages/master/oracle_builtin_functions/vsize.adoc @@ -0,0 +1,103 @@ + +:sectnums: +:sectnumlevels: 5 + + += **功能概述** + +IvorySQL提供兼容Oracle内置函数 ```VSIZE('parameter')``` ,用于返回参数在内部存储表示中所占用的字节数, +即返回参数的“存储大小”。对于字符类型数据,返回其字节长度(不含变长头);对于定长类型(如 NUMBER、 +BOOLEAN、DATE、TIMESTAMP 等),返回该类型的存储宽度;参数为 NULL 时返回 NULL。 + +== 实现原理 + +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` 中完成: +```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 */ +``` + +== VSIZE 典型用例 +[cols="8,2"] +|==== +|*用例语句*|*返回值* +|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 +``` diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index e4221c78..1103c116 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -116,6 +116,7 @@ *** xref:master/oracle_builtin_functions/rawtohex.adoc[rawtohex] *** xref:master/oracle_builtin_functions/stragg.adoc[stragg] *** xref:master/oracle_builtin_functions/dbtimezone_impl_en.adoc[dbtimezone] +*** xref:master/oracle_builtin_functions/vsize_en.adoc[vsize] ** xref:master/gb18030.adoc[GB18030 Character Set] * Reference ** xref:master/tools_reference.adoc[Tool Reference] diff --git a/EN/modules/ROOT/pages/master/oracle_builtin_functions/vsize_en.adoc b/EN/modules/ROOT/pages/master/oracle_builtin_functions/vsize_en.adoc new file mode 100644 index 00000000..c2615f44 --- /dev/null +++ b/EN/modules/ROOT/pages/master/oracle_builtin_functions/vsize_en.adoc @@ -0,0 +1,112 @@ + +:sectnums: +:sectnumlevels: 5 + + += **Feature Overview** + +IvorySQL provides the Oracle-compatible built-in function ```VSIZE('parameter')```, which returns the number of +bytes occupied by the argument in its internal storage representation, i.e. the "storage size" of the argument. +For character types, it returns the byte length (excluding the variable-length header); for fixed-width types +(such as NUMBER, BOOLEAN, DATE, TIMESTAMP, etc.), it returns the storage width of that type; when the argument +is NULL, it returns NULL. + +== Implementation + +VSIZE needs to accept an argument of any data type (character, numeric, boolean, date/time, etc.) and compute +the byte count differently depending on its storage representation (variable-length varlena type or fixed-width +type). This kind of logic, which depends on type-specific storage details, cannot be implemented with a simple +SQL wrapper, so this feature is implemented as a C-language extension function `ora_vsize`, registered as: + +``` +sys.vsize(anycompatible) RETURNS int4 +``` + +Using the `anycompatible` pseudo-type as the parameter type allows the function to accept an argument of any +data type; an untyped string literal (such as `'abc'`) is resolved to the text type following PostgreSQL's +default rules, matching the behavior of `VSIZE('abc')` in Oracle. The function is declared `STRICT`, so a NULL +argument directly returns NULL without any extra handling in the function body. + +The function is implemented as `ora_vsize` in `contrib/ivorysql_ora/src/builtin_functions/misc_functions.c`: + +* On the first call, the actual type OID of the argument is obtained via `get_fn_expr_argtype()`, and + `get_typlen()` is called to get that type's `typlen` (storage length), which is cached in + `fcinfo->flinfo->fn_extra` to avoid repeated catalog lookups within the same query; subsequent calls read the + cached value directly from `fn_extra`. +* `typlen == -1`: indicates a variable-length (varlena) type, such as text, varchar2, or numeric. In this case, + `toast_raw_datum_size()` is called to get the logical (decompressed) size of the value -- this function + uniformly handles 1-byte/4-byte headers, compressed storage, and TOASTed (out-of-line) storage, and its return + value is always normalized to the 4-byte header convention, so subtracting `VARHDRSZ` yields the payload byte + count excluding the header. This is the same approach used by `octet_length()` to compute byte length, which + is why `VSIZE('abc') = LENGTHB('abc')`. +* `typlen == -2`: indicates the cstring type, and the string length plus 1 (including the terminating `\0`) is + returned. +* Otherwise: the type is fixed-width, and its `typlen` is returned directly as the storage width (for example, + int4 is 4; int8/float8/date/timestamp/timestamptz are all 8; boolean is 1). + +The function registration is done in `builtin_functions--1.0.sql`: +```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 */ +``` + +== Typical VSIZE examples +[cols="8,2"] +|==== +|*Example statement*|*Return value* +|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 +|==== + +For the same string, `VSIZE` and `LENGTHB` produce the same result: +``` +SELECT vsize('abc') = lengthb('abc') AS same_as_lengthb; + same_as_lengthb +----------------- + t +``` + +Even when the data is stored compressed or TOASTed out-of-line, `VSIZE` still returns its uncompressed logical +byte count: +``` +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 +```