Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
** xref:master/oracle_compatibility/compat_stragg.adoc[23、STRAGG 函数]
** xref:master/oracle_compatibility/compat_alter_index_unusable.adoc[24、禁用索引]
** xref:master/oracle_compatibility/compat_dbtimezone.adoc[25、dbtimezone]
** xref:master/oracle_compatibility/dbms_random.adoc[26、DBMS_RANDOM]
* 容器化与云服务
** 容器化指南
*** xref:master/containerization/k8s_deployment.adoc[K8S部署]
Expand Down Expand Up @@ -112,6 +113,7 @@
**** xref:master/compatibility_features_design/with_function_procedure_impl.adoc[WITH FUNCTION/PROCEDURE]
**** xref:master/compatibility_features_design/create_index_online.adoc[索引 ONLINE 参数]
**** xref:master/compatibility_features_design/alter_index_unusable_impl.adoc[禁用索引]
**** xref:master/compatibility_features_design/dbms_random.adoc[DBMS_RANDOM]
*** 内置函数
**** xref:master/oracle_builtin_functions/sys_context.adoc[sys_context]
**** xref:master/oracle_builtin_functions/userenv.adoc[userenv]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
:sectnums:
:sectnumlevels: 5

= DBMS_RANDOM 包设计

== 目标与范围

`DBMS_RANDOM` 在 `ivorysql_ora` 扩展中提供 Oracle 风格的随机数和字符串接口:`INITIALIZE`、`SEED`、`TERMINATE`、`NORMAL`、`RANDOM`、`STRING` 和 `VALUE`。设计目标是保持接口及常用行为兼容,并支持在 IvorySQL 内通过种子重放序列。底层生成器与 Oracle 不同,因此不保证跨数据库得到相同序列。

== 代码组织

[cols="2,3",options="header"]
|===
|文件 |职责
|`src/builtin_packages/dbms_random/dbms_random.c` |种子处理、随机值生成及状态重置
|`src/builtin_packages/dbms_random/dbms_random--1.0.sql` |注册 `sys` 模式 C 函数,声明和实现 PL/iSQL 包
|`sql/dbms_random.sql`、`expected/dbms_random.out` |回归测试及期望输出
|`Makefile`、`meson.build`、`ivorysql_ora_merge_sqls` |编译和安装注册
|`src/ivorysql_ora.c`、`src/include/ivorysql_ora.h` |将包状态重置接入扩展钩子
|===

上述路径均相对于 `contrib/ivorysql_ora/`。

== 调用层次

SQL 注册文件创建 `sys.ora_dbms_random_*` C 函数,并以 `CREATE OR REPLACE PACKAGE dbms_random AUTHID CURRENT_USER` 声明 Oracle 风格的包接口。包体调用相应的 `sys` 函数;`GRANT EXECUTE ON PACKAGE dbms_random TO PUBLIC` 允许普通用户调用。

[source,text]
----
dbms_random.value(10, 20)
→ PL/iSQL 包体
→ sys.ora_dbms_random_value_range(NUMBER, NUMBER)
→ C 实现与会话生成器状态
----

所有注册的 C 函数都标记为 `VOLATILE`。数值和字符串种子入口标记为 `STRICT`,因此 `NULL` 种子不会进入 C 转换函数。`STRING` 和双参数 `VALUE` 未标记为 `STRICT`,由 C 代码处理其参数的 `NULL` 行为。

== 生成器状态与种子

C 模块保存静态 `pg_prng_state` 和 `state_seeded` 标志,因此随机序列按后端会话隔离。首次使用时,`ensure_seeded()` 以当前时间戳与后端进程 ID 组合成种子。显式调用 `INITIALIZE` 或 `SEED` 会覆盖当前状态。

数值种子通过 `numeric_int8` 转为 64 位整数,再传给 `pg_prng_seed()`;`INITIALIZE` 与数值版 `SEED` 共用这段逻辑。字符串种子由 `hash_any_extended()` 散列为 64 位值,再传给 `pg_prng_seed()`。相同种子和相同调用顺序在 IvorySQL 内产生可重放的序列。这里的实现不承诺与 Oracle 的专有生成器逐位一致。

`TERMINATE` 是兼容旧接口的空操作。扩展的 `DISCARD ALL` 和 `DISCARD PACKAGES` 钩子调用 `ora_dbms_random_reset()`,清除已设置种子的标志;下一次随机调用重新自动设置种子。

