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 .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ jobs:

yq -i ".content.sources[0].url = \"$NEW_LOCAL_URL\"" "$PLAYBOOK_FILE"

yq -i ".ui.bundle.url = \"./entemplates.zip\"" "$PLAYBOOK_FILE"

yq -i ".content.sources[0].branches = [\"HEAD\"]" "$PLAYBOOK_FILE"

yq -i ".content.sources[0].edit_url = false" "$PLAYBOOK_FILE"
Expand Down
2 changes: 2 additions & 0 deletions CN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
** xref:master/oracle_compatibility/compat_call_into.adoc[19、CALL INTO]
** xref:master/oracle_compatibility/compat_read_only_view.adoc[20、视图只读]
** xref:master/oracle_compatibility/with_function_procedure.adoc[21、WITH FUNCTION/PROCEDURE]
** xref:master/oracle_compatibility/compat_create_index_online.adoc[22、索引 ONLINE 参数]
* 容器化与云服务
** 容器化指南
*** xref:master/containerization/k8s_deployment.adoc[K8S部署]
Expand Down Expand Up @@ -95,6 +96,7 @@
**** xref:master/compatibility_features_design/call_into.adoc[CALL INTO]
**** xref:master/compatibility_features_design/read_only_view.adoc[视图只读]
**** 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/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,142 @@
:sectnums:
:sectnumlevels: 5

:imagesdir: ./_images

= 索引 ONLINE 参数

== 目的

IvorySQL 数据库支持在创建索引时使用 `ONLINE` 参数,用于在线创建索引,同时不阻塞DML操作。

== 实现说明

=== 数据结构扩展

`IndexStmt` 新增 `online_keyword` 字段。

[source,c]
----
bool transformed; /* true when transformIndexStmt is finished */
bool concurrent; /* should this be a concurrent index build? */
bool online_keyword; /* was ONLINE keyword used (as opposed to CONCURRENTLY)? */
----

=== 语法与解析

==== 语法规则扩展

在 `ora_gram.y` 文件中引入一个弹性选项列表 `create_index_opt_list` / `create_index_opt`,类似现有的 `rebuild_index_opt_list`:

[source,yacc]
----
create_index_opt_list:
create_index_opt_list create_index_opt { $$ = lappend($1, $2); }
| /* EMPTY */ { $$ = NIL; }
;

create_index_opt:
ONLINE
{ $$ = makeDefElem("online", (Node *) makeBoolean(true), @1); }
| TABLESPACE name
{ $$ = makeDefElem("tablespace", (Node *) makeString($2), @1); }
;
----

修改 `IndexStmt` 的两个产生式,将 `OptTableSpace` 替换为 `create_index_opt_list`,并在 action 中从选项列表中提取 `online` 和 `tablespace`。
[source,yacc]
----
IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name
ON relation_expr access_method_clause '(' index_params ')'
opt_include opt_unique_null_treatment opt_reloptions
create_index_opt_list where_clause
{
IndexStmt *n = makeNode(IndexStmt);
bool online = false;
bool online_seen = false;
char *tablespace = NULL;
ListCell *lc;

/* 解析 create_index_opt_list,提取 online 和 tablespace */
foreach(lc, $15)
{
DefElem *opt = (DefElem *) lfirst(lc);

if (strcmp(opt->defname, "online") == 0)
{
if (online_seen)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("ONLINE specified multiple times"),
parser_errposition(opt->location)));
online = defGetBoolean(opt);
online_seen = true;
}
else if (strcmp(opt->defname, "tablespace") == 0)
{
if (tablespace != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("TABLESPACE specified multiple times"),
parser_errposition(opt->location)));
tablespace = defGetString(opt);
}
}

/* CONCURRENTLY 与 ONLINE 互斥 */
if ($4 && online)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use both CONCURRENTLY and ONLINE"),
parser_errposition(@4)));

n->unique = $2;
n->concurrent = $4 || online;
n->online_keyword = online;
n->idxname = $5;
n->relation = $7;
n->accessMethod = $8;
n->indexParams = $10;
n->indexIncludingParams = $12;
n->nulls_not_distinct = !$13;
n->options = $14; /* opt_reloptions (WITH clause) */
n->tableSpace = tablespace;
n->whereClause = $16;
n->excludeOpNames = NIL;
n->idxcomment = NULL;
n->indexOid = InvalidOid;
n->oldNumber = InvalidRelFileNumber;
n->oldCreateSubid = InvalidSubTransactionId;
n->oldFirstRelfilelocatorSubid = InvalidSubTransactionId;
n->primary = false;
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
n->transformed = false;
n->if_not_exists = false;
n->reset_default_tblspc = false;
$$ = (Node *) n;
}
----

==== 执行层修改(`indexcmds.c`)

`DefineIndex()` 中已有临时表降级逻辑(`concurrent = false` when temp table),需在相同位置补充分区表的降级逻辑:

[source,c]
----
/* 已有:临时表降级 */
if (stmt->concurrent && get_rel_persistence(tableId) != RELPERSISTENCE_TEMP)
concurrent = true;
else
concurrent = false;

/* 新增:分区表降级
* Oracle 对分区表 CREATE INDEX ONLINE 返回成功(全局非分区索引)。
* PostgreSQL 的 concurrent + partitioned 路径会报错,因此在 Oracle 解析器
* 模式下(stmt->online_keyword = true)对分区表静默降级为普通构建。
*/
if (concurrent && partitioned && stmt->online_keyword)
concurrent = false;
----

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
:sectnums:
:sectnumlevels: 5

:imagesdir: ./_images

