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