Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cherry-pick to 1.1-dev: add foreign key self refer #14887

Merged
merged 10 commits into from Mar 13, 2024

Conversation

daviszhen
Copy link
Contributor

What type of PR is this?

  • API-change
  • BUG
  • Improvement
  • Documentation
  • Feature
  • Test and CI
  • Code Refactoring

Which issue(s) this PR fixes:

issue #https://github.com/matrixorigin/MO-Cloud/issues/2284

https://github.com/matrixorigin/MO-Cloud/issues/1449

https://github.com/matrixorigin/MO-Cloud/issues/1450

What this PR does / why we need it:

docs: 见 https://github.com/matrixorigin/docs/pull/270/files

外键逻辑的修改

例子:
```sql
    create table t1(
        a int primary key,
        b int,
        constraint `c1` foreign key `fk1` (b) references t1(a)
    )
```
  1. create table 增加外键自引用

    • 名称的处理

      mysql 会为b建立次级索引KEY fk1 (b)。将fk1作为次级索引的名称。
      约束名称为c1。

      mo不会在b上建立次级索引。因此fk1不被使用。

    • 新增constraint name

      原先没指定constraint name时,默认是空串。没有constraint name,alter table无法删除外键。

      当输入的constraint name为空串时,生成一个uuid。
      当输入的constraint name为空白符串时,报错。
      其它,用输入的constarint name。

      在增加constraint时,会检查重复。
      在删除constraint时,会检查是否存在。

    • getForeignKeyData拆出来checkFkColsAreValid。

      checkFkColsAreValid检查外键的列是否合法

      对于非自引用外键,getForeignKeyData的逻辑不变。
      对于自引用外键,getForeignKeyData仅处理外键的定义,生产fkdata。
      等tableDef的pk,uk都准备好后。再由checkFkColsAreValid检查外键的列是否合法。
      因为pk,uk在语法上,可能在外键定义之后。

  2. alter table 增加/删除 外键自引用

    • 新增alter table ... drop constraint c1

      alter table ... drop foreign key c1 与 alter table ... drop constraint c1 相同都是删除外键。
      drop foreign key 的名称也要填c1,而不是fk1

    • alter table ... add/drop constraint/foreign key 会检查约束的存在与否。

    • alter table ... add constraint/foreign key

      会检查表中的数据满足外键条件。如果不满足,报错。且add失败。

  3. drop/truncate table

    表被外键引用,都是外键自引用。表是可以删除的。

  4. 外键自引用时,父表id 和 自表id都设置为0.

    plan.ForeignKeyDef.ForeignTbl = 0
    RefChildTableDef.Tables 中自引用的tableId也为0

    因此碰到tableId为0的情况,说明与外键自引用有关,需要特殊处理。

  5. 特殊的case

    • insert into t2 values (1,2),(2,1);

      • mo不报错
      • mysql 报错
    • insert into t2 values (2,2);

      都不报错。

用sql检查外键自引用

mo的外键自引用检查在插入数据之后,进行的。
与mysql的做法不完全相同,mysql没有深入调研。
  1. 生成外键检查sql

    需要检查的场景:

     alter table ... add foreign key
     alter table ... add constraint
     insert
     update
     load
    
     在上述语句构建plan时,会生成一个外键检查的sql。`genSqlForCheckFKConstraints`和`genSqlsForCheckFKSelfRefer`
     构造这样的sql。
    

    构建sql的方式,检查外键约束是否满足。

     单个字段情形
     
         父表:
             T(a)
         子表:
             S(b)
             foreign key (b) references T(a)
    
    
         生成的sql :
             select count(*) == 0 from (
                 select distinct S.b from S where S.b is not null
                 except
                 select distinct T.a from T
             )
         如果结果是true,则表中的数据满足约束条件。
    
     多个字段情形
    
         父表:
             T(a,b)
         子表:
             S(c,d)
             foreign key (c,d) references T(a,b)
    
    
         生成的sql :
             select count(*) == 0 from (
                 select distinct S.c,S.d from S where S.c is not null and S.d is not null
                 except
                 select distinct T.a,T.b from T
             )
         如果结果是true,则表中的数据满足约束条件。
    

    如果有多个外键,每个外键都会生成一个sql。
    对于外键自引用,S和T是同一个表。

  2. 在语句执行完成后,会再执行外键检查sql。

    如果检查失败,会报错。
    在compile.run中,defer函数会执行detectFkSelfRefer来检查外键约束。

