|
31 | 31 | (defmethod driver/database-supports? [:teradata feature] [_driver _feature _db] supported?)) |
32 | 32 |
|
33 | 33 | (defmethod sql-jdbc.sync/database-type->base-type :teradata [_ column-type] |
34 | | - ({:BIGINT :type/BigInteger |
35 | | - :BIGSERIAL :type/BigInteger |
36 | | - :BIT :type/* |
37 | | - :BLOB :type/* |
38 | | - :BOX :type/* |
39 | | - :CHAR :type/Text |
40 | | - :CLOB :type/Text |
41 | | - :BYTE :type/* |
42 | | - :BYTEINT :type/Integer |
43 | | - :DATE :type/Date |
44 | | - :DECIMAL :type/Decimal |
45 | | - :FLOAT :type/Float |
46 | | - :FLOAT4 :type/Float |
47 | | - :FLOAT8 :type/Float |
48 | | - :INTEGER :type/Integer |
49 | | - :INT :type/Integer |
50 | | - :INT2 :type/Integer |
51 | | - :INT4 :type/Integer |
52 | | - :INT8 :type/BigInteger |
53 | | - :INTERVAL :type/* ; time span |
54 | | - :JSON :type/Text |
55 | | - :LONGVARCHAR :type/Text ; Teradata extension |
56 | | - :LSEG :type/* |
57 | | - :MACADDR :type/Text |
58 | | - :MONEY :type/Decimal |
59 | | - :NUMERIC :type/Decimal |
60 | | - :PATH :type/* |
61 | | - :POINT :type/* |
62 | | - :REAL :type/Float |
63 | | - :SERIAL :type/Integer |
64 | | - :SERIAL2 :type/Integer |
65 | | - :SERIAL4 :type/Integer |
66 | | - :SERIAL8 :type/BigInteger |
67 | | - :SMALLINT :type/Integer |
68 | | - :SMALLSERIAL :type/Integer |
69 | | - :TIME :type/Time |
70 | | - (keyword "TIME WITH TIME ZONE") :type/Time |
71 | | - :TIMESTAMP :type/DateTime |
72 | | - (keyword "TIMESTAMP WITH TIME ZONE") :type/DateTime |
73 | | - :TSQUERY :type/* |
74 | | - :TSVECTOR :type/* |
75 | | - :TXID_SNAPSHOT :type/* |
76 | | - :UUID :type/UUID |
77 | | - :VARBIT :type/* |
78 | | - :VARBYTE :type/* ; byte array |
79 | | - :VARCHAR :type/Text |
80 | | - :XML :type/Text |
81 | | - (keyword "bit varying") :type/* |
82 | | - (keyword "character varying") :type/Text |
83 | | - (keyword "double precision") :type/Float |
84 | | - (keyword "time with time zone") :type/Time |
85 | | - (keyword "time without time zone") :type/Time |
86 | | - (keyword "timestamp with timezone") :type/DateTime |
87 | | - (keyword "timestamp without timezone") :type/DateTime}, column-type)) |
| 34 | + (let [type-mapping |
| 35 | + {:BIGINT :type/BigInteger |
| 36 | + :BIGSERIAL :type/BigInteger |
| 37 | + :BIT :type/* |
| 38 | + :BLOB :type/* |
| 39 | + :BOX :type/* |
| 40 | + :CHAR :type/Text |
| 41 | + :CLOB :type/Text |
| 42 | + :BYTE :type/* |
| 43 | + :BYTEINT :type/Integer |
| 44 | + :DATE :type/Date |
| 45 | + :DECIMAL :type/Decimal |
| 46 | + :FLOAT :type/Float |
| 47 | + :FLOAT4 :type/Float |
| 48 | + :FLOAT8 :type/Float |
| 49 | + :INTEGER :type/Integer |
| 50 | + :INT :type/Integer |
| 51 | + :INT2 :type/Integer |
| 52 | + :INT4 :type/Integer |
| 53 | + :INT8 :type/BigInteger |
| 54 | + :INTERVAL :type/* ; time span |
| 55 | + :JSON :type/Text |
| 56 | + :LONGVARCHAR :type/Text ; Teradata extension |
| 57 | + :LSEG :type/* |
| 58 | + :MACADDR :type/Text |
| 59 | + :MONEY :type/Decimal |
| 60 | + :NUMERIC :type/Decimal |
| 61 | + :NUMBER :type/Decimal ; Add this mapping |
| 62 | + :PATH :type/* |
| 63 | + :POINT :type/* |
| 64 | + :REAL :type/Float |
| 65 | + :SERIAL :type/Integer |
| 66 | + :SERIAL2 :type/Integer |
| 67 | + :SERIAL4 :type/Integer |
| 68 | + :SERIAL8 :type/BigInteger |
| 69 | + :SMALLINT :type/Integer |
| 70 | + :SMALLSERIAL :type/Integer |
| 71 | + :TIME :type/Time |
| 72 | + (keyword "TIME WITH TIME ZONE") :type/Time |
| 73 | + :TIMESTAMP :type/DateTime |
| 74 | + (keyword "TIMESTAMP WITH TIME ZONE") :type/DateTime |
| 75 | + :TSQUERY :type/* |
| 76 | + :TSVECTOR :type/* |
| 77 | + :TXID_SNAPSHOT :type/* |
| 78 | + :UUID :type/UUID |
| 79 | + :VARBIT :type/* |
| 80 | + :VARBYTE :type/* ; byte array |
| 81 | + :VARCHAR :type/Text |
| 82 | + :XML :type/Text |
| 83 | + (keyword "bit varying") :type/* |
| 84 | + (keyword "character varying") :type/Text |
| 85 | + (keyword "double precision") :type/Float |
| 86 | + (keyword "time with time zone") :type/Time |
| 87 | + (keyword "time without time zone") :type/Time |
| 88 | + (keyword "timestamp with timezone") :type/DateTime |
| 89 | + (keyword "timestamp without timezone") :type/DateTime}] |
| 90 | + (get type-mapping column-type :type/*))) ; Default to :type/* if no mapping is found |
88 | 91 |
|
89 | 92 | (defn- dbnames-set |
90 | 93 | "Transform the string of databases to a set of strings." |
|
93 | 96 | (set (map #(s/trim %) (s/split (s/trim dbnames) #","))))) |
94 | 97 |
|
95 | 98 | (defn- jdbc-fields-metadata |
96 | | - "Reducible metadata about the Fields belonging to a Table, fetching using JDBC DatabaseMetaData methods." |
| 99 | + "Fetch metadata about the Fields belonging to a Table or View using a SELECT * query." |
97 | 100 | [driver ^Connection conn db-name-or-nil schema table-name] |
98 | | - (sql-jdbc.sync.common/reducible-results |
99 | | - #(.getColumns (.getMetaData conn) |
100 | | - db-name-or-nil |
101 | | - (some->> schema (driver/escape-entity-name-for-metadata driver)) |
102 | | - (some->> table-name (driver/escape-entity-name-for-metadata driver)) |
103 | | - nil) |
104 | | - (fn [^ResultSet rs] |
105 | | - #(let [default (.getString rs "COLUMN_DEF") |
106 | | - no-default? (contains? #{nil "NULL" "null"} default) |
107 | | - nullable (.getInt rs "NULLABLE") |
108 | | - not-nullable? (= 0 nullable) |
109 | | - column-name (.getString rs "COLUMN_NAME") |
110 | | - required? (and no-default? not-nullable?)] |
111 | | - (merge |
112 | | - {:name column-name |
113 | | - :database-type (.getString rs "TYPE_NAME") |
114 | | - :database-required required?} |
115 | | - (when-let [remarks (.getString rs "REMARKS")] |
116 | | - (when-not (s/blank? remarks) |
117 | | - {:field-comment remarks}))))))) |
118 | | - |
119 | | -(defn fallback-fields-metadata-from-select-query |
120 | | - "In some rare cases `:column_name` is blank (eg. SQLite's views with group by) fallback to sniffing the type from a |
121 | | - SELECT * query." |
122 | | - [driver ^Connection conn db-name-or-nil table-schema table-name] |
123 | | - (let [[sql & params] (sql-jdbc.sync.interface/fallback-metadata-query driver db-name-or-nil table-schema table-name)] |
124 | | - (reify clojure.lang.IReduceInit |
125 | | - (reduce [_ rf init] |
126 | | - (with-open [stmt (sql-jdbc.sync.common/prepare-statement driver conn sql params) |
127 | | - rs (.executeQuery stmt)] |
128 | | - (let [metadata (.getMetaData rs)] |
129 | | - (reduce |
130 | | - ((map (fn [^Integer i] |
131 | | - {:name (.getColumnName metadata i) |
132 | | - :database-type (.getColumnTypeName metadata i)})) rf) |
133 | | - init |
134 | | - (range 1 (inc (.getColumnCount metadata)))))))))) |
| 101 | + (let [sql (str "SELECT * FROM " (when schema (str schema ".")) table-name " WHERE 1=0")] ; Query with no rows |
| 102 | + (with-open [stmt (.createStatement conn) |
| 103 | + rs (.executeQuery stmt sql)] |
| 104 | + (let [metadata (.getMetaData rs)] |
| 105 | + (mapv (fn [i] |
| 106 | + (let [column-name (.getColumnName metadata i) |
| 107 | + database-type (.getColumnTypeName metadata i) |
| 108 | + column-size (.getColumnDisplaySize metadata i) |
| 109 | + nullable (.isNullable metadata i) |
| 110 | + remarks (.getColumnLabel metadata i)] |
| 111 | + {:name column-name |
| 112 | + :database-type database-type |
| 113 | + :column-size column-size |
| 114 | + :nullable? (= nullable DatabaseMetaData/columnNullable) |
| 115 | + :remarks remarks})) |
| 116 | + (range 1 (inc (.getColumnCount metadata)))))))) |
135 | 117 |
|
136 | 118 | (defn ^:private fields-metadata |
137 | 119 | [driver ^Connection conn {schema :schema, table-name :name} ^String db-name-or-nil] |
138 | 120 | {:pre [(instance? Connection conn) (string? table-name)]} |
139 | | - (reify clojure.lang.IReduceInit |
140 | | - (reduce [_ rf init] |
141 | | - ;; 1. Return all the Fields that come back from DatabaseMetaData that include type info. |
142 | | - ;; |
143 | | - ;; 2. Iff there are some Fields that don't have type info, concatenate |
144 | | - ;; `fallback-fields-metadata-from-select-query`, which fetches the same Fields using a different method. |
145 | | - ;; |
146 | | - ;; 3. Filter out any duplicates between the two methods using `m/distinct-by`. |
147 | | - (let [has-fields-without-type-info? (volatile! false) |
148 | | - jdbc-metadata (eduction |
149 | | - (remove (fn [{:keys [database-type]}] |
150 | | - (when (s/blank? database-type) |
151 | | - (vreset! has-fields-without-type-info? true) |
152 | | - true))) |
153 | | - (jdbc-fields-metadata driver conn db-name-or-nil schema table-name)) |
154 | | - fallback-metadata (reify clojure.lang.IReduceInit |
155 | | - (reduce [_ rf init] |
156 | | - (reduce |
157 | | - rf |
158 | | - init |
159 | | - (when @has-fields-without-type-info? |
160 | | - (fallback-fields-metadata-from-select-query driver conn db-name-or-nil schema table-name)))))] |
161 | | - ;; VERY IMPORTANT! DO NOT REWRITE THIS TO BE LAZY! IT ONLY WORKS BECAUSE AS NORMAL-FIELDS GETS REDUCED, |
162 | | - ;; HAS-FIELDS-WITHOUT-TYPE-INFO? WILL GET SET TO TRUE IF APPLICABLE AND THEN FALLBACK-FIELDS WILL RUN WHEN |
163 | | - ;; IT'S TIME TO START EVALUATING THAT. |
164 | | - (reduce |
165 | | - ((comp cat (m/distinct-by :name)) rf) |
166 | | - init |
167 | | - [jdbc-metadata fallback-metadata]))))) |
| 121 | + ;; Attempt to fetch metadata using DatabaseMetaData.getColumns |
| 122 | + (let [jdbc-metadata (jdbc-fields-metadata driver conn db-name-or-nil schema table-name)] |
| 123 | + jdbc-metadata)) |
168 | 124 |
|
169 | 125 | (defmethod sql-jdbc.describe-table/describe-table-fields :teradata |
170 | 126 | [driver conn table db-name-or-nil] |
|
0 commit comments