ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LanceDB 物化视图定义接口 MaterializedViewDefinition 完全解析:SQL 语义、存储布局与刷新机制

LanceDB 物化视图定义接口 MaterializedViewDefinition 完全解析:SQL 语义、存储布局与刷新机制 LanceDB 物化视图定义接口 MaterializedViewDefinition 完全解析SQL 语义、存储布局与刷新机制【免费下载链接】lancedbDeveloper-friendly OSS embedded retrieval library for multimodal AI. Search More; Manage Less.项目地址: https://gitcode.com/gh_mirrors/la/lancedb物化视图Materialized View是 LanceDB 中将查询结果固化为可检索、可索引、可搜索的表的关键能力。本文围绕 Node.js 客户端中的MaterializedViewDefinition接口展开深入讲解物化视图定义查询的 SQL 语法SELECT ... FROM [ns.]table [, function(args) AS alias | , UNNEST(column) AS alias] [WHERE predicate] [LIMIT n]、它在 schema 元数据中的存储布局mv.definition与 format 版本化机制、读取解析流程以及结合 Rust 核心源码的增量刷新原理。读完本文你将掌握如何定义、创建、读取、刷新和删除物化视图并理解其底层实现约束。接口概览什么是 MaterializedViewDefinitionMaterializedViewDefinition是描述物化视图定义查询的接口它回答了一个核心问题这个视图的每一行是从哪张源表、按什么规则算出来的在 nodejs/lancedb/materialized_view.ts 中接口定义如下export interface MaterializedViewDefinition { /** The defining query, in the canonical spelling the server stores. */ query: string; }接口只有一个属性query它是一个 SQL 字符串且是服务器存储的规范拼写canonical spelling——也就是说无论你创建视图时怎么写 SQL大小写、引号、空格读回来的query都是经过解析-渲染归一化后的标准形式。这一点在测试 nodejs/test/materialized_view.test.ts 中有直接验证创建视图时只传where: age 18读回的定义是SELECT name, age FROM people WHERE age 18。这个接口并非孤立存在它对应 Rust 核心层的同名结构体 rust/lancedb/src/materialized_view.rs后者用强类型字段描述同一份定义pub struct MaterializedViewDefinition { pub source_table: String, // 源表名与视图同库 pub source_namespace: VecString, // 源表所在命名空间路径空表示根命名空间 pub lateral: OptionViewLateral, // 每源行计算的 FROM 项UNNEST 或函数 pub projections: VecViewProjection, // 投影输出列视图 schema 顺序 pub filter: OptionString, // WHERE 谓词 pub limit: Optionu64, // 行数上限 }Rust 层的MaterializedViewDefinition::from_sql(sql)负责把 SQL 文本解析成这个结构to_sql()渲染回规范拼写to_json()生成用于存储的 JSON 布局。Node 侧的MaterializedViewDefinition.query字符串正是这一整套结构化定义在语言绑定边界的最终落点。定义查询的 SQL 语法详解物化视图的定义查询遵循一个严格受限的 SELECT 形状rust/lancedb/src/materialized_view.rs 的from_sql文档SELECT column | expr AS name | *, ... FROM [ns.]table [, function(args) AS alias | , UNNEST(column) AS alias] [WHERE predicate] [LIMIT n]各部分语义如下SELECT 子句投影列可以是裸列名、表达式 AS 别名或*表示源表全部列。投影可以重命名列例如SELECT upper(name) AS Shout FROM people。FROM 子句必须恰好是一个源表可带命名空间前缀如ns.docs。在源表之后可以通过逗号追加两类横向项lateral item每个横向项在每一源行上计算、每返回一个元素生成一行function(args) AS alias一个函数Function出现在 FROM 位置其参数是作用于源表的 SQL 表达式返回值为行集合UNNEST(column) AS alias将源表的一个列表列list column展开别名用于在投影中读取元素例如SELECT c.chunk FROM docs, UNNEST(chunks) AS c。WHERE 子句布尔谓词筛选视图保留的行。LIMIT 子句视图持有行数的上限按物化顺序截断。Rust 层用ViewLateral/LateralSource枚举建模横向项其中LateralSource::Unnest表示UNNEST(column)LateralSource::Function表示name(args)。需要注意的是函数形态的视图Function in FROM仅在 LanceDB Cloud 与 Enterprise 上受支持——本地数据库在刷新时若遇到函数形态却没有 staging 绑定会直接报NotSupported错误见physical_unnest实现。除上述子句外任何其他 SQL 子句JOIN、GROUP BY、ORDER BY 等都会被拒绝。理由在源码注释中写得很明确引擎无法维护它不完全理解的查询一份无法完整理解的定义绝不能被物化。CROSS JOIN [LATERAL]与逗号形式的横向项表达的是同一关系会归一化为同一条规范查询。底层存储布局mv.definition 元数据与格式版本物化视图在物理上就是一张普通表其视图身份与定义查询一起存放在schema 元数据中键名为mv.definitionDEFINITION_META_KEY定义于 nodejs/lancedb/materialized_view.ts 与 rust/lancedb/src/materialized_view.rs。当前版本写入的布局DEFINITION_FORMAT 1是一个 JSON 对象{kind: query, format: 1, query: SELECT name, age FROM people WHERE age 18}其中query是to_sql()渲染出的规范拼写kind: query用于让比 format 编号更老的读者把它识别为不可刷新的视图而非读元数据失败。在引入 format 编号之前旧版本写入的是结构化布局legacy layout按kind分为两种{kind: select, ...}根命名空间下的源表{kind: namespaced_select, ...}带命名空间路径的源表。结构化布局包含source_table、source_namespace、projections每项含output与expression、filter、limit等字段。例如{ kind: namespaced_select, source_table: people, source_namespace: [ns], projections: [ {output: name, expression: name}, {output: Shout, expression: upper(name)} ], filter: age 18, limit: 42 }这条旧布局会被读取器渲染成SELECTname, upper(name) ASShoutFROM ns.people WHERE age 18 LIMIT 42——测试 nodejs/test/materialized_view.test.ts 对该还原逻辑做了逐字符断言。旧布局只读不写视图一旦被刷新就会以当前 format 重写。除了定义本身物化视图还会使用其他mv.*元数据键来维护运行状态它们全部声明于 rust/lancedb/src/materialized_view.rs元数据键常量含义mv.definitionDEFINITION_META_KEY视图定义查询format 1 布局mv.incarnationINCARNATION_META_KEY视图物理创建的化身令牌用于区分同名同定义被删除重建的视图mv.source_versionSOURCE_VERSION_META_KEY上次刷新到的源表版本首次刷新前不存在mv.refreshed_at_msREFRESHED_AT_MS_META_KEY上次刷新的墙上时钟时间毫秒mv.stagingSTAGING_META_KEY函数形态视图的输出暂存表绑定mv.view_versionVIEW_VERSION_META_KEY上次成功刷新后的视图表版本视图上任何其他提交都被视为漂移mv.source_version_tsSOURCE_VERSION_TS_META_KEY水印所对应源清单的提交时间戳用于识别源表被删除重建读取与解析流程从元数据到 MaterializedViewDefinitionNode 端读取定义的入口是definitionFromMetadata与definitionFromJson见 nodejs/lancedb/materialized_view.tsMaterializedView.definition()方法最终调用它们async definition(): PromiseMaterializedViewDefinition { return definitionFromJson( await this.inner.materializedViewDefinition(), this.name, ); }解析逻辑的关键点有三带 format 的读取若 JSON 含format字段读取器检查value.format DEFINITION_FORMAT即 1。若更新的版本写入了一个新 format当前版本拒绝猜测直接抛错materialized view ... is stored in format X, which this version of lancedb cannot refresh。旧布局还原若 JSON 无format字段则检查kind是否为select/namespaced_select否则同样视为无法刷新。旧布局按legacyQuery渲染成规范 SQL。若旧布局带limit还会校验其是否能被 JS Number 精确表示——JSON.parse会把超过 2^53 的整数取整因此!Number.isSafeInteger(limit)时抛出 stored limit too large to represent exactly。非视图识别如果表元数据中根本没有mv.definition键则抛出 Table X is not a materialized view。这是把物化视图与普通表区分开来的关键防线——Rust 侧的read_definition返回Ok(None)即普通表任何无法解析的定义都会报错因为把视图当普通表处理会允许它被覆写。前向兼容性测试覆盖了这些分支{format:2,...}与{kind:select_v3,...}都会被拒为 cannot refreshlimit: 9007199254740993会被拒为 too large to represent exactly。实战创建、读取与刷新一个物化视图前置条件源表必须启用稳定行 ID物化视图依赖源表的稳定_rowid作为行级溯源视图内部列__source_row_id记录每行来自哪个源行。因此源表创建时必须传newTableEnableStableRowIds: true存储选项且该选项在表创建后无法再开启。测试中直接验证了这一点对未启用稳定行 ID 的表创建视图会抛出 stable row ids 错误。import { connect } from lancedb/lancedb; const db await connect(./data); // 源表必须启用稳定行 ID await db.createTable(people, [ { name: ada, age: 36 }, { name: kid, age: 7 }, { name: grace, age: 85 }, ], { storageOptions: { newTableEnableStableRowIds: true } });创建视图Connection.createMaterializedView(name, source, options)声明于 nodejs/lancedb/connection.ts创建视图在创建返回前即完成首次填充除非withNoData: true。options 支持select、where、limit、withNoDataconst view await db.createMaterializedView(adults, people, { select: [name, [shout, upper(name)]], // 裸列名 [别名, 表达式] 对 where: age 18, });select参数的类型是MaterializedViewSelect支持三种形态见 nodejs/lancedb/materialized_view.tstype MaterializedViewSelect | (string | [string, string])[] // 裸列名投影自身[别名, SQL 表达式] | Recordstring, string; // { 别名: SQL 表达式 }裸列名会被normalizeSelect自动用反引号转义内部替换为 因此任何合法列名包括含空格的列名都能安全使用——测试 quotes bare select names 用order item列名验证了这一点。而[别名, 表达式] 对的右侧是表达式按原样保留。limit与后续refresh({ sourceVersion })都会经过validateNonNegativeInteger校验Infinity、NaN、负数、小数1.5在到达 Rust 之前就被拒绝——这是为了规避 N-API 会把Infinity静默转成 0、1.5转成 1 的隐患。测试对[-5, 1.5, Infinity, NaN]四种非法值逐一断言。读取定义创建后即可通过view.definition()读回规范查询const view await db.openMaterializedView(adults); const definition await view.definition(); console.log(definition.query); // SELECT name, age FROM people WHERE age 18视图本身就是一个普通表句柄——查询、建索引、向量搜索全部照常可用const rows await view.table().query().toArray();刷新视图MaterializedView.refresh(options?)从源表重新计算视图内容nodejs/lancedb/materialized_view.tsconst result await view.refresh(); // 或强制全量重建 / 刷新到指定源版本 await view.refresh({ full: true }); await view.refresh({ sourceVersion: 42 });refresh返回RefreshMaterializedViewResult其字段在 Rust 侧定义rust/lancedb/src/materialized_view/refresh.rs字段含义mode刷新方式Rebuild全量重建、Incremental增量追加/重算、NoOp已是最新rows_written写入行数重建时为全部行增量时为新增行与被重算行的总和source_version视图现在反映的源表版本version刷新后的视图表版本RefreshMode三态在测试 refreshes incrementally after an append 中有完整演示首次refresh()后追加一行第二次refresh()返回mode: incremental、rowsWritten: 1再次刷新则返回mode: no_op。生命周期管理列出与删除db.listMaterializedViews()返回库中所有物化视图名。注意实现是逐个读取每张表的 schema来判断因此代价是每个表一次 open见 nodejs/lancedb/connection.ts 注释。db.openMaterializedView(name)打开视图若表存在但不是物化视图则拒绝。db.dropMaterializedView(name, namespacePath?)删除视图。视图可能在物理清理完成前就不可用。db.dropMaterializedViewAsync(name, namespacePath?)异步删除返回Job可保留句柄等待清理完成。测试中job.id为null且job.wait()后视图确实消失。并发刷新语义同一视图的并发刷新不会重复写入行两次刷新若规划到相同的源行会在提交时冲突失败方抛错而非二次写入。Rust 侧通过进程内按 URI 分片的refresh_lock互斥锁rust/lancedb/src/materialized_view/refresh.rs 的refresh_lock保证同一进程内同一视图同一时刻只有一个刷新在跑。增量刷新原理增量、重建与 NoOp 的判定增量刷新是物化视图的核心价值。Rust 实现rust/lancedb/src/materialized_view/refresh.rs的判定逻辑分两层事务日志走查transaction walk从上次水印mv.source_version起读源表的 delta识别出新增 fragmentappend、被Rewrite移动的 fragment、被更新原地修改的 fragment。此路径精确且高效。fragment 签名兜底fragment-signature check当事务走查读不了某些 delta 时退化为纯追加检查——对比每个旧 fragment 在视图所读列上的签名数据文件 overlay 删除文件是否原样保留。压缩compaction、删除、更新都会破坏签名从而强制全量重建而视图未读的列发生变化不影响签名这正是兜底路径能放过事务走查读不到的 delta 的原因。WHERE/投影中的表达式在规划时必须满足不可变性约束ensure_immutable会拒绝任何非 immutable 的函数如now()也拒绝version、arrow_typeof等标记为 immutable 但不随行值确定的函数否则增量维护会把不同次求值的结果混在同一视图里。测试 rejects an invalid expression at create time 验证了创建时的静态校验missing 1引用了不存在的列会立即报错。另外还有一些静态约束在规划期plan检查而不是留到刷新期LIMIT与UNNEST不能同时使用——扫描的 limit 按源行计数而 UNNEST 会把一行展开成多行二者语义冲突limit超过i64::MAX被拒绝保证创建与刷新对视图有效性的判断一致视图列名__source_row_id与_rowid为保留名投影输出重名报ColumnAlreadyExistsWHERE 表达式必须是布尔谓词否则报 view filter must be a boolean predicate。使用限制与注意事项仅本地表支持刷新execute_refresh明确要求本地表materialized views are supported only on local tables且视图表不能处于 MemWAL/LSM 未压缩状态。函数形态视图依赖云端FROM位置的函数Function输出会暂存到隐藏表mv.staging本地数据库无法刷新该形态仅 LanceDB Cloud 与 Enterprise 支持。旧布局兼容format 1 之前的select/namespaced_select布局仍可读取并还原为 SQL但只读不写比 format 1 更新的布局一律拒绝并报告 cannot refresh绝不猜测。源表必须启用稳定行 ID且创建后不可补开否则创建视图直接报错。listMaterializedViews成本较高逐个 open 表读取 schema 元数据。相关源码与测试索引接口定义与读取解析nodejs/lancedb/materialized_view.ts连接层视图 API创建/打开/列出/删除nodejs/lancedb/connection.tsNode 原生绑定视图创建 nodejs/src/connection.rs、刷新与定义读取 nodejs/src/table.rsRust 核心定义结构体、解析、规划与校验 rust/lancedb/src/materialized_view.rsRust 核心刷新模式与增量算法 rust/lancedb/src/materialized_view/refresh.rs端到端测试定义往返、增量刷新、非法输入、并发语义nodejs/test/materialized_view.test.ts综上MaterializedViewDefinition虽只有一个query字段其背后却串联了完整的解析—规范渲染—元数据存储—格式版本化—增量维护链路。理解这条链路你就能准确预测视图定义读回的形式、识别哪些查询可以物化、以及刷新时每一步的结果与代价。【免费下载链接】lancedbDeveloper-friendly OSS embedded retrieval library for multimodal AI. Search More; Manage Less.项目地址: https://gitcode.com/gh_mirrors/la/lancedb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表