From 17aa073720964f210bd84406a73a5f7422783722 Mon Sep 17 00:00:00 2001 From: Howon Lee <hlee.howon@gmail.com> Date: Thu, 12 May 2022 02:27:46 -0700 Subject: [PATCH] MySQL JSON (22174) (#22175) Pursuant to https://github.com/metabase/metabase/issues/22174. Make the JSON columns work on MySQL in exactly the same way as they do in Postgres. Bit of a refactor about some bits of Postgres implementation. Importantly, MariaDB JSON columns work significantly more like SQL Server JSON columns. That is, they aren't JSON columns at all with respect to type, they're text columns - and we don't detect them very well as JSON columns qua JSON columns. So MariaDB is not supported at this time. --- src/metabase/driver/mysql.clj | 52 +++++++- src/metabase/driver/postgres.clj | 5 +- src/metabase/driver/sql/query_processor.clj | 9 ++ test/metabase/driver/mysql_test.clj | 124 ++++++++++++++++++ test/metabase/driver/postgres_test.clj | 6 +- .../test/data/dataset_definitions.clj | 3 + .../test/data/dataset_definitions/json.edn | 20 +++ test/metabase/test/data/mysql.clj | 1 + 8 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 test/metabase/test/data/dataset_definitions/json.edn diff --git a/src/metabase/driver/mysql.clj b/src/metabase/driver/mysql.clj index 7b9a28c31ae..c830f00e50c 100644 --- a/src/metabase/driver/mysql.clj +++ b/src/metabase/driver/mysql.clj @@ -5,6 +5,7 @@ [clojure.string :as str] [clojure.tools.logging :as log] [honeysql.core :as hsql] + [honeysql.format :as hformat] [java-time :as t] [metabase.config :as config] [metabase.db.spec :as mdb.spec] @@ -14,9 +15,13 @@ [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn] [metabase.driver.sql-jdbc.execute :as sql-jdbc.execute] [metabase.driver.sql-jdbc.sync :as sql-jdbc.sync] + [metabase.driver.sql-jdbc.sync.describe-table :as sql-jdbc.describe-table] [metabase.driver.sql.query-processor :as sql.qp] [metabase.driver.sql.util.unprepare :as unprepare] + [metabase.models.field :as field] + [metabase.query-processor.store :as qp.store] [metabase.query-processor.timezone :as qp.timezone] + [metabase.query-processor.util.add-alias-info :as add] [metabase.util :as u] [metabase.util.honeysql-extensions :as hx] [metabase.util.i18n :refer [deferred-tru trs]]) @@ -30,11 +35,12 @@ (defmethod driver/display-name :mysql [_] "MySQL") +(defmethod driver/database-supports? [:mysql :nested-field-columns] [_ _ _] true) + (defmethod driver/supports? [:mysql :regex] [_ _] false) (defmethod driver/supports? [:mysql :percentile-aggregations] [_ _] false) - ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | metabase.driver impls | ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -168,6 +174,18 @@ [_] :sunday) +(def ^:const max-nested-field-columns + "Maximum number of nested field columns." + 100) + +(defmethod sql-jdbc.sync/describe-nested-field-columns :mysql + [driver database table] + (let [spec (sql-jdbc.conn/db->pooled-connection-spec database) + fields (sql-jdbc.describe-table/describe-nested-field-columns driver spec table)] + (if (> (count fields) max-nested-field-columns) + #{} + fields))) + ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | metabase.driver.sql impls | @@ -205,6 +223,31 @@ [driver [_ arg]] (hsql/call :char_length (sql.qp/->honeysql driver arg))) +(defmethod sql.qp/json-query :mysql + [_ identifier stored-field] + (letfn [(handle-name [x] (str "\"" (if (number? x) (str x) (name x)) "\""))] + (let [nfc-path (:nfc_path stored-field) + unwrapped-identifier (:form identifier) + parent-identifier (field/nfc-field->parent-identifier unwrapped-identifier stored-field) + jsonpath-query (format "$.%s" (str/join "." (map handle-name (rest nfc-path))))] + (reify + hformat/ToSql + (to-sql [_] + (hformat/to-params-default jsonpath-query "nfc_path") + (format "JSON_EXTRACT(%s, ?)" (hformat/to-sql parent-identifier))))))) + +(defmethod sql.qp/->honeysql [:mysql :field] + [driver [_ id-or-name opts :as clause]] + (let [stored-field (when (integer? id-or-name) + (qp.store/field id-or-name)) + parent-method (get-method sql.qp/->honeysql [:sql :field]) + identifier (parent-method driver clause)] + (if (field/json-field? stored-field) + (if (::sql.qp/forced-alias opts) + (keyword (::add/source-alias opts)) + (sql.qp/json-query :mysql identifier stored-field)) + identifier))) + ;; Since MySQL doesn't have date_trunc() we fake it by formatting a date to an appropriate string and then converting ;; back to a date. See http://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_date-format for an ;; explanation of format specifiers @@ -307,6 +350,13 @@ ;; strip off " UNSIGNED" from end if present (keyword (str/replace (name database-type) #"\sUNSIGNED$" "")))) +(defmethod sql-jdbc.sync/column->semantic-type :mysql + [_ database-type _] + ;; More types to be added when we start caring about them + (case database-type + "JSON" :type/SerializedJSON + nil)) + (def ^:private default-connection-args "Map of args for the MySQL/MariaDB JDBC connection string." { ;; 0000-00-00 dates are valid in MySQL; convert these to `null` when they come back because they're illegal in Java diff --git a/src/metabase/driver/postgres.clj b/src/metabase/driver/postgres.clj index 6b2396db90c..f9245cf9d07 100644 --- a/src/metabase/driver/postgres.clj +++ b/src/metabase/driver/postgres.clj @@ -301,7 +301,8 @@ (pretty [_] (format "%s::%s" (pr-str expr) (name psql-type))))) -(defn- json-query [identifier nfc-field] +(defmethod sql.qp/json-query :postgres + [_ identifier nfc-field] (letfn [(handle-name [x] (if (number? x) (str x) (name x)))] (let [field-type (:database_type nfc-field) nfc-path (:nfc_path nfc-field) @@ -328,7 +329,7 @@ (field/json-field? stored-field) (if (::sql.qp/forced-alias opts) (keyword (::add/source-alias opts)) - (json-query identifier stored-field)) + (sql.qp/json-query :postgres identifier stored-field)) :else identifier))) diff --git a/src/metabase/driver/sql/query_processor.clj b/src/metabase/driver/sql/query_processor.clj index 10ce0ef31d2..59f49388a0c 100644 --- a/src/metabase/driver/sql/query_processor.clj +++ b/src/metabase/driver/sql/query_processor.clj @@ -233,6 +233,15 @@ [_ _ honeysql-form _] honeysql-form) +(defmulti json-query + "Reaches into a JSON field (that is, a field with a defined :nfc_path). + + Lots of SQL DB's have denormalized JSON fields and they all have some sort of special syntax for dealing with indexing into it. Implement the special syntax in this multimethod." + {:arglists '([driver identifier json-field]), :added "0.43.1"} + (fn [driver _ _] (driver/dispatch-on-initialized-driver driver)) + :hierarchy #'driver/hierarchy) + + ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | Low-Level ->honeysql impls | diff --git a/test/metabase/driver/mysql_test.clj b/test/metabase/driver/mysql_test.clj index 37a37b4bcdd..11ee4990478 100644 --- a/test/metabase/driver/mysql_test.clj +++ b/test/metabase/driver/mysql_test.clj @@ -9,6 +9,8 @@ [metabase.driver :as driver] [metabase.driver.mysql :as mysql] [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn] + [metabase.driver.sql-jdbc.sync :as sql-jdbc.sync] + [metabase.driver.sql.query-processor :as sql.qp] [metabase.models.database :refer [Database]] [metabase.models.field :refer [Field]] [metabase.models.table :refer [Table]] @@ -351,3 +353,125 @@ "Skipping %s because %s env var is not set" "mysql-connect-with-ssl-and-pem-cert-test" "MB_MYSQL_SSL_TEST_SSL_CERT"))))) + +;; MariaDB doesn't have support for explicit JSON columns, it does it in a more SQL Server-ish way +;; where LONGTEXT columns are the actual JSON columns and there's JSON functions that just work on them, +;; construed as text. +;; You could even have mixed JSON / non JSON columns... +;; Therefore, we can't just automatically get JSON columns in MariaDB. Therefore, no JSON support. +;; Therefore, no JSON tests. +(defn- version-query [db-id] {:type :native, :native {:query "SELECT VERSION();"}, :database db-id}) + +(defn- is-mariadb? [db-id] (str/includes? + (or (get-in (qp/process-userland-query (version-query db-id)) [:data :rows 0 0]) "") + "Maria")) + +(deftest nested-field-column-test + (mt/test-driver :mysql + (mt/dataset json + (when (not (is-mariadb? (u/id (mt/db)))) + (testing "Nested field column listing" + (is (= #{{:name "json_bit → 1234123412314", + :database-type "timestamp", + :base-type :type/DateTime, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "1234123412314"]} + {:name "json_bit → boop", + :database-type "timestamp", + :base-type :type/DateTime, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "boop"]} + {:name "json_bit → genres", + :database-type "text", + :base-type :type/Array, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "genres"]} + {:name "json_bit → 1234", + :database-type "integer", + :base-type :type/Integer, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "1234"]} + {:name "json_bit → doop", + :database-type "text", + :base-type :type/Text, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "doop"]} + {:name "json_bit → noop", + :database-type "timestamp", + :base-type :type/DateTime, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "noop"]} + {:name "json_bit → zoop", + :database-type "timestamp", + :base-type :type/DateTime, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "zoop"]} + {:name "json_bit → published", + :database-type "text", + :base-type :type/Text, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "published"]} + {:name "json_bit → title", + :database-type "text", + :base-type :type/Text, + :database-position 0, + :visibility-type :normal, + :nfc-path [:json_bit "title"]}} + (sql-jdbc.sync/describe-nested-field-columns + :mysql + (mt/db) + {:name "json"})))))))) + +(deftest big-nested-field-column-test + (mt/test-driver :mysql + (mt/dataset json + (when (not (is-mariadb? (u/id (mt/db)))) + (testing "Nested field column listing, but big" + (is (= #{} + (sql-jdbc.sync/describe-nested-field-columns + :mysql + (mt/db) + {:name "big_json"})))))))) + +(deftest json-query-test + (let [boop-identifier (hx/with-type-info (hx/identifier :field "boop" "bleh -> meh") {})] + (testing "Transforming MBQL query with JSON in it to mysql query works" + (let [boop-field {:nfc_path [:bleh :meh] :database_type "integer"}] + (is (= ["JSON_EXTRACT(boop.bleh, ?)" "$.\"meh\""] + (hsql/format (#'sql.qp/json-query :mysql boop-identifier boop-field)))))) + (testing "What if types are weird and we have lists" + (let [weird-field {:nfc_path [:bleh "meh" :foobar 1234] :database_type "integer"}] + (is (= ["JSON_EXTRACT(boop.bleh, ?)" "$.\"meh\".\"foobar\".\"1234\""] + (hsql/format (#'sql.qp/json-query :mysql boop-identifier weird-field)))))) + (testing "Doesn't complain when field is boolean" + (let [boolean-boop-field {:database_type "boolean" :nfc_path [:bleh "boop" :foobar 1234]}] + (is (= ["JSON_EXTRACT(boop.bleh, ?)" "$.\"boop\".\"foobar\".\"1234\""] + (hsql/format (#'sql.qp/json-query :mysql boop-identifier boolean-boop-field)))))))) + +(deftest json-alias-test + (mt/test-driver :mysql + (when (not (is-mariadb? (u/id (mt/db)))) + (testing "json breakouts and order bys have alias coercion" + (mt/dataset json + (let [table (db/select-one Table :db_id (u/id (mt/db)) :name "json")] + (sync/sync-table! table) + (let [field (db/select-one Field :table_id (u/id table) :name "json_bit → 1234") + compile-res (qp/compile + {:database (u/the-id (mt/db)) + :type :query + :query {:source-table (u/the-id table) + :aggregation [[:count]] + :breakout [[:field (u/the-id field) nil]]}})] + (is (= (str "SELECT JSON_EXTRACT(`json`.`json_bit`, ?) AS `json_bit → 1234`, " + "count(*) AS `count` FROM `json` GROUP BY JSON_EXTRACT(`json`.`json_bit`, ?) " + "ORDER BY JSON_EXTRACT(`json`.`json_bit`, ?) ASC") + (:query compile-res))) + (is (= '("$.\"1234\"" "$.\"1234\"" "$.\"1234\"") (:params compile-res)))))))))) diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj index fd7f79cca99..b692b586649 100644 --- a/test/metabase/driver/postgres_test.clj +++ b/test/metabase/driver/postgres_test.clj @@ -281,15 +281,15 @@ (testing "Transforming MBQL query with JSON in it to postgres query works" (let [boop-field {:nfc_path [:bleh :meh] :database_type "integer"}] (is (= ["(boop.bleh#>> ?::text[])::integer " "{meh}"] - (hsql/format (#'postgres/json-query boop-identifier boop-field)))))) + (hsql/format (#'sql.qp/json-query :postgres boop-identifier boop-field)))))) (testing "What if types are weird and we have lists" (let [weird-field {:nfc_path [:bleh "meh" :foobar 1234] :database_type "integer"}] (is (= ["(boop.bleh#>> ?::text[])::integer " "{meh,foobar,1234}"] - (hsql/format (#'postgres/json-query boop-identifier weird-field)))))) + (hsql/format (#'sql.qp/json-query :postgres boop-identifier weird-field)))))) (testing "Give us a boolean cast when the field is boolean" (let [boolean-boop-field {:database_type "boolean" :nfc_path [:bleh "boop" :foobar 1234]}] (is (= ["(boop.bleh#>> ?::text[])::boolean " "{boop,foobar,1234}"] - (hsql/format (#'postgres/json-query boop-identifier boolean-boop-field)))))))) + (hsql/format (#'sql.qp/json-query :postgres boop-identifier boolean-boop-field)))))))) (deftest json-alias-test (mt/test-driver :postgres diff --git a/test/metabase/test/data/dataset_definitions.clj b/test/metabase/test/data/dataset_definitions.clj index e045ef8eb71..8a34fab76bf 100644 --- a/test/metabase/test/data/dataset_definitions.clj +++ b/test/metabase/test/data/dataset_definitions.clj @@ -68,6 +68,9 @@ columns (i.e., `TIMESTAMP WITH TIME ZONE`) instead of `:type/DateType`, to make it easier to use this test data across multiple databases.") +(tx/defdataset-edn json + "Dataset with some JSON columns in it. Used to test JSON columns.") + (defn- date-only "Convert date or datetime temporal value to `t` to an appropriate date type, discarding time information." [t] diff --git a/test/metabase/test/data/dataset_definitions/json.edn b/test/metabase/test/data/dataset_definitions/json.edn new file mode 100644 index 00000000000..14eacf69440 --- /dev/null +++ b/test/metabase/test/data/dataset_definitions/json.edn @@ -0,0 +1,20 @@ + +[["json" + [{:field-name "bloop" + :base-type :type/Text} + {:field-name "json_bit" + :base-type :type/JSON + :semantic-type :type/SerializedJSON}] + [["doopyboop" "{\"boop\": \"2012-04-23T18:44:43.511Z\", \"title\": \"meep meep weep\", \"genres\": [\"Productivity\", \"Reference\"], \"published\": true}"] + ["moopywoop" "{\"zoop\": \"2012-04-23T18:44:43.511Z\", \"doop\": \"boop\", \"title\": \"meep meep weep\", \"genres\": [\"Productivity\", \"Reference\"], \"published\": true}"] + ["doopdoopy" "{\"1234123412314\": \"2012-04-23T18:44:43.511Z\", \"doop\": \"boop\", \"title\": \"meep meep weep\", \"genres\": [\"Productivity\", \"Reference\"], \"published\": true}"] + ["zoopyzoop" "{\"zoop\": \"2012-04-23T18:44:43.511Z\", \"doop\": \"boop\", \"title\": \"meep meep weep\", \"genres\": [\"Productivity\", \"Reference\"], \"1234\": 1234, \"published\": 1234}"] + ["woopywoop" "{\"noop\": \"2012-04-23T18:44:43.511Z\", \"doop\": \"boop\", \"title\": \"meep meep weep\", \"genres\": [\"Productivity\", \"Reference\"], \"published\": true}"]]] + ["big_json" + [{:field-name "bloop" + :base-type :type/Text} + {:field-name "json_bit" + :base-type :type/JSON + :semantic-type :type/SerializedJSON}] + [["woopywoop" + "{\"pay\":1829464693,\"blank\":{\"silence\":false,\"ocean\":true,\"dress\":1726015015,\"rate\":false,\"radio\":false,\"nor\":\"constantly\",\"aid\":\"property\",\"experience\":true,\"success\":true,\"interior\":1.4466014248940077E9,\"willing\":false,\"can\":3.3433528555526686E8,\"highest\":true,\"stopped\":\"exclaimed\",\"battle\":true,\"numeral\":false,\"involved\":\"pipe\",\"greater\":-3.5572626225210285E8,\"sit\":\"continued\",\"brave\":true,\"size\":-1516446390,\"paint\":1.9652678604175234E9,\"ourselves\":true,\"curve\":-682141267,\"dozen\":true,\"happily\":\"few\",\"hidden\":\"here\",\"rain\":1365707063,\"tears\":false,\"solar\":\"sister\",\"inch\":\"lay\",\"triangle\":\"desert\",\"mostly\":false,\"grow\":\"to\",\"equipment\":false,\"central\":false,\"selection\":\"end\",\"check\":true,\"blow\":\"into\",\"coast\":false,\"state\":583549949,\"melted\":true,\"pour\":-886031893,\"bright\":57030902,\"particularly\":true,\"especially\":1776321836,\"canal\":true,\"frequently\":false,\"character\":\"policeman\",\"dinner\":false,\"lack\":false,\"hold\":false,\"crop\":\"library\",\"brown\":-5.1526758349051094E8,\"horn\":\"ask\",\"it\":63552025,\"liquid\":1405394023,\"plain\":\"clay\",\"belt\":1.4084834922978644E9,\"floor\":606477246,\"climb\":\"younger\",\"want\":\"balance\",\"purple\":-1.241476660973824E9,\"basket\":572698510,\"thee\":\"sweet\",\"sun\":\"road\",\"paid\":-55861076,\"discussion\":false,\"round\":\"lunch\",\"southern\":-1447048188,\"church\":true,\"view\":\"comfortable\",\"parent\":\"spin\",\"vertical\":false,\"recently\":\"nearly\",\"nest\":false,\"simplest\":1086895390,\"hole\":-9.843196973796997E8,\"secret\":2.0385325339219956E9,\"establish\":\"raise\",\"nearby\":1.7049512327791004E9,\"swam\":\"leave\",\"cage\":false,\"mighty\":true,\"rope\":true,\"cookies\":\"couple\",\"visit\":true,\"your\":\"form\",\"exist\":-8.917500598032928E8,\"alike\":false,\"studied\":299735806,\"struggle\":-7.734959568802319E8,\"take\":true,\"daughter\":1.1494595910893664E9,\"whom\":1.8303523102387733E9,\"discuss\":\"outside\",\"calm\":-8.164911480286121E8,\"funny\":true,\"nuts\":\"neighbor\",\"during\":false,\"third\":-1.1160282345614305E9,\"somewhere\":\"escape\",\"agree\":\"ask\",\"some\":\"stock\",\"work\":false,\"real\":1965944881,\"nearest\":true,\"remarkable\":false,\"example\":-9.774120664323139E8,\"strong\":-1.5006229253362703E8,\"generally\":39868912,\"separate\":2124925055,\"door\":1158182209,\"frog\":\"modern\",\"bend\":\"plastic\",\"busy\":false,\"excellent\":\"opinion\",\"newspaper\":-1016037492,\"enjoy\":\"call\",\"gulf\":false,\"basic\":633166046,\"tight\":-6.672209656037045E8,\"heart\":-1.2400524931321864E9,\"green\":\"opinion\",\"my\":true,\"handle\":\"pet\",\"tool\":false,\"wrote\":\"percent\",\"spider\":-2.1331654242855597E9,\"pipe\":false,\"see\":\"ago\",\"record\":-1737935344,\"yet\":\"around\",\"tightly\":1783105308,\"solid\":true,\"gently\":\"happened\",\"neighborhood\":true,\"sat\":-1537131698,\"soldier\":\"post\",\"rapidly\":true,\"wing\":2.536875636054778E8,\"enough\":-234748204,\"climate\":-1.68219117880617E9,\"furniture\":155517172,\"fallen\":1536452352,\"snow\":true,\"definition\":-1.2530235304876246E9,\"lunch\":-1439453092,\"us\":1.142669552708558E9,\"national\":true,\"differ\":false,\"poet\":false,\"leader\":\"capital\",\"cost\":\"check\",\"way\":783844249,\"he\":\"fix\",\"dream\":\"save\",\"customs\":\"page\",\"minerals\":\"stock\",\"world\":\"island\",\"seat\":false,\"would\":-1.877201713216281E8,\"plate\":true,\"evening\":1.659527131089242E9,\"fun\":false,\"milk\":true,\"mile\":1861274176,\"shaking\":\"willing\",\"visitor\":-1884550539,\"danger\":\"highway\",\"she\":true,\"skill\":\"direction\",\"suggest\":-1596166022,\"teacher\":-1.1919878175802455E9,\"me\":-1.8737515583601594E9,\"water\":false,\"claws\":\"whole\",\"known\":\"ice\",\"century\":\"service\",\"signal\":\"audience\",\"sand\":2073744687,\"held\":true,\"forget\":false,\"any\":true,\"tried\":-1512673413,\"strip\":true,\"local\":\"block\",\"between\":\"has\",\"four\":\"forth\",\"shoot\":true,\"exercise\":false,\"sets\":\"principle\",\"carried\":1642729498,\"spell\":false,\"police\":false,\"her\":\"impossible\",\"everything\":-964188030,\"watch\":\"saddle\",\"log\":312538570,\"left\":false,\"action\":\"reach\",\"difference\":6.295659613969855E8,\"cook\":-7.706505883664899E8,\"weather\":\"moving\",\"level\":false,\"wave\":\"planned\",\"constantly\":false,\"title\":\"tonight\",\"river\":\"company\",\"large\":3.082383573927922E8,\"feel\":true,\"negative\":false,\"list\":false,\"east\":\"finest\",\"something\":\"action\",\"two\":\"center\",\"arrow\":\"entire\",\"feet\":true,\"six\":\"slabs\",\"tall\":\"original\",\"brief\":1001597647,\"beautiful\":1216968775,\"castle\":-1545265894,\"food\":-639799117,\"warn\":\"front\",\"cry\":-948630383,\"cup\":true,\"die\":4.8843249305655384E8,\"surrounded\":507415725,\"through\":\"buried\",\"such\":793139359,\"bag\":-849501968,\"wolf\":false,\"hide\":4.8098795325114155E8,\"everyone\":\"bent\",\"read\":-1.904078871738134E9,\"properly\":\"ago\",\"several\":-468974612,\"brass\":\"breathe\",\"itself\":\"lady\",\"travel\":\"car\",\"rocky\":false,\"sound\":\"especially\",\"farm\":-1.6089797417500873E9,\"occur\":5.763463718586736E8,\"development\":553874448,\"soft\":true,\"contain\":4.3986973208728075E8,\"flag\":false,\"possible\":false,\"enemy\":false,\"torn\":true,\"industry\":\"yet\",\"peace\":-1.277413173048685E9,\"longer\":\"pass\",\"hang\":\"well\",\"blank\":\"story\",\"straight\":false,\"syllable\":true,\"army\":-243292723,\"indeed\":1265437450,\"small\":1595637605,\"guide\":-1.289517612568349E9,\"protection\":-2058895516,\"form\":true,\"interest\":true,\"yard\":true,\"grandfather\":false,\"get\":false,\"practice\":\"eleven\",\"somebody\":true,\"asleep\":\"lot\",\"task\":\"choose\",\"stop\":\"that\",\"fierce\":true,\"husband\":4.5770789143212223E8,\"one\":false,\"support\":5.818418735259213E8,\"salmon\":false},\"seven\":false,\"heavy\":474053316,\"purpose\":1598679862,\"dress\":\"nation\",\"iron\":1.5580743346223989E9,\"remarkable\":\"separate\",\"silk\":-1346447809,\"rhyme\":false,\"method\":-643692155,\"fish\":\"simple\",\"area\":\"progress\",\"easily\":-1.3084675411700974E9,\"trace\":false,\"location\":1.235168182142147E9,\"spent\":1847588696,\"chamber\":\"fifty\",\"court\":true,\"sit\":\"mighty\",\"labor\":true,\"root\":\"own\",\"chapter\":false,\"feathers\":-1.5448892123713818E9,\"off\":\"twice\",\"settle\":1.3979389318716426E9,\"sail\":true,\"means\":\"poem\",\"invented\":false,\"broken\":-4.584760186030197E8,\"fallen\":\"everyone\",\"star\":false,\"cutting\":689443552,\"earn\":1.7941288138147035E9,\"sudden\":1.2640708320689063E9,\"work\":\"girl\",\"moon\":\"off\",\"ahead\":\"dust\",\"fighting\":1116938006,\"popular\":1.6713412137210078E9,\"picture\":\"finger\",\"pitch\":\"equator\",\"finally\":-1221725855,\"except\":false,\"discovery\":false,\"pale\":true,\"swung\":-7.030788891866021E8,\"thrown\":282082034,\"sink\":false,\"pleasure\":\"whenever\",\"slabs\":true,\"dark\":true,\"while\":true,\"sweet\":false,\"fill\":true,\"hand\":true,\"best\":true,\"signal\":\"morning\",\"middle\":1448812036,\"rise\":false,\"substance\":-2.0244826721532536E9,\"street\":\"glass\",\"balance\":\"just\",\"birthday\":384917419,\"guide\":\"begun\",\"problem\":\"cream\",\"relationship\":false,\"dollar\":false,\"feed\":561980159,\"future\":true,\"sheet\":\"warn\",\"coach\":true,\"soil\":83389400,\"gone\":true,\"pull\":\"theory\",\"wire\":\"sister\",\"more\":\"away\",\"curve\":\"flag\",\"imagine\":\"was\",\"system\":\"bad\",\"couple\":1.5711838434325132E9,\"report\":-1600433556,\"unless\":\"driver\",\"stream\":\"pipe\",\"parts\":-1.812106248320609E9,\"wish\":-2125049800,\"men\":-193073503,\"everybody\":\"total\",\"foot\":false,\"private\":-1009319174,\"however\":false,\"why\":false,\"bright\":-1200947370,\"bound\":\"least\",\"product\":-285014648,\"car\":false,\"greatly\":\"long\",\"flower\":-865735202,\"complex\":-2.0742375310099578E9,\"meet\":false,\"equator\":-3.767687378429308E8,\"bad\":-608540443,\"else\":\"storm\",\"stage\":\"dollar\",\"animal\":true,\"tobacco\":659205087,\"blanket\":false,\"early\":1458012197,\"mostly\":false,\"lucky\":true,\"canal\":\"powder\",\"whistle\":true,\"gain\":\"excited\",\"everywhere\":\"your\",\"volume\":\"excitement\",\"close\":1.6470387007110162E9,\"central\":-7.849623009816828E8,\"it\":-3.8581273108225155E8,\"task\":true,\"south\":-1179392224,\"since\":\"horse\",\"hang\":false,\"duty\":false,\"count\":false,\"effect\":9.87737876262661E8,\"snow\":-1305087471,\"discover\":true,\"thick\":\"principle\",\"energy\":true,\"so\":-239356246,\"sure\":-2.0087199485674186E9,\"eat\":\"feet\",\"that\":\"driven\",\"earth\":\"broad\",\"motion\":false,\"factor\":true,\"heading\":1.89788743093585E8,\"realize\":true,\"drawn\":\"electricity\",\"mad\":true,\"send\":\"radio\",\"fall\":true,\"soldier\":992524270,\"breathe\":true,\"tears\":true,\"oldest\":true,\"mother\":true,\"balloon\":\"driven\",\"exclaimed\":1820323437,\"making\":-1.69672294658603E9,\"bicycle\":-757913608,\"union\":false,\"magnet\":-999254446,\"inch\":\"wise\",\"education\":\"amount\",\"swept\":\"national\",\"journey\":687841974,\"organized\":\"said\",\"first\":\"lovely\",\"conversation\":4.892243501278925E7,\"key\":\"characteristic\",\"parent\":\"pig\",\"original\":false,\"rush\":true,\"obtain\":false,\"ship\":true,\"suit\":\"dinner\",\"everyone\":\"wire\",\"near\":\"announced\",\"citizen\":\"grandmother\",\"order\":-547589682,\"only\":-1.7963266791879425E9,\"above\":false,\"garage\":-1563957216,\"lay\":1.660907177355288E9,\"industrial\":false,\"nearest\":1728469290,\"constantly\":false,\"manner\":564155071,\"basket\":1114815821,\"place\":false,\"given\":2.7756577310441017E7,\"kind\":700451063,\"welcome\":\"together\",\"slightly\":\"sides\",\"will\":\"physical\",\"valley\":7.775478980729909E8,\"here\":false,\"dirt\":\"jet\",\"one\":\"poem\",\"pain\":\"affect\",\"action\":1476959100,\"mainly\":\"has\",\"attempt\":-2083690419,\"blood\":-230565221,\"army\":false,\"indicate\":-2.654658023633604E8,\"shape\":true,\"event\":false,\"happen\":\"noise\",\"image\":false,\"suddenly\":\"mix\",\"particularly\":true,\"remember\":1.8835864813066344E9,\"hardly\":-1550075069,\"saved\":1.8759134288312669E9,\"hidden\":362501093,\"sold\":1028775577,\"engine\":-144975658,\"little\":1.8194242552887187E9,\"chicken\":\"nose\",\"yellow\":-394212170,\"list\":315196077,\"sun\":true,\"you\":true,\"tea\":false,\"frozen\":true,\"shake\":false,\"strange\":true,\"percent\":\"whose\",\"after\":true,\"spoken\":1781609523,\"pot\":-1.9028409490493884E9,\"tongue\":false,\"try\":-2.0576021682942119E9,\"cloth\":-1.5190924874309402E9,\"describe\":\"atmosphere\",\"clothing\":false,\"pictured\":false,\"vessels\":1.5308410412665882E9,\"shadow\":true,\"own\":-661123701,\"fast\":false,\"powder\":\"tape\",\"stood\":4.6667040573806334E8,\"round\":\"compare\",\"instance\":\"buried\",\"available\":true,\"previous\":false,\"film\":\"soldier\",\"plastic\":\"lonely\",\"shoe\":-2131380880,\"doubt\":\"glass\",\"kill\":true,\"struck\":\"copy\",\"breakfast\":2107894137,\"ancient\":true,\"several\":-1991027.658285141,\"does\":\"field\",\"individual\":\"foreign\",\"dry\":\"exclaimed\",\"lower\":false,\"face\":1.716642722164609E9,\"moving\":false,\"level\":true,\"sound\":1417518683,\"along\":\"active\",\"alone\":\"cutting\",\"nearer\":3.761239189345498E8,\"lion\":1.0646221107017965E9,\"excitement\":false,\"generally\":true,\"shaking\":\"repeat\",\"fewer\":-2.0542761167506304E9,\"outline\":\"join\",\"increase\":true,\"year\":\"rough\",\"feature\":\"industry\",\"managed\":\"case\",\"shorter\":1.2789265672287965E9,\"flew\":\"happened\",\"arrangement\":-1815302822,\"special\":-1.5080930645624113E8,\"during\":-7.182568349023013E8,\"spell\":true,\"differ\":117289469,\"add\":false,\"larger\":false,\"excited\":1.93939199165763E9,\"title\":false}"]]]] diff --git a/test/metabase/test/data/mysql.clj b/test/metabase/test/data/mysql.clj index 118d694c7c1..9c57ed01df9 100644 --- a/test/metabase/test/data/mysql.clj +++ b/test/metabase/test/data/mysql.clj @@ -22,6 +22,7 @@ :type/Decimal "DECIMAL" :type/Float "DOUBLE" :type/Integer "INTEGER" + :type/JSON "JSON" :type/Text "TEXT" :type/Time "TIME(3)"}] (defmethod sql.tx/field-base-type->sql-type [:mysql base-type] [_ _] database-type)) -- GitLab