全部文档
文档中心DeepModel功能数据重写(Mutation Rewrite)

数据重写(Mutation Rewrite)

数据重写用于在新建或更新对象数据时,自动将指定属性/链接的值重写为表达式的计算结果。可以实现自动赋值、联动修改值、缓存计算等功能。

数据重写与触发器互补:触发器不能修改触发操作的对象本身,而数据重写正是为此而设计的——它直接修改当前正在保存的对象的属性/链接值。


数据重写定义在属性或链接上(而非对象级别),当该属性/链接所属的对象发生 insert 或 update 时,系统会用你定义的表达式替换该字段的值。

在属性/链接定义中声明:

Copy
type Order {
    required property order_no: str;

    property modified_time: cal::local_datetime {
        -- 更新时自动设为当前时间
        rewrite update using (cal::local_datetime_of_statement())
    };

    property created_time: cal::local_datetime {
        -- 新建时自动设为当前时间
        rewrite insert using (cal::local_datetime_of_statement())
    };
};

关键字

说明

rewrite

声明数据重写规则

insert / update

运行时机,可用逗号同时指定:rewrite insert, update using (...)

using (expr)

重写表达式,计算结果作为该属性的新值

每个属性/链接最多有一条 insert 重写和一条 update 重写(或一条同时覆盖 insert 和 update 的重写)。

在重写表达式中可以使用以下特殊变量:

变量

可用时机

含义

__subject__

insert、update

操作的对象(包含本次设置的新属性/链接值)

__old__

仅 update

操作的对象(更新前的旧值)

__specified__

insert、update

命名元组,每个属性/链接对应一个 bool,表示本次是否被显式赋值

.属性名

insert、update

