OneUptime API 查询过滤器 Includes 详解:用 JSON 实现多值集合匹配
【免费下载链接】oneuptimeComplete open-source monitoring and observability platform.项目地址: https://gitcode.com/GitHub_Trending/on/oneuptime
本文围绕 OneUptime 官方 API 参考文档中的 Includes 查询代码示例,系统讲解Includes查询过滤器的 JSON 请求格式、字段语义、在数据库层的 SQL 转换原理,以及它在标签(Labels)、监控类型、库存状态等场景下的实战用法。读完本文,你将能够独立构造Includes查询体,理解其与EqualTo、IncludesAll、IncludesNone等兄弟过滤器的差异,并能在 OneUptime 的查询 API 中正确使用多值匹配过滤。
Includes 查询过滤器是什么
在 OneUptime 的 API 查询体系中,Includes是一种"多值包含"查询过滤器:它匹配字段值属于给定数组中任意一个值的所有对象。用集合的语言描述,就是判断field ∈ value[],等价于 SQL 中的IN (...)语义。
官方数据类型注册表在 App/FeatureSet/APIReference/Utils/DataTypes.ts 中将其定义为:
A query filter that matches objects where a field value is included in the specified array of values.
典型的应用场景包括:
- 按标签集合过滤资源:找出带有某个标签或某几个标签之一的所有监控器、事件、告警;
- 按枚举集合过滤:找出
monitorType为Ping、API或Port之一的所有监控器; - 按状态集合过滤:找出库存资源中
inventoryStatus为stale或recent的记录; - 按 ID 集合过滤:一次请求内批量按主键或外键筛选多个目标对象。
请求体格式:从官方代码示例说起
官方示例文件 Includes.md 给出了一个完整的查询请求体:
{ "query": { "labels": { "_type": "Includes", "value": [ "aaa00000-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "bbb00000-bbbb-bbbb-bbbb-bbbbbbbbbbbb" ] } } }该请求体语义为:查询所有labels(标签)字段值为aaa00000-aaaa-aaaa-aaaa-aaaaaaaaaaaa或bbb00000-bbbb-bbbb-bbbb-bbbbbbbbbbbb中任意一个的对象(注意示例文件中\_type的下划线转义在真实 JSON 中即为_type)。
请求体结构拆解
| 层级 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 顶层 | query | object | 查询过滤条件容器,其 key 为要过滤的字段名,value 为过滤器对象 |
| 过滤器 | _type | string | 过滤器类型标记,此处固定为Includes |
| 过滤器 | value | array | 候选值数组,元素类型支持string、number(以及后端支持的ObjectID) |
这个 "_type判别字段 +value载荷" 的序列化协议是整个 OneUptime 查询层的通用线格式。官方数据类型详情页 App/FeatureSet/APIReference/Service/DataTypeDetail.ts 对Includes的属性描述为:
value(必填):Array<string | number>,一个候选值数组,只要字段值等于其中任意一个即匹配。
请求体的加载与渲染
在 API Reference 站点实现中,Service/DataType.ts 通过LocalCache.getOrSetString缓存并读取Includes.md内容,将其渲染到数据类型指南页面,供开发者在浏览参考文档时直接复制使用:
pageData["includesCode"] = await LocalCache.getOrSetString( "data-type", "includes", async () => { return await LocalFile.read( `${CodeExamplesPath}/DataTypes/Includes.md`, ); }, );这意味着该示例是文档站点上Includes数据类型的官方配套样例,可以直接作为手写查询体的起点。
后端实现:Includes 查询操作符的序列化与反序列化
Includes在类型系统中被建模为一个查询操作符(Query Operator),其核心实现在 Common/Types/BaseDatabase/Includes.ts:
export type IncludesType = Array<string> | Array<ObjectID> | Array<number>; export default class Includes extends QueryOperator<IncludesType> { private _values: IncludesType = []; public get values(): IncludesType { return this._values; } public set values(v: IncludesType) { this._values = v; } public constructor(values: IncludesType) { super(); this.values = values; } public override toJSON(): JSONObject { return { _type: ObjectType.Includes, value: (this as Includes)._values, }; } public static override fromJSON(json: JSONObject): Includes { if (json["_type"] === ObjectType.Includes) { const valuesArray: Array<string> = []; for (const value of (json["value"] as Array<string>) || []) { valuesArray.push(JSONFunctions.deserializeValue(value) as string); } return new Includes(valuesArray); } throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); } }关键点:
- 受支持的元素类型:
IncludesType明确限定为string、ObjectID(UUID 标识符)或number,与文档示例中的两个 UUID 字符串一致; - 线格式对称:
toJSON()输出{ _type: "Includes", value: [...] },fromJSON()严格校验_type后重建对象,保证请求从浏览器经 HTTP 到达服务端后能够无损还原; ObjectType.Includes枚举:该判别值定义于 Common/Types/JSON.ts,与IncludesAll、IncludesNone、InBetween等过滤器一同被注册在 JSON 序列化类型表中。
底层原理:Includes 如何转换为 SQL
普通标量列的转换:SQLIN
当Includes作用于普通标量列时,Common/Server/Types/Database/QueryUtil.ts 会根据列元数据类型做分派:
} else if ( query[key] && query[key] instanceof Includes && tableColumnMetadata ) { if ( tableColumnMetadata.type === TableColumnType.EntityArray || tableColumnMetadata.type === TableColumnType.Entity ) { query[key] = (query[key] as Includes).values as any; } else { query[key] = QueryHelper.any((query[key] as Includes).values) as any; } }- 当目标列为Entity / EntityArray 关系列(如
labels这种多对多标签关联),直接传入值数组交由 TypeORM 处理关系查询; - 当目标列为普通标量列(如
monitorType枚举字符串),则交给QueryHelper.any()。
QueryHelper.any()与底层私有方法in()实现在 Common/Server/Types/Database/QueryHelper.ts:
public static any( values: Array<string | ObjectID | number>, ): FindWhereProperty<any> { return this.in(values); // any and in are the same } private static in( values: Array<string | ObjectID | number>, ): FindWhereProperty<any> { values = values.map((value) => value.toString()); const rid: string = Text.generateRandomText(10); if (!values || values.length === 0) { return Raw(() => { return `TRUE = FALSE`; // this will always return false }, {}); } return Raw( (alias: string) => { return `(${alias} IN (:...${rid}))`; }, { [rid]: values }, ); }由此可知:
- 标量列上的
Includes最终编译为参数化 SQL:WHERE "字段" IN (:...参数); - 所有元素统一
toString()后绑定为参数,天然防 SQL 注入(参数绑定而非字符串拼接); - 空数组的特殊语义:若传入
value: [],生成的谓词恒为TRUE = FALSE,即匹配不到任何对象——空数组按"匹配零行"处理(fail closed),而非匹配全部。
JSONB 列上的 Includes:展开为 OR 等值判断
当Includes作用于customFields这类 JSONB 列时,走的是 Common/Server/Types/Database/JSONColumnQuery.ts 中的专用分支:
if (value instanceof Includes) { const values: Array<ScalarValue> = toScalarArray(value.values); return values.length === 0 ? null : this.anyOf(values); }anyOf将候选数组展开为若干个"该键的标量值等于 X 或数组包含 X"的谓词,以OR连接。其底层equals谓词同时处理两种存储形态:
- 键值以标量存储(单选用例):
col ->> 'key' = CAST(:v AS TEXT); - 键值以数组存储(多选用例):
col -> 'key' @> CAST(:v AS JSONB)(jsonb 包含运算)。
这正是 JSONColumnQuery.ts 中注释强调的"value matches whether it is stored as a scalar or inside an array":无论自定义字段被配置为单选还是多选,同一个Includes查询都能得到一致结果。同时,每个值都会被展开成独立谓词,因此该模块设置了MAX_JSON_QUERY_VALUES_PER_KEY = 200的上限,防止无限值列表把一条查询编译成巨型 SQL。
阈值与边界
- JSONB 列过滤器单键最多200 个候选值,整条 JSON 查询最多50 个键,单个键名最长500 字符,超限即抛出
BadDataException(返回 400); - 普通 SQL 列上的
Includes无此显式上限,但同样遵循数据库参数数量限制,建议按业务面大小控制候选值规模。
使用示例:真实测试用例验证
仓库测试代码可以直接印证上述行为。例如 Common/Tests/Server/API/DashboardPublicResourceListAPI.test.ts 中通过new Includes(...)构造标签与监控类型过滤:
monitorType: new Includes(["Ping"]), labels: new Includes([fixedLabelId]), labels: new Includes([firstLabelId, secondLabelId]),而 Common/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.ts 展示了库存过滤场景:
expect(query["entityType"]).toEqual(new Includes([EntityType.Service])); expect(query["inventoryStatus"]).toEqual(new Includes(["stale"])); new Includes([EntityType.Host, EntityType.KubernetesPod]),可以看到,Includes在项目内被广泛用于:
- 资源列表 API 的标签多选过滤(
labels字段,EntityArray 列); - 监控器类型过滤(
monitorType枚举); - 库存资源的实体类型、数据源、存活状态过滤;
- 遥测指标按
host.name等属性多值过滤(如 DashboardPublicMetricsAggregateAPI.test.ts 中的attributes: { "host.name": new Includes(["web-1", "web-2"]) })。
与其他查询过滤器的对比与选型
Includes并非唯一的集合型过滤器。理解它与其他操作符的差异有助于写出正确的查询:
| 过滤器 | 语义 | 典型 JSON | 说明 |
|---|---|---|---|
Includes | 字段值 ∈ 候选集合(OR) | { "_type": "Includes", "value": ["a", "b"] } | 本文主角,匹配任一值 |
IncludesAll | 数组字段同时包含集合中所有值(AND) | { "_type": "IncludesAll", "value": ["a", "b"] } | 要求数组字段同时含 a 与 b,见 Common/Types/BaseDatabase/IncludesAll.ts |
IncludesNone | 数组字段不含集合中任意值(NOT IN) | { "_type": "IncludesNone", "value": ["a"] } | 排除型过滤,见 Common/Types/BaseDatabase/IncludesNone.ts |
EqualTo | 字段值等于单值 | { "_type": "EqualTo", "value": "a" } | 单值等值比较,见 EqualTo.md |
EqualToOrNull | 字段值等于单值或为 null | { "_type": "EqualToOrNull", "value": "a" } | 等值 + 空值兜底 |
InBetween | 字段值落在闭区间 [start, end] | { "_type": "InBetween", ... } | 数值/日期区间过滤 |
实现层面,IncludesAll与IncludesNone的完整行为同样定义在 JSONColumnQuery.ts 的build()分支中(allOf用 AND 连接、IncludesNone对整个 OR 谓词取反),而 JSON 序列化判别枚举集中在 Common/Types/JSON.ts。当需要"任选其一"时用Includes;需要"全部满足"时用IncludesAll;需要"排除若干值"时用IncludesNone。
使用建议与注意事项
- 标签过滤的首选方案:多对多标签(
labels)过滤请直接使用Includes,请求体会被正确路由到 EntityArray 关系查询路径; - 空数组行为:
value: []表示"匹配不到任何对象",若想表达"不限制",应省略该过滤器字段而不是传空数组; - 候选值类型一致性:
value元素应使用与字段类型匹配的字符串(如 UUID)、数字或枚举文本;ObjectID在序列化后即为 UUID 字符串,可安全混用; - JSONB 自定义字段:作用于
customFields时,值既可以匹配标量存储(单选)也可以匹配数组存储(多选),无需关心字段在界面上的配置形态; - 规模控制:JSONB 列单键最多 200 个候选值;普通列虽无硬性上限,仍建议控制规模以保证 SQL 执行性能。
小结
Includes是 OneUptime 查询体系中实现"多值集合匹配"的核心过滤器。从 官方代码示例 出发,我们梳理了其{ _type, value }的线格式协议、Includes.ts 中的类型实现、QueryUtil.ts 与 QueryHelper.ts 中的 SQLIN转换,以及 JSONColumnQuery.ts 中针对 jsonb 列的 OR 展开语义,并通过仓库测试用例验证了其在标签、监控类型、库存状态等场景的真实用法。掌握Includes及其兄弟过滤器,即可高效构造 OneUptime API 的复杂列表查询。
【免费下载链接】oneuptimeComplete open-source monitoring and observability platform.项目地址: https://gitcode.com/GitHub_Trending/on/oneuptime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考