Skip to content

[metadata-core] sys_view_definition 的"活跃行唯一"从未生效——归档视图仍占用 (name, organization_id, owner) 名额 #5839

Description

@os-zhuang

发现于 #5248(IndexSchema.type / partial 退役)的生产者清点。不在该 PR 范围内——退役本身是零 DDL 变化,这条是被退役暴露出来的既有行为缺口,修它是行为变更。

事实

packages/metadata-core/src/objects/sys-view-definition.object.ts 声明:

indexes: [
  // A given view name is unique per (organization, owner) among active rows —
  // a shared view (owner NULL) and each user's personal views don't collide.
  {
    name: 'idx_sys_view_def_active',
    fields: ['name', 'organization_id', 'owner'],
    unique: true,
    partial: "state = 'active'",   // ← #5248 退役前的原文
  },

注释说的是"among active rows",但 partial 从来没有任何 driver 消费者:SqlDriver.syncDeclaredIndexes 走 knex 的 table.unique(fields, { indexName }),无法表达 WHERE。所以实际建出来的一直是无谓词的全量 UNIQUE 索引

实测(#5248 PR 里的 packages/drivers/driver-sql/src/declared-index-retired-keys.test.ts 同一手法,真实 SQLite 存储的 DDL):

WITH partial    -> CREATE UNIQUE INDEX `idx_sys_view_def_active` on `t` (`name`, `organization_id`, `owner`)
WITHOUT partial -> CREATE UNIQUE INDEX `idx_sys_view_def_active` on `t` (`name`, `organization_id`, `owner`)

两者逐字节相同,且都不含 WHERE

sys_metadata 的关键差别

sys_metadata 的同类声明(idx_sys_metadata_overlay_active)有一个运行时兜底:packages/metadata-protocol/src/protocol.tsensureOverlayIndex() 用 raw SQL 发 CREATE UNIQUE INDEX … WHERE state = 'active'(并且顺带修了 COALESCE(package_id,'') 的 NULL-distinct 问题),所以那张表的活跃行范围是真的交付了的。

sys_view_definition 没有任何等价的运行时迁移——全仓检索 idx_sys_view_def_active 只有声明处一个命中。也就是说这张表的"活跃行唯一"在任何一层都没有实现。

用户可达的后果

sys_view_definitionstate 字段(defaultValue: 'active')。一旦某个视图被置为非 active(归档/重置/软删),它的 (name, organization_id, owner) 组合仍然被全量 UNIQUE 索引占用,于是:

  • 用户删除/归档一个名为 "我的管道" 的个人视图后,无法再新建同名视图——唯一约束会拒绝插入;
  • 反过来,如果当初的意图是"同名的历史版本可以共存",那么当前的全量唯一约束比设计更严。

两种读法下当前行为都不是声明所描述的那个。

可选修法(需要拍板,故不自行实施)

  1. 补运行时迁移——照 ensureOverlayIndex 的形状,为 sys_view_definition 发 partial UNIQUE,MySQL 侧降级为普通复合索引 + 应用层 upsert 语义。交付注释所承诺的语义。
  2. 接受全量唯一并改注释——如果产品上就是"视图名在 (org, owner) 内永久唯一,含已归档",那么把注释里的 "among active rows" 删掉即可,零代码。
  3. state 纳入索引列——(name, organization_id, owner, state),允许同名的一个 active + 多个非 active;但这不等价于 partial(多个 active 之间仍互斥,而多个同 state 的归档行会互斥),语义要单独想清楚。

倾向 1 或 2:取决于产品是否要求视图名可回收。这是行为决策,不是机械修复。

相关

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions