已关闭
[Bug]: USTORE表COPY FROM不校验外键约束(ASTORE正常) #8360
wuchenlong创建于  7月28日关闭于  8月6日
wuchenlong
wuchenlong成员
7月28日 创建

测试类型

SQL功能
存储功能

测试版本

6.0.6LTS/7.0.0LTS

问题描述

【ustore】ustore 表存在外键时,COPY FROM 导入数据未受外键约束。空父表情况下 COPY 子表可成功并写入孤儿行;同结构 ASTORE 会正确报 FK 错误;USTORE 上 INSERT 仍检查外键。官方文档未声明该差异,且称 USTORE 与 ASTORE SQL 语法基本一致。

操作系统和硬件信息

openEuler release 24.03 (LTS) aarch64

测试环境

企业版主备

操作步骤

-- 1) USTORE 父子表 + 外键,导出后空库反序 COPY
DROP DATABASE IF EXISTS t2_fkcopy;
\c postgres
DROP TABLE IF EXISTS locations, countries, regions CASCADE;

CREATE TABLE regions (
  region_id number PRIMARY KEY,
  region_name varchar2(25)
) WITH (storage_type=ustore);

CREATE TABLE countries (
  country_id char(2) PRIMARY KEY,
  country_name varchar2(40),
  region_id number,
  CONSTRAINT fk_c_r FOREIGN KEY (region_id) REFERENCES regions(region_id)
) WITH (storage_type=ustore);

CREATE TABLE locations (
  location_id number(4) PRIMARY KEY,
  street_address varchar2(40),
  postal_code varchar2(12),
  city varchar2(30) NOT NULL,
  state_province varchar2(25),
  country_id char(2),
  CONSTRAINT fk_l_c FOREIGN KEY (country_id) REFERENCES countries(country_id)
) WITH (storage_type=ustore);

INSERT INTO regions VALUES (1,'Europe'),(2,'Americas'),(3,'Asia'),(4,'ME');
INSERT INTO countries VALUES ('IT','Italy',1),('JP','Japan',3),('US','USA',2);
INSERT INTO locations VALUES (1000,'a','1','Roma',NULL,'IT'),(1200,'b','2','Tokyo','TP','JP');

COPY locations TO '/tmp/locations_fk.sql';
COPY countries TO '/tmp/countries_fk.sql';
COPY regions TO '/tmp/regions_fk.sql';

CREATE DATABASE t2_fkcopy;
\c t2_fkcopy
-- 仅建空 USTORE 结构(同上)
-- ... create regions/countries/locations with storage_type=ustore ...

-- 2) 父表为空时 COPY 子表(期望应违反外键)
COPY locations FROM '/tmp/locations_fk.sql';
SELECT l.location_id, l.country_id
FROM locations l
LEFT JOIN countries c ON l.country_id=c.country_id
WHERE c.country_id IS NULL;

COPY countries FROM '/tmp/countries_fk.sql';
SELECT c.country_id, c.region_id
FROM countries c
LEFT JOIN regions r ON c.region_id=r.region_id
WHERE r.region_id IS NULL;

-- 3) 对照:同结构 ASTORE,同样反序 COPY(期望报错)
-- ... create astore tables with same FK ...
COPY locations FROM '/tmp/locations_fk.sql';  -- ASTORE 报 FK 错误

预期输出

与 ASTORE / INSERT 行为一致:在父表键不存在时,COPY FROM 子表应报外键约束错误,例如:
ERROR: insert or update on table "locations" violates foreign key constraint "..."
DETAIL: Key (country_id)=(IT) is not present in table "countries".
不应写入孤儿行。

实际输出

-- USTORE:父表为空时 COPY 成功,并产生孤儿行
=== USTORE: COPY locations while countries empty ===
COPY 2
orphan locations:
 location_id | country_id
-------------+------------
        1000 | IT
        1200 | JP
(2 rows)

=== USTORE: COPY countries while regions empty ===
COPY 3
orphan countries:
 country_id | region_id
------------+-----------
 IT         |         1
 JP         |         3
 US         |         2
(3 rows)

-- ASTORE 对照:同样反序 COPY 正确拦截
=== ASTORE: COPY locations while countries empty ===
ERROR:  insert or update on table "locations" violates foreign key constraint "fk_l_c2"
DETAIL:  Key (country_id)=(IT) is not present in table "countries".

=== ASTORE: COPY countries while regions empty ===
ERROR:  insert or update on table "countries" violates foreign key constraint "fk_c_r2"
DETAIL:  Key (region_id)=(1) is not present in table "regions".

-- 补充:USTORE 上 INSERT 违反外键仍会报错(仅 COPY FROM 不校验)
ERROR:  insert or update on table "countries" violates foreign key constraint "countr_reg_fk"
DETAIL:  Key (region_id)=(99) is not present in table "regions".

