|
5 | 5 | (:require [clojure.tools.logging :as log] |
6 | 6 | [java-time :as t] |
7 | 7 | [metabase.models |
| 8 | + [database :refer [Database]] |
8 | 9 | [query :as query] |
9 | 10 | [query-execution :as query-execution :refer [QueryExecution]]] |
10 | 11 | [metabase.query-processor.util :as qputil] |
|
32 | 33 | (query/save-query-and-update-average-execution-time! query query-hash running-time) |
33 | 34 | (if-not context |
34 | 35 | (log/warn (trs "Cannot save QueryExecution, missing :context")) |
35 | | - (db/insert! QueryExecution (dissoc query-execution :json_query)))) |
| 36 | + (db/insert! QueryExecution (dissoc query-execution :json_query :original_query_stmt)))) |
36 | 37 |
|
37 | 38 | (defn- save-query-execution! |
38 | 39 | "Save a `QueryExecution` row containing `execution-info`. Done asynchronously when a query is finished." |
|
63 | 64 | ;;; | Middleware | |
64 | 65 | ;;; +----------------------------------------------------------------------------------------------------------------+ |
65 | 66 |
|
| 67 | +(defn result-with-original-query |
| 68 | + "Modify result with original query (ie. query without modification(done by add-limit-query). |
| 69 | + This is done as this raw query is used for json and csv download" |
| 70 | + [result, query-execution-info] |
| 71 | + (if (and (get-in query-execution-info[:native]) (some? (get-in query-execution-info[:original_query_stmt]))) |
| 72 | + (-> result |
| 73 | + (assoc-in [:data :native_form :query] (get-in query-execution-info [:original_query_stmt])) |
| 74 | + (assoc-in [:json_query :native :query] (get-in query-execution-info [:original_query_stmt])) |
| 75 | + ) |
| 76 | + result |
| 77 | + ) |
| 78 | + ) |
| 79 | + |
66 | 80 | (defn- success-response [{query-hash :hash, :as query-execution} {cached? :cached, :as result}] |
67 | 81 | (merge |
| 82 | + (result-with-original-query result query-execution) |
68 | 83 | (-> query-execution |
69 | 84 | add-running-time |
70 | | - (dissoc :error :hash :executor_id :card_id :dashboard_id :pulse_id :result_rows :native)) |
71 | | - result |
| 85 | + (dissoc :error :hash :executor_id :card_id :dashboard_id :pulse_id :result_rows :native :original_query_stmt)) |
72 | 86 | {:status :completed |
73 | 87 | :average_execution_time (when cached? |
74 | 88 | (query/average-execution-time-ms query-hash))})) |
|
95 | 109 | (rf result row)))))) |
96 | 110 |
|
97 | 111 | (defn- query-execution-info |
98 | | - "Return the info for the QueryExecution entry for this `query`." |
| 112 | + "Return the info for the QueryExecution entry for this `query`. |
| 113 | + setting original_query in query-execution-info for the context of query passed by client for other middleware's |
| 114 | + " |
99 | 115 | {:arglists '([query])} |
100 | 116 | [{{:keys [executed-by query-hash context card-id dashboard-id pulse-id]} :info |
101 | 117 | database-id :database |
102 | 118 | query-type :type |
103 | 119 | :as query}] |
104 | 120 | {:pre [(instance? (Class/forName "[B") query-hash)]} |
105 | | - {:database_id database-id |
106 | | - :executor_id executed-by |
107 | | - :card_id card-id |
108 | | - :dashboard_id dashboard-id |
109 | | - :pulse_id pulse-id |
110 | | - :context context |
111 | | - :hash query-hash |
112 | | - :native (= (keyword query-type) :native) |
113 | | - :json_query (cond-> (dissoc query :info) |
114 | | - (empty? (:parameters query)) (dissoc :parameters)) |
115 | | - :started_at (t/zoned-date-time) |
116 | | - :running_time 0 |
117 | | - :result_rows 0 |
118 | | - :start_time_millis (System/currentTimeMillis)}) |
| 121 | + {:database_id database-id |
| 122 | + :executor_id executed-by |
| 123 | + :card_id card-id |
| 124 | + :dashboard_id dashboard-id |
| 125 | + :pulse_id pulse-id |
| 126 | + :context context |
| 127 | + :hash query-hash |
| 128 | + :native (= (keyword query-type) :native) |
| 129 | + :original_query_stmt (get-in query[:native :original_query]) |
| 130 | + :json_query (cond-> (dissoc query :info) |
| 131 | + (empty? (:parameters query)) (dissoc :parameters)) |
| 132 | + :started_at (t/zoned-date-time) |
| 133 | + :running_time 0 |
| 134 | + :result_rows 0 |
| 135 | + :start_time_millis (System/currentTimeMillis)}) |
| 136 | + |
| 137 | +(defn adhoc-or-question? |
| 138 | + "Check if adhoc or question query" |
| 139 | + [context] |
| 140 | + (if (or (clojure.string/includes? context "question") (clojure.string/includes? context "ad-hoc")) |
| 141 | + true false |
| 142 | + ) |
| 143 | + ) |
| 144 | + |
| 145 | +(defn limit-native-query? |
| 146 | + [database] |
| 147 | + (let[engine (:engine (db/select-one (into [Database] [:id :engine]) :id database))] |
| 148 | + (if (some (partial = (name engine)) ["presto" "mysql" "postgres" "redshift" "athena"]) true false) |
| 149 | + )) |
| 150 | + |
| 151 | +(defn add-limit-query |
| 152 | + "Add limit to query. If is native query(adhoc or question limit is set to 10000 else json or csv then limit set to 100000. |
| 153 | + Works only for native queries" |
| 154 | + |
| 155 | + [{:keys [database], :as query}] |
| 156 | + (if (and (contains? query :native) (limit-native-query? database) (clojure.string/includes? (clojure.string/lower-case (get-in query[:native :query])) "select")) |
| 157 | + (let [context (get-in query [:info :context]) |
| 158 | + query (update-in query[:native] assoc :original_query (get-in query[:native :query])) |
| 159 | + raw_query (clojure.string/replace (get-in query[:native :query]) #"[ ;]*$" "" ) |
| 160 | + limit (if(adhoc-or-question? context) (atom 10000) (atom 100000))] |
| 161 | + (if-not (re-find #"(?i)limit " raw_query) |
| 162 | + (update-in query[:native] assoc :query (str raw_query " limit " @limit )) |
| 163 | + query |
| 164 | + ) |
| 165 | + ) |
| 166 | + query |
| 167 | + ) |
| 168 | + ) |
119 | 169 |
|
120 | 170 | (defn process-userland-query |
121 | 171 | "Do extra handling 'userland' queries (i.e. ones ran as a result of a user action, e.g. an API call, scheduled Pulse, |
122 | 172 | etc.). This includes recording QueryExecution entries and returning the results in an FE-client-friendly format." |
123 | 173 | [qp] |
124 | 174 | (fn [query rff {:keys [raisef], :as context}] |
125 | | - (let [query (assoc-in query [:info :query-hash] (qputil/query-hash query)) |
| 175 | + (let [query (add-limit-query query) |
| 176 | + query (assoc-in query [:info :query-hash] (qputil/query-hash query)) |
126 | 177 | execution-info (query-execution-info query)] |
| 178 | + (log/debug "Original query:\n %s -> Limit query:\n %s " (get-in query[:native :original_query]) (get-in query[:native :query])) |
127 | 179 | (letfn [(rff* [metadata] |
128 | 180 | (add-and-save-execution-info-xform! metadata execution-info (rff metadata))) |
129 | 181 | (raisef* [^Throwable e context] |
|
0 commit comments