== 各接口实现

[cols="2,3",options="header"]
|===
|接口 |实现要点
|`RANDOM` |从 `pg_prng_uint32()` 取值,转成有符号 32 位整数,再转换为 `NUMBER`
|`NORMAL` |使用 `pg_prng_double_normal()` 生成标准正态分布值,并转为 `NUMBER`
|`VALUE` |使用 `pg_prng_double()` 生成 `[0, 1)` 值,并转为 `NUMBER`
|`VALUE(low, high)` |保留 `NUMBER` 边界,以 `low + (high - low) × fraction` 计算结果,避免先把边界转成 `float8`
|`STRING(opt, len)` |根据选项选择字符集,为每个字符生成位置并拼接字符串
|===

当 `VALUE` 的两个边界相等时直接返回边界,不消耗随机值;反向边界得到 `(high, low]` 范围内的结果。任一边界为 `NULL` 时返回 `NULL`。这些边界行为由回归测试覆盖。

`STRING` 支持 `U`(大写字母)、`L`(小写字母)、`A`(大小写字母)、`X`(大写字母和数字)和 `P`(可打印字符),选项不区分大小写。`NULL`、空字符串和未知单字符选项回退为 `U`;多字符选项报错。长度先截去小数部分,小于等于 0 时返回 `NULL`,大于 4000 时限制为 4000,`NULL` 长度报错。

== 回归验证

`sql/dbms_random.sql` 覆盖数值和字符串种子、重放序列、`RANDOM` 范围、`NORMAL`、`STRING` 字符类别及长度、`VALUE` 的正向/反向/相等边界、大数值边界、`TERMINATE` 和 `DISCARD ALL` 后的状态。种子序列的测试使用确定的结果;自动设置种子的结果只验证范围或可用性。
98 changes: 98 additions & 0 deletions CN/modules/ROOT/pages/master/oracle_compatibility/dbms_random.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
:sectnums:
:sectnumlevels: 5

= DBMS_RANDOM 包

== 概述

`DBMS_RANDOM` 提供随机数和随机字符串生成接口。包在会话中自动初始化;调用 `INITIALIZE` 或 `SEED` 可使相同种子在 IvorySQL 中重放相同的随机序列。IvorySQL 的序列不保证与 Oracle 的序列逐位一致。

随机数适用于测试数据和抽样,不适合作为密码、密钥或安全令牌。

== 接口

[cols="2,3",options="header"]
|===
|接口 |行为
|`INITIALIZE(val IN NUMBER)` |使用数值种子初始化随机数生成器;在 IvorySQL 中等同于数值版 `SEED`
|`SEED(val IN NUMBER)` |使用数值种子重新初始化生成器
|`SEED(val IN VARCHAR2)` |使用字符串种子重新初始化生成器
|`TERMINATE` |保留的兼容接口;在 IvorySQL 中不执行操作
|`NORMAL` |返回均值为 0、标准差为 1 的正态分布 `NUMBER`
|`RANDOM` |返回范围为 -2147483648 至 2147483647 的整数值
|`STRING(opt IN CHAR, len IN NUMBER)` |按字符类别生成指定长度的 `VARCHAR2`
|`VALUE` |返回 `[0, 1)` 范围内的 `NUMBER`
|`VALUE(low IN NUMBER, high IN NUMBER)` |按给定边界返回 `NUMBER`
|===

Oracle 已将 `INITIALIZE`、`RANDOM` 和 `TERMINATE` 标记为弃用接口。新代码可优先使用 `SEED` 和 `VALUE`;这些接口仍可用于迁移现有应用。

== 设置种子

`INITIALIZE` 和数值版 `SEED` 接受 `NUMBER`。IvorySQL 将数值种子转换为 64 位整数;字符串版 `SEED` 将字符串字节散列为种子。再次使用相同种子并以相同顺序调用函数,可重放相同序列。

[source,sql]
----
CALL dbms_random.seed(CAST(42 AS NUMBER));
SELECT dbms_random.random() AS first_value;

CALL dbms_random.seed(CAST(42 AS NUMBER));
SELECT dbms_random.random() AS first_value_again;

CALL dbms_random.seed('test-data');
SELECT dbms_random.value() AS sample_value;
----