已知的问题

乐观事务下,下面的case 与 悲观事务的行为不一致。

```                                        
    [SCRIPT   FILE]: foreign_key/fk_self_refer2.sql
    [ROW    NUMBER]: 117
    [SQL STATEMENT]: insert into t1 values (1,2,3);
    [EXPECT RESULT]:
    Duplicate entry '1' for key '__mo_index_idx_col'
    [ACTUAL RESULT]:
    Cannot add or update a child row: a foreign key constraint fails
                       
    [SCRIPT   FILE]: foreign_key/fk_self_refer3.sql
    [ROW    NUMBER]: 43
    [SQL STATEMENT]: update t1 set b = 3 where b = 4;
    [EXPECT RESULT]:
    Duplicate entry '(1,3)' for key '__mo_cpkey_col'
    [ACTUAL RESULT]:
    Duplicate entry '3a15013a1503' for key '__mo_cpkey_col'

    [SCRIPT   FILE]: foreign_key/fk_self_refer3.sql
    [ROW    NUMBER]: 58
    [SQL STATEMENT]: update t1 set c = 2 where b = 5;
    [EXPECT RESULT]:
    Duplicate entry '(1,2)' for key '__mo_index_idx_col'
    [ACTUAL RESULT]:
    Duplicate entry '3a15013a1502' for key '__mo_index_idx_col'

    [SCRIPT   FILE]: foreign_key/fk_self_refer3.sql
    [ROW    NUMBER]: 160
    [SQL STATEMENT]: update t1 set c = 4 where b = 3;
    [EXPECT RESULT]:
    Duplicate entry '(1,4)' for key '__mo_index_idx_col'
    [ACTUAL RESULT]:
    Duplicate entry '3a15013a1504' for key '__mo_index_idx_col'

```

进一步

foreign_key_checks
在次级索引上加外键

daviszhen and others added 2 commits March 11, 2024 14:58
docs: 见 https://github.com/matrixorigin/docs/pull/270/files

例子:
```sql
create table t1(
a int primary key,
b int,
constraint `c1` foreign key `fk1` (b) references t1(a)
)
```

1. create table 增加外键自引用

- 名称的处理

mysql 会为b建立次级索引KEY `fk1` (b)。将fk1作为次级索引的名称。
约束名称为c1。

mo不会在b上建立次级索引。因此fk1不被使用。

- 新增constraint name

原先没指定constraint name时,默认是空串。没有constraint name,alter table无法删除外键。

当输入的constraint name为空串时,生成一个uuid。
当输入的constraint name为空白符串时,报错。
其它,用输入的constarint name。

在增加constraint时,会检查重复。
在删除constraint时,会检查是否存在。

- getForeignKeyData拆出来checkFkColsAreValid。

checkFkColsAreValid检查外键的列是否合法

对于非自引用外键,getForeignKeyData的逻辑不变。
对于自引用外键,getForeignKeyData仅处理外键的定义,生产fkdata。
等tableDef的pk,uk都准备好后。再由checkFkColsAreValid检查外键的列是否合法。
因为pk,uk在语法上,可能在外键定义之后。

2. alter table 增加/删除 外键自引用

- 新增alter table ... drop constraint `c1`

alter table ... drop foreign key `c1` 与 alter table ... drop constraint `c1` 相同都是删除外键。
drop foreign key 的名称也要填`c1`,而不是`fk1`

- alter table ... add/drop constraint/foreign key 会检查约束的存在与否。

- alter table ... add constraint/foreign key

会检查表中的数据满足外键条件。如果不满足,报错。且add失败。

3. drop/truncate table

表被外键引用,都是外键自引用。表是可以删除的。

