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