001  (ns org.clojars.punit-naik.sqles.parse-sql
002    (:require [clojure.string :as str]
003              [org.clojars.punit-naik.sqles.query :as query]
004              [org.clojars.punit-naik.sqles.parse-sql.utils :refer [handle-clause-data]]
005              [org.clojars.punit-naik.sqles.parse-sql.where :as where]))
006  
007  (defn query->es-op
008    [clause]
009    (let [clause (str/lower-case clause)]
010      (get {"select" "/_search"
011            "count"  "/count"}
012           clause)))
013  
014  (defn clause->query-fn
015    [clause]
016    (when clause
017      (let [clause (str/lower-case clause)]
018        (get {"select" query/select
019              "where" query/where->es
020              "from" (constantly nil)
021              "limit" query/limit
022              "order by" query/order-by
023              "order" (constantly nil)}
024             clause))))
025  
026  (defn find-index
027    "Finds the index to be queried from the SQL query"
028    [sql-query]
029    (let [parts (->> (str/split sql-query #" ")
030                     (map-indexed vector)
031                     (into {}))
032          from-clauses #{"from" "FROM"}]
033      (->> (filter (fn [[k _]]
034                     (contains? from-clauses (get parts (dec k)))) parts)
035           vals first)))
036  
037  (defn take-till-next-clause
038    "Gets the data for a particular clause from query parts
039     Till the next clause is found"
040    [query-parts]
041    (take-while #(not (clause->query-fn %)) query-parts))
042  
043  (defmethod handle-clause-data "select"
044    [_ clause-data]
045    (map str/trim
046         (mapcat (fn [cd]
047                   (remove empty? (str/split cd #",")))
048                 clause-data)))
049  
050  (defmethod handle-clause-data "where"
051    [_ clause-data]
052    (where/handle-clause-data clause-data))
053  
054  (defmethod handle-clause-data "limit"
055    [_ [limit]]
056    limit)
057  
058  (defmethod handle-clause-data "order by"
059    [_ clause-data]
060    (let [;; NOTE: Joining by `\s` first as we are not splitting the string on `,` in case of multiple `order by` fields
061          clause-data-str (str/join " " clause-data)
062          clause-data (->> (str/split clause-data-str #",")
063                           (map (fn [s] (str/split s #"\s"))))]
064      (if-not (every? (fn [[_ order :as x]]
065                        (and (<= (count x) 2)
066                             (or (nil? order)
067                                 (= order "asc")
068                                 (= order "desc")))) clause-data)
069        (throw (AssertionError. "Wrong `ORDER BY` clause data"))
070        clause-data)))
071  
072  (defn clean-query
073    "Removes spaces before and after commas
074     And spaces after opened round bracket and before closed round bracket
075     Won't match commas/round brackets between quotes, single or double"
076    [query]
077    (-> (str/replace query #"(?!\B\"[^(\"|\'|\`)]*)[\s+]?,(?![^(\"|\'|\`)]*(\"|\'|\`)\B)\s+" ",")
078        (str/replace #"\((?![^(\"|\'|\`)]*(\"|\'|\`)\B)\s+" "(")
079        (str/replace #"(?!\B\"[^(\"|\'|\`)]*)[\s+]?\)" ")")
080        (str/replace #"NULL|null" "nil")))
081  
082  (defn get-clause
083    [[first-part second-part]]
084    (let [first-part (str/lower-case first-part)]
085      (if (contains? #{"group" "order"} first-part)
086        [first-part second-part]
087        [first-part])))
088  
089  (defn count-query?
090    "Returns true if the SQL query only has `count(.)`"
091    [sql-query]
092    (seq (re-matches #"select count\(.*\) from.*" (str/lower-case sql-query))))
093  
094  (defn parse-query
095    [sql-query]
096    (let [sql-query (clean-query sql-query)
097          index (find-index sql-query)
098          parts (str/split sql-query
099                           #"\s+(?=([^\"\'\`]*[\"|\'|\`][^\"\'\`]*[\"|\'|\`])*[^\"\'\`]*$)")]
100      (loop [ps parts
101             result {:url (str (query/from index)
102                               (query->es-op
103                                (if (count-query? sql-query)
104                                  "count"
105                                  (first parts))))
106                     :body {}
107                     :method :post}]
108        (if (empty? ps)
109          result
110          (let [clause-coll (get-clause ps)
111                clause (str/lower-case (str/join " " clause-coll))
112                remaining-parts (drop (count clause-coll) ps)
113                clause-data (take-till-next-clause remaining-parts)
114                remaining-parts (drop (count clause-data) remaining-parts)
115                clause-data (handle-clause-data clause clause-data)
116                intermediate-es-query ((clause->query-fn clause) clause-data)]
117            (recur remaining-parts
118                   (update result :body merge intermediate-es-query)))))))