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