日志信息

文档核查(opengauss/docs 本地最新):

  1. configuring_ustore.md:明确「SQL语法和原有的ASTORE存储引擎使用基本保持一致」
  2. in_place_update_storage_engine.md 特性约束:列出物化视图/gin/btree/DEFERRABLE 等限制,未声明 COPY 可跳过外键,也未声明 USTORE 外键在 COPY 下不生效
  3. copy.md:未说明 USTORE 表 COPY FROM 不检查外键
  4. create_table.md:仅说明 Ustore 不支持 DEFERRABLE/INITIALLY DEFERRED,未豁免普通外键检查

本地复现环境:

  • cmp wcl0716 DN 15700:openGauss 6.0.6 build e9b830f7(本次归档)
  • lts wcl0623 DN 15600:openGauss 7.0.0-RC3 build 48ebd016(同日亦复现,行为一致)
    pg_is_in_recovery()=f
    完整 SQL/输出:issues/issue-to-submit/repro_1060640_ustore_copy_fk.sql 、result_1060640.txt

提单组织

测试团队

likedislike
wuchenlongwuchenlong成员
7月28日 添加了label:bug
opengauss_bot
opengauss_bot成员
7月28日 评论:

This issue requires an assignee. Since you haven't specified one, we've assigned TestManager as the default assignee for this issue.

likedislike
opengauss_botopengauss_bot成员
7月28日 将 TestManager 设为负责人
opengauss_botopengauss_bot成员
7月28日 添加了label:sig/StorageEngine,sig/AI,sig/CM,sig/CloudNative,sig/SecurityTechnology,sig/SQLEngine
opengauss_bot
opengauss_bot成员
7月28日 评论:

Welcome To openGauss Community

Hey @wuchenlong , thanks for your contribution to the community.

Bot Usage Manual

I'm the Bot here serving you. You can find the instructions on how to interact with me at Here . That means you can comment below every pull request or issue to trigger Bot Commands.

Contact Guide

If you have any questions, please contact the SIG: StorageEngine, AI, CM, CloudNative, SecurityTechnology, SQLEngine ,
and any of the maintainers: @CarrotGo, @chendong76, @chenxiaobin19, @congzhou2603, @dodders, @hwworkholic, @jemappellehc, @muyulinzhong, @quemingjian, @shenzheng4, @shirley_zhengx, @superlchf, @totaj, @wlff234, @wofanzheng, @ywzq1161327784 ,
and any of the committers: @Igali, @bihua111, @cailei19, @h_ray, @levy53071, @libiao2024, @lihaixiao, @mrzack, @wangfeihuo, @wuyuechuan, @xiong_xjun, @zhangfengzhi123, @zhangxubo, @zhangzq131, @zjh_hw .

likedislike
wuchenlongwuchenlong成员
7月28日 修改了issue 的描述
徐文贵
徐文贵成员
7月28日 评论:

是问题。USTORE 有索引表的批量 COPY 路径遗漏 AFTER ROW INSERT 触发器调用。

  1. oG的外键校验不是普通 CHECK,而是内部的 AFTER ROW INSERT 触发器.

  2. COPY FROM 遇到只有 AFTER 触发器的表,仍会启用批量插入优化。

  3. 在 UHeapCopyFromInsertBatch() 中:
    表有索引时,只执行数据写入和索引更新,然后直接释放 recheckIndexes。漏掉了 ExecARInsertTriggers() 调用。
    表无索引时反而会正常调用 AFTER 触发器

    ASTORE 的相同分支会正确调用 ExecARInsertTriggers(),所以能够报外键错误。
    COPY FROM
    → USTORE 批量插入
    → 写数据
    → 更新主键/其他索引
    → 漏执行 AFTER INSERT 触发器
    → 外键未检查
    → 孤儿行写入成功

likedislike
wuchenlongwuchenlong成员
7月29日 关联了看板:临时看板
chendong76chendong76成员
8月3日 关联了看板:openGauss 7.0.0-LTS
chendong76chendong76成员
8月3日 移除了看板:临时看板
xudabiao2024xudabiao2024成员
8月3日 关联了pull request:Fix UStore COPY foreign key trigger execution
xudabiao2024xudabiao2024成员
8月3日 关联了pull request:Fix UStore COPY foreign key trigger execution
xudabiao2024xudabiao2024成员
8月3日 将 xudabiao2024 设为负责人
wuchenlongwuchenlong成员
8月3日 issue优先级由 无优先级 改变为 主要
xudabiao2024
xudabiao2024成员
8月5日 评论:

验证版本:606B002UB
验证结果:通过
image.png

