全部文档
文档中心DeepModel功能DeepQL常用语法与使用技巧九、自省查询(Introspection)

九、自省查询(Introspection)

DeepModel 的所有模式信息(对象类型、属性、链接、约束等)都存储在内置的 schema 模块中,可以通过 自省查询(Introspection) 直接用 DeepQL 查询元数据——无需外部工具或额外接口。

自省查询的本质:schema 本身也是对象。DeepModel 把模式元数据建模为 schema::ObjectType、schema::Pointer(属性和链接的基类)等内置对象类型,你可以像查业务数据一样用 select / filter / order by 来查模式信息。

常用的自省类型:

自省类型

说明

schema::ObjectType

对象类型(类似 SQL 的表)

schema::Pointer

属性和链接的统一基类

schema::Property

属性(标量字段)

schema::Link

链接(指向其他对象)

schema::ScalarType

标量类型(str、int64、json 等)

schema::Constraint

约束定义及其作用对象、实际校验表达式和错误提示

schema::Annotation

注解(可存描述信息等自定义元数据)

每个对象类型都有 .pointers 链接,指向该类型的所有属性和链接(Pointer)。每个 Pointer 有:

属性/链接

说明

.name

字段编码

.target

字段的目标类型(标量类型或对象类型)

.source

字段所属的对象类型

.cardinality

基数(One / Many)

.required

是否必填

.readonly

是否只读

.default

默认值表达式

.expr

计算字段的表达式(非计算字段为空)

.annotations

注解集合(如 std::description)

.target.builtin

目标类型是否内置——内置为属性(property),非内置为链接(link)

Copy
select schema::ObjectType {
    name
}
filter .name like 'default::%'
order by .name

default 是默认模块名。实际项目中模块名取决于部署配置(如 myapp::Order)。

Copy
select schema::ObjectType {
    name,
    pointers: {
        name,
        target_type := .target.name,
        is_link := not .target.builtin,
        cardinality := <str>.cardinality,
        required,
        readonly,
    }
}
filter .name = 'default::Order'

结果示意:

Copy
[
  {
    "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 }
    ]
  }
]

以下是一个实用的通用元数据查询,传入模块名,返回该模块下所有对象的属性和链接信息,包括字段编码、中文名(从注解中取)、类型、基数等:

Copy
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):

Copy
from deepfos.element.deepmodel import DeepModel

dm = DeepModel()
result = dm.query(deepql_string, current_module="default")

第一段 with:准备过滤条件

Copy
current_module_prefix := <str>$current_module ++ "::",
builtin_pointer := {"id", "__type__", "order", "is_active"},
object_name := current_module_prefix ++ "%",
  • 拼接模块前缀(如 "default::"),用于后续 like 匹配。

  • builtin_pointer 定义需要排除的内置字段名集合。

第二段 with:取所有 Pointer 并提取注解

Copy
pointer := (
    select schema::ObjectType
    filter .name like object_name
).pointers,
  • 先找到模块下所有对象类型,再通过 .pointers 取出所有属性和链接。

Copy
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 循环:展开为扁平结果

Copy
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

这张表就是模块的「数据字典」——一条查询拿到所有对象的完整字段结构,在项目交接、文档生成、动态表单构建等场景非常实用。

写入失败时,错误信息可能只包含底层表名和约束名,例如:

Copy
new row for relation "<物理表UUID>" violates check constraint "<约束UUID>;schemaconstr"

这表示写入记录没有通过某项 CHECK 校验。CheckViolationError 本身不能说明是哪一个字段、哪一种条件,更不能直接认定为枚举错误。可以通过约束 UUID 查询 schema,先确定校验发生在哪里,再检查实际写入值。

先区分错误中出现的几种 ID:

ID 的来源

如何使用

constraint 后面的约束 UUID

查询 schema::Constraint,定位作用字段或对象及具体校验条件

relation 后面的物理表 UUID

查询 schema::ObjectType,辅助确认业务对象;桥表不一定能由该查询直接定位

