Fetch

fetch 操作用来计算某个查询的值。 只要可能,它就会优先复用已经缓存(memoized)的值。

输入查询

输入查询只需要直接从表中读取结果即可。

Interned 查询

Interned 查询会把输入映射到一个 hashmap 中, 查找是否已经存在对应的整数 id。 如果不存在,就创建一个新值。

派生查询

派生查询的逻辑要更复杂一些。 这里先总结高层思路; 如果想继续深入,流程图会很有帮助。 术语一节也同样值得配合阅读; 在某些地方,我们会在术语第一次出现时直接链接过去。

  • 如果找到了现有的 memo, 我们先检查它是否已经在当前 revision 中被 verified。 如果是,就可以直接返回缓存值。
  • 否则,如果这个 memo 里保存了缓存值, 那么我们就必须检查 dependencies 是否发生了变化:
    • R 为这个 memo 上一次被验证的修订; 我们想知道自修订 R 以来,依赖中是否有任何一个发生了变化。
    • 首先检查 durability。 对每个 memo,我们都会记录其依赖的最小 durability。 如果某个 memo 的 durability 是 D, 并且自上次验证以来,没有任何 durability 为 D 的输入发生过变化, 那么无需额外工作,也可以把这个 memo 视为已验证。
    • 如果 durability 检查还不够, 那就必须逐个检查依赖。 做法是遍历每个依赖 D, 并调用 maybe changed after 来判断 D 自修订 R 之后是否已经变化。
    • 如果没有任何依赖变化:
      • 那么就可以把这个 memo 标记为已验证,并直接返回它缓存的值。
  • 如果依赖发生了变化,或者 memo 根本没有保存缓存值:
    • 那么就执行用户的查询函数。
    • 接下来,计算这个缓存值最后一次发生变化的修订:
      • Backdate: 如果之前存在旧的缓存值,并且新值与旧值相等, 那么我们就可以对这个 memo 做 backdate, 也就是沿用之前的 changed at 修订。
        • 借助 backdating, 就有可能出现这样的情况:查询的某个依赖在修订 R1 中变化了, 但查询本身的输出却是在某个更早的修订 R2 中最后一次变化的,其中 R2 < R1
      • 否则,就使用当前修订。
    • 然后为这个新值构造一个新的 memo 并返回。