Skip to content

Commit

Permalink
Unprepare args for BigQuery [ci drivers]
Browse files Browse the repository at this point in the history
  • Loading branch information
camsaul committed Feb 8, 2017
1 parent e75b2cb commit 2172f0b
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 6 deletions.
7 changes: 5 additions & 2 deletions src/metabase/driver/bigquery.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
[metabase.driver.google :as google]
[metabase.driver.generic-sql :as sql]
[metabase.driver.generic-sql.query-processor :as sqlqp]
[metabase.driver.generic-sql.util.unprepare :as unprepare]
(metabase.models [database :refer [Database]]
[field :as field]
[table :as table])
Expand Down Expand Up @@ -294,8 +295,10 @@
:table-name table-name
:mbql? true})))

(defn- execute-query [{{{:keys [dataset-id]} :details, :as database} :database, {sql :query, :keys [table-name mbql?]} :native, :as outer-query}]
(let [sql (str "-- " (qp/query->remark outer-query) "\n" sql)
(defn- execute-query [{{{:keys [dataset-id]} :details, :as database} :database, {sql :query, params :params, :keys [table-name mbql?]} :native, :as outer-query}]
(let [sql (str "-- " (qp/query->remark outer-query) "\n" (if (seq params)
(unprepare/unprepare (cons sql params))
sql))
results (process-native* database sql)
results (if mbql?
(post-process-mbql dataset-id table-name results)
Expand Down
26 changes: 26 additions & 0 deletions src/metabase/driver/generic_sql/util/unprepare.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
(ns metabase.driver.generic-sql.util.unprepare
"Utility functions for converting a prepared statement with `?` into a plain SQL query."
(:require [clojure.string :as str]
[honeysql.core :as hsql]
[metabase.util :as u]
[metabase.util.honeysql-extensions :as hx])
(:import java.util.Date))

(defprotocol ^:private IUnprepare
(^:private unprepare-arg ^String [this]))

(extend-protocol IUnprepare
nil (unprepare-arg [this] "NULL")
String (unprepare-arg [this] (str \' (str/replace this "'" "\\\\'") \')) ; escape single-quotes
Boolean (unprepare-arg [this] (if this "TRUE" "FALSE"))
Number (unprepare-arg [this] (str this))
Date (unprepare-arg [this] (first (hsql/format (hsql/call :timestamp (hx/literal (u/date->iso-8601 this))))))) ; TODO - this probably doesn't work for every DB!

(defn unprepare
"Convert a normal SQL `[statement & prepared-statement-args]` vector into a flat, non-prepared statement."
^String [[sql & args]]
(loop [sql sql, [arg & more-args, :as args] args]
(if-not (seq args)
sql
(recur (str/replace-first sql #"(?<!\?)\?(?!\?)" (unprepare-arg arg))
more-args))))
8 changes: 4 additions & 4 deletions src/metabase/query_processor/sql_parameters.clj
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,10 @@
;;
;; The details maps returned have the format:
;;
;; {:param-key :timestamp ; name of the param being replaced
;; :original-snippet "[[AND timestamp < {{timestamp}}]]" ; full text of the snippet to be replaced
;; :optional-snippet "AND timestamp < {{timestamp}}" ; portion of the snippet inside [[optional]] brackets, or `nil` if the snippet isn't optional
;; :variable-snippet "{{timestamp}}"} ; portion of the snippet referencing the variable itself, e.g. {{x}}
;; {:param-key :timestamp ; name of the param being replaced
;; :original-snippet "[[AND timestamp < {{timestamp}}]]" ; full text of the snippet to be replaced
;; :optional-snippet "AND timestamp < {{timestamp}}" ; portion of the snippet inside [[optional]] brackets, or `nil` if the snippet isn't optional
;; :variable-snippet "{{timestamp}}"} ; portion of the snippet referencing the variable itself, e.g. {{x}}

(s/defn ^:private ^:always-validate param-snippet->param-name :- s/Keyword
"Return the keyword name of the param being referenced inside PARAM-SNIPPET.
Expand Down
10 changes: 10 additions & 0 deletions test/metabase/driver/generic_sql/util/unprepare_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(ns metabase.driver.generic-sql.util.unprepare-test
(:require [expectations :refer :all]
[metabase.driver.generic-sql.util.unprepare :as unprepare]))

(expect
"SELECT 'Cam\\'s Cool Toucan' FROM TRUE WHERE x ?? y AND z = timestamp('2017-01-01T00:00:00.000Z')"
(unprepare/unprepare ["SELECT ? FROM ? WHERE x ?? y AND z = ?"
"Cam's Cool Toucan"
true
#inst "2017-01-01T00:00:00.000Z"]))

0 comments on commit 2172f0b

Please sign in to comment.