DeepModel 的所有模式信息(对象类型、属性、链接、约束等)都存储在内置的 schema 模块中,可以通过 自省查询(Introspection) 直接用 DeepQL 查询元数据——无需外部工具或额外接口。
自省查询的本质:schema 本身也是对象。DeepModel 把模式元数据建模为 schema::ObjectType、schema::Pointer(属性和链接的基类)等内置对象类型,你可以像查业务数据一样用 select / filter / order by 来查模式信息。
常用的自省类型:
|
自省类型 |
说明 |
|---|---|
|
|
对象类型(类似 SQL 的表) |
|
|
属性和链接的统一基类 |
|
|
属性(标量字段) |
|
|
链接(指向其他对象) |
|
|
标量类型(str、int64、json 等) |
|
|
约束定义及其作用对象、实际校验表达式和错误提示 |
|
|
注解(可存描述信息等自定义元数据) |
每个对象类型都有 .pointers 链接,指向该类型的所有属性和链接(Pointer)。每个 Pointer 有:
|
属性/链接 |
说明 |
|---|---|
|
|
字段编码 |
|
|
字段的目标类型(标量类型或对象类型) |
|
|
字段所属的对象类型 |
|
|
基数(One / Many) |
|
|
是否必填 |
|
|
是否只读 |
|
|
默认值表达式 |
|
|
计算字段的表达式(非计算字段为空) |
|
|
注解集合(如 |
|
|
目标类型是否内置——内置为属性(property),非内置为链接(link) |
select schema::ObjectType {
name
}
filter .name like 'default::%'
order by .name
default 是默认模块名。实际项目中模块名取决于部署配置(如 myapp::Order)。
select schema::ObjectType {
name,
pointers: {
name,
target_type := .target.name,
is_link := not .target.builtin,
cardinality := <str>.cardinality,
required,
readonly,
}
}
filter .name = 'default::Order'
结果示意:
[
{
"name": "default::Order",
"pointers": [
{ "name": "id", "target_type": "std::uuid", "is_link": false, "cardinality": "One", "required": true, "readonly": true },
{ "name": "order_no", "target_type": "std::str", "is_link": false, "cardinality": "One", "required": true, "readonly": false },
{ "name": "order_date", "target_type": "cal::local_date", "is_link": false, "cardinality": "One", "required": false, "readonly": false },
{ "name": "customer", "target_type": "default::Customer", "is_link": true, "cardinality": "One", "required": false, "readonly": false }
]
}
]
以下是一个实用的通用元数据查询,传入模块名,返回该模块下所有对象的属性和链接信息,包括字段编码、中文名(从注解中取)、类型、基数等:
with
current_module_prefix := <str>$current_module ++ "::",
builtin_pointer := {"id", "__type__", "order", "is_active"},
object_name := current_module_prefix ++ "%",
pointer := (
select schema::ObjectType
filter .name like object_name
).pointers,
pointer := (
select pointer {
anno_desc := assert_single((
select .annotations filter .name = 'std::description'
))
} {
field_id := <str>.id,
field_code := .name,
field_name := <str>json_get(to_json(.anno_desc@value), 'zh-cn'),
object_name := str_replace(.source.name, current_module_prefix, ''),
field_type := 'property' if .target.builtin else 'link',
data_type := str_replace(.target.name, current_module_prefix, ''),
}
filter
.name not in builtin_pointer
order by
.object_name
then .field_code
then .field_type desc
then .id
),
for p in pointer union (
select {
field_id := p.field_id,
field_code := p.field_code,
field_name := p.field_name,
object_name := p.object_name,
field_type := p.field_type,
data_type := p.data_type,
expr := p.expr,
cardinality := <str>p.cardinality,
required := p.required,
readonly := p.readonly,
default := p.default,
}
)
参数 $current_module 传入模块名(如 "default"),调用示例(Python SDK):
from deepfos.element.deepmodel import DeepModel
dm = DeepModel()
result = dm.query(deepql_string, current_module="default")
第一段 with:准备过滤条件
current_module_prefix := <str>$current_module ++ "::",
builtin_pointer := {"id", "__type__", "order", "is_active"},
object_name := current_module_prefix ++ "%",
拼接模块前缀(如 "default::"),用于后续 like 匹配。
builtin_pointer 定义需要排除的内置字段名集合。
第二段 with:取所有 Pointer 并提取注解
pointer := (
select schema::ObjectType
filter .name like object_name
).pointers,
先找到模块下所有对象类型,再通过 .pointers 取出所有属性和链接。
pointer := (
select pointer {
anno_desc := assert_single((
select .annotations filter .name = 'std::description'
))
} {
field_id := <str>.id,
field_code := .name,
field_name := <str>json_get(to_json(.anno_desc@value), 'zh-cn'),
object_name := str_replace(.source.name, current_module_prefix, ''),
field_type := 'property' if .target.builtin else 'link',
data_type := str_replace(.target.name, current_module_prefix, ''),
}
filter .name not in builtin_pointer
order by .object_name then .field_code then .field_type desc then .id
),
要点:
两层形状嵌套:第一层 { anno_desc := ... } 先把注解提取为计算字段,第二层 { field_id, field_code, ... } 基于第一层做进一步转换。这是一种「管道式」写法——先提取中间变量,再基于它计算最终字段。
assert_single(...) 确保每个字段最多只有一条 std::description 注解。
.anno_desc@value 用链接属性语法取注解的 value 值(注解值是 link property)。
json_get(to_json(...), 'zh-cn') 从多语言 JSON 中取中文名。
.target.builtin 为 true 说明目标是内置标量类型(str、int64 等),即属性(property);否则是自定义对象类型,即链接(link)。
for 循环:展开为扁平结果
for p in pointer union (
select {
field_id := p.field_id,
...
}
)
for ... union 遍历每个 Pointer,逐个构造自由对象并合并为最终结果集。这样输出是一张扁平的字段清单,方便导出或展示。
|
object_name |
field_code |
field_name |
field_type |
data_type |
cardinality |
required |
expr |
|---|---|---|---|---|---|---|---|
|
Customer |
code |
客户编码 |
property |
std::str |
One |
true | |
|
Customer |
name |
客户名称 |
property |
std::json |
One |
true | |
|
Order |
order_no |
订单编号 |
property |
std::str |
One |
true | |
|
Order |
order_date |
订单日期 |
property |
cal::local_date |
One |
false | |
|
Order |
status |
订单状态 |
property |
std::str |
One |
false | |
|
Order |
customer |
客户 |
link |
Customer |
One |
false | |
|
Order |
warehouse |
仓库 |
link |
Warehouse |
One |
false | |
|
OrderLine |
line_no |
行号 |
property |
std::int64 |
One |
true | |
|
OrderLine |
product_name |
商品名称 |
property |
std::str |
One |
true | |
|
OrderLine |
qty |
数量 |
property |
std::float64 |
One |
false | |
|
OrderLine |
price |
单价 |
property |
std::float64 |
One |
false | |
|
OrderLine |
order |
所属订单 |
link |
Order |
One |
true |
这张表就是模块的「数据字典」——一条查询拿到所有对象的完整字段结构,在项目交接、文档生成、动态表单构建等场景非常实用。
写入失败时,错误信息可能只包含底层表名和约束名,例如:
new row for relation "<物理表UUID>" violates check constraint "<约束UUID>;schemaconstr"
这表示写入记录没有通过某项 CHECK 校验。CheckViolationError 本身不能说明是哪一个字段、哪一种条件,更不能直接认定为枚举错误。可以通过约束 UUID 查询 schema,先确定校验发生在哪里,再检查实际写入值。
先区分错误中出现的几种 ID:
|
ID 的来源 |
如何使用 |
|---|---|
|
|
查询 |
|
|
查询 |
|
请求 ID、日志关联 ID 或平台错误码 |
用于查日志或识别错误类别,不是 schema 对象 UUID,不能直接用于本节的约束查询 |
在发生报错的同一环境、空间和应用中执行下面的查询。约束 UUID 只取错误信息中 ;schemaconstr 前面的部分,不要带后缀;不要与 relation 后面的物理表 UUID 混用。
with
constraint_id := <uuid>$constraint_id,
c := (
select schema::Constraint
filter .id = constraint_id
)
select c {
id,
name,
subject: { name },
object_name := c.subject[is schema::Pointer].source.name
?? c.subject[is schema::ObjectType].name,
finalexpr,
errmessage,
};
$constraint_id 是查询参数。查询接口的 variables 中配置同名参数,值为从错误中提取的 UUID 字符串:
{
"constraint_id": "11111111-1111-4111-8111-111111111111"
}
上面的 UUID 仅演示格式,执行前需要替换成实际错误中的 UUID。通过 Python SDK 调用时,参数写法与示例 3 相同:dm.query(deepql_string, constraint_id=constraint_uuid),其中 constraint_uuid 为从错误中提取的 UUID 字符串。
返回字段的含义:
|
字段 |
说明 |
|---|---|
|
|
本次定位到的约束 UUID |
|
|
约束名称,用于识别校验类型;具体条件还需要查看 |
|
|
约束的作用主体,可能是字段、对象类型,也可能是标量类型 |
|
|
字段约束返回字段所属对象;对象级约束返回对象自身的名称,通常带模块前缀 |
|
|
实际校验表达式,包含该约束实例化后的条件,用于核对允许值、长度、范围等 |
|
|
约束配置的错误提示模板,用于辅助理解失败原因 |
字段约束通过 c.subject[is schema::Pointer].source.name 找到所属对象。例如,subject.name 为 status、object_name 为 default::Order,就应检查订单的 status 写入值。
对象级约束可能同时检查多个字段。此时 subject.name 是对象名,Pointer 类型过滤得到空集,查询会通过 ?? 回退到 c.subject[is schema::ObjectType].name。应根据 finalexpr 检查对象中所有参与条件的字段,不能把整个对象名当成字段名。
如果主体是标量类型,上面两种对象归属路径都可能为空。此时应先看 subject.name,结合字段元数据确认哪些字段使用了这个类型;不能仅凭空的 object_name 判断约束无效。
约束的通用定义中,expr 可能引用参数,不能只凭它推断本次校验的具体值。排查时优先查看返回的 finalexpr,并对照失败记录实际提交给 DeepModel 的值以及上游字段映射。
|
查出的条件 |
检查内容 |
|---|---|
|
|
是否写入编码而非显示名称;是否有前后空格、大小写差异或空字符串 |
|
文本长度不能超过上限 |
实际字符串是否超长;拼接、补充说明等转换是否增加了长度 |
|
文本需要符合正则表达式 |
写入值的格式是否符合规则;是否带有隐藏空白或换行 |
|
数值需要处于指定范围 |
转换后的数值和单位是否正确;边界是包含还是不包含 |
|
对象级条件涉及多个字段 |
同一条记录中相关字段组合是否满足条件,例如开始日期与结束日期的先后关系 |
错误里的 Failing row 通常没有可靠的列名对应关系,不要只按值的位置猜测字段。应结合约束归属、字段元数据和实际写入参数定位。空字符串、空集与未传值也不是同一回事,需要分别核对。
|
错误信息或类别 |
排查方向 |
|---|---|
|
|
通过约束 UUID 查看字段或对象的实际 CHECK 条件 |
|
|
核对唯一字段或字段组合,以及已存在的数据;不是本例的 CHECK 校验 |
|
|
核对必填字段、实际提交值和默认值;非空约束不等同于 CHECK |
|
|
核对被引用的对象记录及链接值;外键错误不等同于 CHECK |
平台或驱动可能包装底层错误,应结合原始消息判断。并非所有写入错误都含有 ;schemaconstr,也并非每一种约束都表现为同一种错误。定位后修正数据、映射或不符合业务要求的配置,不要通过删除约束绕过校验。
先确认查询与错误发生在同一环境、空间和应用,并检查是否把物理表 UUID 当成了约束 UUID,或遗漏了 ;schemaconstr 后缀的清理。还应确认错误发生后是否修改或重建了 schema:旧错误里的 UUID 可能已不再对应当前约束。
如仍无法定位,可使用下面的物理表反查辅助确认对象,同时保留完整错误和实际写入参数供进一步排查。
当错误包含 relation "<物理表UUID>" 时,可以先用该 UUID 查询对象类型:
with
table_id := <uuid>$table_id
select schema::ObjectType {
id,
name,
}
filter .id = table_id;
$table_id 的参数配置方式与示例 4 相同,传入 relation 后面的 UUID,不带双引号。若返回 default::Order,表示可以从订单对象及其字段配置继续排查;它并不能单独说明失败的是哪一项约束。
并非所有物理表 UUID 都对应 schema::ObjectType。 多选非计算属性或链接的数据可能存放在桥表中。因此,该查询返回空集时,不能直接判断对象不存在。优先使用示例 4 的约束 UUID 定位其 subject 和对象归属,再结合字段基数及存储方式排查;同时核对环境和 schema 变更情况。
|
能力 |
说明 |
|---|---|
|
|
查询对象类型元数据 |
|
|
根据约束 UUID 定位字段或对象,查看实际校验条件 |
|
|
获取对象的所有属性和链接 |
|
|
区分属性(内置标量)和链接(自定义对象) |
|
|
从注解中取自定义元数据(如中文名) |
|
|
将集合展开为扁平的自由对象列表 |
|
自省 + 参数化 |
传入模块名,一条查询生成完整数据字典 |
下一步可了解窗口函数等进阶用法,见「十、进阶」。
回到顶部
咨询热线