请求 ID、日志关联 ID 或平台错误码

用于查日志或识别错误类别,不是 schema 对象 UUID,不能直接用于本节的约束查询

在发生报错的同一环境、空间和应用中执行下面的查询。约束 UUID 只取错误信息中 ;schemaconstr 前面的部分,不要带后缀;不要与 relation 后面的物理表 UUID 混用。

Copy
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 字符串:

Copy
{
  "constraint_id": "11111111-1111-4111-8111-111111111111"
}

上面的 UUID 仅演示格式,执行前需要替换成实际错误中的 UUID。通过 Python SDK 调用时,参数写法与示例 3 相同:dm.query(deepql_string, constraint_id=constraint_uuid),其中 constraint_uuid 为从错误中提取的 UUID 字符串。

返回字段的含义:

字段

说明

id

本次定位到的约束 UUID

name

约束名称,用于识别校验类型;具体条件还需要查看 finalexpr

subject.name

约束的作用主体,可能是字段、对象类型,也可能是标量类型

object_name

字段约束返回字段所属对象;对象级约束返回对象自身的名称,通常带模块前缀

finalexpr

实际校验表达式,包含该约束实例化后的条件,用于核对允许值、长度、范围等

errmessage

约束配置的错误提示模板,用于辅助理解失败原因

字段约束通过 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 的值以及上游字段映射。

查出的条件

检查内容

Order.status 只允许指定编码

是否写入编码而非显示名称;是否有前后空格、大小写差异或空字符串

文本长度不能超过上限

实际字符串是否超长;拼接、补充说明等转换是否增加了长度

文本需要符合正则表达式

写入值的格式是否符合规则;是否带有隐藏空白或换行

数值需要处于指定范围

转换后的数值和单位是否正确;边界是包含还是不包含

对象级条件涉及多个字段

同一条记录中相关字段组合是否满足条件,例如开始日期与结束日期的先后关系

错误里的 Failing row 通常没有可靠的列名对应关系,不要只按值的位置猜测字段。应结合约束归属、字段元数据和实际写入参数定位。空字符串、空集与未传值也不是同一回事,需要分别核对。

错误信息或类别

排查方向

CheckViolationError / violates check constraint

通过约束 UUID 查看字段或对象的实际 CHECK 条件

violates unique constraint / 唯一性冲突

核对唯一字段或字段组合,以及已存在的数据;不是本例的 CHECK 校验

violates not-null constraint / 缺少必填值

核对必填字段、实际提交值和默认值;非空约束不等同于 CHECK

violates foreign key constraint / 链接引用失败

核对被引用的对象记录及链接值;外键错误不等同于 CHECK

平台或驱动可能包装底层错误,应结合原始消息判断。并非所有写入错误都含有 ;schemaconstr,也并非每一种约束都表现为同一种错误。定位后修正数据、映射或不符合业务要求的配置,不要通过删除约束绕过校验。

先确认查询与错误发生在同一环境、空间和应用,并检查是否把物理表 UUID 当成了约束 UUID,或遗漏了 ;schemaconstr 后缀的清理。还应确认错误发生后是否修改或重建了 schema:旧错误里的 UUID 可能已不再对应当前约束。

如仍无法定位,可使用下面的物理表反查辅助确认对象,同时保留完整错误和实际写入参数供进一步排查。

当错误包含 relation "<物理表UUID>" 时,可以先用该 UUID 查询对象类型:

Copy
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 变更情况。

能力

说明

schema::ObjectType

查询对象类型元数据

schema::Constraint

根据约束 UUID 定位字段或对象,查看实际校验条件

.pointers

获取对象的所有属性和链接

.target.builtin

区分属性(内置标量)和链接(自定义对象)

.annotations + @value

从注解中取自定义元数据(如中文名)

for ... union

将集合展开为扁平的自由对象列表

自省 + 参数化

传入模块名,一条查询生成完整数据字典

下一步可了解窗口函数等进阶用法,见「十、进阶」。

回到顶部

咨询热线

400-821-9199