未显式设置种子时,首次使用会基于当前时间和后端进程 ID 自动设置种子。同一会话中的调用共享生成器状态。`DISCARD ALL` 或 `DISCARD PACKAGES` 会清除该状态,之后的首次调用重新自动设置种子。`TERMINATE` 不清除状态。

== 生成数字

`VALUE` 返回大于等于 0 且小于 1 的值。`VALUE(low, high)` 在 `low < high` 时返回 `[low, high)` 范围内的值。

[source,sql]
----
SELECT dbms_random.value() AS unit_value;
SELECT dbms_random.value(10, 20) AS bounded_value;
SELECT dbms_random.normal() AS standard_normal_value;
SELECT dbms_random.random() AS integer_value;
----

IvorySQL 还支持以下边界行为:

* `low = high` 时直接返回共同的边界值。
* `low > high` 时返回 `(high, low]` 范围内的值。
* 任一边界为 `NULL` 时返回 `NULL`。

[source,sql]
----
SELECT dbms_random.value(10, 10) AS equal_bound; -- 10
SELECT dbms_random.value(11, 0) AS reverse_range; -- 大于 0 且不大于 11
----

Oracle 官方文档定义的双参数范围为 `[low, high)`;相等与反向边界是本实现支持的行为。

== 生成字符串

`STRING(opt, len)` 的 `opt` 不区分大小写:

[cols="1,3",options="header"]
|===
|`opt` |字符类别
|`U` |大写字母
|`L` |小写字母
|`A` |大小写字母
|`X` |大写字母和数字
|`P` |可打印 ASCII 字符
|===

[source,sql]
----
SELECT dbms_random.string('U', 8) AS upper_text;
SELECT dbms_random.string('X', 12) AS code_text;
----

当 `opt` 为 `NULL`、空字符串或未识别的单字符时,使用大写字母。多字符选项会报错。`len` 截去小数部分后,若不大于 0 则返回 `NULL`,若大于 4000 则生成 4000 个字符;`len` 为 `NULL` 时会报错。

== 与 Oracle 的差异

相同种子在 IvorySQL 内可重放序列,但不保证得到与 Oracle 相同的值。IvorySQL 使用 PostgreSQL 的 `pg_prng` 生成器,且 `VALUE` 不承诺 Oracle 文档所述的 38 位小数精度。需依赖精确 Oracle 序列或精度的应用应重新验证结果。
4 changes: 3 additions & 1 deletion EN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
** xref:master/oracle_compatibility/compat_create_index_online.adoc[22、ONLINE Parameter for CREATE INDEX]
** xref:master/oracle_compatibility/compat_stragg.adoc[23、STRAGG function]
** xref:master/oracle_compatibility/compat_alter_index_unusable_en.adoc[24、Alter Index Unusable]
** xref:master/oracle_compatibility/compat_dbtimezone_en.adoc[24、dbtimezone]
** xref:master/oracle_compatibility/compat_dbtimezone_en.adoc[25、dbtimezone]
** xref:master/oracle_compatibility/dbms_random.adoc[26、DBMS_RANDOM]
* Containerization and Cloud Service
** Containerization
*** xref:master/containerization/k8s_deployment.adoc[K8S deployment]
Expand Down Expand Up @@ -112,6 +113,7 @@
*** xref:master/compatibility_features_design/with_function_procedure_impl_en.adoc[WITH FUNCTION/PROCEDURE]
*** xref:master/compatibility_features_design/create_index_online.adoc[ONLINE Parameter for CREATE INDEX]
*** xref:master/compatibility_features_design/alter_index_unusable_impl_en.adoc[Alter Index Unusable]
*** xref:master/compatibility_features_design/dbms_random.adoc[DBMS_RANDOM]
** Built-in Functions
*** xref:master/oracle_builtin_functions/sys_context.adoc[sys_context]
*** xref:master/oracle_builtin_functions/userenv.adoc[userenv]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
:sectnums:
:sectnumlevels: 5

= DBMS_RANDOM package design

== Goals and scope

`DBMS_RANDOM` provides Oracle-style random number and string interfaces in the `ivorysql_ora` extension: `INITIALIZE`, `SEED`, `TERMINATE`, `NORMAL`, `RANDOM`, `STRING`, and `VALUE`. The implementation aims to preserve the package interface and common behavior while allowing seeded sequences to be replayed within IvorySQL. Because the underlying generator differs from Oracle's, the sequences are not guaranteed to match across databases.

== Code organization

