22 (:require [clojure
33 [set :as set]
44 [string :as s]]
5+ [medley.core :as m]
56 [clojure.tools.logging :as log]
67 [clojure.java.jdbc :as jdbc]
78 [honeysql.core :as hsql]
1011 [driver :as driver]
1112 [util :as u]
1213 [config :as config]]
13- [metabase.driver.sql-jdbc
14- [common :as sql-jdbc.common]
15- [connection :as sql-jdbc.conn]
16- [execute :as sql-jdbc.execute]
17- [sync :as sql-jdbc.sync]]
14+ [metabase.driver.sql-jdbc.common :as sql-jdbc.common]
15+ [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn]
16+ [metabase.driver.sql-jdbc.execute :as sql-jdbc.execute]
17+ [metabase.driver.sql-jdbc.sync :as sql-jdbc.sync]
18+ [metabase.driver.sql-jdbc.sync.common :as sql-jdbc.sync.common]
19+ [metabase.driver.sql-jdbc.sync.interface :as sql-jdbc.sync.interface]
20+ [metabase.driver.sql-jdbc.sync.describe-table :as sql-jdbc.describe-table]
1821 [metabase.driver.sql.query-processor :as sql.qp]
1922 [metabase.driver.sql.util.deduplicate :as deduplicateutil]
2023 [metabase.query-processor.util :as qputil]
2124 [metabase.util
2225 [honeysql-extensions :as hx]
2326 [i18n :refer [trs]]])
24- (:import [java.sql DatabaseMetaData ResultSet Types PreparedStatement]
27+ (:import [java.sql Connection DatabaseMetaData ResultSet Types PreparedStatement]
2528 [java.time OffsetDateTime OffsetTime]
2629 [java.util Calendar TimeZone]))
2730
8891 [dbnames]
8992 (when dbnames
9093 (set (map #(s/trim %) (s/split (s/trim dbnames) #"," )))))
94+ (defn- jdbc-fields-metadata
95+ " Reducible metadata about the Fields belonging to a Table, fetching using JDBC DatabaseMetaData methods."
96+ [driver ^Connection conn db-name-or-nil schema table-name]
97+ (sql-jdbc.sync.common/reducible-results
98+ #(.getColumns (.getMetaData conn)
99+ db-name-or-nil
100+ (some->> schema (driver/escape-entity-name-for-metadata driver))
101+ (some->> table-name (driver/escape-entity-name-for-metadata driver))
102+ nil )
103+ (fn [^ResultSet rs]
104+ #(let [default (.getString rs " COLUMN_DEF" )
105+ no-default? (contains? #{nil " NULL" " null" } default )
106+ nullable (.getInt rs " NULLABLE" )
107+ not-nullable? (= 0 nullable)
108+ column-name (.getString rs " COLUMN_NAME" )
109+ required? (and no-default? not-nullable?)]
110+ (merge
111+ {:name column-name
112+ :database-type (.getString rs " TYPE_NAME" )
113+ :database-required required?}
114+ (when-let [remarks (.getString rs " REMARKS" )]
115+ (when-not (s/blank? remarks)
116+ {:field-comment remarks})))))))
117+
118+ (defn fallback-fields-metadata-from-select-query
119+ " In some rare cases `:column_name` is blank (eg. SQLite's views with group by) fallback to sniffing the type from a
120+ SELECT * query."
121+ [driver ^Connection conn table-schema table-name]
122+ (let [[sql & params] (sql-jdbc.sync.interface/fallback-metadata-query driver table-schema table-name)]
123+ (reify clojure.lang.IReduceInit
124+ (reduce [_ rf init]
125+ (with-open [stmt (sql-jdbc.sync.common/prepare-statement driver conn sql params)
126+ rs (.executeQuery stmt)]
127+ (let [metadata (.getMetaData rs)]
128+ (reduce
129+ ((map (fn [^Integer i]
130+ {:name (.getColumnName metadata i)
131+ :database-type (.getColumnTypeName metadata i)})) rf)
132+ init
133+ (range 1 (inc (.getColumnCount metadata))))))))))
134+
135+ (defn ^:private fields-metadata
136+ [driver ^Connection conn {schema :schema , table-name :name } ^String db-name-or-nil]
137+ {:pre [(instance? Connection conn) (string? table-name)]}
138+ (reify clojure.lang.IReduceInit
139+ (reduce [_ rf init]
140+ ; ; 1. Return all the Fields that come back from DatabaseMetaData that include type info.
141+ ; ;
142+ ; ; 2. Iff there are some Fields that don't have type info, concatenate
143+ ; ; `fallback-fields-metadata-from-select-query`, which fetches the same Fields using a different method.
144+ ; ;
145+ ; ; 3. Filter out any duplicates between the two methods using `m/distinct-by`.
146+ (let [has-fields-without-type-info? (volatile! false )
147+ jdbc-metadata (eduction
148+ (remove (fn [{:keys [database-type]}]
149+ (when (s/blank? database-type)
150+ (vreset! has-fields-without-type-info? true )
151+ true )))
152+ (jdbc-fields-metadata driver conn db-name-or-nil schema table-name))
153+ fallback-metadata (reify clojure.lang.IReduceInit
154+ (reduce [_ rf init]
155+ (reduce
156+ rf
157+ init
158+ (when @has-fields-without-type-info?
159+ (fallback-fields-metadata-from-select-query driver conn schema table-name)))))]
160+ ; ; VERY IMPORTANT! DO NOT REWRITE THIS TO BE LAZY! IT ONLY WORKS BECAUSE AS NORMAL-FIELDS GETS REDUCED,
161+ ; ; HAS-FIELDS-WITHOUT-TYPE-INFO? WILL GET SET TO TRUE IF APPLICABLE AND THEN FALLBACK-FIELDS WILL RUN WHEN
162+ ; ; IT'S TIME TO START EVALUATING THAT.
163+ (reduce
164+ ((comp cat (m/distinct-by :name )) rf)
165+ init
166+ [jdbc-metadata fallback-metadata])))))
167+
168+ (defmethod sql-jdbc.describe-table /describe-table-fields :teradata
169+ [driver conn table db-name-or-nil]
170+ (into
171+ #{}
172+ (sql-jdbc.describe-table/describe-table-fields-xf driver table)
173+ (fields-metadata driver conn table db-name-or-nil)))
91174
92175(defn- teradata-spec
93176 " Create a database specification for a teradata database. Opts should include keys
244327 (fn []
245328 (when-let [value (.getTimestamp rs i)]
246329 (.toLocalDateTime value))))
247-
330+
248331(defmethod sql-jdbc.execute /read-column-thunk [:teradata Types/TIMESTAMP_WITH_TIMEZONE]
249332 [_ rs _ i]
250333 (fn []
262345 (fn []
263346 (when-let [value (.getTime rs i)]
264347 (.toLocalTime value))))
265-
348+
266349(defmethod sql-jdbc.execute /read-column-thunk [:teradata Types/TIME_WITH_TIMEZONE]
267350 [_ rs _ i]
268351 (fn []
330413 ((some-fn :db
331414 :dbname
332415 :sid
333- :catalog ))))})
416+ :catalog ))))})
0 commit comments