存储层 schema 与查询范围模块
本文档是
storage_schema_and_query_ranges父模块的概述文档,介绍该模块的设计意图和核心组件。详细内容请参阅各子模块文档。
模块概述
storage_schema_and_query_ranges 是 OpenViking 存储层的核心子模块,负责定义向量数据库集合的 schema 结构以及用于过滤和范围查询的表达式抽象。该模块位于存储层的最前端,为上层业务逻辑提供数据类型定义和查询条件构建的抽象接口。
从功能职责角度看,这个模块扮演着数据模型契约定义者的角色:它定义了集合应该包含哪些字段、这些字段的类型是什么、以及如何通过表达式来筛选符合特定条件的数据记录。所有的向量检索、标量排序、数据删除等操作都依赖于这里定义的 schema 和表达式类型。
核心组件
该模块包含两个主要的子模块:
1. collection_schemas (集合 schema 定义)
collection_schemas 子模块提供了预定义的集合 schema 模板和初始化逻辑。最核心的是 CollectionSchemas 类,它定义了统一上下文集合(Unified Context Collection)的 schema 结构。这个集合用于存储所有的上下文数据,包括:
- 资源的向量嵌入(dense vector 和 sparse vector)
- 元数据字段如 uri、type、context_type、level
- 时间戳字段 created_at 和 updated_at
- 分层信息字段 parent_uri、level(用于区分 L0/L1/L2 摘要/概览/详情层级)
- 业务字段 name、description、tags、abstract
这个 schema 设计体现了 OpenViking 对上下文数据的多层次组织方式:同一个资源可以有不同的抽象层级,检索时可以根据 level 字段选择合适的粒度。
2. expr (过滤表达式 AST)
expr 子模块定义了用于构建查询过滤条件的抽象语法树(AST)。这是一个纯数据类型模块,不包含任何业务逻辑,仅定义了一组不可变的数据类(dataclass)来表示不同的过滤表达式类型。这些表达式类型包括:
- 逻辑运算:And(且)、Or(或)
- 比较运算:Eq(等于)、In(包含于)、Range(范围)、Contains(字符串包含)
- 时间范围:TimeRange(专门用于时间字段的范围查询)
- 原始表达式:RawDSL(用于传递后端特定的 DSL 结构)
这种设计模式在数据库查询构建器中非常常见,称为组合式查询构建(Composable Query Building)。通过将查询条件表示为不可变的数据结构,可以方便地进行组合、修改和序列化,而不用担心意外的副作用。
数据流关系
┌─────────────────────────┐ ┌──────────────────────────┐
│ 上层业务代码 │ │ 向量数据库适配器 │
│ (检索、删除等操作) │────▶│ (CollectionAdapter) │
└─────────────────────────┘ └───────────┬──────────────┘
│
▼
┌─────────────────────┐
│ _compile_filter │
│ (表达式编译) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ expr 模块 │
│ FilterExpr │
│ (表达式定义) │
└─────────────────────┘
上层业务代码通过构造 FilterExpr 表达式对象来描述查询条件,然后传递给 CollectionAdapter.query() 或 CollectionAdapter.delete() 等方法。适配器内部调用 _compile_filter() 方法将这些表达式编译为后端兼容的字典格式,最终传递给向量数据库的搜索接口。
设计决策
2.1 表达式不可变性
所有表达式类型都使用了 frozen=True 的 dataclass,这意味着实例创建后其属性不可修改。这是一个有意为之的设计选择,原因有两点:
第一,在多线程环境下,不可变对象天然是线程安全的,不需要加锁保护。由于这些表达式可能被多个协程同时使用(特别是在异步检索场景中),这个特性避免了潜在的并发问题。
第二,不可变性使得表达式可以被安全地缓存和复用。当构建复杂的过滤条件时,可以预先创建基础表达式组件,然后根据需要组合成更大的表达式,而不用担心原始组件被意外修改。
2.2 多格式兼容性
_compile_filter() 方法接受三种形式的过滤条件:FilterExpr 对象、普通的 dict,或者 None。这种设计体现了对不同使用习惯的兼容:
- 使用表达式对象:类型安全,IDE 支持好,适合复杂的动态查询构建
- 使用普通字典:简单直接,适合已知结构的手写查询
- 使用 None:表示不添加过滤条件,返回所有结果
编译方法内部对三种形式一视同仁,最终都转换为后端需要的字典格式。这种宽松输入、标准化输出的模式降低了调用方的使用门槛。
2.3 RawDSL 的必要性
RawDSL 类型的引入是为了解决一个现实问题:不同的向量数据库后端可能支持不同的查询 DSL 语法。即使上层尝试用统一的表达式类型来描述查询,总会有一些后端特有的查询能力无法被完全抽象。通过允许直接传递原始 DSL 字典,调用方可以在需要时绕过抽象层,直接利用特定后端的高级特性。
与其他模块的关系
该模块被以下模块依赖:
collection_adapters_abstraction_and_backends:使用 expr 模块定义的表达式类型进行查询编译vectorization_and_storage_adapters:在执行向量检索时传递过滤条件retrieval_and_evaluation:检索模块需要使用这些表达式来构建查询条件
该模块依赖的核心类型包括:
datetime:TimeRange 表达式需要处理时间类型的起始和结束值typing.Any:表达式中的 value 和 values 字段需要接受任意类型的比较值
使用建议
对于新加入的开发者,建议按以下方式使用这个模块:
-
优先使用表达式对象:当需要构建动态查询条件时,优先构造
And、Or、Eq等表达式对象,而不是直接写字典。这样可以获得更好的类型检查和 IDE 支持。 -
注意 TimeRange 的边界语义:
TimeRange的 start 采用闭区间(gte),end 采用开区间(lt)。这与 Python 的 slice 语义一致:包含起始时间,不包含结束时间。 -
了解 Range 的可选参数:
Range表达式支持 gte/gt/lte/lt 四个可选参数,可以自由组合。但需要注意,同时设置 gte 和 gt 会产生语义冲突(前者是闭区间,后者是开区间),这种情况下的行为是未定义的。 -
利用空值优化:
And和Or表达式在编译时会自动过滤掉None和空字典条件。这种设计允许代码构造可能为空的复合条件,而不需要额外的判空逻辑。
文档导航
- expr 模块详细文档 - 过滤表达式 AST 的完整技术规格
- collection_schemas 模块详细文档 - 集合 schema 定义和使用指南
- Collection Adapter 文档 - 表达式编译器的调用方实现