diff --git a/.circleci/config.yml b/.circleci/config.yml
index dba1264c59e45de4112aeb5c676ed29b60a7e69c..915d1bd9ab271672d83969180041cfce90b39052 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -134,7 +134,7 @@ executors:
        - image: metabase/ci:lein-2.9.5-clojure-1.10.3.814
        - image: circleci/mongo:4.0
 
-  presto:
+  presto-186:
     working_directory: /home/circleci/metabase/metabase/
     docker:
       - image: metabase/ci:lein-2.9.5-clojure-1.10.3.814
@@ -145,6 +145,25 @@ executors:
     # OOM sometimes with the default medium size.
     resource_class: large
 
+  presto-jdbc-env:
+    working_directory: /home/circleci/metabase/metabase/
+    docker:
+      - image: metabase/ci:lein-2.9.5-clojure-1.10.3.814
+      - image: metabase/presto-mb-ci:latest # version 0.254
+        environment:
+          JAVA_TOOL_OPTIONS: "-Xmx2g"
+          MB_PRESTO_JDBC_TEST_CATALOG: test_data
+          MB_PRESTO_JDBC_TEST_HOST: localhost
+          MB_PRESTO_JDBC_TEST_PORT: 8443
+          MB_PRESTO_JDBC_TEST_SSL: true
+          MB_PRESTO_JDBC_TEST_USER: metabase
+          MB_PRESTO_JDBC_TEST_PASSWORD: metabase
+          MB_ENABLE_PRESTO_JDBC_DRIVER: true
+          MB_PRESTO_JDBC_TEST_ADDITIONAL_OPTIONS: >
+            SSLTrustStorePath=/tmp/cacerts-with-presto-ssl.jks&SSLTrustStorePassword=changeit
+    # (see above)
+    resource_class: large
+
   sparksql:
     working_directory: /home/circleci/metabase/metabase/
     docker:
@@ -487,6 +506,15 @@ commands:
             wget --output-document=plugins/<< parameters.dest >> ${<< parameters.source >>}
           no_output_timeout: 15m
 
+  run-command:
+    parameters:
+      command:
+        type: string
+    steps:
+      - run:
+          name: Run command
+          command: << parameters.command >>
+
 jobs:
 
 ########################################################################################################################
@@ -652,6 +680,9 @@ jobs:
       before-steps:
         type: steps
         default: []
+      after-steps:
+        type: steps
+        default: []
       description:
         type: string
         default: ""
@@ -675,6 +706,7 @@ jobs:
                 no_output_timeout: << parameters.timeout >>
             - store_test_results:
                 path: /home/circleci/metabase/metabase/target/junit
+            - steps: << parameters.after-steps >>
 
   test-build-scripts:
     executor: clojure-and-node
@@ -793,7 +825,9 @@ jobs:
                   - /home/circleci/metabase/metabase/modules/drivers/googleanalytics/target
                   - /home/circleci/metabase/metabase/modules/drivers/mongo/target
                   - /home/circleci/metabase/metabase/modules/drivers/oracle/target
+                  - /home/circleci/metabase/metabase/modules/drivers/presto-common/target
                   - /home/circleci/metabase/metabase/modules/drivers/presto/target
+                  - /home/circleci/metabase/metabase/modules/drivers/presto-jdbc/target
                   - /home/circleci/metabase/metabase/modules/drivers/redshift/target
                   - /home/circleci/metabase/metabase/modules/drivers/snowflake/target
                   - /home/circleci/metabase/metabase/modules/drivers/sparksql/target
@@ -1093,12 +1127,48 @@ workflows:
           name: be-tests-presto-ee
           requires:
             - be-tests-ee
-          e: presto
+          e: presto-186
           before-steps:
             - wait-for-port:
                 port: 8080
           driver: presto
 
+      - test-driver:
+          name: be-tests-presto-jdbc-ee
+          requires:
+            - be-tests-ee
+          e: presto-jdbc-env # specific env for running Presto JDBC tests (newer Presto version, SSL, etc.)
+          before-steps:
+            - wait-for-port:
+                port: 8443
+            - run:
+                name: Create temp cacerts file based on bundled JDK one
+                command: cp $JAVA_HOME/lib/security/cacerts /tmp/cacerts-with-presto-ssl.jks
+            - run:
+                name: Install openssl
+                command: apk add openssl
+            - run:
+                name: Capture Presto server self signed CA
+                command: |
+                  while [[ ! -s /tmp/presto-ssl-ca.pem ]];
+                    do echo "Waiting to capture SSL CA" \
+                      && openssl s_client -connect localhost:8443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /tmp/presto-ssl-ca.pem \
+                      && sleep 1; done
+            - run:
+                name: Convert Presto CA from PEM to DER
+                command: openssl x509 -outform der -in /tmp/presto-ssl-ca.pem -out /tmp/presto-ssl-ca.der
+            - run:
+                name: Import Presto CA into temp cacerts file
+                command: |
+                  keytool -noprompt -import -alias presto -keystore /tmp/cacerts-with-presto-ssl.jks \
+                   -storepass changeit -file /tmp/presto-ssl-ca.der -trustcacerts
+          after-steps:
+            - run:
+                name: Capture max memory usage
+                command: cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes
+                when: always
+          driver: presto-jdbc
+
       - test-driver:
           name: be-tests-redshift-ee
           requires:
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj
index f3b223cc40fc3c3229b371c65d339c0b0c661107..088d893eee11c5d326fdc61048ac2c81c9598b12 100644
--- a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj
@@ -107,7 +107,7 @@
   {:query (mt/native-query
             {:query
              (format-honeysql
-              {:select   [(identifier :venues :name)]
+              {:select   [(identifier :venues :id) (identifier :venues :name)]
                :from     [(identifier :venues)]
                :order-by [(identifier :venues :id)]})})})
 