__subject__ 的简写形式(路径前缀未变时可省略 __subject__.

简单场景下可以直接用 .属性名 代替 __subject__.属性名。但如果表达式中有子查询改变了路径上下文(如 select OtherType filter ...),则必须显式使用 __subject__ 引用当前对象的字段。

default

数据重写

触发时机

仅在 insert 且未赋值

每次 insert / update 都触发(无论是否赋值)

能否覆盖用户输入

❌ 用户赋了值就用用户的

✅ 总是用表达式的结果覆盖

适用场景

提供初始默认值

自动计算值、强制覆盖值


在 DeepModel 中,通过属性/链接的属性栏配置数据重写规则。

运行时机包括:新建后、更新后,各运行时机下至多配一条规则。

  • 可分别配一条新建后、一条更新后的规则,执行语句不同:

  • 开启运行时机含更新后,可配一条新建与更新后的规则,即执行语句相同:

执行语句是赋值表达式 := 的右边部分(不需要写 属性 :=,只写表达式本身)。表达式的返回类型和基数必须与属性/链接的类型和基数匹配。

属性类型

固定值示例

文本

'文本内容'

多语言文本

to_json('{"en":"English","zh-cn":"中文"}')

布尔值

true / false

整数

12345

小数

<decimal>123.45

日期时间

<cal::local_datetime>'2025-02-15 12:30:00'

枚举值

'feature'(输入枚举编码)

链接(单选)

<Component>'COMP_005'(Cast 转业务主键为对象)

运行时机针对最终生成的 DeepQL 语句。例如:在数据管理或 UX 页面新建包含自我链接的数据时,底层实际为「先 insert,再 update 自我链接」,会分别触发 insert 和 update 的重写规则。


业务主键属性 code,新建时自动填入 UUID 字符串(替代业务规则生成的随机字符):

运行时机

执行语句

新建

<str>.id

自动记录数据的创建和最后更新时间:

属性

运行时机

执行语句

created_time

新建

cal::local_datetime_of_statement()

modified_time

更新

cal::local_datetime_of_statement()

对应的 SDL 定义:

Copy
property created_time: cal::local_datetime {
    rewrite insert using (cal::local_datetime_of_statement())
};
property modified_time: cal::local_datetime {
    rewrite update using (cal::local_datetime_of_statement())
};

自动记录操作用户 ID:

属性

运行时机

执行语句

created_user_id

新建

global ${空间模块}::current_user_id

modified_user_id

更新

global ${空间模块}::current_user_id

其中 ${空间模块} 替换为实际的空间模块编码,如 global spacezauoyn::current_user_id

如果只想在数据真正发生变化时才更新 modified_time 和 modified_user_id,可以通过比较 __subject____old__ 的 JSON 来判断:

属性

运行时机

执行语句

modified_time

更新

cal::local_datetime_of_statement() if (<json>__subject__ { ** } != <json>__old__ { ** }) else __old__.modified_time

modified_user_id

更新

global spacezauoyn::current_user_id if (<json>__subject__ { ** } != <json>__old__ { ** }) else __old__.modified_user_id

解读<json>__subject__ { ** } 把更新后的对象(含全部属性和一层链接)转为 JSON,与 <json>__old__ { ** }(更新前)比较。如果不同,说明数据确实变了,才更新时间/用户;否则保留旧值。

以 Task 对象的 p_start_date(计划开始时间)为例,新建时如果用户没有填写,自动赋一个默认值:

场景

执行语句

为空则取当前时间

.p_start_date ?? cal::local_datetime_of_statement()

为空则取当前时间 +7 天

.p_start_date ?? (cal::local_datetime_of_statement() + <cal::relative_duration>'7 days')

为空则取所属需求的迭代开始时间

.p_start_date ?? .requirement.iteration.start_date

为空则取当前迭代的开始时间

.p_start_date ?? (global current_iteration).start_date

?? 是空值合并操作符:左边非空取左边,左边为空取右边。这样用户填了值就用用户的,没填就用默认值。

订单行的 line_no(行号),新建时自动取当前订单下最大行号 +1:

运行时机

执行语句

新建

(max(.order.<order[is OrderLine].line_no) ?? 0) + 1

注意:数据重写是逐条执行的(不像触发器有批量模式),所以这个自增序号只在逐条新建时有效。如果一次批量导入多条订单行,它们在同一时刻查到的 max 值相同,会导致序号重复。批量场景下的序号生成需要通过业务规则或前端控制实现。

__specified__ 是一个命名元组,每个属性/链接都有一个对应的 bool 值,表示本次操作中该字段是否被显式赋值(无论值是否真的变了)。

场景:Order 的 status_modified_time 属性,只在 status 字段被显式更新时才记录时间,其他字段的更新不影响它:

Copy
property status_modified_time: cal::local_datetime {
    rewrite update using (
        cal::local_datetime_of_statement()
        if __specified__.status
        else __old__.status_modified_time
    )
};

解读

  • __specified__.statustrue 表示本次 update 语句中显式设置了 status 字段

  • 满足条件时更新为当前时间;否则用 __old__ 保留旧值

  • 注意:__specified__ 只判断「是否显式赋值」,不判断「值是否真的变了」——如果 update 中写了 status := .status(赋了相同的值),__specified__.status 仍然是 true

__specified__ vs JSON 比较 的区别:

方式

判断依据

适用场景

__specified__.字段

本次 update 是否显式设置了该字段

追踪某个特定字段的更新操作

<json>__subject__ {**} != <json>__old__ {**}

数据是否真正发生变化

只在数据实际变了才更新时间戳


数据重写可以在保存时根据其他字段或关联数据自动计算当前字段的值,类似计算属性但值会落库,查询时不需要重复计算。

Requirement 对象的 description 属性,新建和更新时自动拼接为「【组件名】需求名」:

运行时机

执行语句

新建、更新

(('【' ++ <str>json_get(.component.name, 'zh-cn')) ++ '】') ++ .name

对应的 SDL 定义:

Copy
property description: str {
    rewrite insert, update using (
        '【' ++ <str>json_get(__subject__.component.name, 'zh-cn') ++ '】'
        ++ __subject__.name
    )
};

注意这里引用了关联对象 .component.name——数据重写的表达式可以沿链接取关联数据。

订单头的 total_amount(总金额),每次更新时自动重新计算所有订单行的金额合计:

Copy
property total_amount: decimal {
    rewrite insert, update using (
        sum(.<order[is OrderLine].qty * .<order[is OrderLine].price)
    )
};

这相当于「缓存的计算属性」——值在写入时计算并存储,查询时直接读取,无需每次重复聚合。与计算属性的区别是:计算属性每次查询都实时计算,数据重写只在 insert/update 时计算一次。

订单头的 is_large_order(是否大单)属性,保存时根据总金额自动判断:

Copy
property is_large_order: bool {
    rewrite insert, update using (
        true if __subject__.total_amount > <decimal>10000 else false
    )
};

当重写表达式中有子查询且路径上下文发生变化时,必须显式使用 __subject__

Copy
-- author_name 属性,根据 author_email 自动查找用户名
property author_name: str {
    rewrite insert, update using (
        -- 这里 select 内部的 .email 指向 SystemUser,
        -- 所以必须用 __subject__.author_email 引用当前对象
        (select SystemUser filter .email = __subject__.author_email).name
    )
};

如果没有子查询,直接用 .属性名 即可,因为路径上下文就是当前对象:

Copy
-- 简写形式,等价于 __subject__.name
property upper_name: str {
    rewrite insert, update using (str_upper(.name))
};

在 update 重写中,__old__ 可以获取更新前的旧值,实现变更追踪:

Copy
-- 每次更新时,把上一次的状态追加到历史数组中
property status_history: array<str> {
    default := <array<str>>[];
    rewrite update using (
        __old__.status_history ++ [__subject__.status]
    )
};

数据重写

触发器

作用对象

当前正在保存的对象的单个属性/链接

可以操作任何对象(insert / update / delete)

能否修改当前对象

✅ 这正是它的用途

❌ 不能修改触发对象

定义位置

属性/链接级别

对象级别

运行时机

insert / update

insert / update / delete

典型用途

自动赋值、联动计算、缓存

日志记录、校验、联动更新其他对象

能力

说明

SDL 语法

rewrite insert, update using (expr)

运行时机

insert / update,可单选或同时覆盖

__subject__

操作后的当前对象,简单场景可用 .属性名 代替

__old__

操作前的对象(仅 update 可用)

__specified__

命名元组,判断本次操作是否显式设置了某字段

自动赋值

主键、时间、用户 ID、默认值、自增序号(注意自增仅逐条有效)

联动修改

根据其他字段/关联数据自动计算当前字段

缓存计算

写入时计算并存储,查询时直接读取(类似物化计算属性)

变更检测

<json>__subject__ {**} != <json>__old__ {**} 判断数据是否真正变化

变更历史

__old__.history ++ [__subject__.value] 追加历史记录

与 default 的区别

default 仅 insert 且未赋值时生效,重写每次都覆盖

与触发器的区别

重写修改当前对象的属性,触发器操作其他对象

回到顶部

咨询热线

400-821-9199

我们使用 ChatGPT,基于文档中心的内容以及对话上下文回答您的问题。

ctrl+Enter to send