|
| 1 | +(ns macaw.scope-experiments |
| 2 | + (:require |
| 3 | + [macaw.core :as m] |
| 4 | + [macaw.walk :as mw]) |
| 5 | + (:import |
| 6 | + (net.sf.jsqlparser.schema Column Table))) |
| 7 | + |
| 8 | +(defn- node->clj [node] |
| 9 | + (cond |
| 10 | + (instance? Column node) [:column |
| 11 | + (some-> (.getTable node) .getName) |
| 12 | + (.getColumnName node)] |
| 13 | + (instance? Table node) [:table (.getName node)] |
| 14 | + :else [(type node) node])) |
| 15 | + |
| 16 | +(defn- semantic-map [sql] |
| 17 | + (mw/fold-query (m/parsed-query sql) |
| 18 | + {:every-node (fn [acc node ctx] |
| 19 | + (let [id (m/scope-id (first ctx)) |
| 20 | + node (node->clj node)] |
| 21 | + (-> acc |
| 22 | + (update-in [:scopes id] |
| 23 | + (fn [scope] |
| 24 | + (-> scope |
| 25 | + (update :path #(or % (mapv m/scope-label (reverse ctx)))) |
| 26 | + (update :children (fnil conj []) node)))) |
| 27 | + ((fn [acc'] |
| 28 | + (if-let [parent-id (some-> (second ctx) m/scope-id)] |
| 29 | + (-> acc' |
| 30 | + (update :parents assoc id parent-id) |
| 31 | + (update-in [:children parent-id] (fnil conj #{}) id)) |
| 32 | + acc'))) |
| 33 | + (update :sequence (fnil conj []) [id node]))))} |
| 34 | + {:scopes {} ;; id -> {:path [labels], :children [nodes]} |
| 35 | + :parents {} ;; what scope is this inside? |
| 36 | + :children {} ;; what scopes are inside? |
| 37 | + :sequence []})) ;; [scope-id, node] |
| 38 | + |
| 39 | +(comment |
| 40 | + (semantic-map "select x from t, u, v left join w on w.id = v.id where t.id = u.id and u.id = v.id limit 3") |
| 41 | + ;{:scopes {1 {:path ["SELECT"], :children [[:column "x"]]}, |
| 42 | + ; 2 {:path ["SELECT" "FROM"], :children [[:table "t"]]}, |
| 43 | + ; 4 {:path ["SELECT" "JOIN" "FROM"], :children [[:table "u"]]}, |
| 44 | + ; 5 {:path ["SELECT" "JOIN" "FROM"], :children [[:table "v"]]}, |
| 45 | + ; 6 {:path ["SELECT" "JOIN" "FROM"], :children [[:table "w"]]}, |
| 46 | + ; 3 {:path ["SELECT" "JOIN"], :children [[:column "id"] [:table "w"] [:column "id"] [:table "v"]]}, |
| 47 | + ; 7 {:path ["SELECT" "WHERE"], |
| 48 | + ; :children [[:column "id"] |
| 49 | + ; [:table "t"] |
| 50 | + ; [:column "id"] |
| 51 | + ; [:table "u"] |
| 52 | + ; [:column "id"] |
| 53 | + ; [:table "u"] |
| 54 | + ; [:column "id"] |
| 55 | + ; [:table "v"]]}}, |
| 56 | + ; :parents {2 1, 4 3, 5 3, 6 3, 3 1, 7 1}, |
| 57 | + ; :children {1 #{7 3 2}, 3 #{4 6 5}}, |
| 58 | + ; :sequence [[1 [:column "x"]] |
| 59 | + ; [2 [:table "t"]] |
| 60 | + ; [4 [:table "u"]] |
| 61 | + ; [5 [:table "v"]] |
| 62 | + ; [6 [:table "w"]] |
| 63 | + ; [3 [:column "id"]] |
| 64 | + ; [3 [:table "w"]] |
| 65 | + ; [3 [:column "id"]] |
| 66 | + ; [3 [:table "v"]] |
| 67 | + ; [7 [:column "id"]] |
| 68 | + ; [7 [:table "t"]] |
| 69 | + ; [7 [:column "id"]] |
| 70 | + ; [7 [:table "u"]] |
| 71 | + ; [7 [:column "id"]] |
| 72 | + ; [7 [:table "u"]] |
| 73 | + ; [7 [:column "id"]] |
| 74 | + ; [7 [:table "v"]]]} |
| 75 | + |
| 76 | + |
| 77 | + (semantic-map "select t.a,b,c,d from t") |
| 78 | + ;{:scopes {1 {:path ["select"], :children [[:column "a"] [:column "b"] [:column "c"] [:column "d"]]}, |
| 79 | + ; 2 {:path ["select" "from"], :children [[:table "t"]]}}, |
| 80 | + ; :parents {2 1}, |
| 81 | + ; :children {1 #{2}}, |
| 82 | + ; :sequence [[1 [:column "a"]] [1 [:column "b"]] [1 [:column "c"]] [1 [:column "d"]] [2 [:table "t"]]]} |
| 83 | + ) |
| 84 | + |
| 85 | +(defn- get-descendants-map [parent-children-map] |
| 86 | + (letfn [(get-all-descendants [parent] |
| 87 | + (let [children (get parent-children-map parent [])] |
| 88 | + (into #{} (concat children |
| 89 | + (mapcat #(get-all-descendants %) |
| 90 | + children)))))] |
| 91 | + (into {} |
| 92 | + (for [parent (keys parent-children-map)] |
| 93 | + [parent (get-all-descendants parent)])))) |
| 94 | + |
| 95 | +(defn fields->tables-in-scope [sql] |
| 96 | + (let [sm (semantic-map sql) |
| 97 | + tables (filter (comp #{:table} first second) (:sequence sm)) |
| 98 | + scope->tables (reduce |
| 99 | + (fn [m [scope-id [_ table-name]]] |
| 100 | + (update m scope-id (fnil conj #{}) table-name)) |
| 101 | + {} |
| 102 | + tables) |
| 103 | + scope->descendants (get-descendants-map (:children sm)) |
| 104 | + scope->nested-tables (reduce |
| 105 | + (fn [m parent-id] |
| 106 | + (assoc m parent-id |
| 107 | + (into (set (scope->tables parent-id)) (mapcat scope->tables (scope->descendants parent-id))))) |
| 108 | + {} |
| 109 | + (keys (:scopes sm))) |
| 110 | + columns (filter (comp #{:column} first second) (:sequence sm))] |
| 111 | + |
| 112 | + (vec (distinct (for [[scope-id [_ table-name column-name]] columns] |
| 113 | + [[scope-id column-name] |
| 114 | + (if table-name |
| 115 | + #{table-name} |
| 116 | + (scope->nested-tables scope-id))]))))) |
| 117 | + |
| 118 | +(defn- fields-to-search [f->ts] |
| 119 | + (into (sorted-set) |
| 120 | + (mapcat (fn [[[_ column-name] table-names]] |
| 121 | + (map #(vector :table % :column column-name) table-names))) |
| 122 | + |
| 123 | + f->ts)) |
| 124 | + |
| 125 | +(comment |
| 126 | + ;; like source-columns, but understands scope |
| 127 | + (fields-to-search |
| 128 | + (fields->tables-in-scope "select x from t, u, v left join w on w.a = v.a where t.b = u.b and u.c = v.c limit 3")) |
| 129 | + ;#{[:table "t" :column "b"] |
| 130 | + ; [:table "t" :column "x"] |
| 131 | + ; [:table "u" :column "b"] |
| 132 | + ; [:table "u" :column "c"] |
| 133 | + ; [:table "u" :column "x"] |
| 134 | + ; [:table "v" :column "a"] |
| 135 | + ; [:table "v" :column "c"] |
| 136 | + ; [:table "v" :column "x"] |
| 137 | + ; [:table "w" :column "a"] |
| 138 | + ; [:table "w" :column "x"]} |
| 139 | + ) |
0 commit comments