[cols="2,3",options="header"]
|===
|File |Responsibility
|`src/builtin_packages/dbms_random/dbms_random.c` |Seed handling, value generation, and state reset
|`src/builtin_packages/dbms_random/dbms_random--1.0.sql` |C function registration in `sys` and PL/iSQL package declaration and body
|`sql/dbms_random.sql`, `expected/dbms_random.out` |Regression tests and expected output
|`Makefile`, `meson.build`, `ivorysql_ora_merge_sqls` |Build and installation registration
|`src/ivorysql_ora.c`, `src/include/ivorysql_ora.h` |Integration of package state reset with extension hooks
|===

These paths are relative to `contrib/ivorysql_ora/`.

== Call path

The SQL registration file creates `sys.ora_dbms_random_*` C functions and declares the Oracle-style interface with `CREATE OR REPLACE PACKAGE dbms_random AUTHID CURRENT_USER`. The package body calls the corresponding `sys` functions. `GRANT EXECUTE ON PACKAGE dbms_random TO PUBLIC` allows ordinary users to invoke the package.

[source,text]
----
dbms_random.value(10, 20)
→ PL/iSQL package body
→ sys.ora_dbms_random_value_range(NUMBER, NUMBER)
→ C implementation and session generator state
----

All registered C functions are marked `VOLATILE`. The numeric and text seed entry points are marked `STRICT`, so NULL seeds never reach the C conversion functions. `STRING` and the two-argument `VALUE` are not marked `STRICT`; their C implementations handle NULL arguments.

== Generator state and seeding

The C module stores a static `pg_prng_state` and a `state_seeded` flag, isolating the sequence within each backend session. On first use, `ensure_seeded()` combines the current timestamp and backend process ID to form a seed. Calling `INITIALIZE` or `SEED` explicitly replaces the current state.

Numeric seeds are converted through `numeric_int8` to 64-bit integers and passed to `pg_prng_seed()`; `INITIALIZE` and numeric `SEED` share this logic. Text seeds are hashed to 64 bits with `hash_any_extended()` and passed to `pg_prng_seed()`. The same seed and call order replay a sequence within IvorySQL. The implementation does not promise a bit-for-bit match with Oracle's proprietary generator.

`TERMINATE` is a no-op retained for compatibility with legacy calls. The extension hooks for `DISCARD ALL` and `DISCARD PACKAGES` call `ora_dbms_random_reset()` to clear the seeded flag. The next random call seeds the generator automatically again.

== Subprogram implementation

[cols="2,3",options="header"]
|===
|Subprogram |Implementation
|`RANDOM` |Obtains a value from `pg_prng_uint32()`, interprets it as a signed 32-bit integer, and converts it to `NUMBER`
|`NORMAL` |Uses `pg_prng_double_normal()` for a standard normal value and converts it to `NUMBER`
|`VALUE` |Uses `pg_prng_double()` for a value in `[0, 1)` and converts it to `NUMBER`
|`VALUE(low, high)` |Keeps the bounds as `NUMBER` and calculates `low + (high - low) × fraction` without first converting the bounds to `float8`
|`STRING(opt, len)` |Selects a character set, generates an index for each character, and joins the characters
|===

When the two `VALUE` bounds are equal, the function returns the bound without consuming a random value. Reversed bounds produce a result in `(high, low]`. A NULL bound returns NULL. Regression tests cover these cases.

`STRING` supports `U` (uppercase letters), `L` (lowercase letters), `A` (mixed-case letters), `X` (uppercase letters and digits), and `P` (printable characters), case-insensitively. NULL, empty, and unknown single-character options fall back to `U`; a multi-character option raises an error. The length is truncated to an integer: zero or less returns NULL, values above 4000 are capped at 4000, and a NULL length raises an error.

== Regression coverage

`sql/dbms_random.sql` covers numeric and text seeds, replayed sequences, the `RANDOM` range, `NORMAL`, `STRING` character classes and lengths, forward/reversed/equal `VALUE` bounds, large numeric bounds, `TERMINATE`, and state after `DISCARD ALL`. Seeded tests assert exact results; auto-seeded tests check only range or availability.
98 changes: 98 additions & 0 deletions EN/modules/ROOT/pages/master/oracle_compatibility/dbms_random.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
:sectnums:
:sectnumlevels: 5

= DBMS_RANDOM package

== Overview

