Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

According to 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.

Question: Why would one need these functions? That is, why would one need to directly manipulate a query abstract syntax tree (which I'm assuming are clojure maps that represent a query tree, along with some meta data) in om next?

like image 278
George Avatar asked Feb 27 '16 21:02

George


2 Answers

There are some scenarios where you need to manipulate the query ast directly. In remote parsing mode, the parser expects your read functions to return either {:remote-name true } or a (possibly modified) {:remote-name AST-node} (which comes in as :ast in env). Most often you'll have to modify the AST to restructure it or add some data.

Example 1: You have a query: [{:widget {:list [:name :created]}}] The :widget part is pure UI related, your server doesn't need to know it exists, it only cares/knows about the :list. Basically you'll have to modify the AST in the parser:

(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)}))

If you use om/process-rootsin your send function, it'll pick up the :query-root out of the ast and rewrite the query from [{:widget {:list [:name :created]}}] to [{:list [:name :created]}].

Example 2: Another example would be when you want to mutate something at a remote:

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

Here you need to explicitly tell Om to include the data you want to send in the AST. At your remote you then pick apart :data to update the title at the given id

Most of the time you won't use the functions you described in your questions directly. The env available in every method of the parser has the ast in it.

like image 161
Seneca Avatar answered Oct 06 '22 18:10

Seneca


Something I stumbled on, while trying to use Compassus:

Let's say you have a complex union/join query that includes parametric sub-queries. Something like this:

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

Now let's say you want to set parameters so on the server you can parse it with om/parser and see those params as 3rd argument of read dispatch. Of course it's possible to write a function that would find all necessary parameters in the query and set the values. That's not easy though, and as I said - imagine your queries can be quite complex.

So what you can do - is to modify ast, ast includes :children :params key. So let's say the actual values for :foo-id and :foo-desc are in the state atom under :route-params key:

(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'}))

So basically you are: - grabbing ast - modifying it with actual values you think maybe you can send it to server right then. Alas, no! Not yet. Thing is - when you do {:remote ast}, Om takes :query part of the ast, composes ast out of it and then sends it to the server. So you actually need to: turn your modified ast into query and then convert it back to ast again.

Notes:

  • set-ast-params function in this example would only work for the first level (if you have nested parametrized queries - it won't work), make it recursive - it's not difficult

  • there are two different ways to turn ast to query and vice-versa:

    (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)
    
like image 27
iLemming Avatar answered Oct 06 '22 18:10

iLemming