|
| 1 | + |
| 2 | +:sectnums: |
| 3 | +:sectnumlevels: 5 |
| 4 | + |
| 5 | + |
| 6 | += **Feature Overview** |
| 7 | + |
| 8 | +IvorySQL provides the Oracle-compatible built-in function ```VSIZE('parameter')```, which returns the number of |
| 9 | +bytes occupied by the argument in its internal storage representation, i.e. the "storage size" of the argument. |
| 10 | +For character types, it returns the byte length (excluding the variable-length header); for fixed-width types |
| 11 | +(such as NUMBER, BOOLEAN, DATE, TIMESTAMP, etc.), it returns the storage width of that type; when the argument |
| 12 | +is NULL, it returns NULL. |
| 13 | + |
| 14 | +== Implementation |
| 15 | + |
| 16 | +VSIZE needs to accept an argument of any data type (character, numeric, boolean, date/time, etc.) and compute |
| 17 | +the byte count differently depending on its storage representation (variable-length varlena type or fixed-width |
| 18 | +type). This kind of logic, which depends on type-specific storage details, cannot be implemented with a simple |
| 19 | +SQL wrapper, so this feature is implemented as a C-language extension function `ora_vsize`, registered as: |
| 20 | + |
| 21 | +``` |
| 22 | +sys.vsize(anycompatible) RETURNS int4 |
| 23 | +``` |
| 24 | + |
| 25 | +Using the `anycompatible` pseudo-type as the parameter type allows the function to accept an argument of any |
| 26 | +data type; an untyped string literal (such as `'abc'`) is resolved to the text type following PostgreSQL's |
| 27 | +default rules, matching the behavior of `VSIZE('abc')` in Oracle. The function is declared `STRICT`, so a NULL |
| 28 | +argument directly returns NULL without any extra handling in the function body. |
| 29 | + |
| 30 | +The function is implemented as `ora_vsize` in `contrib/ivorysql_ora/src/builtin_functions/misc_functions.c`: |
| 31 | + |
| 32 | +* On the first call, the actual type OID of the argument is obtained via `get_fn_expr_argtype()`, and |
| 33 | + `get_typlen()` is called to get that type's `typlen` (storage length), which is cached in |
| 34 | + `fcinfo->flinfo->fn_extra` to avoid repeated catalog lookups within the same query; subsequent calls read the |
| 35 | + cached value directly from `fn_extra`. |
| 36 | +* `typlen == -1`: indicates a variable-length (varlena) type, such as text, varchar2, or numeric. In this case, |
| 37 | + `toast_raw_datum_size()` is called to get the logical (decompressed) size of the value -- this function |
| 38 | + uniformly handles 1-byte/4-byte headers, compressed storage, and TOASTed (out-of-line) storage, and its return |
| 39 | + value is always normalized to the 4-byte header convention, so subtracting `VARHDRSZ` yields the payload byte |
| 40 | + count excluding the header. This is the same approach used by `octet_length()` to compute byte length, which |
| 41 | + is why `VSIZE('abc') = LENGTHB('abc')`. |
| 42 | +* `typlen == -2`: indicates the cstring type, and the string length plus 1 (including the terminating `\0`) is |
| 43 | + returned. |
| 44 | +* Otherwise: the type is fixed-width, and its `typlen` is returned directly as the storage width (for example, |
| 45 | + int4 is 4; int8/float8/date/timestamp/timestamptz are all 8; boolean is 1). |
| 46 | + |
| 47 | +The function registration is done in `builtin_functions--1.0.sql`: |
| 48 | +```sql |
| 49 | +/* VSIZE */ |
| 50 | +/* |
| 51 | + * VSIZE: Oracle-compatible function returning the number of bytes in the |
| 52 | + * internal representation of the argument. Returns NULL for NULL input. |
| 53 | + * For varlena types the logical (decompressed) data size, excluding the |
| 54 | + * varlena header, is returned; for fixed-width types the storage width is |
| 55 | + * returned. |
| 56 | + * |
| 57 | + * The anycompatible pseudo-type accepts a value of any data type, and an |
| 58 | + * untyped string literal is resolved to text, so VSIZE('abc') works just |
| 59 | + * like in Oracle. |
| 60 | + */ |
| 61 | +CREATE FUNCTION sys.vsize(anycompatible) |
| 62 | +RETURNS int4 |
| 63 | +AS 'MODULE_PATHNAME', 'ora_vsize' |
| 64 | +LANGUAGE C |
| 65 | +STRICT |
| 66 | +IMMUTABLE; |
| 67 | +/* End - VSIZE */ |
| 68 | +``` |
| 69 | + |
| 70 | +== Typical VSIZE examples |
| 71 | +[cols="8,2"] |
| 72 | +|==== |
| 73 | +|*Example statement*|*Return value* |
| 74 | +|SELECT vsize('abc'); | 3 |
| 75 | +|SELECT vsize(CAST('abc' AS VARCHAR2)); | 3 |
| 76 | +|SELECT vsize('abc'::varchar); | 3 |
| 77 | +|SELECT vsize('abc'::char(10)); | 10 |
| 78 | +|SELECT vsize('你好'::text); | 6 |
| 79 | +|SELECT vsize(0::number); | 2 |
| 80 | +|SELECT vsize(1::number); | 4 |
| 81 | +|SELECT vsize(123::number); | 4 |
| 82 | +|SELECT vsize(1.23::number); | 6 |
| 83 | +|SELECT vsize(123::int4); | 4 |
| 84 | +|SELECT vsize(123::int8); | 8 |
| 85 | +|SELECT vsize(1.23::float8); | 8 |
| 86 | +|SELECT vsize('NaN'::float8); | 8 |
| 87 | +|SELECT vsize(true); | 1 |
| 88 | +|SELECT vsize('2024-01-01'::date); | 8 |
| 89 | +|SELECT vsize('2024-01-01 10:00:00'::timestamp); | 8 |
| 90 | +|SELECT vsize('2024-01-01 10:00:00+08'::timestamptz); | 8 |
| 91 | +|SELECT vsize(NULL::text); | NULL |
| 92 | +|SELECT vsize(repeat('a', 100000)); | 100000 |
| 93 | +|==== |
| 94 | + |
| 95 | +For the same string, `VSIZE` and `LENGTHB` produce the same result: |
| 96 | +``` |
| 97 | +SELECT vsize('abc') = lengthb('abc') AS same_as_lengthb; |
| 98 | + same_as_lengthb |
| 99 | +----------------- |
| 100 | + t |
| 101 | +``` |
| 102 | +
|
| 103 | +Even when the data is stored compressed or TOASTed out-of-line, `VSIZE` still returns its uncompressed logical |
| 104 | +byte count: |
| 105 | +``` |
| 106 | +CREATE TABLE vsize_big(a text); |
| 107 | +INSERT INTO vsize_big SELECT repeat('b', 200000) FROM generate_series(1, 10); |
| 108 | +SELECT bool_and(vsize(a) = lengthb(a)) AS toasted_matches_lengthb, min(vsize(a)) AS min_size FROM vsize_big; |
| 109 | + toasted_matches_lengthb | min_size |
| 110 | +-------------------------+---------- |
| 111 | + t | 200000 |
| 112 | +``` |
0 commit comments