|
2 | 2 | (:require [clojure |
3 | 3 | [set :as set] |
4 | 4 | [string :as s]] |
| 5 | + [clojure.tools.logging :as log] |
5 | 6 | [clojure.java.jdbc :as jdbc] |
6 | 7 | [honeysql.core :as hsql] |
7 | 8 | [java-time :as t] |
|
17 | 18 | [metabase.driver.sql.util.deduplicate :as deduplicateutil] |
18 | 19 | [metabase.query-processor.util :as qputil] |
19 | 20 | [metabase.util |
20 | | - [honeysql-extensions :as hx]]) |
| 21 | + [honeysql-extensions :as hx] |
| 22 | + [i18n :refer [trs]]]) |
21 | 23 | (:import [java.sql DatabaseMetaData ResultSet Types PreparedStatement] |
22 | 24 | [java.time OffsetDateTime OffsetTime] |
23 | 25 | [java.util Calendar TimeZone])) |
|
229 | 231 | {:tables (fast-active-tables, driver, ^DatabaseMetaData metadata, database)})) |
230 | 232 |
|
231 | 233 | ;; We can't use getObject(int, Class) as the underlying Resultset used by the Teradata jdbc driver is based on jdk6. |
232 | | -(defmethod sql-jdbc.execute/read-column [:teradata Types/TIMESTAMP] |
233 | | - [_ _ rs _ i] |
234 | | - (.toLocalDateTime (.getTimestamp rs i))) |
235 | | - |
236 | | -(defmethod sql-jdbc.execute/read-column [:teradata Types/TIMESTAMP_WITH_TIMEZONE] |
237 | | - [_ _ rs _ i] |
238 | | - (OffsetDateTime/parse (.getString rs i))) |
239 | | - |
240 | | -(defmethod sql-jdbc.execute/read-column [:teradata Types/DATE] |
241 | | - [_ _ rs _ i] |
242 | | - (.toLocalDate (.getDate rs i))) |
243 | | - |
244 | | -(defmethod sql-jdbc.execute/read-column [:teradata Types/TIME] |
245 | | - [_ _ rs _ i] |
246 | | - (.toLocalTime (.getTime rs i))) |
247 | | - |
248 | | -(defmethod sql-jdbc.execute/read-column [:teradata Types/TIME_WITH_TIMEZONE] |
249 | | - [_ _ rs _ i] |
250 | | - (OffsetTime/parse (.getTime rs i))) |
| 234 | +(defmethod sql-jdbc.execute/read-column-thunk [:teradata Types/TIMESTAMP] |
| 235 | + [_ rs _ i] |
| 236 | + (fn [] |
| 237 | + (.toLocalDateTime (.getTimestamp rs i)))) |
| 238 | + |
| 239 | +(defmethod sql-jdbc.execute/read-column-thunk [:teradata Types/TIMESTAMP_WITH_TIMEZONE] |
| 240 | + [_ rs _ i] |
| 241 | + (fn [] |
| 242 | + (OffsetDateTime/parse (.getString rs i)))) |
| 243 | + |
| 244 | +(defmethod sql-jdbc.execute/read-column-thunk [:teradata Types/DATE] |
| 245 | + [_ rs _ i] |
| 246 | + (fn [] |
| 247 | + (.toLocalDate (.getDate rs i)))) |
| 248 | + |
| 249 | +(defmethod sql-jdbc.execute/read-column-thunk [:teradata Types/TIME] |
| 250 | + [_ rs _ i] |
| 251 | + (fn [] |
| 252 | + (.toLocalTime (.getTime rs i)))) |
| 253 | + |
| 254 | +(defmethod sql-jdbc.execute/read-column-thunk [:teradata Types/TIME_WITH_TIMEZONE] |
| 255 | + [_ rs _ i] |
| 256 | + (fn [] |
| 257 | + (OffsetTime/parse (.getTime rs i)))) |
251 | 258 |
|
252 | 259 | ;; TODO: use metabase.driver.sql-jdbc.execute.legacy-impl instead of re-implementing everything here |
253 | 260 | (defmethod sql-jdbc.execute/set-parameter [:teradata OffsetDateTime] |
|
256 | 263 | t (t/sql-timestamp t)] |
257 | 264 | (.setTimestamp ps i t cal))) |
258 | 265 |
|
259 | | -(defn- run-query |
260 | | - "Run the query itself without setting the timezone connection parameter as this must not be changed on a Teradata connection. |
261 | | - Setting connection attributes like timezone would make subsequent queries behave unexpectedly." |
262 | | - [{sql :query, :keys [params remark max-rows]} connection] |
263 | | - (let [sql (s/replace (str "-- " remark "\n" sql) "OFFSET" "") |
264 | | - [columns & rows] (jdbc/query |
265 | | - connection (into [sql] params) |
266 | | - {:identifiers identity |
267 | | - :as-arrays? true |
268 | | - :read-columns (partial #'metabase.driver.sql-jdbc.execute/read-columns :teradata) |
269 | | - :set-parameters (partial #'metabase.driver.sql-jdbc.execute/set-parameters :teradata) |
270 | | - :max-rows max-rows})] |
271 | | - {:rows (or rows []) |
272 | | - :columns (map u/qualified-name columns)})) |
273 | | - |
274 | | -(defn- run-query-without-timezone [driver settings connection query] |
275 | | - (#'metabase.driver.sql-jdbc.execute/do-in-transaction connection (partial run-query query))) |
276 | | - |
277 | | -(defmethod driver/execute-query :teradata |
278 | | - [driver {:keys [database settings], query :native, :as outer-query}] |
279 | | - (let [query (assoc query :remark (qputil/query->remark outer-query))] |
280 | | - (#'metabase.driver.sql-jdbc.execute/do-with-try-catch |
281 | | - (fn [] |
282 | | - (let [db-connection (sql-jdbc.conn/db->pooled-connection-spec database)] |
283 | | - (run-query-without-timezone driver settings db-connection query)))))) |
| 266 | +;; Run the query itself without setting the timezone connection parameter as this must not be changed on a Teradata connection. |
| 267 | +;; Setting connection attributes like timezone would make subsequent queries behave unexpectedly. |
| 268 | +(defmethod sql-jdbc.execute/connection-with-timezone :teradata |
| 269 | + [driver database ^String timezone-id] |
| 270 | + (let [conn (.getConnection (sql-jdbc.execute/datasource database))] |
| 271 | + (try |
| 272 | + (sql-jdbc.execute/set-best-transaction-level! driver conn) |
| 273 | + (try |
| 274 | + (.setReadOnly conn true) |
| 275 | + (catch Throwable e |
| 276 | + (log/debug e (trs "Error setting connection to read-only")))) |
| 277 | + (try |
| 278 | + (.setHoldability conn ResultSet/CLOSE_CURSORS_AT_COMMIT) |
| 279 | + (catch Throwable e |
| 280 | + (log/debug e (trs "Error setting default holdability for connection")))) |
| 281 | + conn |
| 282 | + (catch Throwable e |
| 283 | + (.close conn) |
| 284 | + (throw e))))) |
| 285 | + |
| 286 | +(defn- cleanup-query |
| 287 | + "Remove the OFFSET keyword." |
| 288 | + [query] |
| 289 | + (update-in query [:native :query] (fn [value] (s/replace value "OFFSET" "")))) |
| 290 | + |
| 291 | +(defmethod driver/execute-reducible-query :teradata |
| 292 | + [driver query context respond] |
| 293 | + ((get-method driver/execute-reducible-query :sql-jdbc) driver (cleanup-query query) context respond)) |
284 | 294 |
|
285 | 295 | (defmethod sql.qp/current-datetime-fn :teradata [_] now) |
286 | 296 |
|
|
0 commit comments