`DBMS_RANDOM` provides functions for generating random numbers and strings. The package initializes automatically in a session. Calling `INITIALIZE` or `SEED` lets the same seed replay the same sequence within IvorySQL. IvorySQL does not guarantee a bit-for-bit match with Oracle's sequence.

Use these values for test data and sampling, not for passwords, keys, or security tokens.

== Package interface

[cols="2,3",options="header"]
|===
|Subprogram |Behavior
|`INITIALIZE(val IN NUMBER)` |Initializes the generator with a numeric seed; equivalent to numeric `SEED` in IvorySQL
|`SEED(val IN NUMBER)` |Reseeds the generator with a number
|`SEED(val IN VARCHAR2)` |Reseeds the generator with a string
|`TERMINATE` |Compatibility entry point that does nothing in IvorySQL
|`NORMAL` |Returns a `NUMBER` from a normal distribution with mean 0 and standard deviation 1
|`RANDOM` |Returns an integer value from -2147483648 through 2147483647
|`STRING(opt IN CHAR, len IN NUMBER)` |Returns a `VARCHAR2` of the requested length and character class
|`VALUE` |Returns a `NUMBER` in `[0, 1)`
|`VALUE(low IN NUMBER, high IN NUMBER)` |Returns a `NUMBER` within the specified bounds
|===

Oracle marks `INITIALIZE`, `RANDOM`, and `TERMINATE` as deprecated. New code can use `SEED` and `VALUE`; the deprecated entry points remain available for migrated applications.

== Seeding the generator

`INITIALIZE` and the numeric `SEED` overload accept a `NUMBER`. IvorySQL converts numeric seeds to 64-bit integers and hashes the bytes of text seeds. Reusing a seed and calling functions in the same order replays the sequence.

[source,sql]
----
CALL dbms_random.seed(CAST(42 AS NUMBER));
SELECT dbms_random.random() AS first_value;

CALL dbms_random.seed(CAST(42 AS NUMBER));
SELECT dbms_random.random() AS first_value_again;

CALL dbms_random.seed('test-data');
SELECT dbms_random.value() AS sample_value;
----

If no seed is supplied, the first use seeds the generator from the current time and backend process ID. Calls in one session share the generator state. `DISCARD ALL` or `DISCARD PACKAGES` clears that state; the next call seeds it automatically again. `TERMINATE` does not clear the state.

== Generating numbers

`VALUE` returns a value greater than or equal to 0 and less than 1. When `low < high`, `VALUE(low, high)` returns a value in `[low, high)`.

[source,sql]
----
SELECT dbms_random.value() AS unit_value;
SELECT dbms_random.value(10, 20) AS bounded_value;
SELECT dbms_random.normal() AS standard_normal_value;
SELECT dbms_random.random() AS integer_value;
----

IvorySQL also supports these boundary cases:

* If `low = high`, the common bound is returned.
* If `low > high`, the result is in `(high, low]`.
* If either bound is `NULL`, the result is `NULL`.

[source,sql]
----
SELECT dbms_random.value(10, 10) AS equal_bound; -- 10
SELECT dbms_random.value(11, 0) AS reverse_range; -- greater than 0 and at most 11
----

Oracle's documented two-argument range is `[low, high)`. Equal and reversed bounds are additional behaviors of this implementation.

== Generating strings

The `opt` argument of `STRING(opt, len)` is case-insensitive:

[cols="1,3",options="header"]
|===
|`opt` |Character class
|`U` |Uppercase letters
|`L` |Lowercase letters
|`A` |Mixed-case letters
|`X` |Uppercase letters and digits
|`P` |Printable ASCII characters
|===

[source,sql]
----
SELECT dbms_random.string('U', 8) AS upper_text;
SELECT dbms_random.string('X', 12) AS code_text;
----

If `opt` is `NULL`, empty, or an unrecognized single character, the function uses uppercase letters. A multi-character option raises an error. The fractional part of `len` is discarded: a resulting length of zero or less returns `NULL`, and a length above 4000 is capped at 4000 characters. A `NULL` length raises an error.

== Differences from Oracle

The same seed replays a sequence within IvorySQL, but the values are not guaranteed to match Oracle's. IvorySQL uses PostgreSQL's `pg_prng` generator, and `VALUE` does not promise the 38 decimal digits described in Oracle's documentation. Applications that depend on Oracle's exact sequence or precision should verify their results again.
Loading