diff --git a/src/metabase/api/meta/dataset.clj b/src/metabase/api/meta/dataset.clj index 9950cb538af4668aa6cb15f141dab80ce738a707..b5e094628beaab7e73b42584473323f627f09ea2 100644 --- a/src/metabase/api/meta/dataset.clj +++ b/src/metabase/api/meta/dataset.clj @@ -5,6 +5,8 @@ [metabase.driver :as driver])) (defendpoint POST "/" [:as {:keys [body]}] - (driver/dataset-query body {:executed_by *current-user-id*})) + (let [{:keys [status] :as response} (driver/dataset-query body {:executed_by *current-user-id*})] + {:status (if (= status :completed) 200 400) + :body response})) (define-routes) diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index 0b84d9120ad1f48267eb7ad4d68847f84d729864..d32e67612db03dacd26e579c63938d9087d2e4e0 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -135,9 +135,9 @@ (defn query-fail "Save QueryExecution state and construct a failed query response" - [query-execution msg] - (let [updates {:status "failed" - :error msg + [query-execution error-message] + (let [updates {:status :failed + :error error-message :finished_at (util/new-sql-timestamp) :running_time (- (System/currentTimeMillis) (:start_time_millis query-execution))}] ;; record our query execution and format response @@ -146,7 +146,8 @@ (merge updates) (save-query-execution) ;; this is just for the response for clien - (assoc :row_count 0 + (assoc :error error-message + :row_count 0 :data {:rows [] :cols [] :columns []})))) diff --git a/src/metabase/driver/generic_sql/native.clj b/src/metabase/driver/generic_sql/native.clj index 70f762ce1046f263cd6c9eeb8e2abec08e600ba0..6fdb8a7c5f0a7b1ed73904dd502e2ecf07d2608c 100644 --- a/src/metabase/driver/generic_sql/native.clj +++ b/src/metabase/driver/generic_sql/native.clj @@ -47,6 +47,7 @@ columns first-row)}}) (catch java.sql.SQLException e {:status :failed - :error (->> (.getMessage e) ; error message comes back like 'Column "ZID" not found; SQL statement: ... [error-code] - (re-find #"^(.*);") ; the user already knows the SQL, and error code is meaningless - second)}))) ; so just return the part of the exception that is relevant + :error (or (->> (.getMessage e) ; error message comes back like 'Column "ZID" not found; SQL statement: ... [error-code]' sometimes + (re-find #"^(.*);") ; the user already knows the SQL, and error code is meaningless + second) ; so just return the part of the exception that is relevant + (.getMessage e))}))) diff --git a/src/metabase/driver/generic_sql/query_processor.clj b/src/metabase/driver/generic_sql/query_processor.clj index cfddf3737a9ea54894d23ce9199d86579cf77cb8..3d2526b8f20b79b4c6225accf330cf2cd650a57e 100644 --- a/src/metabase/driver/generic_sql/query_processor.clj +++ b/src/metabase/driver/generic_sql/query_processor.clj @@ -47,10 +47,17 @@ {:pre [(integer? (:database query)) ; double check that the query being passed is valid (map? (:query query)) (= (name (:type query)) "query")]} - (->> (process query) - eval - (post-process query) - (annotate/annotate query))) + (try + (->> (process query) + eval + (post-process query) + (annotate/annotate query)) + (catch java.sql.SQLException e + {:status :failed + :error (or (->> (.getMessage e) ; error message comes back like "Error message ... [status-code]" sometimes + (re-find #"(?s)(^.*)\s+\[[\d-]+\]$") ; status code isn't useful and makes unit tests hard to write so strip it off + second) + (.getMessage e))}))) ; (?s) = Pattern.DOTALL - tell regex `.` to match newline characters as well (defn process-and-run diff --git a/src/metabase/models/query_execution.clj b/src/metabase/models/query_execution.clj index 6cfff0b19d176f5e65abb4dec8d53e5c04a19c14..fe0ca1deb2ec870e32fc746b0c0b555c9823430e 100644 --- a/src/metabase/models/query_execution.clj +++ b/src/metabase/models/query_execution.clj @@ -45,17 +45,18 @@ :result_rows :query_id]) -(defmethod pre-insert QueryExecution [_ {:keys [json_query] :as query-execution}] - (assoc query-execution :json_query (if (string? json_query) json_query - (json/write-str json_query)))) +(defmethod pre-insert QueryExecution [_ {:keys [status json_query] :as query-execution}] + (cond-> (assoc query-execution :json_query (if (string? json_query) json_query + (json/write-str json_query))) + status (assoc :status (name status)))) (defmethod post-select QueryExecution [_ {:keys [query_id result_rows] :as query-execution}] (-> query-execution - (realize-json :json_query :result_data) - (assoc* :row_count (or result_rows 0) ; sadly we have 2 ways to reference the row count :( - :query (delay - (check query_id 500 "Can't get execution: QueryExecution doesn't have a :query_id.") - (sel :one Query :id query_id))))) + (realize-json :json_query :result_data) + (assoc* :row_count (or result_rows 0) ; sadly we have 2 ways to reference the row count :( + :query (delay + (check query_id 500 "Can't get execution: QueryExecution doesn't have a :query_id.") + (sel :one Query :id query_id))))) (defn build-response diff --git a/test/metabase/driver/generic_sql/query_processor_test.clj b/test/metabase/driver/generic_sql/query_processor_test.clj index 25780c14f1b9a53419fea6693c02c4d48113c333..c24ec10463c18acafbd2ff5700e226f3c379be21 100644 --- a/test/metabase/driver/generic_sql/query_processor_test.clj +++ b/test/metabase/driver/generic_sql/query_processor_test.clj @@ -436,3 +436,18 @@ ;; Rows come back like `[value timestamp]` but it is hard to compare timestamps directly since the values that come back are casted ;; to Dates and the exact value depends on the locale of the machine running the tests. So just drop the timestamps from the results. (update-in [:data :rows] (partial map first)))) + + +;; # ERROR RESPONSES + +;; Check that we get an error response formatted the way we'd expect +(expect + {:status :failed + :error "Column \"CHECKINS.NAME\" not found; SQL statement:\nSELECT \"CHECKINS\".* FROM \"CHECKINS\" WHERE (\"CHECKINS\".\"NAME\" = ?)"} + (process-and-run {:database @db-id + :type :query + :query {:source_table (table->id :checkins) + :filter ["=" (field->id :venues :name) 1] ; wrong Field + :aggregation ["rows"] + :breakout [nil] + :limit nil}}))