4. 外键自引用时,父表id 和 自表id都设置为0.

plan.ForeignKeyDef.ForeignTbl = 0
RefChildTableDef.Tables 中自引用的tableId也为0

因此碰到tableId为0的情况,说明与外键自引用有关,需要特殊处理。

5. 特殊的case

- insert into t2 values (1,2),(2,1);

- mo不报错
- mysql 报错

- insert into t2 values (2,2);

都不报错。

mo的外键自引用检查在插入数据之后,进行的。
与mysql的做法不完全相同,mysql没有深入调研。

1. 生成外键检查sql

需要检查的场景:

alter table ... add foreign key
alter table ... add constraint
insert
update
load

在上述语句构建plan时,会生成一个外键检查的sql。`genSqlForCheckFKConstraints`和`genSqlsForCheckFKSelfRefer`
构造这样的sql。

构建sql的方式,检查外键约束是否满足。

单个字段情形

父表:
T(a)
子表:
S(b)
foreign key (b) references T(a)

生成的sql :
select count(*) == 0 from (
select distinct S.b from S where S.b is not null
except
select distinct T.a from T
)
如果结果是true,则表中的数据满足约束条件。

多个字段情形

父表:
T(a,b)
子表:
S(c,d)
foreign key (c,d) references T(a,b)

生成的sql :
select count(*) == 0 from (
select distinct S.c,S.d from S where S.c is not null and S.d is not null
except
select distinct T.a,T.b from T
)
如果结果是true,则表中的数据满足约束条件。

如果有多个外键,每个外键都会生成一个sql。
对于外键自引用,S和T是同一个表。

2. 在语句执行完成后,会再执行外键检查sql。

如果检查失败,会报错。
在compile.run中,defer函数会执行`detectFkSelfRefer`来检查外键约束。

乐观事务下,下面的case 与 悲观事务的行为不一致。

```
[SCRIPT   FILE]: foreign_key/fk_self_refer2.sql
[ROW    NUMBER]: 117
[SQL STATEMENT]: insert into t1 values (1,2,3);
[EXPECT RESULT]:
Duplicate entry '1' for key '__mo_index_idx_col'
[ACTUAL RESULT]:
Cannot add or update a child row: a foreign key constraint fails

[SCRIPT   FILE]: foreign_key/fk_self_refer3.sql
[ROW    NUMBER]: 43
[SQL STATEMENT]: update t1 set b = 3 where b = 4;
[EXPECT RESULT]:
Duplicate entry '(1,3)' for key '__mo_cpkey_col'
[ACTUAL RESULT]:
Duplicate entry '3a15013a1503' for key '__mo_cpkey_col'

[SCRIPT   FILE]: foreign_key/fk_self_refer3.sql
[ROW    NUMBER]: 58
[SQL STATEMENT]: update t1 set c = 2 where b = 5;
[EXPECT RESULT]:
Duplicate entry '(1,2)' for key '__mo_index_idx_col'
[ACTUAL RESULT]:
Duplicate entry '3a15013a1502' for key '__mo_index_idx_col'

[SCRIPT   FILE]: foreign_key/fk_self_refer3.sql
[ROW    NUMBER]: 160
[SQL STATEMENT]: update t1 set c = 4 where b = 3;
[EXPECT RESULT]:
Duplicate entry '(1,4)' for key '__mo_index_idx_col'
[ACTUAL RESULT]:
Duplicate entry '3a15013a1504' for key '__mo_index_idx_col'

```

foreign_key_checks
在次级索引上加外键

Approved by: @nnsgmsone, @ouyuanning, @iamlinjunhong, @heni02, @aunjgr, @zhangxu19830126
@mergify mergify bot added the kind/bug Something isn't working label Mar 11, 2024
@matrix-meow matrix-meow added the size/XXL Denotes a PR that changes 2000+ lines label Mar 11, 2024
@mergify mergify bot merged commit efe2037 into matrixorigin:1.1-dev Mar 13, 2024
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Something isn't working size/XXL Denotes a PR that changes 2000+ lines
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants