Om Next 的 query->ast 和 ast->query 函数

Om Next's query->ast and ast->query functions

根据Om Next's documentation

query->ast

(om.next/query->ast '[(:foo {:bar 1})])

Given a query expression return the AST.

ast->query

(om.next/ast->query ast)

Given a query expression AST, unparse it into a query expression.

问题:为什么需要这些功能?也就是说,为什么需要在 om next 中直接操作查询抽象语法树(我假设它是代表查询树的 clojure 映射以及一些元数据)?

有些场景需要直接操作查询ast。在远程解析模式下,解析器希望您的读取函数 return {:remote-name true } 或(可能已修改){:remote-name AST-node}(它来自如 :ast 在环境中)。大多数情况下,您必须修改 AST 以重组它或添加一些数据。

示例 1: 您有一个问题:[{:widget {:list [:name :created]}}] :widget 部分是纯 UI 相关的,您的服务器不需要知道它的存在,它只 cares/knows 关于 :list。 基本上你必须在解析器中修改 AST:

(defmethod read :list
  [{:keys [ast query state]} key _ ]
  (let [st @state]
    {:value (om/db->tree query (get st key) st)
     :remote (assoc ast :query-root true)}))

如果您在发送函数中使用 om/process-roots,它将从 ast 中提取 :query-root 并将查询从 [{:widget {:list [:name :created]}}] 重写为 [{:list [:name :created]}]

示例 2: 另一个例子是当你想在远程改变某些东西时:

(defmethod mutate 'item/update
  [{:keys [state ast]} key {:keys [id title]}]
  {:remote (assoc ast :params {:data {:id id :title title })})

这里你需要明确告诉Om在AST中包含你要发送的数据。然后在您的遥控器上选择 :data 以更新给定 id

的标题

大多数时候您不会直接使用您在问题中描述的功能。解析器的每个方法中可用的 env 中都有 ast

我在尝试使用 Compassus 时偶然发现的事情:

假设您有一个包含参数子查询的复杂 union/join 查询。像这样:

`[({:foo/info
        {:foo/header [:foo-id :name]
        :foo/details [:id :description :title]}} {:foo-id ~'?foo-id
                                                  :foo-desc ~'?foo-desc})]

现在假设您要设置参数,以便在服务器上您可以使用 om/parser 解析它并将这些参数视为 read 调度的第三个参数。当然,可以编写一个函数来查找查询中所有必需的参数并设置值。但这并不容易,正如我所说 - 想象一下您的查询可能会非常复杂。

那么您可以做的是修改 ast,ast 包含 :children :params 键。因此,假设 :foo-id:foo-desc 的实际值位于 :route-params 键下的状态原子中:

(defn set-ast-params [children params]
  "traverses given vector of `children' in an AST and sets `params`"
  (mapv
    (fn [c]
      (let [ks (clojure.set/intersection (-> params keys set) 
                                         (-> c :params keys set))]
        (update-in c [:params] #(merge % (select-keys params (vec ks))))))
    children))

(defmethod readf :foo/info
  [{:keys [state query ast] :as env} k params]
  (let [{:keys [route-params] :as st} @state
        ast' (-> ast 
                 (update :children #(set-ast-params % route-params))
                 om/ast->query
                 om.next.impl.parser/expr->ast)]
    {:value  (get st k)
    :remote ast'}))

所以基本上你是: - 抓住 ast - 用实际值修改它 您认为也许您可以立即将其发送到服务器。唉,不!还没有。事情是 - 当你做 {:remote ast} 时,Om 获取 ast 的 :query 部分,从中组成 ast,然后将它发送到服务器。因此,您实际上需要:将修改后的 ast 转换为查询,然后再将其转换回 ast。

备注:

    本例中的
  • set-ast-params 函数仅适用于第一级(如果您有嵌套的参数化查询 - 它不会起作用), 让它递归 - 不难

  • 有两种不同的方法可以将 ast 转换为查询,反之亦然:

    (om/ast->query) ;; retrieves query from ast and sets the params based 
                    ;; of `:params` key of the ast, BUT. it modifies the query, 
                    ;; if you have a join query it takes only the first item in it. e.g. :
    [({:foo/foo [:id]
        :bar/bar [:id]} {:id ~'?id})]
    ;; will lose its `:bar` part 
    
    (om.next.impl.parser/ast->expr) ;; retrieves query from an ast, 
                                    ;; but doesn't set query params based on `:params` keys of the ast.
    
    ;; there are also
    (om/query->ast) ;; and
    (om.next.impl.parser/expr->ast)