@@ -271,10 +271,11 @@
                (run-venues-count-query)))))
 
     (testing "Make sure that you can still use a SQL-based GTAP without needing to have SQL read perms for the Database"
-      (is (= [["Red Medicine"] ["Stout Burgers & Beers"]]
-             (mt/rows
+      (is (= [[1 "Red Medicine"]
+              [2 "Stout Burgers & Beers"]]
+             (mt/formatted-rows [int str]
                (mt/with-gtaps {:gtaps {:venues (venue-names-native-gtap-def)}}
-                 (mt/run-mbql-query venues {:limit 2}))))))
+                 (mt/run-mbql-query venues {:limit 2, :order-by [[:asc [:field (mt/id :venues :id)]]]}))))))
 
     (testing (str "When no card_id is included in the GTAP, should default to a query against the table, with the GTAP "
                   "criteria applied")
@@ -327,10 +328,12 @@
 
 (defn- row-level-restrictions-fk-drivers
   "Drivers to test row-level restrictions against foreign keys with. Includes BigQuery, which for whatever reason does
-  not normally have FK tests ran for it."
+  not normally have FK tests ran for it. Excludes Presto JDBC, because that driver does NOT support fetching foreign
+  keys from the JDBC metadata, even though we enable the feature in the UI."
   []
   (cond-> (mt/normal-drivers-with-feature :nested-queries :foreign-keys)
-    (@tx.env/test-drivers :bigquery) (conj :bigquery)))
+    (@tx.env/test-drivers :bigquery) (conj :bigquery)
+    true                             (disj :presto-jdbc)))
 
 (deftest e2e-fks-test
   (mt/test-drivers (row-level-restrictions-fk-drivers)
@@ -914,8 +917,13 @@
                          (mt/rows (mt/run-mbql-query orders {:limit 1})))))))))))))
 
 (deftest pivot-query-test
-  ;; sample-dataset doesn't work on Redshift yet -- see #14784
-  (mt/test-drivers (disj (mt/normal-drivers-with-feature :foreign-keys :nested-queries :left-join) :redshift)
+  (mt/test-drivers (disj
+                     (mt/normal-drivers-with-feature :foreign-keys :nested-queries :left-join)
+                     ;; sample-dataset doesn't work on Redshift yet -- see #14784
+                     :redshift
+                     ;; this test relies on a FK relation between $product_id->products.category, so skip for Presto
+                     ;; JDBC, because that driver doesn't support resolving FKs from the JDBC metadata
+                     :presto-jdbc)
     (testing "Pivot table queries should work with sandboxed users (#14969)"
       (mt/dataset sample-dataset
         (mt/with-gtaps {:gtaps      (mt/$ids
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/load_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/load_test.clj
index fa14d3c385075ef3bd10e9353779f4e495fce104..8b7234ed735e504002668988f70d85996b2be95b 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/load_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/load_test.clj
@@ -265,7 +265,7 @@
     ;; in case it already exists
     (u/ignore-exceptions
       (delete-directory! dump-dir))
-    (mt/test-drivers (-> (mt/normal-drivers-with-feature :basic-aggregations :binning :expressions)
+    (mt/test-drivers (-> (mt/normal-drivers-with-feature :basic-aggregations :binning :expressions :foreign-keys)
                          ;; We will run this roundtrip test against any database supporting these features ^ except
                          ;; certain ones for specific reasons, outlined below.
                          ;;
diff --git a/modules/drivers/presto-common/project.clj b/modules/drivers/presto-common/project.clj
new file mode 100644
index 0000000000000000000000000000000000000000..6fd0ab15bb36b3cc832085e3bcf6576c5a454746
--- /dev/null
+++ b/modules/drivers/presto-common/project.clj
@@ -0,0 +1,24 @@
+(defproject metabase/presto-common-driver "1.0.0-SNAPSHOT"
+  :min-lein-version "2.5.0"
+
+  :description "Common code for all Presto drivers. Defines HoneySQL behavior, query generation, etc."
+
+  :aliases
+  {"install-for-building-drivers" ["with-profile" "+install-for-building-drivers" "install"]}
+
+  :profiles
+  {:provided
+   {:dependencies
+    [[org.clojure/clojure "1.10.1"]
+     [metabase-core "1.0.0-SNAPSHOT"]]}
+
+   :install-for-building-drivers
+   {:auto-clean true
+    :aot        :all}
+
+   :uberjar
+   {:auto-clean    true
+    :aot           :all
+    :javac-options ["-target" "1.8", "-source" "1.8"]
+    :target-path   "target/%s"
+    :uberjar-name  "presto-common.metabase-driver.jar"}})
diff --git a/modules/drivers/presto-common/resources/metabase-plugin.yaml b/modules/drivers/presto-common/resources/metabase-plugin.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..9d6c8c96c48c5e70e6473dca4171646403880ae0
--- /dev/null
+++ b/modules/drivers/presto-common/resources/metabase-plugin.yaml
@@ -0,0 +1,11 @@
+info:
+  name: Presto Common Driver
+  version: 1.0.0-SNAPSHOT
+  description: Common code for all Presto drivers. Defines HoneySQL behavior, query generation, etc.
+driver:
+  name: presto-common
+  abstract: true
+  lazy-load: true
+init:
+  - step: load-namespace
+    namespace: metabase.driver.presto-common
diff --git a/modules/drivers/presto-common/src/metabase/driver/presto_common.clj b/modules/drivers/presto-common/src/metabase/driver/presto_common.clj
new file mode 100644
index 0000000000000000000000000000000000000000..498a424e3691770187a0bd2df7a601e021bb5152
--- /dev/null
+++ b/modules/drivers/presto-common/src/metabase/driver/presto_common.clj
@@ -0,0 +1,212 @@
+(ns metabase.driver.presto-common
+  "Abstract common driver for Presto. It only defines SQL generation logic and doesn't involve the transport/execution
+  mechanism for actually connecting to Presto."
+  (:require [buddy.core.codecs :as codecs]
+            [honeysql.core :as hsql]
+            [honeysql.format :as hformat]
+            [honeysql.helpers :as h]
+            [java-time :as t]
+            [metabase.driver :as driver]
+            [metabase.driver.common :as driver.common]
+            [metabase.driver.sql-jdbc.sync :as sql-jdbc.sync]
+            [metabase.driver.sql.query-processor :as sql.qp]
+            [metabase.driver.sql.util :as sql.u]
+            [metabase.driver.sql.util.unprepare :as unprepare]
+            [metabase.util :as u]
+            [metabase.util.date-2 :as u.date]
+            [metabase.util.honeysql-extensions :as hx])
+  (:import java.sql.Time
+           [java.time OffsetDateTime ZonedDateTime]))
+
+(driver/register! :presto-common, :abstract? true, :parent :sql)
+
+;;; Presto API helpers
+
+(def presto-type->base-type
+  "Function that returns a `base-type` for the given `presto-type` (can be a keyword or string)."
+  (sql-jdbc.sync/pattern-based-database-type->base-type
+    [[#"(?i)boolean"                    :type/Boolean]
+     [#"(?i)tinyint"                    :type/Integer]
+     [#"(?i)smallint"                   :type/Integer]
+     [#"(?i)integer"                    :type/Integer]
+     [#"(?i)bigint"                     :type/BigInteger]
+     [#"(?i)real"                       :type/Float]
+     [#"(?i)double"                     :type/Float]
+     [#"(?i)decimal.*"                  :type/Decimal]
+     [#"(?i)varchar.*"                  :type/Text]
+     [#"(?i)char.*"                     :type/Text]
+     [#"(?i)varbinary.*"                :type/*]
+     [#"(?i)json"                       :type/Text] ; TODO - this should probably be Dictionary or something
+     [#"(?i)date"                       :type/Date]
+     [#"(?i)^timestamp$"                :type/DateTime]
+     [#"(?i)^timestamp with time zone$" :type/DateTimeWithTZ]
+     [#"(?i)^time$"                     :type/Time]
+     [#"(?i)^time with time zone$"      :type/TimeWithTZ]
+     #_[#"(?i)time.+"                     :type/DateTime] ; TODO - get rid of this one?
+     [#"(?i)array"                      :type/Array]
+     [#"(?i)map"                        :type/Dictionary]
+     [#"(?i)row.*"                      :type/*] ; TODO - again, but this time we supposedly have a schema
+     [#".*"                             :type/*]]))
+
+(defmethod sql.qp/add-interval-honeysql-form :presto-common
+  [_ hsql-form amount unit]
+  (hsql/call :date_add (hx/literal unit) amount hsql-form))
+
+(defn describe-catalog-sql
+  "The SHOW SCHEMAS statement that will list all schemas for the given `catalog`."
+  {:added "0.39.0"}
+  [driver catalog]
+  (str "SHOW SCHEMAS FROM " (sql.u/quote-name driver :database catalog)))
+
+(defn describe-schema-sql
+  "The SHOW TABLES statement that will list all tables for the given `catalog` and `schema`."
+  {:added "0.39.0"}
+  [driver catalog schema]
+  (str "SHOW TABLES FROM " (sql.u/quote-name driver :schema catalog schema)))
+
+(defn describe-table-sql
+  "The DESCRIBE  statement that will list information about the given `table`, in the given `catalog` and schema`."
+  {:added "0.39.0"}
+  [driver catalog schema table]
+  (str "DESCRIBE " (sql.u/quote-name driver :table catalog schema table)))
+
+(def excluded-schemas
+  "The set of schemas that should be excluded when querying all schemas."
+  #{"information_schema"})
+
+(defmethod driver/db-start-of-week :presto-common
+  [_]
+  :monday)
+
+(defmethod sql.qp/cast-temporal-string [:presto-common :Coercion/YYYYMMDDHHMMSSString->Temporal]
+  [_ _coercion-strategy expr]
+  (hsql/call :date_parse expr (hx/literal "%Y%m%d%H%i%s")))
+
+(defmethod sql.qp/cast-temporal-byte [:presto-common :Coercion/YYYYMMDDHHMMSSBytes->Temporal]
+  [driver _coercion-strategy expr]
+  (sql.qp/cast-temporal-string driver :Coercion/YYYYMMDDHHMMSSString->Temporal
+    (hsql/call :from_utf8 expr)))
+
+(defmethod sql.qp/->honeysql [:presto-common Boolean]
+  [_ bool]
+  (hsql/raw (if bool "TRUE" "FALSE")))
+
+(defmethod sql.qp/->honeysql [:presto-common :time]
+  [_ [_ t]]
+  (hx/cast :time (u.date/format-sql (t/local-time t))))
+
+(defmethod sql.qp/->float :presto-common
+  [_ value]
+  (hx/cast :double value))
+
+(defmethod sql.qp/->honeysql [:presto-common :regex-match-first]
+  [driver [_ arg pattern]]
+  (hsql/call :regexp_extract (sql.qp/->honeysql driver arg) (sql.qp/->honeysql driver pattern)))
+
+(defmethod sql.qp/->honeysql [:presto-common :median]
+  [driver [_ arg]]
+  (hsql/call :approx_percentile (sql.qp/->honeysql driver arg) 0.5))
+
+(defmethod sql.qp/->honeysql [:presto-common :percentile]
+  [driver [_ arg p]]
+  (hsql/call :approx_percentile (sql.qp/->honeysql driver arg) (sql.qp/->honeysql driver p)))
+
+;; Presto mod is a function like mod(x, y) rather than an operator like x mod y
+(defmethod hformat/fn-handler (u/qualified-name ::mod)
+  [_ x y]
+  (format "mod(%s, %s)" (hformat/to-sql x) (hformat/to-sql y)))
+
+(def ^:dynamic *param-splice-style*
+  "How we should splice params into SQL (i.e. 'unprepare' the SQL). Either `:friendly` (the default) or `:paranoid`.
+  `:friendly` makes a best-effort attempt to escape strings and generate SQL that is nice to look at, but should not
+  be considered safe against all SQL injection -- use this for 'convert to SQL' functionality. `:paranoid` hex-encodes
+  strings so SQL injection is impossible; this isn't nice to look at, so use this for actually running a query."
+  :friendly)
+
+(defmethod unprepare/unprepare-value [:presto-common String]
+  [_ ^String s]
+  (case *param-splice-style*
+    :friendly (str \' (sql.u/escape-sql s :ansi) \')
+    :paranoid (format "from_utf8(from_hex('%s'))" (codecs/bytes->hex (.getBytes s "UTF-8")))))
+
+;; See https://prestodb.io/docs/current/functions/datetime.html
+
+;; This is only needed for test purposes, because some of the sample data still uses legacy types
+(defmethod unprepare/unprepare-value [:presto-common Time]
+  [driver t]
+  (unprepare/unprepare-value driver (t/local-time t)))
+
+(defmethod unprepare/unprepare-value [:presto-common OffsetDateTime]
+  [_ t]
+  (format "timestamp '%s %s %s'" (t/local-date t) (t/local-time t) (t/zone-offset t)))
+
+(defmethod unprepare/unprepare-value [:presto-common ZonedDateTime]
+  [_ t]
+  (format "timestamp '%s %s %s'" (t/local-date t) (t/local-time t) (t/zone-id t)))
+
+;;; `:sql-driver` methods
+
+(defmethod sql.qp/apply-top-level-clause [:presto-common :page]
+  [_ _ honeysql-query {{:keys [items page]} :page}]
+  (let [offset (* (dec page) items)]
+    (if (zero? offset)
+      ;; if there's no offset we can simply use limit
+      (h/limit honeysql-query items)
+      ;; if we need to do an offset we have to do nesting to generate a row number and where on that
+      (let [over-clause (format "row_number() OVER (%s)"
+                                (first (hsql/format (select-keys honeysql-query [:order-by])
+                                                    :allow-dashed-names? true
+                                                    :quoting :ansi)))]
+        (-> (apply h/select (map last (:select honeysql-query)))
+            (h/from (h/merge-select honeysql-query [(hsql/raw over-clause) :__rownum__]))
+            (h/where [:> :__rownum__ offset])
+            (h/limit items))))))
+
+(defmethod sql.qp/date [:presto-common :default]         [_ _ expr] expr)
+(defmethod sql.qp/date [:presto-common :minute]          [_ _ expr] (hsql/call :date_trunc (hx/literal :minute) expr))
+(defmethod sql.qp/date [:presto-common :minute-of-hour]  [_ _ expr] (hsql/call :minute expr))
+(defmethod sql.qp/date [:presto-common :hour]            [_ _ expr] (hsql/call :date_trunc (hx/literal :hour) expr))
+(defmethod sql.qp/date [:presto-common :hour-of-day]     [_ _ expr] (hsql/call :hour expr))
+(defmethod sql.qp/date [:presto-common :day]             [_ _ expr] (hsql/call :date_trunc (hx/literal :day) expr))
+(defmethod sql.qp/date [:presto-common :day-of-month]    [_ _ expr] (hsql/call :day expr))
+(defmethod sql.qp/date [:presto-common :day-of-year]     [_ _ expr] (hsql/call :day_of_year expr))
+
+(defmethod sql.qp/date [:presto-common :day-of-week]
+  [driver _ expr]
+  (sql.qp/adjust-day-of-week driver (hsql/call :day_of_week expr)))
+
+(defmethod sql.qp/date [:presto-common :week]
+  [driver _ expr]
+  (sql.qp/adjust-start-of-week driver (partial hsql/call :date_trunc (hx/literal :week)) expr))
+
+(defmethod sql.qp/date [:presto-common :month]           [_ _ expr] (hsql/call :date_trunc (hx/literal :month) expr))
+(defmethod sql.qp/date [:presto-common :month-of-year]   [_ _ expr] (hsql/call :month expr))
+(defmethod sql.qp/date [:presto-common :quarter]         [_ _ expr] (hsql/call :date_trunc (hx/literal :quarter) expr))
+(defmethod sql.qp/date [:presto-common :quarter-of-year] [_ _ expr] (hsql/call :quarter expr))
+(defmethod sql.qp/date [:presto-common :year]            [_ _ expr] (hsql/call :date_trunc (hx/literal :year) expr))
+
+(defmethod sql.qp/unix-timestamp->honeysql [:presto-common :seconds]
+  [_ _ expr]
+  (hsql/call :from_unixtime expr))
+
+(defmethod driver.common/current-db-time-date-formatters :presto-common
+  [_]
+  (driver.common/create-db-time-formatters "yyyy-MM-dd'T'HH:mm:ss.SSSZ"))
+
+(defmethod driver.common/current-db-time-native-query :presto-common
+  [_]
+  "select to_iso8601(current_timestamp)")
+
+(defmethod driver/current-db-time :presto-common
+  [& args]
+  (apply driver.common/current-db-time args))
+
+(doseq [[feature supported?] {:set-timezone                    true
+                              :basic-aggregations              true
+                              :standard-deviation-aggregations true
+                              :expressions                     true
+                              :native-parameters               true
+                              :expression-aggregations         true
+                              :binning                         true
+                              :foreign-keys                    true}]
+  (defmethod driver/supports? [:presto-common feature] [_ _] supported?))
diff --git a/modules/drivers/presto-common/test/metabase/test/data/presto_common.clj b/modules/drivers/presto-common/test/metabase/test/data/presto_common.clj
new file mode 100644
index 0000000000000000000000000000000000000000..182d3278046cef7b35bfdc68b8395663ee1a2673
--- /dev/null
+++ b/modules/drivers/presto-common/test/metabase/test/data/presto_common.clj
@@ -0,0 +1,15 @@
+(ns metabase.test.data.presto-common
+  "Common functionality for handling test datasets in Presto."
+  (:require [metabase.test.data.interface :as tx]))
+
+(defmethod tx/aggregate-column-info :presto-common
+  ([driver ag-type]
+   ((get-method tx/aggregate-column-info ::tx/test-extensions) driver ag-type))
+
+  ([driver ag-type field]
+   (merge
+     ((get-method tx/aggregate-column-info ::tx/test-extensions) driver ag-type field)
+     (when (= ag-type :sum)
+       {:base_type :type/BigInteger}))))
+
+(prefer-method tx/aggregate-column-info :presto-common ::tx/test-extensions)
diff --git a/modules/drivers/presto-jdbc/parents b/modules/drivers/presto-jdbc/parents
new file mode 100644
index 0000000000000000000000000000000000000000..1b4a8dec8e88c0a560332d2c956a0644eae955b0
--- /dev/null
+++ b/modules/drivers/presto-jdbc/parents
@@ -0,0 +1 @@
+presto-common
diff --git a/modules/drivers/presto-jdbc/project.clj b/modules/drivers/presto-jdbc/project.clj
new file mode 100644
index 0000000000000000000000000000000000000000..9bd5b358a66a1c307d403e927e91f108f67a1ef2
--- /dev/null
+++ b/modules/drivers/presto-jdbc/project.clj
@@ -0,0 +1,19 @@
+(defproject metabase/presto-jdbc-driver "1.0.0-0.254-SNAPSHOT"
+  :min-lein-version "2.5.0"
+
+  :dependencies
+  [[com.facebook.presto/presto-jdbc "0.254"]]
+
+  :profiles
+  {:provided
+   {:dependencies
+    [[org.clojure/clojure "1.10.1"]
+     [metabase-core "1.0.0-SNAPSHOT"]
+     [metabase/presto-common-driver "1.0.0-SNAPSHOT"]]}
+
+   :uberjar
+   {:auto-clean    true
+    :aot           :all
+    :javac-options ["-target" "1.8", "-source" "1.8"]
+    :target-path   "target/%s"
+    :uberjar-name  "presto-jdbc.metabase-driver.jar"}})
diff --git a/modules/drivers/presto-jdbc/resources/metabase-plugin.yaml b/modules/drivers/presto-jdbc/resources/metabase-plugin.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..973edfb617e3d041edf3188841f6b843fad644d4
--- /dev/null
+++ b/modules/drivers/presto-jdbc/resources/metabase-plugin.yaml
@@ -0,0 +1,37 @@
+info:
+  name: Metabase Presto JDBC Driver
+  version: 1.0.0-350-SNAPSHOT
+  description: Allows Metabase to connect to Presto databases using the Presto JDBC driver
+dependencies:
+  - plugin: Presto Common Driver
+driver:
+  name: presto-jdbc
+  display-name: Presto
+  lazy-load: true
+  parent: presto-common
+  connection-properties:
+    - host
+    - merge:
+        - port
+        - default: 8080
+    - merge:
+        - dbname
+        - name: catalog
+          placeholder: hive
+          required: false
+    - name: schema
+      required: false
+    - user
+    - password
+    - ssl
+    - merge:
+        - additional-options
+        - placeholder: "trustServerCertificate=false"
+init:
+  - step: load-namespace
+    namespace: metabase.driver.presto-common
+  - step: load-namespace
+    namespace: metabase.driver.presto-jdbc
+  - step: register-jdbc-driver
+    class: com.facebook.presto.jdbc.PrestoDriver
+
diff --git a/modules/drivers/presto-jdbc/src/metabase/driver/presto_jdbc.clj b/modules/drivers/presto-jdbc/src/metabase/driver/presto_jdbc.clj
new file mode 100644
index 0000000000000000000000000000000000000000..3a9cd285c197d49bc6c08c5bc5eb0f47b38ac94f
--- /dev/null
+++ b/modules/drivers/presto-jdbc/src/metabase/driver/presto_jdbc.clj
@@ -0,0 +1,477 @@
+(ns metabase.driver.presto-jdbc
+  "Presto JDBC driver. See https://prestodb.io/docs/current/ for complete dox."
+  (:require [clojure.java.jdbc :as jdbc]
+            [clojure.set :as set]
+            [clojure.string :as str]
+            [clojure.tools.logging :as log]
+            [honeysql.core :as hsql]
+            [honeysql.format :as hformat]
+            [java-time :as t]
+            [metabase.db.spec :as db.spec]
+            [metabase.driver :as driver]
+            [metabase.driver.presto-common :as presto-common]
+            [metabase.driver.sql-jdbc.common :as sql-jdbc.common]
+            [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn]
+            [metabase.driver.sql-jdbc.execute :as sql-jdbc.execute]
+            [metabase.driver.sql-jdbc.execute.legacy-impl :as legacy]
+            [metabase.driver.sql-jdbc.sync :as sql-jdbc.sync]
+            [metabase.driver.sql-jdbc.sync.describe-database :as sql-jdbc.describe-database]
+            [metabase.driver.sql.parameters.substitution :as sql.params.substitution]
+            [metabase.driver.sql.query-processor :as sql.qp]
+            [metabase.query-processor.timezone :as qp.timezone]
+            [metabase.util :as u]
+            [metabase.util.date-2 :as u.date]
+            [metabase.util.honeysql-extensions :as hx]
+            [metabase.util.i18n :refer [trs]])
+  (:import com.facebook.presto.jdbc.PrestoConnection
+           com.mchange.v2.c3p0.C3P0ProxyConnection
+           [java.sql Connection PreparedStatement ResultSet ResultSetMetaData Time Types]
+           [java.time LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime]
+           java.time.format.DateTimeFormatter
+           [java.time.temporal ChronoField Temporal]))
+
+(driver/register! :presto-jdbc, :parent #{:presto-common :sql-jdbc ::legacy/use-legacy-classes-for-read-and-set})
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                          Custom HoneySQL Clause Impls                                          |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(def ^:private ^:const timestamp-with-time-zone-db-type "timestamp with time zone")
+
+(defmethod sql.qp/->honeysql [:presto-jdbc :log]
+  [driver [_ field]]
+  ;; recent Presto versions have a `log10` function (not `log`)
+  (hsql/call :log10 (sql.qp/->honeysql driver field)))
+
+(defmethod sql.qp/->honeysql [:presto-jdbc :count-where]
+  [driver [_ pred]]
+  ;; Presto will use the precision given here in the final expression, which chops off digits
+  ;; need to explicitly provide two digits after the decimal
+  (sql.qp/->honeysql driver [:sum-where 1.00M pred]))
+
+(defmethod sql.qp/->honeysql [:presto-jdbc :time]
+  [_ [_ t]]
+  ;; make time in UTC to avoid any interpretation by Presto in the connection (i.e. report) time zone
+  (hx/cast "time with time zone" (u.date/format-sql (t/offset-time (t/local-time t) 0))))
+
+(defmethod sql.qp/->honeysql [:presto-jdbc ZonedDateTime]
+  [_ ^ZonedDateTime t]
+  ;; use the Presto cast to `timestamp with time zone` operation to interpret in the correct TZ, regardless of
+  ;; connection zone
+  (hx/cast timestamp-with-time-zone-db-type (u.date/format-sql t)))
+
+(defmethod sql.qp/->honeysql [:presto-jdbc OffsetDateTime]
+  [_ ^OffsetDateTime t]
+  ;; use the Presto cast to `timestamp with time zone` operation to interpret in the correct TZ, regardless of
+  ;; connection zone
+  (hx/cast timestamp-with-time-zone-db-type (u.date/format-sql t)))
+
+(defrecord AtTimeZone
+  ;; record type to support applying Presto's `AT TIME ZONE` operator to an expression
+  [expr zone]
+  hformat/ToSql
+  (to-sql [_]
+    (format "%s AT TIME ZONE %s"
+      (hformat/to-sql expr)
+      (hformat/to-sql (hx/literal zone)))))
+
+(defn- in-report-zone
+  "Returns a HoneySQL form to interpret the `expr` (a temporal value) in the current report time zone, via Presto's
+  `AT TIME ZONE` operator. See https://prestodb.io/docs/current/functions/datetime.html"
+  [expr]
+  (let [report-zone (qp.timezone/report-timezone-id-if-supported :presto-jdbc)
+        ;; if the expression itself has type info, use that, or else use a parent expression's type info if defined
+        type-info   (hx/type-info expr)
+        db-type     (hx/type-info->db-type type-info)]
+    (if (and ;; AT TIME ZONE is only valid on these Presto types; if applied to something else (ex: `date`), then
+             ;; an error will be thrown by the query analyzer
+             (contains? #{"timestamp" "timestamp with time zone" "time" "time with time zone"} db-type)
+             ;; if one has already been set, don't do so again
+             (not (::in-report-zone? (meta expr)))
+             report-zone)
+      (-> (hx/with-database-type-info (->AtTimeZone expr report-zone) timestamp-with-time-zone-db-type)
+        (vary-meta assoc ::in-report-zone? true))
+      expr)))
+
+;; most date extraction and bucketing functions need to account for report timezone
+
+(defmethod sql.qp/date [:presto-jdbc :default]
+  [_ _ expr]
+  expr)
+
+(defmethod sql.qp/date [:presto-jdbc :minute]
+  [_ _ expr]
+  (hsql/call :date_trunc (hx/literal :minute) (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :minute-of-hour]
+  [_ _ expr]
+  (hsql/call :minute (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :hour]
+  [_ _ expr]
+  (hsql/call :date_trunc (hx/literal :hour) (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :hour-of-day]
+  [_ _ expr]
+  (hsql/call :hour (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :day]
+  [_ _ expr]
+  (hsql/call :date (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :day-of-week]
+  [_ _ expr]
+  (sql.qp/adjust-day-of-week :presto-jdbc (hsql/call :day_of_week (in-report-zone expr))))
+
+(defmethod sql.qp/date [:presto-jdbc :day-of-month]
+  [_ _ expr]
+  (hsql/call :day (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :day-of-year]
+  [_ _ expr]
+  (hsql/call :day_of_year (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :week]
+  [_ _ expr]
+  (sql.qp/adjust-start-of-week :presto-jdbc (partial hsql/call :date_trunc (hx/literal :week)) (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :month]
+  [_ _ expr]
+  (hsql/call :date_trunc (hx/literal :month) (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :month-of-year]
+  [_ _ expr]
+  (hsql/call :month (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :quarter]
+  [_ _ expr]
+  (hsql/call :date_trunc (hx/literal :quarter) (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :quarter-of-year]
+  [_ _ expr]
+  (hsql/call :quarter (in-report-zone expr)))
+
+(defmethod sql.qp/date [:presto-jdbc :year]
+  [_ _ expr]
+  (hsql/call :date_trunc (hx/literal :year) (in-report-zone expr)))
+
+(defmethod sql.qp/unix-timestamp->honeysql [:presto-jdbc :seconds]
+  [_ _ expr]
+  (let [report-zone (qp.timezone/report-timezone-id-if-supported :presto-jdbc)]
+    (hsql/call :from_unixtime expr (hx/literal (or report-zone "UTC")))))
+
+(defmethod sql.qp/unix-timestamp->honeysql [:presto-jdbc :milliseconds]
+  [_ _ expr]
+  ;; from_unixtime doesn't support milliseconds directly, but we can add them back in
+  (let [report-zone (qp.timezone/report-timezone-id-if-supported :presto-jdbc)
+        millis      (hsql/call (u/qualified-name ::mod) expr 1000)]
+    (hsql/call :date_add
+               (hx/literal "millisecond")
+               millis
+               (hsql/call :from_unixtime (hsql/call :/ expr 1000) (hx/literal (or report-zone "UTC"))))))
+
+(defmethod sql.qp/unix-timestamp->honeysql [:presto-jdbc :microseconds]
+  [driver _ expr]
+  ;; Presto can't even represent microseconds, so convert to millis and call that version
+  (sql.qp/unix-timestamp->honeysql driver :milliseconds (hsql/call :/ expr 1000)))
+
+(defmethod sql.qp/current-datetime-honeysql-form :presto-jdbc
+  [_]
+  ;; the current_timestamp in Presto returns a `timestamp with time zone`, so this needs to be overridden
+  (hx/with-type-info :%now {::hx/database-type timestamp-with-time-zone-db-type}))
+
+(defmethod hformat/fn-handler (u/qualified-name ::mod)
+  [_ x y]
+  ;; Presto mod is a function like mod(x, y) rather than an operator like x mod y
+  (format "mod(%s, %s)" (hformat/to-sql x) (hformat/to-sql y)))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                  Connectivity                                                  |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defn- db-name
+  "Creates a \"DB name\" for the given catalog `c` and (optional) schema `s`.  If both are specified, a slash is
+  used to separate them.  See examples at:
+  https://prestodb.io/docs/current/installation/jdbc.html#connecting"
+  [c s]
+  (cond
+    (str/blank? c)
+    ""
+
+    (str/blank? s)
+    c
+
+    :else
+    (str c "/" s)))
+
+(defn- jdbc-spec
+  "Creates a spec for `clojure.java.jdbc` to use for connecting to Presto via JDBC, from the given `opts`."
+  [{:keys [host port catalog schema]
+    :or   {host "localhost", port 5432, catalog ""}
+    :as   opts}]
+  (-> (merge
+        {:classname                     "com.facebook.presto.jdbc.PrestoDriver"
+         :subprotocol                   "presto"
+         :subname                       (db.spec/make-subname host port (db-name catalog schema))}
+        (dissoc opts :host :port :db :catalog :schema))
+    sql-jdbc.common/handle-additional-options))
+
+(defmethod sql-jdbc.conn/connection-details->spec :presto-jdbc
+  [_ {ssl? :ssl, :as details-map}]
+  (let [props (-> details-map
+                (update :port (fn [port]
+                                (if (string? port)
+                                  (Integer/parseInt port)
+                                  port)))
+                (assoc :SSL ssl?)
+                (dissoc :ssl))]
+    (jdbc-spec props)))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                      Sync                                                      |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defmethod sql-jdbc.sync/database-type->base-type :presto-jdbc
+  [_ field-type]
+  (presto-common/presto-type->base-type field-type))
+
+(defn- have-select-privilege?
+  "Checks whether the connected user has permission to select from the given `table-name`, in the given `schema`.
+  Adapted from the legacy Presto driver implementation."
+  [driver conn schema table-name]
+  (try
+   (let [sql (sql-jdbc.describe-database/simple-select-probe-query driver schema table-name)]
+        ;; if the query completes without throwing an Exception, we can SELECT from this table
+        (jdbc/reducible-query {:connection conn} sql)
+        true)
+   (catch Throwable _
+     false)))
+
+(defn- describe-schema
+  "Gets a set of maps for all tables in the given `catalog` and `schema`. Adapted from the legacy Presto driver
+  implementation."
+  [driver conn catalog schema]
+  (let [sql (presto-common/describe-schema-sql driver catalog schema)]
+    (log/trace (trs "Running statement in describe-schema: {0}" sql))
+    (into #{} (comp (filter (fn [{table-name :table}]
+                                (have-select-privilege? driver conn schema table-name)))
+                    (map (fn [{table-name :table}]
+                             {:name        table-name
+                              :schema      schema})))
+              (jdbc/reducible-query {:connection conn} sql))))
+
+(defn- all-schemas
+  "Gets a set of maps for all tables in all schemas in the given `catalog`. Adapted from the legacy Presto driver
+  implementation."
+  [driver conn catalog]
+  (let [sql (presto-common/describe-catalog-sql driver catalog)]
+    (log/trace (trs "Running statement in all-schemas: {0}" sql))
+    (into []
+          (map (fn [{:keys [schema] :as full}]
+                 (when-not (contains? presto-common/excluded-schemas schema)
+                   (describe-schema driver conn catalog schema))))
+          (jdbc/reducible-query {:connection conn} sql))))
+
+(defmethod driver/describe-database :presto-jdbc
+  [driver {{:keys [catalog schema] :as details} :details :as database}]
+  (with-open [conn (-> (sql-jdbc.conn/db->pooled-connection-spec database)
+                       jdbc/get-connection)]
+    (let [schemas (if schema #{(describe-schema driver conn catalog schema)}
+                             (all-schemas driver conn catalog))]
+      {:tables (reduce set/union schemas)})))
+
+(defmethod driver/describe-table :presto-jdbc
+  [driver {{:keys [catalog] :as details} :details :as database} {schema :schema, table-name :name}]
+  (with-open [conn (-> (sql-jdbc.conn/db->pooled-connection-spec database)
+                     jdbc/get-connection)]
+    (let [sql (presto-common/describe-table-sql driver catalog schema table-name)]
+      (log/trace (trs "Running statement in describe-table: {0}" sql))
+      {:schema schema
+       :name   table-name
+       :fields (into
+                 #{}
+                 (map-indexed (fn [idx {:keys [column type] :as col}]
+                                {:name column
+                                 :database-type type
+                                 :base-type         (presto-common/presto-type->base-type type)
+                                 :database-position idx}))
+                 (jdbc/reducible-query {:connection conn} sql))})))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                            sql-jdbc implementations                                            |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defmethod sql-jdbc.execute/prepared-statement :presto-jdbc
+  [driver ^Connection conn ^String sql params]
+  ;; with Presto JDBC driver, result set holdability must be HOLD_CURSORS_OVER_COMMIT
+  ;; defining this method simply to omit setting the holdability
+  (let [stmt (.prepareStatement conn
+                                sql
+                                ResultSet/TYPE_FORWARD_ONLY
+                                ResultSet/CONCUR_READ_ONLY)]
+       (try
+         (try
+           (.setFetchDirection stmt ResultSet/FETCH_FORWARD)
+           (catch Throwable e
+             (log/debug e (trs "Error setting prepared statement fetch direction to FETCH_FORWARD"))))
+         (sql-jdbc.execute/set-parameters! driver stmt params)
+         stmt
+         (catch Throwable e
+           (.close stmt)
+           (throw e)))))
+
+
+(defmethod sql-jdbc.execute/statement :presto-jdbc
+  [_ ^Connection conn]
+  ;; and similarly for statement (do not set holdability)
+  (let [stmt (.createStatement conn
+                               ResultSet/TYPE_FORWARD_ONLY
+                               ResultSet/CONCUR_READ_ONLY)]
+    (try
+      (.setFetchDirection stmt ResultSet/FETCH_FORWARD)
+      (catch Throwable e
+        (log/debug e (trs "Error setting statement fetch direction to FETCH_FORWARD"))))
+    stmt))
+
+(defmethod driver/can-connect? :sql-jdbc
+  [driver details]
+  (sql-jdbc.conn/can-connect? driver details))
+
+(defn- ^PrestoConnection pooled-conn->presto-conn
+  "Unwraps the C3P0 `pooled-conn` and returns the underlying `PrestoConnection` it holds."
+  [^C3P0ProxyConnection pooled-conn]
+  (.unwrap pooled-conn PrestoConnection))
+
+(defmethod sql-jdbc.execute/connection-with-timezone :presto-jdbc
+  [driver database ^String timezone-id]
+  ;; Presto supports setting the session timezone via a `PrestoConnection` instance method. Under the covers,
+  ;; this is equivalent to the `X-Presto-Time-Zone` header in the HTTP request (i.e. the `:presto` driver)
+  (let [conn            (.getConnection (sql-jdbc.execute/datasource-with-diagnostic-info! driver database))
+        underlying-conn (pooled-conn->presto-conn conn)]
+    (try
+      (sql-jdbc.execute/set-best-transaction-level! driver conn)
+      (when-not (str/blank? timezone-id)
+        ;; set session time zone if defined
+        (.setTimeZoneId underlying-conn timezone-id))
+      (try
+        (.setReadOnly conn true)
+        (catch Throwable e
+          (log/debug e (trs "Error setting connection to read-only"))))
+      ;; as with statement and prepared-statement, cannot set holdability on the connection level
+      conn
+      (catch Throwable e
+        (.close conn)
+        (throw e)))))
+
+(defn- date-time->substitution [ts-str]
+  (sql.params.substitution/make-stmt-subs "from_iso8601_timestamp(?)" [ts-str]))
+
+(defmethod sql.params.substitution/->prepared-substitution [:presto-jdbc ZonedDateTime]
+  [_ ^ZonedDateTime t]
+  ;; for native query parameter substitution, in order to not conflict with the `PrestoConnection` session time zone
+  ;; (which was set via report time zone), it is necessary to use the `from_iso8601_timestamp` function on the string
+  ;; representation of the `ZonedDateTime` instance, but converted to the report time zone
+  #_(date-time->substitution (.format (t/offset-date-time (t/local-date-time t) (t/zone-offset 0)) DateTimeFormatter/ISO_OFFSET_DATE_TIME))
+  (let [report-zone       (qp.timezone/report-timezone-id-if-supported :presto-jdbc)
+        ^ZonedDateTime ts (if (str/blank? report-zone) t (t/with-zone-same-instant t (t/zone-id report-zone)))]
+    ;; the `from_iso8601_timestamp` only accepts timestamps with an offset (not a zone ID), so only format with offset
+    (date-time->substitution (.format ts DateTimeFormatter/ISO_OFFSET_DATE_TIME))))
+
+(defmethod sql.params.substitution/->prepared-substitution [:presto-jdbc LocalDateTime]
+  [_ ^LocalDateTime t]
+  ;; similar to above implementation, but for `LocalDateTime`
+  ;; when Presto parses this, it will account for session (report) time zone
+  (date-time->substitution (.format t DateTimeFormatter/ISO_LOCAL_DATE_TIME)))
+
+(defmethod sql.params.substitution/->prepared-substitution [:presto-jdbc OffsetDateTime]
+  [_ ^OffsetDateTime t]
+  ;; similar to above implementation, but for `ZonedDateTime`
+  ;; when Presto parses this, it will account for session (report) time zone
+  (date-time->substitution (.format t DateTimeFormatter/ISO_OFFSET_DATE_TIME)))
+
+(defn- set-time-param
+  "Converts the given instance of `java.time.temporal`, assumed to be a time (either `LocalTime` or `OffsetTime`)
+  into a `java.sql.Time`, including milliseconds, and sets the result as a parameter of the `PreparedStatement` `ps`
+  at index `i`."
+  [^PreparedStatement ps ^Integer i ^Temporal t]
+  ;; for some reason, `java-time` can't handle passing millis to java.sql.Time, so this is the most straightforward way
+  ;; I could find to do it
+  ;; reported as https://github.com/dm3/clojure.java-time/issues/74
+  (let [millis-of-day (.get t ChronoField/MILLI_OF_DAY)]
+    (.setTime ps i (Time. millis-of-day))))
+
+(defmethod sql-jdbc.execute/set-parameter [:presto-jdbc OffsetTime]
+  [_ ^PreparedStatement ps ^Integer i t]
+  ;; necessary because `PrestoPreparedStatement` does not implement the `setTime` overload having the final `Calendar`
+  ;; param
+  (let [adjusted-tz (t/with-offset-same-instant t (t/zone-offset 0))]
+    (set-time-param ps i adjusted-tz)))
+
+(defmethod sql-jdbc.execute/set-parameter [:presto-jdbc LocalTime]
+  [_ ^PreparedStatement ps ^Integer i t]
+  ;; same rationale as above
+  (set-time-param ps i t))
+
+(defn- ^LocalTime sql-time->local-time
+  "Converts the given instance of `java.sql.Time` into a `java.time.LocalTime`, including milliseconds. Needed for
+  similar reasons as `set-time-param` above."
+  [^Time sql-time]
+  ;; Java 11 adds a simpler `ofInstant` method, but since we need to run on JDK 8, we can't use it
+  ;; https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/LocalTime.html#ofInstant(java.time.Instant,java.time.ZoneId)
+  (let [^LocalTime lt (t/local-time sql-time)
+        ^Long millis  (mod (.getTime sql-time) 1000)]
+    (.with lt ChronoField/MILLI_OF_SECOND millis)))
+
+(defmethod sql-jdbc.execute/read-column-thunk [:presto-jdbc Types/TIME]
+  [_ ^ResultSet rs ^ResultSetMetaData rs-meta ^Integer i]
+  (let [type-name  (.getColumnTypeName rs-meta i)
+        base-type  (presto-common/presto-type->base-type type-name)
+        with-tz?   (isa? base-type :type/TimeWithTZ)]
+    (fn []
+      (let [local-time (-> (.getTime rs i)
+                           sql-time->local-time)]
+        ;; for both `time` and `time with time zone`, the JDBC type reported by the driver is `Types/TIME`, hence
+        ;; we also need to check the column type name to differentiate between them here
+        (if with-tz?
+          ;; even though this value is a `LocalTime`, the base-type is time with time zone, so we need to shift it back to
+          ;; the UTC (0) offset
+          (t/offset-time
+            local-time
+            (t/zone-offset 0))
+          ;; else the base-type is time without time zone, so just return the local-time value
+          local-time)))))
+
+(defn- ^PrestoConnection rs->presto-conn
+  "Returns the `PrestoConnection` associated with the given `ResultSet` `rs`."
+  [^ResultSet rs]
+  (-> (.. rs getStatement getConnection)
+      pooled-conn->presto-conn))
+
+(defmethod sql-jdbc.execute/read-column-thunk [:presto-jdbc Types/TIMESTAMP]
+  [_ ^ResultSet rset ^ResultSetMetaData rsmeta ^Integer i]
+  (let [zone     (.getTimeZoneId (rs->presto-conn rset))]
+    (fn []
+      (when-let [s (.getString rset i)]
+        (when-let [t (u.date/parse s)]
+          (cond
+            (or (instance? OffsetDateTime t)
+              (instance? ZonedDateTime t))
+            (-> (t/offset-date-time t)
+              ;; tests are expecting this to be in the UTC offset, so convert to that
+              (t/with-offset-same-instant (t/zone-offset 0)))
+
+            ;; presto "helpfully" returns local results already adjusted to session time zone offset for us, e.g.
+            ;; '2021-06-15T00:00:00' becomes '2021-06-15T07:00:00' if the session timezone is US/Pacific. Undo the
+            ;; madness and convert back to UTC
+            zone
+            (-> (t/zoned-date-time t zone)
+              (u.date/with-time-zone-same-instant "UTC")
+              t/local-date-time)
+            :else
+            t))))))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                           Other Driver Method Impls                                            |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(prefer-method driver/supports? [:presto-common :set-timezone] [:sql-jdbc :set-timezone])
diff --git a/modules/drivers/presto-jdbc/test/metabase/driver/presto_jdbc_test.clj b/modules/drivers/presto-jdbc/test/metabase/driver/presto_jdbc_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..17ed31f617675edee87a8e0d64b0660b30f7bf5b
--- /dev/null
+++ b/modules/drivers/presto-jdbc/test/metabase/driver/presto_jdbc_test.clj
@@ -0,0 +1,192 @@
+(ns metabase.driver.presto-jdbc-test
+  (:require [clojure.java.jdbc :as jdbc]
+            [clojure.test :refer :all]
+            [honeysql.core :as hsql]
+            [honeysql.format :as hformat]
+            [java-time :as t]
+            [metabase.db.metadata-queries :as metadata-queries]
+            [metabase.driver :as driver]
+            [metabase.driver.presto-jdbc :as presto-jdbc]
+            [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn]
+            [metabase.driver.sql.query-processor :as sql.qp]
+            [metabase.models.database :refer [Database]]
+            [metabase.models.field :refer [Field]]
+            [metabase.models.table :as table :refer [Table]]
+            [metabase.query-processor :as qp]
+            [metabase.sync :as sync]
+            [metabase.test :as mt]
+            [metabase.test.fixtures :as fixtures]
+            [metabase.test.util :as tu]
+            [toucan.db :as db]))
+
+(use-fixtures :once (fixtures/initialize :db))
+
+(deftest describe-database-test
+  (mt/test-driver :presto-jdbc
+    (is (= {:tables #{{:name "categories" :schema "default"}
+                      {:name "venues" :schema "default"}
+                      {:name "checkins" :schema "default"}
+                      {:name "users" :schema "default"}}}
+           (-> (driver/describe-database :presto-jdbc (mt/db))
+               (update :tables (comp set (partial filter (comp #{"categories"
+                                                                 "venues"
+                                                                 "checkins"
+                                                                 "users"}
+                                                               :name)))))))))
+
+(deftest describe-table-test
+  (mt/test-driver :presto-jdbc
+    (is (= {:name   "venues"
+            :schema "default"
+            :fields #{{:name          "name",
+                       ;; for HTTP based Presto driver, this is coming back as varchar(255)
+                       ;; however, for whatever reason, the DESCRIBE statement results do not return the length
+                       :database-type "varchar"
+                       :base-type     :type/Text
+                       :database-position 1}
+                      {:name          "latitude"
+                       :database-type "double"
+                       :base-type     :type/Float
+                       :database-position 3}
+                      {:name          "longitude"
+                       :database-type "double"
+                       :base-type     :type/Float
+                       :database-position 4}
+                      {:name          "price"
+                       :database-type "integer"
+                       :base-type     :type/Integer
+                       :database-position 5}
+                      {:name          "category_id"
+                       :database-type "integer"
+                       :base-type     :type/Integer
+                       :database-position 2}
+                      {:name          "id"
+                       :database-type "integer"
+                       :base-type     :type/Integer
+                       :database-position 0}}}
+           (driver/describe-table :presto-jdbc (mt/db) (db/select-one 'Table :id (mt/id :venues)))))))
+
+(deftest table-rows-sample-test
+  (mt/test-driver :presto-jdbc
+    (is (= [[1 "Red Medicine"]
+            [2 "Stout Burgers & Beers"]
+            [3 "The Apple Pan"]
+            [4 "Wurstküche"]
+            [5 "Brite Spot Family Restaurant"]]
+           (->> (metadata-queries/table-rows-sample (Table (mt/id :venues))
+                  [(Field (mt/id :venues :id))
+                   (Field (mt/id :venues :name))]
+                  (constantly conj))
+                (sort-by first)
+                (take 5))))))
+
+(deftest page-test
+  (testing ":page clause"
+    (is (= {:select ["name" "id"]
+            :from   [{:select   [[:default.categories.name "name"]
+                                 [:default.categories.id "id"]
+                                 [(hsql/raw "row_number() OVER (ORDER BY \"default\".\"categories\".\"id\" ASC)")
+                                  :__rownum__]]
+                      :from     [:default.categories]
+                      :order-by [[:default.categories.id :asc]]}]
+            :where  [:> :__rownum__ 5]
+            :limit  5}
+           (sql.qp/apply-top-level-clause :presto-jdbc :page
+                                          {:select   [[:default.categories.name "name"] [:default.categories.id "id"]]
+                                           :from     [:default.categories]
+                                           :order-by [[:default.categories.id :asc]]}
+                                          {:page {:page  2
+                                                  :items 5}})))))
+
+(deftest db-default-timezone-test
+  (mt/test-driver :presto-jdbc
+    (is (= "UTC"
+           (tu/db-timezone-id)))))
+
+(deftest template-tag-timezone-test
+  (mt/test-driver :presto-jdbc
+    (testing "Make sure date params work correctly when report timezones are set (#10487)"
+      (mt/with-temporary-setting-values [report-timezone "Asia/Hong_Kong"]
+        ;; the `read-column-thunk` for `Types/TIMESTAMP` always returns an `OffsetDateTime`, not a `LocalDateTime`, as
+        ;; the original Presto version of this test expected; therefore, convert the `ZonedDateTime` corresponding to
+        ;; midnight on this date (at the report TZ) to `OffsetDateTime` for comparison's sake
+        (is (= [[(-> (t/zoned-date-time 2014 8 2 0 0 0 0 (t/zone-id "Asia/Hong_Kong"))
+                     t/offset-date-time
+                     (t/with-offset-same-instant (t/zone-offset 0)))
+                 (t/local-date 2014 8 2)]]
+               (mt/rows
+                 (qp/process-query
+                   {:database     (mt/id)
+                    :type         :native
+                    :middleware   {:format-rows? false} ; turn off formatting so we can check the raw local date objs
+                    :native       {:query         "SELECT {{date}}, cast({{date}} AS date)"
+                                   :template-tags {:date {:name "date" :display_name "Date" :type "date"}}}
+                    :parameters   [{:type   "date/single"
+                                    :target ["variable" ["template-tag" "date"]]
+                                    :value  "2014-08-02"}]}))))))))
+
+(deftest splice-strings-test
+  (mt/test-driver :presto-jdbc
+    (let [query (mt/mbql-query venues
+                  {:aggregation [[:count]]
+                   :filter      [:= $name "wow"]})]
+      (testing "The native query returned in query results should use user-friendly splicing"
+        (is (= (str "SELECT count(*) AS \"count\" "
+                    "FROM \"default\".\"venues\" "
+                    "WHERE \"default\".\"venues\".\"name\" = 'wow'")
+               (:query (qp/query->native-with-spliced-params query))
+               (-> (qp/process-query query) :data :native_form :query)))))))
+
+(deftest connection-tests
+  (testing "db-name is correct in all cases"
+    (doseq [[c s expected] [[nil nil ""]
+                            ["" "" ""]
+                            ["my_catalog" nil "my_catalog"]
+                            ["my_catalog" "" "my_catalog"]
+                            ["my_catalog" "my_schema" "my_catalog/my_schema"]]]
+      (is (= expected (#'presto-jdbc/db-name c s)))))
+  (testing "jdbc-spec is correct"
+    (is (= {:classname   "com.facebook.presto.jdbc.PrestoDriver"
+            :subname     "//my-presto-server:1234/my_catalog?Option1=Value1&Option2=Value2"
+            :subprotocol "presto"}
+           (#'presto-jdbc/jdbc-spec {:host "my-presto-server"
+                                     :port 1234
+                                     :catalog "my_catalog"
+                                     :schema nil
+                                     :additional-options "Option1=Value1&Option2=Value2"})))))
+
+(deftest honeysql-tests
+  (testing "Complex HoneySQL conversions work as expected"
+    (testing "unix-timestamp with microsecond precision"
+      (is (= [(str "date_add('millisecond', mod((1623963256123456 / 1000), 1000),"
+                   " from_unixtime(((1623963256123456 / 1000) / 1000), 'UTC'))")]
+             (-> (sql.qp/unix-timestamp->honeysql :presto-jdbc :microseconds (hsql/raw 1623963256123456))
+               (hformat/format)))))))
+
+(defn- execute-ddl! [ddl-statements]
+  (mt/with-driver :presto-jdbc
+    (let [jdbc-spec (sql-jdbc.conn/connection-details->spec :presto-jdbc (:details (mt/db)))]
+      (with-open [conn (doto (jdbc/get-connection jdbc-spec))]
+        (doseq [ddl-stmt ddl-statements]
+          (with-open [stmt (.prepareStatement conn ddl-stmt)]
+            (.executeUpdate stmt)))))))
+
+(deftest specific-schema-sync-test
+  (mt/test-driver :presto-jdbc
+    (testing "When a specific schema is designated, only that one is synced"
+      (let [s           "specific_schema"
+            t           "specific_table"
+            db-details  (:details (mt/db))
+            with-schema (assoc db-details :schema s)]
+        (execute-ddl! [(format "DROP TABLE IF EXISTS %s.%s" s t)
+                       (format "DROP SCHEMA IF EXISTS %s" s)
+                       (format "CREATE SCHEMA %s" s)
+                       (format "CREATE TABLE %s.%s (pk INTEGER, val1 VARCHAR(512))" s t)])
+        (mt/with-temp Database [db {:engine :presto-jdbc, :name "Temp Presto JDBC Schema DB", :details with-schema}]
+          (mt/with-db db
+            ;; same as test_data, but with schema, so should NOT pick up venues, users, etc.
+            (sync/sync-database! db)
+            (is (= [{:name t, :schema s, :db_id (mt/id)}]
+                   (map #(select-keys % [:name :schema :db_id]) (db/select Table :db_id (mt/id)))))))
+        (execute-ddl! [(format "DROP TABLE %s.%s" s t)
+                       (format "DROP SCHEMA %s" s)])))))
diff --git a/modules/drivers/presto-jdbc/test/metabase/test/data/presto_jdbc.clj b/modules/drivers/presto-jdbc/test/metabase/test/data/presto_jdbc.clj
new file mode 100644
index 0000000000000000000000000000000000000000..96c4fc9defcffa3725550f1a3881a95786015db8
--- /dev/null
+++ b/modules/drivers/presto-jdbc/test/metabase/test/data/presto_jdbc.clj
@@ -0,0 +1,136 @@
+(ns metabase.test.data.presto-jdbc
+  "Presto JDBC driver test extensions."
+  (:require [clojure.string :as str]
+            [metabase.config :as config]
+            [metabase.connection-pool :as connection-pool]
+            [metabase.driver :as driver]
+            [metabase.driver.sql-jdbc.execute :as sql-jdbc.execute]
+            [metabase.test.data.interface :as tx]
+            [metabase.test.data.presto-common]
+            [metabase.test.data.sql :as sql.tx]
+            [metabase.test.data.sql-jdbc :as sql-jdbc.tx]
+            [metabase.test.data.sql-jdbc.execute :as execute]
+            [metabase.test.data.sql-jdbc.load-data :as load-data]
+            [metabase.test.data.sql.ddl :as ddl])
+  (:import [java.sql Connection DriverManager PreparedStatement]))
+
+(sql-jdbc.tx/add-test-extensions! :presto-jdbc)
+
+;; during unit tests don't treat presto as having FK support
+(defmethod driver/supports? [:presto-jdbc :foreign-keys] [_ _] (not config/is-test?))
+
+(doseq [[base-type db-type] {:type/BigInteger             "BIGINT"
+                             :type/Boolean                "BOOLEAN"
+                             :type/Date                   "DATE"
+                             :type/DateTime               "TIMESTAMP"
+                             :type/DateTimeWithTZ         "TIMESTAMP WITH TIME ZONE"
+                             :type/DateTimeWithZoneID     "TIMESTAMP WITH TIME ZONE"
+                             :type/DateTimeWithZoneOffset "TIMESTAMP WITH TIME ZONE"
+                             :type/Decimal                "DECIMAL"
+                             :type/Float                  "DOUBLE"
+                             :type/Integer                "INTEGER"
+                             :type/Text                   "VARCHAR"
+                             :type/Time                   "TIME"
+                             :type/TimeWithTZ             "TIME WITH TIME ZONE"}]
+  (defmethod sql.tx/field-base-type->sql-type [:presto-jdbc base-type] [_ _] db-type))
+
+;; in the past, we had to manually update our Docker image and add a new catalog for every new dataset definition we
+;; added. That's insane. Just use the `test-data` catalog and put everything in that, and use
+;; `db-qualified-table-name` like everyone else.
+(def ^:private test-catalog-name "test_data")
+
+(def ^:private test-schema-name "default")
+
+(defn- dash->underscore [nm]
+  (str/replace nm #"-" "_"))
+
+(defmethod tx/dbdef->connection-details :presto-jdbc
+  [_ _ {:keys [database-name]}]
+  {:host               (tx/db-test-env-var-or-throw :presto-jdbc :host "localhost")
+   :port               (tx/db-test-env-var :presto-jdbc :port "8080")
+   :user               (tx/db-test-env-var-or-throw :presto-jdbc :user "metabase")
+   :additional-options (tx/db-test-env-var :presto-jdbc :additional-options nil)
+   :ssl                (tx/db-test-env-var :presto-jdbc :ssl "false")
+   :catalog            (dash->underscore database-name)
+   :schema             (tx/db-test-env-var :presto-jdbc :schema nil)})
+
+(defmethod execute/execute-sql! :presto-jdbc
+  [& args]
+  (apply execute/sequentially-execute-sql! args))
+
+(defn- load-data [dbdef tabledef]
+  ;; the JDBC driver statements fail with a cryptic status 500 error if there are too many
+  ;; parameters being set in a single statement; these numbers were arrived at empirically
+  (let [chunk-size (case (:table-name tabledef)
+                     "people" 30
+                     "reviews" 40
+                     "orders" 30
+                     "venues" 50
+                     "products" 50
+                     "cities" 50
+                     "sightings" 50
+                     "incidents" 50
+                     "checkins" 25
+                     "airport" 50
+                     100)
+        load-fn    (load-data/make-load-data-fn load-data/load-data-add-ids
+                     (partial load-data/load-data-chunked pmap chunk-size))]
+    (load-fn :presto-jdbc dbdef tabledef)))
+
+(defmethod load-data/load-data! :presto-jdbc
+  [_ dbdef tabledef]
+  (load-data dbdef tabledef))
+
+(defn- jdbc-spec->connection
+  "This is to work around some weird interplay between clojure.java.jdbc caching behavior of connections based on URL,
+  combined with the fact that the Presto driver apparently closes the connection when it closes a prepare statement.
+  Therefore, create a fresh connection from the DriverManager."
+  ^Connection [jdbc-spec]
+  (DriverManager/getConnection (format "jdbc:%s:%s" (:subprotocol jdbc-spec) (:subname jdbc-spec))
+    (connection-pool/map->properties (select-keys jdbc-spec [:user :SSL]))))
+
+(defmethod load-data/do-insert! :presto-jdbc
+  [driver spec table-identifier row-or-rows]
+  (let [statements (ddl/insert-rows-ddl-statements driver table-identifier row-or-rows)]
+    (with-open [conn (jdbc-spec->connection spec)]
+      (doseq [[^String sql & params] statements]
+        (try
+          (with-open [^PreparedStatement stmt (.prepareStatement conn sql)]
+            (sql-jdbc.execute/set-parameters! driver stmt params)
+            (let [tbl-nm        ((comp last :components) (into {} table-identifier))
+                  rows-affected (.executeUpdate stmt)]
+              (println (format "[%s] Inserted %d rows into %s." driver rows-affected tbl-nm))))
+          (catch Throwable e
+            (throw (ex-info (format "[%s] Error executing SQL: %s" driver (ex-message e))
+                     {:driver driver, :sql sql, :params params}
+                     e))))))))
+
+(defmethod sql.tx/drop-db-if-exists-sql :presto-jdbc [_ _] nil)
+(defmethod sql.tx/create-db-sql         :presto-jdbc [_ _] nil)
+
+(defmethod sql.tx/qualified-name-components :presto-jdbc
+  ;; use the default schema from the in-memory connector
+  ([_ db-name]                       [(dash->underscore db-name) "default"])
+  ([_ db-name table-name]            [(dash->underscore db-name) "default" (dash->underscore table-name)])
+  ([_ db-name table-name field-name] [(dash->underscore db-name) "default" (dash->underscore table-name) field-name]))
+
+(defmethod sql.tx/pk-sql-type :presto-jdbc
+  [_]
+  "INTEGER")
+
+(defmethod sql.tx/create-table-sql :presto-jdbc
+  [driver dbdef tabledef]
+  ;; strip out the PRIMARY KEY stuff from the CREATE TABLE statement
+  (let [sql ((get-method sql.tx/create-table-sql :sql/test-extensions) driver dbdef tabledef)]
+    (str/replace sql #", PRIMARY KEY \([^)]+\)" "")))
+
+(defmethod tx/format-name :presto-jdbc [_ table-or-field-name]
+  (dash->underscore table-or-field-name))
+
+;; Presto doesn't support FKs, at least not adding them via DDL
+(defmethod sql.tx/add-fk-sql :presto-jdbc
+  [_ _ _ _]
+  nil)
+
+;; FIXME Presto actually has very good timezone support
+#_(defmethod tx/has-questionable-timezone-support? :presto-jdbc [_] true)
diff --git a/modules/drivers/presto/parents b/modules/drivers/presto/parents
new file mode 100644
index 0000000000000000000000000000000000000000..1b4a8dec8e88c0a560332d2c956a0644eae955b0
--- /dev/null
+++ b/modules/drivers/presto/parents
@@ -0,0 +1 @@
+presto-common
diff --git a/modules/drivers/presto/project.clj b/modules/drivers/presto/project.clj
index 037b559f262457a51e139b5aa6dc62f0798f94c4..33c8f12c6277a8232fd4abbe1889d80e91901dd9 100644
--- a/modules/drivers/presto/project.clj
+++ b/modules/drivers/presto/project.clj
@@ -5,7 +5,8 @@
   {:provided
    {:dependencies
     [[org.clojure/clojure "1.10.1"]
-     [metabase-core "1.0.0-SNAPSHOT"]]}
+     [metabase-core "1.0.0-SNAPSHOT"]
+     [metabase/presto-common-driver "1.0.0-SNAPSHOT"]]}
 
    :uberjar
    {:auto-clean    true
diff --git a/modules/drivers/presto/resources/metabase-plugin.yaml b/modules/drivers/presto/resources/metabase-plugin.yaml
index 1dee26628cbce3d9008b6eaaa4156f5f71f181a3..859cb2460f5d939f2627c306f31fcbeba50615fc 100644
--- a/modules/drivers/presto/resources/metabase-plugin.yaml
+++ b/modules/drivers/presto/resources/metabase-plugin.yaml
@@ -2,11 +2,13 @@ info:
   name: Metabase Presto Driver
   version: 1.0.0-SNAPSHOT
   description: Allows Metabase to connect to Presto databases.
+dependencies:
+  - plugin: Presto Common Driver
 driver:
   name: presto
-  display-name: Presto
+  display-name: Presto (Deprecated Driver)
   lazy-load: true
-  parent: sql
+  parent: presto-common
   connection-properties:
     - host
     - merge:
@@ -21,5 +23,8 @@ driver:
     - ssl
   connection-properties-include-tunnel-config: true
 init:
+  - step: load-namespace
+    namespace: metabase.driver.presto-common
   - step: load-namespace
     namespace: metabase.driver.presto
+superseded-by: presto-jdbc
diff --git a/modules/drivers/presto/src/metabase/driver/presto.clj b/modules/drivers/presto/src/metabase/driver/presto.clj
index b07a1ec4d0ed9532b3c77738d385f37a064576f9..96032e372837eed916beba4f8a77d746971251a1 100644
--- a/modules/drivers/presto/src/metabase/driver/presto.clj
+++ b/modules/drivers/presto/src/metabase/driver/presto.clj
@@ -1,19 +1,15 @@
 (ns metabase.driver.presto
-  "Presto driver. See https://prestodb.io/docs/current/ for complete dox."
-  (:require [buddy.core.codecs :as codecs]
-            [clj-http.client :as http]
+  "Presto driver. Executes queries via the REST API. See https://prestodb.io/docs/current/ for complete dox."
+  (:require [clj-http.client :as http]
             [clojure.core.async :as a]
             [clojure.set :as set]
             [clojure.string :as str]
             [clojure.tools.logging :as log]
-            [honeysql.core :as hsql]
-            [honeysql.helpers :as h]
-            [java-time :as t]
             [medley.core :as m]
             [metabase.driver :as driver]
             [metabase.driver.common :as driver.common]
+            [metabase.driver.presto-common :as presto-common]
             [metabase.driver.sql-jdbc.sync.describe-database :as sql-jdbc.describe-database]
-            [metabase.driver.sql.query-processor :as sql.qp]
             [metabase.driver.sql.util :as sql.u]
             [metabase.driver.sql.util.unprepare :as unprepare]
             [metabase.query-processor.context :as context]
@@ -22,15 +18,12 @@
             [metabase.query-processor.util :as qputil]
             [metabase.util :as u]
             [metabase.util.date-2 :as u.date]
-            [metabase.util.honeysql-extensions :as hx]
             [metabase.util.i18n :refer [trs tru]]
             [metabase.util.schema :as su]
             [metabase.util.ssh :as ssh]
-            [schema.core :as s])
-  (:import java.sql.Time
-           [java.time OffsetDateTime ZonedDateTime]))
+            [schema.core :as s]))
 
-(driver/register! :presto, :parent :sql)
+(driver/register! :presto, :parent :presto-common)
 
 ;;; Presto API helpers
 
@@ -40,28 +33,6 @@
    :catalog su/NonBlankString
    s/Any    s/Any})
 
-(defn- presto-type->base-type [field-type]
-  (condp re-matches field-type
-    #"boolean"     :type/Boolean
-    #"tinyint"     :type/Integer
-    #"smallint"    :type/Integer
-    #"integer"     :type/Integer
-    #"bigint"      :type/BigInteger
-    #"real"        :type/Float
-    #"double"      :type/Float
-    #"decimal.*"   :type/Decimal
-    #"varchar.*"   :type/Text
-    #"char.*"      :type/Text
-    #"varbinary.*" :type/*
-    #"json"        :type/Text       ; TODO - this should probably be Dictionary or something
-    #"date"        :type/Date
-    #"time"        :type/Time
-    #"time.+"      :type/DateTime
-    #"array"       :type/Array
-    #"map"         :type/Dictionary
-    #"row.*"       :type/*          ; TODO - again, but this time we supposedly have a schema
-    #".*"          :type/*))
-
 (s/defn ^:private details->uri
   [{:keys [ssl host port]} :- PrestoConnectionDetails, path]
   (str (if ssl "https" "http") "://" host ":" port
@@ -124,7 +95,7 @@
               (if (seq columns)
                 (for [{col-name :name, col-type :type} columns]
                   {:name      col-name
-                   :base_type (presto-type->base-type col-type)})
+                   :base_type (presto-common/presto-type->base-type col-type)})
                 (:cols @next-page)))})))
 
 (defn- cancel-query-with-id! [details query-id info-uri]
@@ -181,7 +152,7 @@
                             (parse-row row))
             cols          (for [{col-name :name, col-type :type} columns]
                             {:name      col-name
-                             :base_type (presto-type->base-type col-type)})
+                             :base_type (presto-common/presto-type->base-type col-type)})
             async-results (delay
                             (when nextUri
                               (fetch-results-async details canceled-chan id infoUri nextUri)))]
@@ -216,14 +187,10 @@
                                 (sql.u/quote-name driver :database catalog)))]
     (= v "information_schema")))
 
-(defmethod sql.qp/add-interval-honeysql-form :presto
-  [_ hsql-form amount unit]
-  (hsql/call :date_add (hx/literal unit) amount hsql-form))
-
 (s/defn ^:private database->all-schemas :- #{su/NonBlankString}
   "Return a set of all schema names in this `database`."
   [driver {{:keys [catalog schema] :as details} :details :as database}]
-  (let [sql            (str "SHOW SCHEMAS FROM " (sql.u/quote-name driver :database catalog))
+  (let [sql            (presto-common/describe-catalog-sql driver catalog)
         {:keys [rows]} (execute-query-for-sync details sql)]
     (set (map first rows))))
 
@@ -237,97 +204,30 @@
       false)))
 
 (defn- describe-schema [driver {{:keys [catalog user] :as details} :details :as db} {:keys [schema]}]
-  (let [sql (str "SHOW TABLES FROM " (sql.u/quote-name driver :schema catalog schema))]
+  (let [sql (presto-common/describe-schema-sql driver catalog schema)]
     (set (for [[table-name & _] (:rows (execute-query-for-sync details sql))
                :when            (have-select-privilege? driver details schema table-name)]
            {:name   table-name
             :schema schema}))))
 
-(def ^:private excluded-schemas #{"information_schema"})
-
 (defmethod driver/describe-database :presto
   [driver database]
-  (let [schemas (remove excluded-schemas (database->all-schemas driver database))]
+  (let [schemas (remove presto-common/excluded-schemas (database->all-schemas driver database))]
     {:tables (reduce set/union (for [schema schemas]
                                  (describe-schema driver database {:schema schema})))}))
 
 (defmethod driver/describe-table :presto
   [driver {{:keys [catalog] :as details} :details} {schema :schema, table-name :name}]
-  (let [sql            (str "DESCRIBE " (sql.u/quote-name driver :table catalog schema table-name))
+  (let [sql            (presto-common/describe-table-sql driver catalog schema table-name)
         {:keys [rows]} (execute-query-for-sync details sql)]
     {:schema schema
      :name   table-name
      :fields (set (for [[idx [name type]] (m/indexed rows)]
                     {:name              name
                      :database-type     type
-                     :base-type         (presto-type->base-type type)
+                     :base-type         (presto-common/presto-type->base-type type)
                      :database-position idx}))}))
 
-(defmethod driver/db-start-of-week :presto
-  [_]
-  :monday)
-
-(defmethod sql.qp/cast-temporal-string [:presto :Coercion/YYYYMMDDHHMMSSString->Temporal]
-  [driver _coercion-strategy expr]
-  (hsql/call :date_parse expr (hx/literal "%Y%m%d%H%i%s")))
-
-(defmethod sql.qp/cast-temporal-byte [:presto :Coercion/YYYYMMDDHHMMSSBytes->Temporal]
-  [driver _coercion-strategy expr]
-  (sql.qp/cast-temporal-string driver :Coercion/YYYYMMDDHHMMSSString->Temporal
-                               (hsql/call :from_utf8 expr)))
-
-(defmethod sql.qp/->honeysql [:presto Boolean]
-  [_ bool]
-  (hsql/raw (if bool "TRUE" "FALSE")))
-
-(defmethod sql.qp/->honeysql [:presto :time]
-  [_ [_ t]]
-  (hx/cast :time (u.date/format-sql (t/local-time t))))
-
-(defmethod sql.qp/->float :presto
-  [_ value]
-  (hx/cast :double value))
-
-(defmethod sql.qp/->honeysql [:presto :regex-match-first]
-  [driver [_ arg pattern]]
-  (hsql/call :regexp_extract (sql.qp/->honeysql driver arg) (sql.qp/->honeysql driver pattern)))
-
-(defmethod sql.qp/->honeysql [:presto :median]
-  [driver [_ arg]]
-  (hsql/call :approx_percentile (sql.qp/->honeysql driver arg) 0.5))
-
-(defmethod sql.qp/->honeysql [:presto :percentile]
-  [driver [_ arg p]]
-  (hsql/call :approx_percentile (sql.qp/->honeysql driver arg) (sql.qp/->honeysql driver p)))
-
-(def ^:private ^:dynamic *param-splice-style*
-  "How we should splice params into SQL (i.e. 'unprepare' the SQL). Either `:friendly` (the default) or `:paranoid`.
-  `:friendly` makes a best-effort attempt to escape strings and generate SQL that is nice to look at, but should not
-  be considered safe against all SQL injection -- use this for 'convert to SQL' functionality. `:paranoid` hex-encodes
-  strings so SQL injection is impossible; this isn't nice to look at, so use this for actually running a query."
-  :friendly)
-
-(defmethod unprepare/unprepare-value [:presto String]
-  [_ ^String s]
-  (case *param-splice-style*
-    :friendly (str \' (sql.u/escape-sql s :ansi) \')
-    :paranoid (format "from_utf8(from_hex('%s'))" (codecs/bytes->hex (.getBytes s "UTF-8")))))
-
-;; See https://prestodb.io/docs/current/functions/datetime.html
-
-;; This is only needed for test purposes, because some of the sample data still uses legacy types
-(defmethod unprepare/unprepare-value [:presto Time]
-  [driver t]
-  (unprepare/unprepare-value driver (t/local-time t)))
-
-(defmethod unprepare/unprepare-value [:presto OffsetDateTime]
-  [_ t]
-  (format "timestamp '%s %s %s'" (t/local-date t) (t/local-time t) (t/zone-offset t)))
-
-(defmethod unprepare/unprepare-value [:presto ZonedDateTime]
-  [_ t]
-  (format "timestamp '%s %s %s'" (t/local-date t) (t/local-time t) (t/zone-id t)))
-
 (defmethod driver/execute-reducible-query :presto
   [driver
    {database-id                  :database
@@ -339,7 +239,7 @@
    respond]
   (let [sql     (str "-- "
                      (qputil/query->remark :presto outer-query) "\n"
-                     (binding [*param-splice-style* :paranoid]
+                     (binding [presto-common/*param-splice-style* :paranoid]
                        (unprepare/unprepare driver (cons sql params))))
         details (merge (:details (qp.store/database))
                        settings)]
@@ -359,70 +259,3 @@
 
     #".*"                               ; default
     message))
-
-;;; `:sql-driver` methods
-
-(defmethod sql.qp/apply-top-level-clause [:presto :page]
-  [_ _ honeysql-query {{:keys [items page]} :page}]
-  (let [offset (* (dec page) items)]
-    (if (zero? offset)
-      ;; if there's no offset we can simply use limit
-      (h/limit honeysql-query items)
-      ;; if we need to do an offset we have to do nesting to generate a row number and where on that
-      (let [over-clause (format "row_number() OVER (%s)"
-                                (first (hsql/format (select-keys honeysql-query [:order-by])
-                                                    :allow-dashed-names? true
-                                                    :quoting :ansi)))]
-        (-> (apply h/select (map last (:select honeysql-query)))
-            (h/from (h/merge-select honeysql-query [(hsql/raw over-clause) :__rownum__]))
-            (h/where [:> :__rownum__ offset])
-            (h/limit items))))))
-
-(defmethod sql.qp/date [:presto :default]         [_ _ expr] expr)
-(defmethod sql.qp/date [:presto :minute]          [_ _ expr] (hsql/call :date_trunc (hx/literal :minute) expr))
-(defmethod sql.qp/date [:presto :minute-of-hour]  [_ _ expr] (hsql/call :minute expr))
-(defmethod sql.qp/date [:presto :hour]            [_ _ expr] (hsql/call :date_trunc (hx/literal :hour) expr))
-(defmethod sql.qp/date [:presto :hour-of-day]     [_ _ expr] (hsql/call :hour expr))
-(defmethod sql.qp/date [:presto :day]             [_ _ expr] (hsql/call :date_trunc (hx/literal :day) expr))
-(defmethod sql.qp/date [:presto :day-of-month]    [_ _ expr] (hsql/call :day expr))
-(defmethod sql.qp/date [:presto :day-of-year]     [_ _ expr] (hsql/call :day_of_year expr))
-
-(defmethod sql.qp/date [:presto :day-of-week]
-  [_ _ expr]
-  (sql.qp/adjust-day-of-week :presto (hsql/call :day_of_week expr)))
-
-(defmethod sql.qp/date [:presto :week]
-  [_ _ expr]
-  (sql.qp/adjust-start-of-week :presto (partial hsql/call :date_trunc (hx/literal :week)) expr))
-
-(defmethod sql.qp/date [:presto :month]           [_ _ expr] (hsql/call :date_trunc (hx/literal :month) expr))
-(defmethod sql.qp/date [:presto :month-of-year]   [_ _ expr] (hsql/call :month expr))
-(defmethod sql.qp/date [:presto :quarter]         [_ _ expr] (hsql/call :date_trunc (hx/literal :quarter) expr))
-(defmethod sql.qp/date [:presto :quarter-of-year] [_ _ expr] (hsql/call :quarter expr))
-(defmethod sql.qp/date [:presto :year]            [_ _ expr] (hsql/call :date_trunc (hx/literal :year) expr))
-
-(defmethod sql.qp/unix-timestamp->honeysql [:presto :seconds]
-  [_ _ expr]
-  (hsql/call :from_unixtime expr))
-
-(defmethod driver.common/current-db-time-date-formatters :presto
-  [_]
-  (driver.common/create-db-time-formatters "yyyy-MM-dd'T'HH:mm:ss.SSSZ"))
-
-(defmethod driver.common/current-db-time-native-query :presto
-  [_]
-  "select to_iso8601(current_timestamp)")
-
-(defmethod driver/current-db-time :presto
-  [& args]
-  (apply driver.common/current-db-time args))
-
-(doseq [[feature supported?] {:set-timezone                    true
-                              :basic-aggregations              true
-                              :standard-deviation-aggregations true
-                              :expressions                     true
-                              :native-parameters               true
-                              :expression-aggregations         true
-                              :binning                         true
-                              :foreign-keys                    true}]
-  (defmethod driver/supports? [:presto feature] [_ _] supported?))
diff --git a/modules/drivers/presto/test/metabase/test/data/presto.clj b/modules/drivers/presto/test/metabase/test/data/presto.clj
index 6596ff2d35aa9ddc1a11c82dc5309eaa3856e512..42e32f2dd01e56805257744524c6fbe3adaaf63e 100644
--- a/modules/drivers/presto/test/metabase/test/data/presto.clj
+++ b/modules/drivers/presto/test/metabase/test/data/presto.clj
@@ -7,9 +7,11 @@
             [metabase.config :as config]
             [metabase.driver :as driver]
             [metabase.driver.presto :as presto]
+            [metabase.driver.presto-common :as presto-common]
             [metabase.driver.sql.util :as sql.u]
             [metabase.driver.sql.util.unprepare :as unprepare]
             [metabase.test.data.interface :as tx]
+            [metabase.test.data.presto-common]
             [metabase.test.data.sql :as sql.tx]))
 
 (sql.tx/add-test-extensions! :presto)
@@ -42,19 +44,20 @@
   ;; we need a dummy value for every base-type to make a properly typed SELECT statement
   (if (keyword? field-type)
     (case field-type
-      :type/Boolean        "TRUE"
-      :type/Integer        "1"
-      :type/BigInteger     "cast(1 AS bigint)"
-      :type/Float          "1.0"
-      :type/Decimal        "DECIMAL '1.0'"
-      :type/Text           "cast('' AS VARCHAR)"
-      :type/Date           "current_timestamp" ; this should probably be a date type, but the test data begs to differ
-      :type/DateTime       "current_timestamp"
-      :type/DateTimeWithTZ "current_timestamp"
-      :type/Time           "cast(current_time as TIME)"
+      :type/Boolean                "TRUE"
+      :type/Integer                "1"
+      :type/BigInteger             "cast(1 AS bigint)"
+      :type/Float                  "1.0"
+      :type/Decimal                "DECIMAL '1.0'"
+      :type/Text                   "cast('' AS VARCHAR)"
+      :type/Date                   "current_timestamp" ; this should be a date type, but the test data begs to differ
+      :type/DateTime               "current_timestamp"
+      :type/DateTimeWithTZ         "current_timestamp"
+      :type/DateTimeWithZoneOffset "current_timestamp" ; needed for office-checkins
+      :type/Time                   "cast(current_time as TIME)"
       "from_hex('00')") ; this might not be the best default ever
     ;; we were given a native type, map it back to a base-type and try again
-    (field-base-type->dummy-value (#'presto/presto-type->base-type field-type))))
+    (field-base-type->dummy-value (presto-common/presto-type->base-type field-type))))
 
 (defmethod sql.tx/create-table-sql :presto
   [driver {:keys [database-name]} {:keys [table-name], :as tabledef}]
@@ -129,15 +132,5 @@
   [_ s]
   (str/lower-case s))
 
-(defmethod tx/aggregate-column-info :presto
-  ([driver ag-type]
-   ((get-method tx/aggregate-column-info ::tx/test-extensions) driver ag-type))
-
-  ([driver ag-type field]
-   (merge
-    ((get-method tx/aggregate-column-info ::tx/test-extensions) driver ag-type field)
-    (when (= ag-type :sum)
-      {:base_type :type/BigInteger}))))
-
 ;; FIXME Presto actually has very good timezone support
 (defmethod tx/has-questionable-timezone-support? :presto [_] true)
diff --git a/src/metabase/db/spec.clj b/src/metabase/db/spec.clj
index 5fe60ecbc023d13a0c0524ac86ce4a4108bb40b5..a7e9377a93e40a8e787855feb85337f43f96e9cb 100644
--- a/src/metabase/db/spec.clj
+++ b/src/metabase/db/spec.clj
@@ -2,7 +2,8 @@
   "Functions for creating JDBC DB specs for a given driver.
   Only databases that are supported as application DBs should have functions in this namespace;
   otherwise, similar functions are only needed by drivers, and belong in those namespaces."
-  (:require [metabase.config :as config]))
+  (:require [clojure.string :as str]
+            [metabase.config :as config]))
 
 (defn h2
   "Create a Clojure JDBC database specification for a H2 database."
@@ -14,8 +15,12 @@
           :subname     db}
          (dissoc opts :db)))
 
-(defn- make-subname [host port db]
-  (str "//" host ":" port "/" db))
+(defn make-subname
+  "Make a subname for the given `host`, `port`, and `db` params.  Iff `db` is not blank, then a slash will
+  precede it in the subname."
+  {:arglists '([host port db]), :added "0.39.0"}
+  [host port db]
+  (str "//" host ":" port (if-not (str/blank? db) (str "/" db) "/")))
 
 (defn postgres
   "Create a Clojure JDBC database specification for a Postgres database."
diff --git a/src/metabase/driver/sql/query_processor.clj b/src/metabase/driver/sql/query_processor.clj
index ef867bc2c7a9c04ffeabd5def305bab584f49110..15bc2f6ca41d57884bab653bb5003b6d52b721ff 100644
--- a/src/metabase/driver/sql/query_processor.clj
+++ b/src/metabase/driver/sql/query_processor.clj
@@ -100,7 +100,8 @@
   :hierarchy #'driver/hierarchy)
 
 (defmulti current-datetime-honeysql-form
-  "HoneySQL form that should be used to get the current `datetime` (or equivalent). Defaults to `:%now`."
+  "HoneySQL form that should be used to get the current `datetime` (or equivalent). Defaults to `:%now`. Should ideally
+  include the database type info on the form (ex: via `hx/with-type-info`)."
   {:arglists '([driver])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
diff --git a/src/metabase/plugins/dependencies.clj b/src/metabase/plugins/dependencies.clj
index 05c930e9ff5c42cafcaddb8500430c6d5587e7ca..42ba20da936b56c504bf1c5bc1bfb246be6060d5 100644
--- a/src/metabase/plugins/dependencies.clj
+++ b/src/metabase/plugins/dependencies.clj
@@ -1,5 +1,7 @@
 (ns metabase.plugins.dependencies
-  (:require [clojure.tools.logging :as log]
+  (:require [clojure.string :as str]
+            [clojure.tools.logging :as log]
+            [environ.core :as env]
             [metabase.plugins.classloader :as classloader]
             [metabase.util :as u]
             [metabase.util.i18n :refer [trs]]))
@@ -7,10 +9,11 @@
 (def ^:private plugins-with-unsatisfied-deps
   (atom #{}))
 
-(defn- dependency-type [{classname :class, plugin :plugin}]
+(defn- dependency-type [{classname :class, plugin :plugin, env-var :env-var}]
   (cond
     classname :class
     plugin    :plugin
+    env-var   :env-var
     :else     :unknown))
 
 (defmulti ^:private dependency-satisfied?
@@ -59,6 +62,16 @@
   (log-once plugin-name (trs "Plugin ''{0}'' depends on plugin ''{1}''" plugin-name dep-plugin-name))
   ((set initialized-plugin-names) dep-plugin-name))
 
+(defmethod dependency-satisfied? :env-var
+  [_ {{plugin-name :name} :info, :as info} {env-var-name :env-var}]
+  (if (str/blank? (env/env (keyword env-var-name)))
+    (do
+      (log-once plugin-name (trs "Plugin ''{0}'' depends on environment variable ''{1}'' being set to something"
+                                 plugin-name
+                                 env-var-name))
+      false)
+    true))
+
 (defn- all-dependencies-satisfied?*
   [initialized-plugin-names {:keys [dependencies], {plugin-name :name} :info, :as info}]
   (let [dep-satisfied? (fn [dep]
diff --git a/src/metabase/util/date_2.clj b/src/metabase/util/date_2.clj
index bb51354f12890559dbf25fae1cf87adaf7293d56..0e5304f8fb58f358880be4794392e385d1ed11df 100644
--- a/src/metabase/util/date_2.clj
+++ b/src/metabase/util/date_2.clj
@@ -467,23 +467,15 @@
     converts it to the corresponding offset/zoned type; for offset/zoned types, this applies an appropriate timezone
     shift."))
 
-;; We don't know what zone offset to shift this to, since the offset for a zone-id can vary depending on the date
-;; part of a temporal value (e.g. DST vs non-DST). So just adjust to the non-DST "standard" offset for the zone in
-;; question.
-(defn- standard-offset
-  "Standard (non-DST) offset for a time zone, for cases when we don't have date information."
-  ^java.time.ZoneOffset [^java.time.ZoneId zone-id]
-  (.. zone-id getRules (getStandardOffset (t/instant 0))))
-
 (extend-protocol WithTimeZoneSameInstant
   ;; convert to a OffsetTime with no offset (UTC); the OffsetTime method impl will apply the zone shift.
   LocalTime
   (with-time-zone-same-instant [t zone-id]
-    (t/offset-time t (standard-offset zone-id)))
+    (t/offset-time t (common/standard-offset zone-id)))
 
   OffsetTime
   (with-time-zone-same-instant [t ^java.time.ZoneId zone-id]
-    (t/with-offset-same-instant t (standard-offset zone-id)))
+    (t/with-offset-same-instant t (common/standard-offset zone-id)))
 
   LocalDate
   (with-time-zone-same-instant [t zone-id]
diff --git a/src/metabase/util/date_2/common.clj b/src/metabase/util/date_2/common.clj
index 17095312a1f65de16645231582af66fed609f98e..8836ee9e85c10798fc5aea28bd10645540ff91fd 100644
--- a/src/metabase/util/date_2/common.clj
+++ b/src/metabase/util/date_2/common.clj
@@ -1,7 +1,9 @@
 (ns metabase.util.date-2.common
   (:require [clojure.string :as str]
+            [java-time :as t]
             [metabase.util :as u])
-  (:import [java.time.temporal ChronoField IsoFields TemporalField WeekFields]))
+  (:import [java.time ZoneId ZoneOffset]
+           [java.time.temporal ChronoField IsoFields TemporalField WeekFields]))
 
 ;; TODO - not sure this belongs here, it seems to be a bit more general than just `date-2`.
 
@@ -33,3 +35,12 @@
     :week-fields/week-of-month           (.weekOfMonth WeekFields/SUNDAY_START)
     :week-fields/week-of-week-based-year (.weekOfWeekBasedYear WeekFields/SUNDAY_START)
     :week-fields/week-of-year            (.weekOfYear WeekFields/SUNDAY_START)}))
+
+;; We don't know what zone offset to shift this to, since the offset for a zone-id can vary depending on the date
+;; part of a temporal value (e.g. DST vs non-DST). So just adjust to the non-DST "standard" offset for the zone in
+;; question.
+(defn standard-offset
+  "Standard (non-DST) offset for a time zone, for cases when we don't have date information.  Gets the offset for the
+  given `zone-id` at January 1 of the current year (since that is the best we can do in this situation)."
+  ^ZoneOffset [^ZoneId zone-id]
+  (.. zone-id getRules (getStandardOffset (t/instant (t/offset-date-time (-> (t/zoned-date-time) t/year t/value) 1 1)))))
diff --git a/src/metabase/util/date_2/parse.clj b/src/metabase/util/date_2/parse.clj
index 518312d1d81440fce4233f9528853c3aaa206c13..65e85c94746ab6e27d920cb2c1829261d7088366 100644
--- a/src/metabase/util/date_2/parse.clj
+++ b/src/metabase/util/date_2/parse.clj
@@ -67,7 +67,7 @@
         [:zone   :date]     (ZonedDateTime/of  local-date (t/local-time 0) zone-id)
         [:offset :date]     (OffsetDateTime/of local-date (t/local-time 0) zone-offset)
         [:local  :date]     local-date
-        [:zone   :time]     (OffsetTime/of local-time zone-offset)
+        [:zone   :time]     (OffsetTime/of local-time (or zone-offset (common/standard-offset zone-id)))
         [:offset :time]     (OffsetTime/of local-time zone-offset)
         [:local  :time]     local-time
         (throw (ex-info (tru "Don''t know how to parse {0} using format {1}" (pr-str s) (pr-str formattr))
diff --git a/src/metabase/util/honeysql_extensions.clj b/src/metabase/util/honeysql_extensions.clj
index 6b04ce1361ed77f7e20103e38ffe87b3e46a4e95..aa940041c52b515dd83412e076626495c0f77791 100644
--- a/src/metabase/util/honeysql_extensions.clj
+++ b/src/metabase/util/honeysql_extensions.clj
@@ -204,12 +204,18 @@
   (unwrap-typed-honeysql-form [this]
     (:form this)))
 
+(defn type-info->db-type
+  "For a given type-info, returns the `database-type`."
+  [type-info]
+  {:added "0.39.0"}
+  (::database-type type-info))
+
 (defn is-of-type?
   "Is `honeysql-form` a typed form with `database-type`?
 
     (is-of-type? expr \"datetime\") ; -> true"
   [honeysql-form database-type]
-  (= (::database-type (type-info honeysql-form))
+  (= (type-info->db-type (type-info honeysql-form))
      (some-> database-type name str/lower-case)))
 
 (s/defn with-database-type-info
diff --git a/test/metabase/query_processor_test/breakout_test.clj b/test/metabase/query_processor_test/breakout_test.clj
index 2347486dac29d4f65c290788cb950b2b2ec11e9b..0eeb1f52f23d23e125a515839a70b5d09e275861 100644
--- a/test/metabase/query_processor_test/breakout_test.clj
+++ b/test/metabase/query_processor_test/breakout_test.clj
@@ -244,7 +244,8 @@
                    {:source-query
                     {:source-table $$venues
                      :aggregation  [[:count]]
-                     :breakout     [[:field %latitude {:binning {:strategy :default}}]]}}))))))
+                     :breakout     [[:field %latitude {:binning {:strategy :default}}]]}
+                    :order-by [[:asc $latitude]]}))))))
 
     (testing "Binning is not supported when there is no fingerprint to determine boundaries"
       ;; Unfortunately our new `add-source-metadata` middleware is just too good at what it does and will pull in
diff --git a/test/metabase/query_processor_test/date_bucketing_test.clj b/test/metabase/query_processor_test/date_bucketing_test.clj
index 36132b145a444333753eb2e895b2adecfe5a3b6e..412a20ed4019204a482c65147d72cf61391b0c6a 100644
--- a/test/metabase/query_processor_test/date_bucketing_test.clj
+++ b/test/metabase/query_processor_test/date_bucketing_test.clj
@@ -16,11 +16,13 @@
   their results."
   (:require [clj-time.core :as time]
             [clj-time.format :as tformat]
+            [clojure.string :as str]
             [clojure.test :refer :all]
             [java-time :as t]
             [metabase.driver :as driver]
             [metabase.driver.sql.query-processor :as sql.qp]
             [metabase.driver.sql.query-processor-test :as sql.qp.test]
+            [metabase.driver.sql-jdbc.sync :as sql-jdbc.sync]
             [metabase.models.database :refer [Database]]
             [metabase.query-processor :as qp]
             [metabase.query-processor-test :as qp.test]
@@ -28,6 +30,7 @@
             [metabase.test :as mt]
             [metabase.util :as u]
             [metabase.util.date-2 :as u.date]
+            [metabase.util.honeysql-extensions :as hx]
             [potemkin.types :as p.types]
             [pretty.core :as pretty]
             [toucan.db :as db])
@@ -848,13 +851,24 @@
   (pretty [_]
     (list 'TimestampDatasetDef. intervalSeconds)))
 
+(defn- driver->current-datetime-base-type
+  "Returns the :base-type of the \"current timestamp\" HoneySQL form defined by the driver `d`. Relies upon the driver
+  implementation having set that explicitly via `hx/with-type-info`. Returns `nil` if it can't be determined."
+  [d]
+  (when (isa? driver/hierarchy driver/*driver* :sql)
+    (let [db-type (-> (sql.qp/current-datetime-honeysql-form d)
+                    hx/type-info
+                    hx/type-info->db-type)]
+      (when-not (str/blank? db-type)
+        (sql-jdbc.sync/database-type->base-type d db-type)))))
+
 (defmethod mt/get-dataset-definition TimestampDatasetDef
   [^TimestampDatasetDef this]
   (let [interval-seconds (.intervalSeconds this)]
     (mt/dataset-definition (str "checkins_interval_" interval-seconds)
       ["checkins"
        [{:field-name "timestamp"
-         :base-type  :type/DateTime}]
+         :base-type  (or (driver->current-datetime-base-type driver/*driver*) :type/DateTime)}]
        (vec (for [i (range -15 15)]
               ;; TIMESTAMP FIXME — not sure if still needed
               ;;
@@ -1077,9 +1091,15 @@
   (testing "Additional tests for filtering against various datetime bucketing units that aren't tested above"
     (mt/test-drivers (mt/normal-drivers)
       (doseq [[expected-count unit filter-value] addition-unit-filtering-vals]
-        (testing (format "\nunit = %s" unit)
-          (is (= expected-count (count-of-checkins unit filter-value))
-              (format "count of rows where (= (%s date) %s) should be %d" (name unit) filter-value expected-count)))))))
+        (doseq [tz [nil "UTC"]] ;iterate on at least two report time zones to suss out bugs related to that
+          (mt/with-temporary-setting-values [report-timezone tz]
+            (testing (format "\nunit = %s" unit)
+              (is (= expected-count (count-of-checkins unit filter-value))
+                  (format
+                    "count of rows where (= (%s date) %s) should be %d"
+                    (name unit)
+                    filter-value
+                    expected-count)))))))))
 
 (deftest legacy-default-datetime-bucketing-test
   (testing (str ":type/Date or :type/DateTime fields that don't have `:temporal-unit` clauses should get default `:day` "
diff --git a/test/metabase/query_processor_test/expressions_test.clj b/test/metabase/query_processor_test/expressions_test.clj
index cff90843b1d1c97b74abffd64c0b3e9165f7dbc5..3957181e3e8ee96d54e9b21ecb3ea2a554f7edb0 100644
--- a/test/metabase/query_processor_test/expressions_test.clj
+++ b/test/metabase/query_processor_test/expressions_test.clj
@@ -353,11 +353,11 @@
                  {:source-query {:source-table (mt/id :venues)
                                  :aggregation  [[:min (mt/id :venues :price)]
                                                 [:max (mt/id :venues :price)]]
-                                 :breakout     [[:field (mt/id :venues :name) nil]]}
+                                 :breakout     [[:field (mt/id :venues :name) nil]]
+                                 :limit        3}
                   :expressions  {:price-range [:-
                                                [:field "max" {:base-type :type/Number}]
-                                               [:field "min" {:base-type :type/Number}]]}
-                  :limit        3})))))))
+                                               [:field "min" {:base-type :type/Number}]]}})))))))
 
 (deftest fk-field-and-duplicate-names-test
   ;; Redshift hangs on sample-dataset -- See #14784
diff --git a/test/metabase/query_processor_test/filter_test.clj b/test/metabase/query_processor_test/filter_test.clj
index 96fc24acda66cf42af0533138c6e55c73294cbe5..067aa12d5dded842c8781107d85fe1fdbf3e8fa9 100644
--- a/test/metabase/query_processor_test/filter_test.clj
+++ b/test/metabase/query_processor_test/filter_test.clj
@@ -445,8 +445,8 @@
   (mt/test-drivers (mt/normal-drivers)
     (testing "The QP should automatically parse String parameters in filter clauses to the correct type"
       (testing "String parameter to an Integer Field"
-        (is (= (mt/rows (mt/run-mbql-query venues {:filter [:= $price 4]}))
-               (mt/rows (mt/run-mbql-query venues {:filter [:= $price "4"]}))))))))
+        (is (= (mt/rows (mt/run-mbql-query venues {:filter [:= $price 4] :order-by [[:asc $id]]}))
+               (mt/rows (mt/run-mbql-query venues {:filter [:= $price "4"] :order-by [[:asc $id]]}))))))))
 
 ;; For the tests below:
 ;;
diff --git a/test/metabase/query_processor_test/parameters_test.clj b/test/metabase/query_processor_test/parameters_test.clj
index ada1b027bee728be4ce868fe8c93e3d8e4b6fbd5..f1de41d12e931f9358c80ea786e379b6ca62dac6 100644
--- a/test/metabase/query_processor_test/parameters_test.clj
+++ b/test/metabase/query_processor_test/parameters_test.clj
@@ -86,6 +86,22 @@
                  :target [:dimension [:template-tag (name field)]]
                  :value  value}]})
 
+;; TODO: fix this test for Presto JDBC (detailed explanation follows)
+;; Spent a few hours and need to move on. Here is the query being generated for the failing case
+;;  SELECT count(*) AS "count" FROM "default"."attempts"
+;;    WHERE date_trunc('day', "default"."attempts"."datetime_tz") = date '2019-11-12';
+;; And here is what it *SHOULD* be to pass the test
+;;  SELECT count(*) AS "count" FROM "default"."attempts"
+;;    WHERE date_trunc('day', "default"."attempts"."datetime_tz" AT TIME ZONE 'UTC') = date '2019-11-12';
+;; Notice the AT TIME ZONE 'UTC' part. In this case, the test does not set a report timezone, so a fallback of UTC
+;; should (evidently) be applied.
+;; We need the type information, that the datetime_tz is `timestamp with time zone`, to be available to
+;; (defmethod sql.qp/date [:presto-jdbc :day]
+;; However, it is not available there. The expression's HSQL type-info and db-type are both nil. Somehow need to tell
+;; the query processor (or something else?) to *include* that type information when running this test, because it's
+;; clearly known (i.e. if you sync the DB and then query the `metabase_field`, it is there and is correct.
+;; Tried manually syncing the DB (with attempted-murders dataset), and storing it to an initialized QP, to no avail.
+
 ;; this isn't a complete test for all possible field filter types, but it covers mostly everything
 (deftest field-filter-param-test
   (letfn [(is-count-= [expected-count table field value-type value]
diff --git a/test/metabase/query_processor_test/timezones_test.clj b/test/metabase/query_processor_test/timezones_test.clj
index 17857873d4e0d40c5c0f295a43d678f77e46b0de..0eb62eed080871a94c1677cd301455b1810a8627 100644
--- a/test/metabase/query_processor_test/timezones_test.clj
+++ b/test/metabase/query_processor_test/timezones_test.clj
@@ -119,8 +119,8 @@
                                              (hsql/raw "{{date1}}")
                                              (hsql/raw "{{date2}}")]
                                   :order-by [[(field-identifier :users :id) :asc]]})
-                 :template-tags {:date1 {:name "date1" :display_name "Date1" :type "date" }
-                                 :date2 {:name "date2" :display_name "Date2" :type "date" }}}
+                 :template-tags {:date1 {:name "date1" :display_name "Date1" :type "date"}
+                                 :date2 {:name "date2" :display_name "Date2" :type "date"}}}
     :parameters [{:type   "date/single"
                   :target ["variable" ["template-tag" "date1"]]
                   :value  "2014-08-02T02:00:00.000000"}
@@ -224,6 +224,6 @@
     (mt/dataset attempted-murders
       (doseq [timezone [nil "US/Pacific" "US/Eastern" "Asia/Hong_Kong"]]
         (mt/with-temporary-setting-values [report-timezone timezone]
-          (let [expected (expected-attempts)]
-            (is (= expected
-                   (select-keys (attempts) (keys expected))))))))))
+          (let [expected (expected-attempts)
+                actual   (select-keys (attempts) (keys expected))]
+            (is (= expected actual))))))))
diff --git a/test/metabase/test/data/dataset_definitions.clj b/test/metabase/test/data/dataset_definitions.clj
index 6eb9851b68ee6f2700c77764bae91a2bd5418b26..df74a3751cc5c2f002a57fb43d093bca148c1d63 100644
--- a/test/metabase/test/data/dataset_definitions.clj
+++ b/test/metabase/test/data/dataset_definitions.clj
@@ -209,5 +209,4 @@
        (t/local-time t)                 ; time
        (t/offset-time t)                ; time-ltz
        (t/offset-time t)                ; time-tz
-       cnt                              ; num-crows
-       ])]])
+       cnt])]])                              ; num-crows
diff --git a/test/metabase/test/data/dataset_definitions/office-checkins.edn b/test/metabase/test/data/dataset_definitions/office-checkins.edn
index 92c42085a468a81c097039a796c48d30ec2da71a..55ff4a78384e5c5842701ee662f86fdf93cde502 100644
--- a/test/metabase/test/data/dataset_definitions/office-checkins.edn
+++ b/test/metabase/test/data/dataset_definitions/office-checkins.edn
@@ -1,5 +1,5 @@
 [["checkins" [{:field-name "person", :base-type :type/Text}
-              {:field-name "timestamp", :base-type :type/DateTime}]
+              {:field-name "timestamp", :base-type :type/DateTimeWithZoneOffset}]
   [["Cam",    #t "2019-01-02T05:30:00.000-07:00"]
    ["Cam",    #t "2019-01-09T05:30:00.000-07:00"]
    ["Kyle",   #t "2019-01-06T08:30:00.000-07:00"]
diff --git a/test/metabase/util/date_2_test.clj b/test/metabase/util/date_2_test.clj
index d84aa4dc9326aaf78a347d7880bc42ce4ebd29b7..69696a8f4b48ed84e98d3e4907ccda8d206ac632 100644
--- a/test/metabase/util/date_2_test.clj
+++ b/test/metabase/util/date_2_test.clj
@@ -586,3 +586,11 @@
   (testing "can handle infinity dates (#12761)"
     (is (u.date/with-time-zone-same-instant java.time.OffsetDateTime/MAX (t/zone-id "UTC")))
     (is (u.date/with-time-zone-same-instant java.time.OffsetDateTime/MIN (t/zone-id "UTC")))))
+
+(deftest standard-offset-test
+  (testing "standard-offset works correctly, regardless of system clock timezone"
+    (doseq [clk [#t "2021-01-01T00:00-06:00[US/Central]"   ; one in CST
+                 #t "2021-07-01T00:00-05:00[US/Central]"]] ; one in CDT
+      (mt/with-clock clk
+        (is (= (t/zone-offset "-06:00") (u.date.common/standard-offset (t/zone-id "America/Chicago"))))
+        (is (= (t/zone-offset "Z") (u.date.common/standard-offset (t/zone-id "UTC"))))))))