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