Skip to content

Commit 4265e01

Browse files
committed
docs: add bilingual DBMS_TRANSACTION documentation
1 parent 62c31fb commit 4265e01

4 files changed

Lines changed: 404 additions & 0 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
= DBMS_TRANSACTION 包设计
5+
6+
== 目的
7+
8+
通过 `ivorysql_ora` 扩展提供 Oracle 风格的事务控制接口,复用已有 PL/iSQL 语句和事务管理函数。本实现支持 `COMMIT`、`ROLLBACK`、`SAVEPOINT`、`ROLLBACK_SAVEPOINT`、`READ_ONLY` 和 `READ_WRITE`,改动限定在扩展内部,不增加内核事务接口。
9+
10+
使用方式及调用限制见 xref:master/oracle_compatibility/compat_dbms_transaction.adoc[DBMS_TRANSACTION 包]。
11+
12+
== 实现说明
13+
14+
=== 代码组织
15+
16+
[cols="3,4",options="header"]
17+
|===
18+
|文件 |职责
19+
|`src/builtin_packages/dbms_transaction/dbms_transaction--1.0.sql` |注册 C 函数,定义包规范与包体,授予包执行权限
20+
|`src/builtin_packages/dbms_transaction/dbms_transaction.c` |保存点名称校验、调用上下文检查及事务函数桥接
21+
|`sql/dbms_transaction.sql` |事务操作、异常输入、自治事务及跨用户调用的回归用例
22+
|`expected/dbms_transaction.out`、`expected/dbms_transaction_1.out` |没有或具备可用 dblink 环境时的期望结果
23+
|`Makefile`、`meson.build`、`ivorysql_ora_merge_sqls` |编译、回归及扩展 SQL 合并注册
24+
|===
25+
26+
上述路径均相对于 `contrib/ivorysql_ora/`。
27+
28+
=== 包接口与权限
29+
30+
包声明使用 `AUTHID CURRENT_USER`,并执行 `GRANT EXECUTE ON PACKAGE dbms_transaction TO PUBLIC`,使普通用户能够以自身权限调用。包级执行授权不改变业务对象的访问权限。
31+
32+
两个保存点 C 函数注册在 `sys` 模式中,均为 `VOLATILE`,且未声明 `STRICT`,以便对 `NULL` 名称给出明确错误。
33+
34+
[source,text]
35+
----
36+
顶层 CALL dbms_transaction.savepoint('sp1')
37+
→ PL/iSQL 包体
38+
→ sys.ora_dbms_transaction_savepoint(text)
39+
→ DefineSavepoint(name)
40+
→ 顶层命令结束时完成子事务状态转换
41+
----
42+
43+
=== 子程序映射
44+
45+
[cols="2,4",options="header"]
46+
|===
47+
|子程序 |实现
48+
|`COMMIT` |包体执行 PL/iSQL 原生 `COMMIT` 语句
49+
|`ROLLBACK` |包体执行 PL/iSQL 原生 `ROLLBACK` 语句
50+
|`SAVEPOINT` |包体通过 `PERFORM` 调用 C 函数,最终调用 `DefineSavepoint()`
51+
|`ROLLBACK_SAVEPOINT` |包体通过 `PERFORM` 调用 C 函数,最终调用 `RollbackToSavepoint()`
52+
|`READ_ONLY` |动态执行 `SET TRANSACTION READ ONLY`
53+
|`READ_WRITE` |动态执行 `SET TRANSACTION READ WRITE`
54+
|===
55+
56+
`COMMIT` 和 `ROLLBACK` 复用 PL/iSQL 的事务终止路径,继承其原子上下文限制。包没有额外开启事务,也不会让原本不允许终止事务的调用上下文获得提交或回滚能力。
57+
58+
读写模式过程只设置访问模式,不改变隔离级别,也不实现额外的 Oracle 快照机制。切换模式的时机和写入限制由底层 `SET TRANSACTION` 规则处理。
59+
60+
=== 保存点桥接
61+
62+
PL/iSQL 没有原生保存点语句,因此两个包过程由 C 函数桥接到 `access/xact.h` 声明的事务管理函数。它们按以下顺序执行:
63+
64+
. `get_savepoint_name()` 拒绝 `NULL` 名称和长度达到 256 字节的名称;上限为 255 字节,名称按字符串传递。
65+
. `RequireTransactionBlock(true, ...)` 确认存在显式事务块,防止在无效状态下调用保存点函数。
66+
. `check_not_nested_in_pl_call()` 通过 `SPI_get_connected() > 0` 判断是否嵌套在其他 PL/iSQL 调用中;若嵌套则报错。
67+
. 调用 `DefineSavepoint()` 或 `RollbackToSavepoint()`,由内核完成后续事务状态转换。
68+
69+
保存点函数只标记待处理的事务状态,实际子事务启动或回滚在顶层命令结束后完成。如果嵌套过程在该转换完成前继续执行,可能访问尚未就绪的事务状态。因此保存点操作必须作为独立顶层 `CALL`,不能在 C 桥接中强行提交命令来绕过限制。
70+
71+
这里直接调用事务管理函数,没有新增 `SPI_savepoint` 等 SPI 接口。`SPI_get_connected()` 仅用于检查调用深度,不负责执行保存点操作。
72+
73+
=== 边界与错误处理
74+
75+
[cols="3,2,4",options="header"]
76+
|===
77+
|条件 |SQLSTATE |诊断
78+
|保存点名称为 `NULL` |`22004` |`DBMS_TRANSACTION savepoint name must not be NULL`
79+
|保存点名称超过 255 字节 |`22023` |`DBMS_TRANSACTION savepoint name too long (max 255 bytes)`
80+
|保存点操作不在显式事务块中 |`25P01` |只能用于事务块的错误
81+
|保存点操作嵌套在其他 PL/iSQL 调用中 |`25000` |必须移到独立顶层 `CALL` 的错误及提示
82+
|回滚到不存在的保存点 |`3B001` |内核返回 `savepoint ... does not exist`
83+
|原子上下文中调用包提交或回滚 |`2D000` |`invalid transaction termination`
84+
|只读事务取得快照后切换为读写模式 |`25001` |`transaction read-write mode must be set before any query`
85+
|===
86+
87+
同名保存点重定义、重复回滚及回滚后撤销后续保存点等行为直接复用内核语义。Oracle 空字符串转 `NULL` 的处理发生在进入 C 桥接之前。
88+
89+
=== 自治事务
90+
91+
保存点嵌套检查不豁免自治事务,也不依据用户可设置的 `plisql.inside_autonomous_transaction` 参数放行。真正的自治事务在独立 dblink 会话执行,当前回归场景没有显式事务块,因此先被 `RequireTransactionBlock()` 拒绝。
92+
93+
包内提交和回滚仍受事务终止上下文约束;当前自治事务用例返回 `invalid transaction termination`。读写模式过程可设置独立会话的事务模式,不影响父事务的访问模式。
94+
95+
== 测试验证
96+
97+
`dbms_transaction` 回归组覆盖提交与回滚、自动提交开关、匿名块调用、保存点回滚与重定义、具名参数、名称边界、读写模式切换、原子上下文、保存点嵌套防护及调用者权限。
98+
99+
自治事务用例先探测 dblink 回环连接;可用时验证自治事务中的事务控制与访问模式,否则跳过该部分并使用另一份期望输出。
100+
101+
在已配置、编译并安装的 IvorySQL 源码目录中,可运行 `make -C contrib/ivorysql_ora oracle-installcheck ORA_REGRESS=dbms_transaction`。目标实例需具备对应版本的 `ivorysql_ora`,自治事务测试还需可用的 dblink 环境。
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
= DBMS_TRANSACTION 包
5+
6+
== 目的
7+
8+
`DBMS_TRANSACTION` 提供提交、回滚、保存点及事务读写模式控制接口,便于迁移使用 Oracle 事务控制包的应用。IvorySQL 当前实现六个过程,执行行为受 PL/iSQL 和底层事务机制的约束。
9+
10+
包以调用者权限运行,并向 `PUBLIC` 授予执行权限。访问业务表仍需调用者具备相应权限。
11+
12+
== 功能说明
13+
14+
=== 支持的接口
15+
16+
[cols="3,4",options="header"]
17+
|===
18+
|接口 |功能
19+
|`COMMIT` |提交当前事务
20+
|`ROLLBACK` |回滚当前事务
21+
|`SAVEPOINT(savept IN VARCHAR2)` |在当前事务中建立命名保存点
22+
|`ROLLBACK_SAVEPOINT(savept IN VARCHAR2)` |回滚到指定保存点,不结束当前事务
23+
|`READ_ONLY` |将当前事务设置为只读
24+
|`READ_WRITE` |将当前事务设置为读写
25+
|===
26+
27+
SQL 中使用 `CALL dbms_transaction.过程名(...)`;PL/iSQL 块内可直接调用包过程。保存点过程支持具名参数,例如 `savept => 'sp1'`。
28+
29+
=== 调用限制
30+
31+
* `COMMIT` 和 `ROLLBACK` 必须在允许事务终止的非原子执行上下文中调用。显式 SQL 事务块内或 psql 关闭自动提交后调用,会报 `invalid transaction termination`。顶层的非原子 PL/iSQL 匿名块可以调用,见下面的提交与回滚示例。
32+
* `SAVEPOINT` 和 `ROLLBACK_SAVEPOINT` 必须在显式事务块内,通过独立的顶层 `CALL` 语句执行。不能放到另一个 PL/iSQL 过程、函数或匿名块内部。
33+
* 保存点名称最多 255 字节,按字符串处理,可以包含空格。`NULL` 名称被拒绝;按 Oracle 语义转换为 `NULL` 的空字符串也被拒绝。
34+
* 同名保存点重复定义时,回滚定位到最近一次定义。回滚后目标保存点仍可使用,但其后建立的保存点被撤销。不存在的保存点会报错。
35+
* 建议在事务开始后、访问业务表之前设置读写模式。同一事务中先调用 `READ_ONLY` 再调用 `READ_WRITE` 会报 `transaction read-write mode must be set before any query`;反向切换允许。
36+
37+
`READ_ONLY` 设置访问模式,不自动改变隔离级别。事务中的快照和读一致性仍由 IvorySQL 的隔离级别决定。
38+
39+
== 使用示例
40+
41+
以下示例在 Oracle 兼容模式的 psql 中执行。保持自动提交开启,提交与回滚示例中的 PL/iSQL 匿名块不要再放入外层 SQL `BEGIN;` 事务块。
42+
43+
=== 提交与回滚
44+
45+
[source,sql]
46+
----
47+
\set AUTOCOMMIT on
48+
CREATE TABLE dbms_tx_demo (id int, val varchar2(100));
49+
50+
BEGIN
51+
INSERT INTO dbms_tx_demo VALUES (1, 'committed');
52+
dbms_transaction.commit();
53+
INSERT INTO dbms_tx_demo VALUES (2, 'rolled back');
54+
dbms_transaction.rollback();
55+
END;
56+
/
57+
58+
SELECT * FROM dbms_tx_demo ORDER BY id;
59+
----
60+
61+
查询只返回 `(1, 'committed')`。第一个插入已提交,第二个插入被回滚。若在自动提交模式下把 `INSERT` 和 `CALL dbms_transaction.rollback()` 分成两条顶层语句,前一条插入已经提交,后续回滚不会撤销它。
62+
63+
=== 保存点回滚
64+
65+
[source,sql]
66+
----
67+
BEGIN;
68+
INSERT INTO dbms_tx_demo VALUES (3, 'before savepoint');
69+
CALL dbms_transaction.savepoint(savept => 'sp1');
70+
INSERT INTO dbms_tx_demo VALUES (4, 'after savepoint');
71+
CALL dbms_transaction.rollback_savepoint(savept => 'sp1');
72+
COMMIT;
73+
74+
SELECT * FROM dbms_tx_demo WHERE id IN (3, 4) ORDER BY id;
75+
----
76+
77+
查询只返回 `(3, 'before savepoint')`。这里的 `BEGIN;` 和 `COMMIT;` 是 SQL 事务语句,保存点操作各自通过独立的 `CALL` 执行。结束这个显式事务块应使用 SQL `COMMIT;` 或 `ROLLBACK;`。
78+
79+
=== 读写模式
80+
81+
[source,sql]
82+
----
83+
BEGIN;
84+
CALL dbms_transaction.read_write();
85+
INSERT INTO dbms_tx_demo VALUES (5, 'read write');
86+
COMMIT;
87+
88+
BEGIN;
89+
CALL dbms_transaction.read_only();
90+
INSERT INTO dbms_tx_demo VALUES (6, 'read only');
91+
-- ERROR: cannot execute INSERT in a read-only transaction
92+
ROLLBACK;
93+
94+
DROP TABLE dbms_tx_demo;
95+
----
96+
97+
读写事务中的第 5 行插入成功;只读事务禁止对该普通表执行插入,第 6 行不会保存。
98+
99+
== 兼容性说明
100+
101+
`PRAGMA AUTONOMOUS_TRANSACTION` 不豁免上述事务上下文限制。当前自治事务回归场景中,保存点调用因没有显式事务块而报错,包内提交与回滚调用报 `invalid transaction termination`;`READ_ONLY` 和 `READ_WRITE` 可以设置自治事务自身的访问模式。
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
= DBMS_TRANSACTION Package Design
5+
6+
== Purpose
7+
8+
Provide Oracle-style transaction control through the `ivorysql_ora` extension by reusing existing PL/iSQL statements and transaction management functions. The implementation supports `COMMIT`, `ROLLBACK`, `SAVEPOINT`, `ROLLBACK_SAVEPOINT`, `READ_ONLY`, and `READ_WRITE`. Changes stay within the extension and add no core transaction interfaces.
9+
10+
See xref:master/oracle_compatibility/compat_dbms_transaction.adoc[DBMS_TRANSACTION Package] for usage and call restrictions.
11+
12+
== Implementation
13+
14+
=== Code organization
15+
16+
[cols="3,4",options="header"]
17+
|===
18+
|File |Responsibility
19+
|`src/builtin_packages/dbms_transaction/dbms_transaction--1.0.sql` |Registers C functions, defines the package specification and body, and grants package execution
20+
|`src/builtin_packages/dbms_transaction/dbms_transaction.c` |Validates savepoint names and call contexts, and bridges to transaction functions
21+
|`sql/dbms_transaction.sql` |Regression cases for transaction operations, invalid input, autonomous transactions, and cross-user execution
22+
|`expected/dbms_transaction.out`, `expected/dbms_transaction_1.out` |Expected results without and with a usable dblink environment, respectively
23+
|`Makefile`, `meson.build`, `ivorysql_ora_merge_sqls` |Build, regression, and extension SQL merge registration
24+
|===
25+
26+
These paths are relative to `contrib/ivorysql_ora/`.
27+
28+
=== Package interface and privileges
29+
30+
The package declares `AUTHID CURRENT_USER` and executes `GRANT EXECUTE ON PACKAGE dbms_transaction TO PUBLIC`, allowing ordinary users to call it with their own privileges. Package execution privileges do not change access rights on application objects.
31+
32+
The two savepoint C functions are registered in the `sys` schema as `VOLATILE`. Neither is declared `STRICT`, so the implementation can report an explicit error for a NULL name.
33+
34+
[source,text]
35+
----
36+
Top-level CALL dbms_transaction.savepoint('sp1')
37+
→ PL/iSQL package body
38+
→ sys.ora_dbms_transaction_savepoint(text)
39+
→ DefineSavepoint(name)
40+
→ Subtransaction state transition completes at command end
41+
----
42+
43+
=== Subprogram mappings
44+
45+
[cols="2,4",options="header"]
46+
|===
47+
|Subprogram |Implementation
48+
|`COMMIT` |Executes the native PL/iSQL `COMMIT` statement in the package body
49+
|`ROLLBACK` |Executes the native PL/iSQL `ROLLBACK` statement in the package body
50+
|`SAVEPOINT` |Uses `PERFORM` to call the C function, which calls `DefineSavepoint()`
51+
|`ROLLBACK_SAVEPOINT` |Uses `PERFORM` to call the C function, which calls `RollbackToSavepoint()`
52+
|`READ_ONLY` |Dynamically executes `SET TRANSACTION READ ONLY`
53+
|`READ_WRITE` |Dynamically executes `SET TRANSACTION READ WRITE`
54+
|===
55+
56+
`COMMIT` and `ROLLBACK` reuse PL/iSQL's transaction termination path and inherit its atomic-context restrictions. The package does not open an additional transaction or enable commit and rollback in contexts that prohibit transaction termination.
57+
58+
The access-mode procedures set the mode without changing the isolation level or adding an Oracle snapshot mechanism. Timing and write restrictions follow the underlying `SET TRANSACTION` rules.
59+
60+
=== Savepoint bridge
61+
62+
PL/iSQL has no native savepoint statement, so two C functions bridge the package procedures to transaction management functions declared in `access/xact.h`. They execute these steps in order:
63+
64+
. `get_savepoint_name()` rejects NULL names and names of 256 bytes or more. The limit is 255 bytes, and names are passed as strings.
65+
. `RequireTransactionBlock(true, ...)` verifies that an explicit transaction block exists, preventing savepoint operations in an invalid state.
66+
. `check_not_nested_in_pl_call()` checks `SPI_get_connected() > 0` to detect nesting inside another PL/iSQL invocation and raises an error if nested.
67+
. `DefineSavepoint()` or `RollbackToSavepoint()` is called, leaving subsequent transaction state transitions to the core.
68+
69+
The savepoint functions mark pending transaction states. Subtransaction startup or rollback completes after the top-level command ends. A nested routine continuing before that transition could access a transaction state that is not ready. Savepoint operations therefore require separate top-level `CALL` statements; forcibly committing the command inside the C bridge cannot safely bypass this restriction.
70+
71+
The bridge calls transaction management functions directly and adds no SPI interfaces such as `SPI_savepoint`. `SPI_get_connected()` only checks invocation depth; it does not execute the savepoint operation.
72+
73+
=== Boundaries and errors
74+
75+
[cols="3,2,4",options="header"]
76+
|===
77+
|Condition |SQLSTATE |Diagnostic
78+
|NULL savepoint name |`22004` |`DBMS_TRANSACTION savepoint name must not be NULL`
79+
|Savepoint name exceeds 255 bytes |`22023` |`DBMS_TRANSACTION savepoint name too long (max 255 bytes)`
80+
|Savepoint operation outside an explicit transaction block |`25P01` |Error requiring a transaction block
81+
|Savepoint operation nested inside another PL/iSQL call |`25000` |Error and hint requiring a separate top-level `CALL`
82+
|Rollback to a nonexistent savepoint |`3B001` |Core error: `savepoint ... does not exist`
83+
|Package commit or rollback in an atomic context |`2D000` |`invalid transaction termination`
84+
|Switch from read-only to read-write after a snapshot |`25001` |`transaction read-write mode must be set before any query`
85+
|===
86+
87+
Redefining savepoints, repeated rollback, and removing later savepoints on rollback use core semantics directly. Oracle empty-string-to-NULL handling takes effect before entering the C bridge.
88+
89+
=== Autonomous transactions
90+
91+
The nested savepoint check does not exempt autonomous transactions and does not bypass the guard based on the user-settable `plisql.inside_autonomous_transaction` parameter. Genuine autonomous transactions execute in a separate dblink session. The current regression scenarios have no explicit transaction block, so `RequireTransactionBlock()` rejects them first.
92+
93+
Package commit and rollback calls remain subject to transaction termination constraints; the current autonomous cases return `invalid transaction termination`. The access-mode procedures can set the independent session's transaction mode without changing the parent transaction's mode.
94+
95+
== Verification
96+
97+
The `dbms_transaction` regression group covers commit and rollback, autocommit settings, anonymous block calls, savepoint rollback and redefinition, named arguments, name boundaries, access-mode switching, atomic contexts, nested savepoint guards, and invoker privileges.
98+
99+
Autonomous transaction tests first probe a dblink loopback connection. If available, they verify transaction control and access modes in autonomous transactions. Otherwise, those cases are skipped and the alternative expected output applies.
100+
101+
From a configured, built, and installed IvorySQL source tree, run `make -C contrib/ivorysql_ora oracle-installcheck ORA_REGRESS=dbms_transaction`. The target instance needs the matching version of `ivorysql_ora`; autonomous transaction tests also require a usable dblink environment.

0 commit comments

Comments
 (0)