likedislike
xudabiao2024xudabiao2024成员
8月5日 issue状态由 待办的 改变为 待回归
wuchenlong
wuchenlong成员
8月6日 评论:

【回归版本】openGauss 7.0.0-RC3 build 7a39d3ac(7.0.0LTS B017,compiled at 2026-08-05 14:35:47)
【回归日期】20260806
【回归人员】wuchenlong
【回归结论】通过

测试结果

USTORE 父子表 + 外键场景:父表为空时对子表执行 COPY FROM,现在正确报外键约束错误,不再写入孤儿行(孤儿查询 0 行);与同结构 ASTORE、普通 INSERT 的外键检查行为一致。

说明

历史异常:USTORE 表存在索引时,COPY FROM 批量插入路径(UHeapCopyFromInsertBatch())遗漏 ExecARInsertTriggers() 调用,导致 AFTER ROW INSERT 触发器未执行、外键不校验、可写入孤儿行。
B017 上 USTORE COPY 与 ASTORE/INSERT 行为一致,不再复现。

【回归步骤】

执行 SQL(postgres 库,A 兼容):

\c postgres
DROP DATABASE IF EXISTS t2_fkcopy;
DROP TABLE IF EXISTS locations CASCADE;
DROP TABLE IF EXISTS countries CASCADE;
DROP TABLE IF EXISTS regions CASCADE;

CREATE TABLE regions (
  region_id number PRIMARY KEY,
  region_name varchar2(25)
) WITH (storage_type=ustore);

CREATE TABLE countries (
  country_id char(2) PRIMARY KEY,
  country_name varchar2(40),
  region_id number,
  CONSTRAINT fk_c_r FOREIGN KEY (region_id) REFERENCES regions(region_id)
) WITH (storage_type=ustore);

CREATE TABLE locations (
  location_id number(4) PRIMARY KEY,
  street_address varchar2(40),
  postal_code varchar2(12),
  city varchar2(30) NOT NULL,
  state_province varchar2(25),
  country_id char(2),
  CONSTRAINT fk_l_c FOREIGN KEY (country_id) REFERENCES countries(country_id)
) WITH (storage_type=ustore);

INSERT INTO regions VALUES (1,'Europe'),(2,'Americas'),(3,'Asia'),(4,'ME');
INSERT INTO countries VALUES ('IT','Italy',1),('JP','Japan',3),('US','USA',2);
INSERT INTO locations VALUES (1000,'a','1','Roma',NULL,'IT'),(1200,'b','2','Tokyo','TP','JP');

COPY locations TO '/tmp/reg_loc_fk.dat';
COPY countries TO '/tmp/reg_cty_fk.dat';
COPY regions  TO '/tmp/reg_reg_fk.dat';

CREATE DATABASE t2_fkcopy;
\c t2_fkcopy
-- 仅建空 USTORE 结构(regions/countries/locations 同上面定义,storage_type=ustore)

-- 2) 父表为空时 COPY 子表(期望报 FK 错误)
COPY locations FROM '/tmp/reg_loc_fk.dat';
SELECT l.location_id, l.country_id FROM locations l
 LEFT JOIN countries c ON l.country_id=c.country_id WHERE c.country_id IS NULL;

COPY countries FROM '/tmp/reg_cty_fk.dat';
SELECT c.country_id, c.region_id FROM countries c
 LEFT JOIN regions r ON c.region_id=r.region_id WHERE r.region_id IS NULL;

执行结果:

=== USTORE: COPY locations while countries empty ===
ERROR:  insert or update on table "locations" violates foreign key constraint "fk_l_c"
DETAIL:  Key (country_id)=(IT) is not present in table "countries".
orphan locations:
 location_id | country_id
-------------+------------
(0 rows)

=== USTORE: COPY countries while regions empty ===
ERROR:  insert or update on table "countries" violates foreign key constraint "fk_c_r"
DETAIL:  Key (region_id)=(1) is not present in table "regions".
orphan countries:
 country_id | region_id
------------+-----------
(0 rows)

-- ASTORE 对照(同结构,同样反序 COPY,报 FK 错误)
=== ASTORE: COPY locations while countries empty ===
ERROR:  insert or update on table "locations" violates foreign key constraint "fk_l_c2"
DETAIL:  Key (country_id)=(IT) is not present in table "countries".

=== ASTORE: COPY countries while regions empty ===
ERROR:  insert or update on table "countries" violates foreign key constraint "fk_c_r2"
DETAIL:  Key (region_id)=(1) is not present in table "regions".
likedislike
wuchenlongwuchenlong成员
8月6日 issue状态由 待回归 改变为 已验收
wuchenlongwuchenlong成员
8月6日 关闭了 issue