= 索引 ONLINE 参数

== 目的

本文档解释 IvorySQL 中 `ONLINE` 参数在创建索引时的功能,实现该参数功能以保持与 Oracle 行为一致。

== 功能说明

- `ONLINE` 参数在创建索引时被指定,可允许 DML 并发执行,类似 PostgreSQL 中的 `CONCURRENTLY`,但不能与 `CONCURRENTLY` 同时出现。
- `ONLINE` 必须支持出现在列列表 `)` 之后、`WHERE` 子句之前的任意位置,且与 `TABLESPACE`、`PARALLEL`(如已支持)等其他属性的顺序无关。
- 临时表上的 `CREATE INDEX ... ONLINE` 自动降级为普通构建(不报错,与 `CONCURRENTLY` 行为一致)。
- 分区表上的 `CREATE INDEX ... ONLINE` **自动降级为普通构建**(不报错)。

== 测试用例

=== 测试环境准备

[source,sql]
----
-- 基础测试表
CREATE TABLE tbl_ci_online (
id NUMBER(10) PRIMARY KEY,
name VARCHAR2(100),
dept_id NUMBER(10),
salary NUMBER(10,2),
status VARCHAR2(20)
);
INSERT INTO tbl_ci_online
SELECT g, 'name'||g, MOD(g,20), g*100.0, CASE WHEN MOD(g,2)=0 THEN 'ACTIVE' ELSE 'INACTIVE' END
FROM generate_series(1, 1000) g;

-- 唯一索引测试表
CREATE TABLE tbl_ci_unique (
id NUMBER(10) PRIMARY KEY,
email VARCHAR2(200) NOT NULL
);
INSERT INTO tbl_ci_unique SELECT g, 'user'||g||'@example.com' FROM generate_series(1, 200) g;

-- 分区表
CREATE TABLE tbl_ci_part (
id NUMBER(10),
region VARCHAR2(20)
) PARTITION BY RANGE (id);
CREATE TABLE tbl_ci_part_p1 PARTITION OF tbl_ci_part FOR VALUES FROM (1) TO (501);
CREATE TABLE tbl_ci_part_p2 PARTITION OF tbl_ci_part FOR VALUES FROM (501) TO (1001);
INSERT INTO tbl_ci_part SELECT g, CASE WHEN g<=500 THEN 'east' ELSE 'west' END
FROM generate_series(1, 1000) g;
----

=== 基础 ONLINE 构建

[source,sql]
----
-- 最简单形式
CREATE INDEX idx_online_name ON tbl_ci_online (name) ONLINE;

-- 验证索引已建立并有效
SELECT indisvalid FROM pg_index
WHERE indexrelid = 'idx_online_name'::regclass;
-- 期望:t

-- 多列索引
CREATE INDEX idx_online_multi ON tbl_ci_online (dept_id, salary) ONLINE;

-- 表达式索引
CREATE INDEX idx_online_expr ON tbl_ci_online (lower(name)) ONLINE;
----

=== 分区表 ONLINE

[source,sql]
----
-- 分区表父级索引 ONLINE
-- 注:ONLINE 在分区表上静默降级为普通构建,与 Oracle 行为一致
CREATE INDEX idx_part_online ON tbl_ci_part (id) ONLINE;

-- 验证索引已创建且有效(降级为普通构建,父级索引 + 各分区子索引均 VALID)
SELECT c.relname, i.indisvalid
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname LIKE '%idx_part_online%'
ORDER BY c.relname;
-- 期望:idx_part_online(父)及两个分区子索引 indisvalid = t

-- 分区表 ONLINE + TABLESPACE
CREATE INDEX idx_part_online_tbs ON tbl_ci_part (region) ONLINE TABLESPACE pg_default;
----

=== CONCURRENTLY 与 ONLINE 互斥

[source,sql]
----
-- CONCURRENTLY 在前,ONLINE 在后
CREATE INDEX CONCURRENTLY idx_both ON tbl_ci_online (name) ONLINE;
-- 期望:ERROR: cannot use both CONCURRENTLY and ONLINE

-- 验证原有 CONCURRENTLY 语法不受影响
CREATE INDEX CONCURRENTLY idx_still_conc ON tbl_ci_online (dept_id);
-- 期望:成功
----

=== 测试环境清理

[source,sql]
----
DROP TABLE IF EXISTS tbl_ci_online CASCADE;
DROP TABLE IF EXISTS tbl_ci_part CASCADE;
DROP TABLE IF EXISTS tbl_ci_unique CASCADE;
----

2 changes: 2 additions & 0 deletions EN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
** xref:master/oracle_compatibility/compat_call_into.adoc[19、CALL INTO]
** xref:master/oracle_compatibility/compat_read_only_view.adoc[20、Read Only View]
** xref:master/oracle_compatibility/with_function_procedure_en.adoc[21、WITH FUNCTION/PROCEDURE]
** xref:master/oracle_compatibility/compat_create_index_online.adoc[22、ONLINE Parameter for CREATE INDEX]
* Containerization and Cloud Service
** Containerization
*** xref:master/containerization/k8s_deployment.adoc[K8S deployment]
Expand Down Expand Up @@ -95,6 +96,7 @@
*** xref:master/compatibility_features_design/call_into.adoc[CALL INTO]
*** xref:master/compatibility_features_design/read_only_view.adoc[Read Only View]
*** 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]
** Built-in Functions
*** xref:master/oracle_builtin_functions/sys_context.adoc[sys_context]
*** xref:master/oracle_builtin_functions/userenv.adoc[userenv]
Expand Down
Loading
Loading