meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
markaddleman 2021-02-11T16:41:39.032300Z

Picking up a dropped thread: I'm using meander to parse and rewrite HoneySQL (well, something close to HoneySQL). One of the things I need to do is "fully qualify" column names by finding the table name from the from clause and threading it through column references in the rest of the query. For example: {:select [{:type :col :col :a}] :from [{:type :table :table :t}] should be rewritten as {:select [{:type :col :col :a :table-ref :t}] :from [{:type :table :table :t}] Things get complicated when I have to deal with subqueries. For example {:select [{:type :col :col :a} {:select [{:type :col :col :b}] :from [:sub-t]} :from [:t]} should be rewritten as {:select [{:type :col :col :a :table-ref :t} {:select [{:type :col :col :b :table-ref :sub-t}] :from [{:type :table :table :sub-t}]} :from [{:type :table :table :t}]} In this example, you can see that I want to match the :from clause that is nearest in the tree to the column. Because columns can exist in several places within a query, I'd like to use the $ operator to find the column references. The problem is that I need to attach some boundary conditions to $ so it find a :from clause from an sub-query. Any ideas?

noprompt 2021-02-11T16:52:42.033500Z

What should this do when there is more in :from?

noprompt 2021-02-11T16:53:35.033900Z

(m/let [table-ref (keyword (gensym "T__"))]
  {:from <what-goes-here>
   :select [(m/or {:type :col :as !column}
                  !not-column)
            ...]})
{:from [{:type :table :table ?table-ref} ...]
 :select [{:table-ref ?table-ref & !column} ...
          !not-column ...]}

markaddleman 2021-02-11T16:56:31.035300Z

I knew I should have discussed that 🙂 In my case, if there is more than one from, the columns are guaranteed to be fully qualified from the start so this qualification rewrite is, essentially, a nop

noprompt 2021-02-11T17:06:29.036700Z

You may want to consider using m/cata for on the right side like so

{:select [!selection ...]
 :from (m/and [{:type :table :table ?table}]
              ?from)}
{:select [(m/cata [::qualify-selection !selection ?table])]
 :from ?from}

;; Rewrite [::qualify-selection ,,,]
[::qualify-selection {:type :col :table-ref nil :as ?col} ?table]
{:table-ref ?table & ?col}

noprompt 2021-02-11T17:07:11.037300Z

Then just make a rule for [::qualify-selection ,,,] that stops or rewrites as needed.

markaddleman 2021-02-11T17:08:44.038600Z

Yes, that's my current approach. Unfortunately, it gets to be verbose because columns can exist in the select clause, where clause, group-by, function calls.... I am looking for a more terse solution but it may not exist

noprompt 2021-02-11T17:24:06.040300Z

Normally qualification tends to a top down approach and for this sort of qualification you will necessarily have a few cases.

noprompt 2021-02-11T17:24:49.041Z

e.g. you’ll be calling (qualify-selection x table) or something like this.

noprompt 2021-02-11T17:26:07.042300Z

Dealing with SQL, and especially HoneySQL, is going to require some code.

markaddleman 2021-02-11T18:01:27.043600Z

Yeah. I think I found a solution that uses a meander pattern to locate and rewrite the deepest subquery. Outside of meander, I can loop over rewrites until I find a stable result

noprompt 2021-02-11T18:40:11.043900Z

Ye olde fix ?

noprompt 2021-02-11T18:41:03.045Z

(defn fix [f]
  (fn [x]
    (let [x* (f x)]
      (if (= x x*)
        x
        (recur x*))))

markaddleman 2021-02-11T18:44:15.045600Z

Yup 🙂 I'm not using a meander strategy for this but I probably should

noprompt 2021-02-11T18:47:41.046Z

I’ve been debating whether or not to keep those around going forward.

markaddleman 2021-02-11T19:02:52.046200Z

Strategies?

markaddleman 2021-02-11T19:03:53.047500Z

I haven't used them so it's hard for me to say but I remember thinking that the most recent epsilon stuff around functions seemed very powerful. I don't recall the specifics right now