ARTICLE DETAIL

资讯详情

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

LokiJS Collection Transforms 完全指南:把查询链序列化为可命名、可持久化的“存储过程“

LokiJS Collection Transforms 完全指南:把查询链序列化为可命名、可持久化的“存储过程“ 数据库后端【免费下载链接】LokiJSjavascript embeddable / in-memory database项目地址https://gitcode.com/gh_mirrors/lo/LokiJS点击查看免费下载LokiJS 的 Collection Transforms 允许把一整套 Resultset 链式查询find、where、排序、分页等抽象为纯对象的步骤step数组定义并可选地命名保存到 Collection 中、随数据库一起序列化持久化从而获得类似存储过程的能力。本文以 tutorials/Collection Transforms.md 为骨架结合 src/lokijs.js 的源码实现与 spec/generic/transforms.spec.js 测试用例完整讲解 transform 的结构、命名、参数化[%lktxp]、执行方式以及在 DynamicView 中的分支提取用法。读完后你将能写出可保存、可复用、可参数化的复杂查询管线并理解其底层执行机制。一、核心概念把查询链变成数据定义LokiJS 常规的查询链是这样写的var results coll.chain() .find({Age: {$gt: 20}}) .where(function(obj) { return obj.Country.indexOf(FR) 0; }) .simplesort(Name) .offset(100) .limit(25) .data();Transform 的基本思想是将上述 Resultset 链式处理过程转换成一个对象数组定义这个数据定义随后可以命名并随 Collection 一起保存在数据库内。它本质上是查询逻辑的数据化、可序列化版本。从源码角度看LokiJS 把 transform 的执行入口放在了Resultset.prototype.transformsrc/lokijs.js它会对步骤数组做switch (step.type)分发逐个映射到对应的 Resultset 链式方法上。这意味着 transform 与普通链式查询共享同一套底层实现行为完全等价。二、适用场景官方文档给出的典型用途包括编写操作 Loki 数据库的工具以数据形式描述查询便于工具解析、生成和转换创建存储过程式的命名查询把频繁使用的复杂查询保存下来按名调用为数据抽取extraction目的转换数据配合 map、mapReduce、eqJoin 等步骤做格式化或提取可扩展自定义元数据通过附加属性描述 transform 的用途、参数说明等信息。Transform 不是必选功能也不打算取代方法链式调用它的价值在于抽象和整理重复性查询使代码更整洁、查询更动态。三、Transform 的结构有序步骤数组一个 transform 是一个有序的 step 对象数组每个 step 对应一次链式操作。支持的 step 类型如下全部可在Resultset.prototype.transform的 switch 分支中找到对应实现step 类型对应链式方法说明findrs.find(value)基于 mongo 风格查询对象过滤可利用索引wherers.where(value)传入 JavaScript 过滤函数simplesortrs.simplesort(property, desc)按属性名排序compoundsortrs.compoundsort(value)多属性组合排序sortrs.sort(value)传入自定义比较函数排序limitrs.limit(value)限制结果数offsetrs.offset(value)跳过前 N 条updaters.update(value)对结果集中的文档执行更新函数removers.remove()从集合中删除结果集中的文档maprs.map(value, dataOptions)映射到新的匿名集合mapReducers.mapReduce(mapFunction, reduceFunction)map reduce 数据转换eqJoinrs.eqJoin(joinData, leftJoinKey, rightJoinKey, mapFun, dataOptions)左连接两套数据其中limit、offset、map、eqJoin、mapReduce等步骤会产生新的 Resultset或直接返回数据数组源码中会重新赋值rs引用rs rs.limit(...)以保证后续步骤在正确的对象上继续执行。步骤的参数可以硬编码也可以使用 LokiJS 专门为 transform 提供的参数替换机制。四、最简单的一步 transformvar tx [ { type: find, value: { owner: odin } } ];这是一个只包含find步骤的 transform硬编码了查询条件owner odin。它可以直接传入chain()执行。五、命名保存 transformaddTransform 与 transforms 存储上面的tx可以命名保存到 Collection 中userCollection.addTransform(OwnerFilter, tx);addTransform的实现src/lokijs.js会先检查同名 transform 是否已存在若存在则抛出Error(a transform by that name already exists)否则存入this.transforms[name]。transforms是一个以名字为键的普通对象在 Collection 构造时初始化为{}见 src/lokijs.js。与之配套的 Collection API 还有getTransform(name)按名取回 transform 定义src/lokijs.jssetTransform(name, transform)覆盖更新一个已存在的命名 transformsrc/lokijs.jsremoveTransform(name)删除命名 transformsrc/lokijs.js。关键点transforms 随 Collection 一起序列化。在 Collection 的序列化/复制逻辑中可以看到copyColl.transforms coll.transforms的拷贝行为src/lokijs.js因此命名 transform 会随数据库的 save/load 周期保留下来这正是存储过程式命名查询能力的根基。六、执行 transform 的两种方式命名保存后transform 可以通过名字执行userCollection.chain(OwnerFilter).data();也可以直接传入原始 transform 数组执行无需事先保存userCollection.chain(tx).data();从源码看Collection.prototype.chain(transform, parameters)src/lokijs.js会先实例化一个新的Resultset然后委托给rs.transform(transform, parameters)。Resultset.prototype.transform内部若收到字符串参数会先从this.collection.transforms中按名查找若既不是名字也不是数组typeof transform ! object || !Array.isArray(transform)则抛出Error(Invalid transform)src/lokijs.js。七、参数化机制[%lktxp] 前缀Transform 参数化的规则是transform 中任何以字符串[%lktxp]开头的对象属性右值right-hand value都会被当作参数占位符在执行时从传入的params对象中取值替换。例如var tx [ { type: find, value: { owner: [%lktxp]OwnerName } } ];执行时需要传入包含对应参数的 params 对象var params { OwnerName: odin }; userCollection.chain(tx, params).data();或按名字执行userCollection.chain(OwnerFilter, params).data();替换值的类型可以是任意数据类型字符串、数字、对象、函数……。参数扫描是递归的源码中的Utils.resolveTransformObjectsrc/lokijs.js会遍历 step 对象的整个对象层级凡发现typeof subObj[prop] string subObj[prop].indexOf([%lktxp]) 0即取出参数名substring(8)并在params.hasOwnProperty(pname)成立时完成替换同时递归深度被限制在10 层if (depth 10) return subObj;避免过度深挖。顶层的Utils.resolveTransformParamssrc/lokijs.js会先对每个 step 执行clone(transform[idx], shallow-recurse-objects)克隆再在克隆副本上做扫描替换因此原始 transform 定义不会被修改——这一点在测试中也有明确断言见 spec/generic/transforms.spec.js。八、where 过滤器 参数化解决函数无法序列化的问题where 过滤函数不能直接保存进数据库函数无法可靠地 JSON 序列化/反序列化。但如果确实需要这类过滤可以借助 transform 加参数化机制把函数放在执行时传入的 params 里从而让保存的 transform与执行期函数干净地分离var tx [ { type: where, value: [%lktxp]NameFilter } ]; items.addTransform(ByFilteredName, tx); // 以下调用可以在保存/加载周期之后立即发生 // 这里使用匿名函数也可以使用具名函数引用 var params { NameFilter: function(obj) { return (obj.name.indexOf(nir) ! -1); } }; var results items.chain(ByFilteredName, params).data();由于where步骤的value是一个[%lktxp]参数占位符整个 transform 定义保持纯数据、可序列化而真正的过滤函数只在执行时注入。九、多步 transform 与终止链的步骤Transform 可以包含多个按顺序执行的步骤。幕后chain()会实例化一个 Resultset把每个步骤当作独立的链式操作依次调用最后返回结果。需要特别注意某些内置步骤如mapReduce会通过返回数据数组来终止 transform/链此时chain()的返回值就是实际数据而不是需要再调用.data()解析的 Resultset。源码注释也明确提示make any of these last in transform stepssrc/lokijs.js。同理update、remove会直接修改/删除当前过滤结果集中的文档应谨慎使用源码注释同样有警告见 src/lokijs.js。一个更复杂的、把三种参数化步骤组合在一起的多步 transformvar tx [ { type: find, value: { owner: { $eq: [%lktxp]customOwner } } }, { type: where, value: [%lktxp]customFilter }, { type: limit, value: [%lktxp]customLimit } ]; function myFilter(obj) { return (obj.name.indexOf(nir) ! -1); } var params { customOwner: odin, customFilter: myFilter, customLimit: 100 } users.chain(tx, params);正如上例所示LokiJS 会扫描对象层级最多 10 层对所有形如参数占位符的右值做替换并用 params 中对应键的值可以是任意类型替换掉那个字符串。注意这里的find步骤内嵌了$eq操作符与参数化的组合这说明参数替换发生在查询对象内部任意层级不只限于最外层。十、多参数步骤simplesort / mapReduce / eqJoin / remove某些步骤除了type和value外还需要具名的专用属性。下面的例子单独展示了这些步骤它们不一定适合放在同一个 transform 中更多是各自独立的形态var step1 { type: simplesort, property: name, desc: true }; var step2 { type: mapReduce, mapFunction: myMap, reduceFunction: myReduce }; var step3 { type: eqJoin, joinData: jd, leftJoinKey: ljk, rightJoinKey: rjk, mapFun: myMapFun }; var step4 { type: remove }对应源码分支如下src/lokijs.jssimplesortrs.simplesort(step.property, step.desc || step.options)—— 第二个参数既可以是布尔desc也可以是包含desc的 options 对象mapReducers.mapReduce(step.mapFunction, step.reduceFunction)eqJoinrs.eqJoin(step.joinData, step.leftJoinKey, step.rightJoinKey, step.mapFun, step.dataOptions)removers.remove()无需额外参数。十一、在 DynamicView 中使用 transformbranchResultset 分页提取Transform 还可以作为DynamicView 的提取extraction方法。典型场景是先建一个 DynamicView 保存广义过滤结果集再由用户自定义的 transform 从这个视图里快速提取、分页。该功能由DynamicView.prototype.branchResultset(transform, parameters)提供src/lokijs.js它既能接受原始 transform 数组也能接受保存在 Collection 层的命名 transform。官方示例var db new loki(test); var coll db.addCollection(mydocs); var dv coll.addDynamicView(myview); var tx [ { type: offset, value: [%lktxp]pageStart }, { type: limit, value: [%lktxp]pageSize } ]; coll.addTransform(viewPaging, tx); // add some records var results dv.branchResultset(viewPaging, { pageStart: 10, pageSize: 10 }).data();一个值得强调的区别分支branching——进而你的 transform 结果——只反映分支那一刻视图的状态。这些 transform 是提取extracts并不会被 DynamicView 内部用于维护视图本身。DynamicView 始终新鲜而分支出来的 Resultset 是即时求值的快照。测试 spec/generic/transforms.spec.js 中就有「动态视图命名 transform」用例验证了dv.branchResultset(tx)与按名调用dv.branchResultset(desc4limit2)的等价行为。十二、为自定义方案附加元数据metaTransform 的一个典型用途是用户驱动的解决方案由 UI 层来构造、管理和执行这些 transform。这种场景下你可能希望给 transform 附加自定义元数据进一步描述 transform、步骤或参数的语义未知 type 的步骤会被忽略任何带type但 LokiJS 不认识的步骤都会被switch的default分支跳过src/lokijs.js。因此你可以约定 transform 的第一个步骤总是type: meta用它携带作者、描述、必需参数说明等信息执行时自然被跳过、零副作用。步骤可以带超出必需的额外属性每个 step 除了定义好的必填属性外还可以嵌入步骤描述、最后修改日期等元数据这些额外字段在执行过程中会被原样忽略不影响逻辑。这一机制让 transform 既可作为可执行的查询定义又可充当带注释的查询元数据载体。十三、测试佐证源码级验证仓库的 spec/generic/transforms.spec.js 为本文大部分结论提供了可运行验证basic find transform单步findtransform 经items.chain(tx).data()返回正确结果数L14-L30basic multi-step transformfindwhere多步串联验证步骤按顺序执行L32-L54parameterized find[%lktxp]OwnerName占位符在chain(tx, params)中被替换L56-L76parameterized find with $and/$or参数化在$or嵌套数组内部同样生效L78 起参数替换不修改原始 transform执行后断言tx2[0].value.age.$gt仍为[%lktxp]minimumAgeL202-L204named find transform / dynamic view named transform验证命名 transform 的存取与在 DynamicView 分支上的使用。你还可以参考 examples/quickstart-transforms.js 中的完整可运行示例以及查询操作符$eq、$gte等的完整讲解 tutorials/Query Examples.mdtransform 的find步骤复用这套查询语法。总结LokiJS Collection Transforms 以很小的额外开销为数据自动化转换建立了一套流程它把 Resultset 链式查询抽象为有序的 step 对象数组支持find、where、simplesort、compoundsort、sort、limit、offset、update、remove、map、mapReduce、eqJoin十二类步骤通过addTransform可命名保存并随数据库持久化形成存储过程式命名查询[%lktxp]参数化机制支持任意数据类型的注入含函数从而绕过 where 函数无法序列化的限制在 DynamicView 上通过branchResultset还能对始终新鲜的视图做即时的分页、提取。它不是链式查询的替代品而是对重复性查询的抽象与组织——用于整洁、动态、可持久化的数据操作场景。赞分享数据库后端【免费下载链接】LokiJSjavascript embeddable / in-memory database项目地址https://gitcode.com/gh_mirrors/lo/LokiJS点击查看免费下载相关推荐Akka Persistence 插件机制完全指南可插拔的 Journal、快照存储与持久化查询后端Akka Persistence 插件机制完全指南可插拔的 Journal、快照存储与持久化查询后端 Akka Persistence 是 Akka 提供的持后端并发编程异步编程图片占位符之外的惊喜BlurHash 的 3 大隐藏用途——敏感图遮挡、平均色提取与着色阴影图片占位符之外的惊喜BlurHash 的 3 大隐藏用途——敏感图遮挡、平均色提取与着色阴影 BlurHash https://link.gitcode.co图像处理如何让微信聊天记录成为你的数字记忆银行WeChatMsg完全使用指南如何让微信聊天记录成为你的数字记忆银行WeChatMsg完全使用指南 你是否曾有过这样的经历更换手机时那些珍贵的聊天记录瞬间消失想要回顾某段重要对话却上一篇ImmortalWrt主题定制教程打造个性化管理界面下一篇Rector条件语句简化提升代码执行效率创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表