ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Gel/EdgeDB 路径作用域(Path Scoping)全面指南:理解 simple_scoping 新算法与旧算法迁移

Gel/EdgeDB 路径作用域(Path Scoping)全面指南:理解 simple_scoping 新算法与旧算法迁移 Gel/EdgeDB 路径作用域Path Scoping全面指南理解 simple_scoping 新算法与旧算法迁移【免费下载链接】edgedbGel supercharges Postgres with a modern data model, graph queries, Auth AI solutions, and much more.项目地址: https://gitcode.com/gh_mirrors/ed/edgedb导读本文基于 Gel 6.0 起引入的路径作用域Path Scoping新语义系统讲解新路径作用域simple_scoping与旧路径作用域两种算法的行为差异、配置方式与迁移路径。你将掌握为什么同一路径在 shape、FILTER、ORDER BY与兄弟上下文中会产生截然不同的结果如何借助warn_old_scoping与simple_scoping两个开关安全完成从旧语义到新语义的升级以及旧算法下笛卡尔积与公共路径提取的底层规则。背景为什么 Gel 6.0 要重写路径作用域算法从 Gel 6.0 开始项目开始逐步淘汰历史上那套声名狼藉的路径作用域算法Path Scoping Algorithm转而采用一套简单得多、但在绝大多数惯用 EdgeQL 查询上与旧行为完全一致的新算法。新算法被命名为simple_scoping简单作用域相关设计讨论记录在 Gel 的 RFC 中1027-no-factoring。这一变更对多数用户而言迁移成本很低Gel 6.0 为此内置了两类迁移辅助设施simple_scoping直接开启新语义warn_old_scoping在检测到查询依赖旧语义时发出警告用于迁移前的安全摸底。两个开关都以future 特性 配置项的双重形式存在详见 Future behavior未来行为。新路径作用域New Path Scoping新算法的核心规则非常直白当一个路径或其上已应用了 shape 的路径作为查询主体出现时该路径会在该 shape 内的计算属性computed pointer表达式中被绑定。例如下面的查询在User的 shape 里用全限定名User.first_name引用路径结果每个User都能正确取到自己的名字拼接select User { name : User.first_name User.last_name }输出{User {name: Peter Parker}, User {name: Tony Stark}}FILTER 与 ORDER BY 中的路径绑定执行SELECT、UPDATE或DELETE时只要主体是一个路径可选地带 shape该路径同样会被绑定到FILTER与ORDER BY子句中select User { name : User.first_name User.last_name } filter User.first_name Peter输出{User {name: Peter Parker}}兄弟上下文Sibling Contexts会产生笛卡尔积新算法与旧算法最本质的差别在于当同一个路径在多个兄弟上下文中被使用时不再做公共路径提取而是计算笛卡尔积select User.first_name User.last_name;输出{Peter Parker, Peter Stark, Tony Parker, Tony Stark}也就是说User.first_name与User.last_name被视为两组相互独立的集合两两组合。如果你想要的是每个 User 一条结果有三种写法用FOR显式表达意图for u in User select u.first_name u.last_name;输出{Peter Parker, Tony Stark}更惯用的方式是直接在 shape 中拼接此时路径被绑定天然是一对一的select User { name : .first_name .last_name }输出{User {name: Peter Parker}, User {name: Tony Stark}}顺带一提数据库设计界早有共识——first_name/last_name这种姓名字段拆分本身就是一个值得重新审视的建模决策例如非拉丁姓名体系、单名用户、复姓等情况都会让该模型失真即便在 EdgeQL 里可以用上面的写法优雅拼接从数据建模角度也应谨慎对待姓名的结构假设。路径作用域配置simple_scoping的 Future 与配置项Gel 6.0 引入了名为simple_scoping的 future 特性以及同名配置设置。二者的关系是future 特性的存在与否决定 schema 内部表达式使用哪种行为同时作为配置项未设置时的默认值来源配置设置则允许在运行时覆盖 future 特性的存在与否。完整组合见下表Query is simply scoped指查询是否使用新语义Schema is simply scoped指 schema 内的表达式是否使用新语义Future 存在配置值查询是否简单作用域Schema 是否简单作用域否{}未设置否否否true是否否false否否是{}未设置是是是true是是是false否是解读这张表的关键结论future 特性控制 schema 侧只要 schema 声明了using future simple_scoping;schema 内表达式就始终使用新语义且该值会成为查询侧未配置时的默认值配置项只控制查询侧simple_scoping : false可以单独把查询拉回旧语义哪怕 future 已开启配置项无法让 schema 倒退即使配置为false只要 future 存在schema 内部表达式依然是新语义表最后一行。源码依据在 edb/lib/cfg.edgeql 中可以看到这两个配置项的正式定义CREATE PROPERTY simple_scoping - std::bool { CREATE ANNOTATION cfg::affects_compilation : true; CREATE ANNOTATION cfg::session_cfg_permissions : *; CREATE ANNOTATION std::description : Whether to use the new simple scoping behavior \ (disable path factoring); }; CREATE PROPERTY warn_old_scoping - std::bool { CREATE ANNOTATION cfg::affects_compilation : true; CREATE ANNOTATION cfg::session_cfg_permissions : *; CREATE ANNOTATION std::description : Whether to warn when depending on old scoping behavior.; };注意两个细节二者都标注了cfg::affects_compilation : true意味着修改该配置会导致相关查询重新编译即它们是编译期语义而非运行期开关session_cfg_permissions : *则表明任何会话角色都可以设置这两个配置。在 edb/schema/futures.py 中simple_scoping与warn_old_scoping被注册为 future 行为处理器——它们的实现注释明确写着与任何 schema 元素无直接关联目前均为占位dummy即开启 future 本身不改变 schema 存储结构而是通过配置与编译器路径共同决定编译行为# These are registered here because they arent directly related to # any schema elements. # They are all dummys now, too. register_handler(simple_scoping) register_handler(warn_old_scoping) register_handler(_scoping_noop_test) def toggle_scoping_future(...): return schema, sd.CommandGroup()旧作用域警告warn_old_scoping为了让迁移过程更安全Gel 6.0 同时引入了warn_old_scopingfuture 特性与同名配置项。激活时服务器在检测到某条查询依赖旧作用域行为时会向客户端发出警告警告的处理方式可以在各语言客户端绑定中配置默认行为是记入日志已知该检测偶尔会产生误报针对那些实际行为并未改变的查询但设计目标是不漏报不产生假阴性——即宁可多警告也不放过真正依赖旧语义的查询。从源码看警告的产生位于查询编译路径中例如 edb/edgeql/compiler/viewgen.py 附近有与warn_old_scoping警告相关的处理逻辑测试侧则通过future warn_old_scoping的场景验证 schema 表达式中的旧语义依赖可参考 tests/test_edgeql_scope.py 中 computable factoring 系列测试。推荐的升级计划官方推荐的最稳妥路径是先让整套 schema 与应用在warn_old_scoping下跑通且不产生任何警告再切换到simple_scoping。这样切换前后行为零变化。如果你对测试覆盖非常有信心也可以跳过warn_old_scoping阶段直接开启simple_scoping。一个行之有效的分步策略执行CONFIGURE CURRENT DATABASE SET warn_old_scoping : true把全部查询对着数据库跑一遍修复所有产生警告的查询调整 schema直到using future warn_old_scoping也能在不产生警告的情况下工作如果你希望第 2、3 步增量进行可以在客户端配置warn_old_scoping对已验证过的查询开启、对尚未验证或尚未修复的查询关闭从而实现渐进式迁移。旧路径作用域Legacy Path Scoping笛卡尔积与公共路径提取本节描述的算法在 EdgeDB 5.0 及之前是唯一实现在 Gel 6.0 中仍是默认行为当simple_scoping未开启时并计划在 Gel 7.0 中移除。基础规则多参数元素级运算作用于笛卡尔积Gel 中多参数的元素级运算element-wise operation默认作用于所有输入集合的笛卡尔积select {aaa, bbb} {ccc, ddd};输出{aaaccc, aaaddd, bbbccc, bbbddd}公共路径提取Path Factoring但是当多个元素级参数共享同一个公共路径如下例中的User.时Gel 会把公共路径提取出来而不是做笛卡尔乘法select User.first_name User.last_name;输出{Mina Murray, Jonathan Harker, Lucy Westenra, John Seward}这里假设共享路径 你想让它们一一对应。如果你确实想要笛卡尔积有三种办法办法一使用detached见 SELECT 中的detached操作符将其中一个参数从公共路径中摘除select User.first_name detached User.last_name;输出 16 个组合4 个 first_name × 4 个 last_name包括Mina Murray、Mina Harker、Jonathan Murray等全部两两组合。办法二使用WITH给同一集合挂一个不同的符号详见 WITHwith U : User select U.first_name User.last_name;输出同样是 16 个组合。办法三利用作用域对路径解析的影响详见下文Scopes一节。为什么WITH有效关键在符号一个自然的疑问是U : User明明指向同一个集合为什么U.first_name与User.last_name没有共享公共路径答案在于——Gel 只在两个引用使用相同符号symbol时才假定你想做路径提取。User.first_name与User.last_name使用相同符号User→ 提取公共路径U.first_name与User.last_name使用不同符号 → 不提取按笛卡尔积解析。作用域Scopes路径解析的进阶规则作用域scope会改变路径解析方式。以下是几条核心规则。兄弟作用域不提取公共路径两个处于**同一层级兄弟**的查询即使使用共同符号也不会做路径提取select ((select User.first_name), (select User.last_name));输出 16 个元组组合如(Mina, Murray)、(Mina, Harker)、(Jonathan, Murray)等。嵌套作用域提取公共路径如果共同符号处于嵌套作用域中则会提取。下面的示例中嵌套的两个查询都使用了与顶层查询相同的User符号因此User被提取为单个对象select User { name : (select User.first_name) (select User.last_name) };输出{default::User {name: Mina Murray}, default::User {name: Jonathan Harker}, default::User {name: Lucy Westenra}, default::User {name: John Seward}}只有一个嵌套时仍然提取如果两个共同作用域中只有一个处于嵌套作用域路径依然会被提取select (Person.name, count(Person.friends));输出{(Fran, 3), (Bam, 2), (Emma, 3), (Geoff, 1), (Tyra, 1)}这里count像所有聚合函数一样会创建嵌套作用域但这并不妨碍路径提取——每个Person元组的第二个元素是该 Person 自己的朋友数。如果路径未被提取朋友数对所有人都会是同一个值即所有friends链接里的总人数那显然不是期望的语义。两个兄弟聚合作用域不提取如果两个聚合函数创建的是兄弟嵌套作用域则路径不会被提取select (array_agg(distinct Person.name), count(Person.friends));输出{([Fran, Bam, Emma, Geoff], 3)}这里array_agg收集全部不重复名字count统计所有人朋友链接的总数——两个聚合互不相关各自独立计算。子句与嵌套Clauses Nesting大多数子句是嵌套的适用上述同一套规则公共符号会被提取假定与外部查询指向同一个对象。这是因为FILTER见 SELECT 的 filter与ORDER BY见 SELECT 的 order by需要作用于结果中的每一个值。而OFFSET与LIMIT见 SELECT 的分页不在作用域内嵌套因为它们需要全局作用于整个结果集。从源码看新算法的实现细节在 edb/edgeql/compiler/stmtctx.py 中有一个非常能说明新算法实现思路的函数_collapse_factoring_protected其文档字符串写道In simple_scoping mode, we protect certain paths by wrapping them in selects so that they dont participate in path factoring.即在simple_scoping模式下编译器会把某些路径用SELECT包裹起来保护使其不参与路径提取factoring。由于这种包裹会生成更冗长的 SQL并抑制部分优化尤其是让ORDER BY足够简单以便 Postgres 优化的努力编译器随后会尝试把这些保护和栅栏折叠掉——只要折叠后不会重新引入路径提取。注释中给出了一个直观例子对于select User filter User.name Elvis外层的User不被保护内层的User会被提取到外层只留下User.name处于受保护的内层作用域中此时User.name没有可提取的对象注入的SELECT就被安全移除了。这段实现印证了本文开头的描述新算法默认不提取、按需保护与旧算法默认提取公共路径形成鲜明对照。总结路径作用域是理解 EdgeQL 查询语义的关键一环新算法simple_scopingshape 内计算属性、FILTER/ORDER BY中的路径自动绑定兄弟上下文中使用同一路径则产生笛卡尔积需要一一对应时请用 shape 内计算属性或FOR显式表达。旧算法Legacy多参数元素级运算对共享公共路径做提取、否则做笛卡尔积可用detached、WITH换符号、或利用作用域结构来获得笛卡尔积。迁移路径先用warn_old_scoping排查全部警告再切换simple_scoping配置项控制查询侧future 特性控制 schema 侧二者按文中的组合表协同生效。语义终结时间线旧算法将在 Gel 7.0 中移除尽早完成迁移可避免未来升级踩坑。【免费下载链接】edgedbGel supercharges Postgres with a modern data model, graph queries, Auth AI solutions, and much more.项目地址: https://gitcode.com/gh_mirrors/ed/edgedb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表