001 (ns org.clojars.punit-naik.sqles.parse-sql.where
002 (:require [clojure.string :as str]))
003
004 (def operators-used-without-spacing
005 "Operatos which can be used without spacing between operands"
006 #{"="
007 "<"
008 "<="
009 ">"
010 ">="
011 "!="})
012
013 (defn un-separated-operands?
014 "Checks if the operands of the where statement are separated (by space) or not
015 For e.g. Operands are separated in the case of \"a = 1\"
016 And not separated in casde of \"a=1\"
017 Returns the matched operation in the statement, if un-separated."
018 [statement]
019 (when-let [matched-op (some #(and (str/includes? statement %) %)
020 operators-used-without-spacing)]
021 (when-not (re-matches (re-pattern (str ".*\\s+\\" matched-op "\\s+.*"))
022 statement)
023 matched-op)))
024
025 (defn separate-operands
026 "If un-separated operands are present, this function separates them and returns"
027 [statements]
028 (->> statements
029 (map (fn [statement]
030 (if (and (not (str/starts-with? statement "("))
031 (not (str/ends-with? statement ")")))
032 (let [op (un-separated-operands? statement)]
033 (if (or (= (count statement) 1)
034 (not op))
035 statement
036 (interpose
037 op
038 (str/split statement
039 (re-pattern (str "\\" op)) 2))))
040 statement)))
041 flatten))
042
043 (declare handle-clause-data)
044
045 (defn nested-handle-clause-data
046 "Applies `handle-clause-data` fn to nested where clauses"
047 [clause-data]
048 (-> clause-data first
049 (str/replace #"\(|\)" "")
050 (str/split #"\s")
051 handle-clause-data))
052
053 (defn separate-statements
054 "Seprates where clause statements based on the logical operators they are tied with"
055 [clause-data]
056 (loop [cd clause-data
057 {:keys [un-decided] :as result} {:and [] :or []}]
058 (if (empty? cd)
059 (let [remove-nil-fn (partial remove nil?)
060 final-result (cond-> (-> result
061 (update :and remove-nil-fn)
062 (update :or remove-nil-fn))
063 un-decided (update :and conj un-decided)
064 un-decided (dissoc :un-decided))]
065 final-result)
066 (let [fcd (-> cd first str/lower-case)
067 logical? (or (= "and" fcd) (= "or" fcd))
068 nested? (and (str/starts-with? fcd "(") (str/ends-with? fcd ")"))
069 not? (if (= (try (nth cd (if logical? 2 1))
070 (catch IndexOutOfBoundsException _ ""))
071 "not")
072 1 0)]
073 (recur (drop (+ (if nested? 1 (if logical? 4 3)) not?) cd)
074 (if logical?
075 (-> result
076 (dissoc :un-decided)
077 (update (keyword fcd) conj un-decided
078 (let [next-clause-data (drop 1 cd)
079 next-nested? (and (str/starts-with?
080 (first next-clause-data) "(")
081 (str/ends-with?
082 (first next-clause-data) ")"))
083 next-clause-data (take (+ (if next-nested? 1 3) not?) next-clause-data)]
084 (cond-> next-clause-data
085 next-nested? nested-handle-clause-data))))
086 (assoc result :un-decided (if nested?
087 nested-handle-clause-data
088 (take (+ 3 not?) cd)))))))))
089
090 (defn separate-nots
091 "Separates normal (true) `not` (false) statements from a list"
092 [statements]
093 (let [predicate (fn [s]
094 (if (map? s)
095 false
096 (let [[_ op _] s]
097 (or (= op "!=")
098 (= op "not")))))]
099 {:true (remove predicate statements)
100 :false (->> (filter predicate statements)
101 (map (fn [s] (remove #(= % "not") s))))}))
102
103 (defn handle-clause-data
104 [clause-data]
105 (->> clause-data
106 separate-operands
107 separate-statements
108 (map (fn [[k statements]]
109 [k (separate-nots statements)]))
110 (into {})))