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