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