diff --git a/src/metabase/driver/bigquery.clj b/src/metabase/driver/bigquery.clj
index b3292c3f978c740189260166e1d7f91e7fe0c699..2aac92b914f695c4550826bca99125d1dc9b3a0d 100644
--- a/src/metabase/driver/bigquery.clj
+++ b/src/metabase/driver/bigquery.clj
@@ -23,6 +23,7 @@
             [metabase.driver.generic-sql.util.unprepare :as unprepare]
             [metabase.query-processor
              [annotate :as annotate]
+             [store :as qp.store]
              [util :as qputil]]
             [metabase.util
              [date :as du]
@@ -468,24 +469,25 @@
 (defn apply-source-table
   "Copy of the Generic SQL implementation of `apply-source-table` that prepends the current dataset ID to the table
   name."
-  [honeysql-form {{table-name :name} :source-table}]
-  {:pre [(seq table-name)]}
-  (h/from honeysql-form (map->BigQueryIdentifier {:table-name table-name})))
+  [honeysql-form {source-table-id :source-table}]
+  (let [{table-name :name} (qp.store/table source-table-id)]
+    (h/from honeysql-form (map->BigQueryIdentifier {:table-name table-name}))))
 
 (defn- apply-join-tables
   "Copy of the Generic SQL implementation of `apply-join-tables`, but prepends the current dataset ID to join-alias."
-  [honeysql-form {join-tables :join-tables, {source-table-name :name} :source-table}]
-  (loop [honeysql-form honeysql-form, [{:keys [table-name pk-field source-field join-alias]} & more] join-tables]
-    (let [honeysql-form
-          (h/merge-left-join honeysql-form
-            [(map->BigQueryIdentifier {:table-name table-name})
-             (map->BigQueryIdentifier {:table-name join-alias})]
-            [:=
-             (map->BigQueryIdentifier {:table-name source-table-name, :field-name (:field-name source-field)})
-             (map->BigQueryIdentifier {:table-name join-alias,        :field-name (:field-name pk-field)})])]
-      (if (seq more)
-        (recur honeysql-form more)
-        honeysql-form))))
+  [honeysql-form {join-tables :join-tables, source-table-id :source-table}]
+  (let [{source-table-name :name} (qp.store/table source-table-id)]
+    (loop [honeysql-form honeysql-form, [{:keys [table-name pk-field source-field join-alias]} & more] join-tables]
+      (let [honeysql-form
+            (h/merge-left-join honeysql-form
+              [(map->BigQueryIdentifier {:table-name table-name})
+               (map->BigQueryIdentifier {:table-name join-alias})]
+              [:=
+               (map->BigQueryIdentifier {:table-name source-table-name, :field-name (:field-name source-field)})
+               (map->BigQueryIdentifier {:table-name join-alias, :field-name (:field-name pk-field)})])]
+        (if (seq more)
+          (recur honeysql-form more)
+          honeysql-form)))))
 
 (defn- apply-order-by [driver honeysql-form {subclauses :order-by}]
   (loop [honeysql-form honeysql-form, [{:keys [field direction]} & more] subclauses]
@@ -515,10 +517,11 @@
      *  Incldues `table-name` in the resulting map (do not remember why we are doing so, perhaps it is needed to run the
         query)"
   [{{{:keys [dataset-id]} :details, :as database} :database
-    {{table-name :name} :source-table}            :query
+    {source-table-id :source-table}               :query
     :as                                           outer-query}]
-  {:pre [(map? database) (seq dataset-id) (seq table-name)]}
-  (let [aliased-query (pre-alias-aggregations outer-query)]
+  {:pre [(map? database) (seq dataset-id)]}
+  (let [aliased-query      (pre-alias-aggregations outer-query)
+        {table-name :name} (qp.store/table source-table-id)]
     (binding [sqlqp/*query* aliased-query]
       {:query      (->> aliased-query
                        (sqlqp/build-honeysql-form bq-driver)
diff --git a/src/metabase/driver/druid/query_processor.clj b/src/metabase/driver/druid/query_processor.clj
index 1f0ebff8d8f7a6216d95ec36553caab3c0d8d26d..8bf38aa489e5d5ef5264611d5e1b98d2578981b7 100644
--- a/src/metabase/driver/druid/query_processor.clj
+++ b/src/metabase/driver/druid/query_processor.clj
@@ -11,6 +11,7 @@
             [metabase.driver.druid.js :as js]
             [metabase.query-processor
              [annotate :as annotate]
+             [store :as qp.store]
              [interface :as i]]
             [metabase.util :as u]
             [metabase.util.date :as du])
@@ -102,10 +103,9 @@
 
 ;;; ### handle-source-table
 
-(defn- handle-source-table [_ {{source-table-name :name} :source-table} query-context]
-  {:pre [(or (string? source-table-name)
-             (keyword? source-table-name))]}
-  (assoc-in query-context [:query :dataSource] source-table-name))
+(defn- handle-source-table [_ {source-table-id :source-table} query-context]
+  (let [{source-table-name :name} (qp.store/table source-table-id)]
+    (assoc-in query-context [:query :dataSource] source-table-name)))
 
 
 ;;; ### handle-aggregation
diff --git a/src/metabase/driver/generic_sql/query_processor.clj b/src/metabase/driver/generic_sql/query_processor.clj
index cc790d658475af4e400a5f74f7547acb4c56af42..d048a8597efaf6bfc9a927dbd0c7bb0f87d655c0 100644
--- a/src/metabase/driver/generic_sql/query_processor.clj
+++ b/src/metabase/driver/generic_sql/query_processor.clj
@@ -10,9 +10,11 @@
              [driver :as driver]
              [util :as u]]
             [metabase.driver.generic-sql :as sql]
+            [metabase.mbql.util :as mbql.u]
             [metabase.query-processor
              [annotate :as annotate]
              [interface :as i]
+             [store :as qp.store]
              [util :as qputil]]
             [metabase.util
              [date :as du]
@@ -23,6 +25,7 @@
            [metabase.query_processor.interface AgFieldRef BinnedField DateTimeField DateTimeValue Expression
             ExpressionRef Field FieldLiteral JoinQuery JoinTable RelativeDateTimeValue TimeField TimeValue Value]))
 
+;; TODO - yet another `*query*` dynamic var. We should really consolidate them all so we only need a single one.
 (def ^:dynamic *query*
   "The outer query currently being processed."
   nil)
@@ -283,9 +286,11 @@
   "Returns a seq of honeysql join clauses, joining to `table-or-query-expr`. `jt-or-jq` can be either a `JoinTable` or
   a `JoinQuery`"
   [table-or-query-expr {:keys [table-name pk-field source-field schema join-alias] :as jt-or-jq}]
-  (let [{{source-table-name :name, source-schema :schema} :source-table} *query*]
+  (let [source-table-id                                  (mbql.u/query->source-table-id *query*)
+        {source-table-name :name, source-schema :schema} (qp.store/table source-table-id)]
     [[table-or-query-expr (keyword join-alias)]
-     [:= (hx/qualify-and-escape-dots source-schema source-table-name (:field-name source-field))
+     [:=
+      (hx/qualify-and-escape-dots source-schema source-table-name (:field-name source-field))
       (hx/qualify-and-escape-dots join-alias (:field-name pk-field))]]))
 
 (defmethod ->honeysql [Object JoinTable]
@@ -331,9 +336,9 @@
 (defn apply-source-table
   "Apply `source-table` clause to `honeysql-form`. Default implementation of `apply-source-table` for SQL drivers.
   Override as needed."
-  [_ honeysql-form {{table-name :name, schema :schema} :source-table}]
-  {:pre [(seq table-name)]}
-  (h/from honeysql-form (hx/qualify-and-escape-dots schema table-name)))
+  [_ honeysql-form {source-table-id :source-table}]
+  (let [{table-name :name, schema :schema} (qp.store/table source-table-id)]
+    (h/from honeysql-form (hx/qualify-and-escape-dots schema table-name))))
 
 (declare apply-clauses)
 
diff --git a/src/metabase/driver/googleanalytics/query_processor.clj b/src/metabase/driver/googleanalytics/query_processor.clj
index 07c6aaca94fdca7f26292a8488876cf7f6751faf..9aa43275839f328f83879ebfe8de6aa976f28d7e 100644
--- a/src/metabase/driver/googleanalytics/query_processor.clj
+++ b/src/metabase/driver/googleanalytics/query_processor.clj
@@ -4,7 +4,9 @@
   (:require [clojure.string :as s]
             [clojure.tools.reader.edn :as edn]
             [medley.core :as m]
-            [metabase.query-processor.util :as qputil]
+            [metabase.query-processor
+             [store :as qp.store]
+             [util :as qputil]]
             [metabase.util :as u]
             [metabase.util.date :as du])
   (:import [com.google.api.services.analytics.model GaData GaData$ColumnHeaders]
@@ -58,9 +60,9 @@
 
 ;;; ### source-table
 
-(defn- handle-source-table [{{source-table-name :name} :source-table}]
-  {:pre [((some-fn keyword? string?) source-table-name)]}
-  {:ids (str "ga:" source-table-name)})
+(defn- handle-source-table [{source-table-id :source-table}]
+  (let [{source-table-name :name} (qp.store/table source-table-id)]
+    {:ids (str "ga:" source-table-name)}))
 
 
 ;;; ### breakout
diff --git a/src/metabase/driver/mongo/query_processor.clj b/src/metabase/driver/mongo/query_processor.clj
index 444774ddccc7a0eec2529d77938a212c71397de7..6f5e0c1b0b9fccff154ad188961d4aeec5998777 100644
--- a/src/metabase/driver/mongo/query_processor.clj
+++ b/src/metabase/driver/mongo/query_processor.clj
@@ -11,7 +11,8 @@
             [metabase.driver.mongo.util :refer [*mongo-connection*]]
             [metabase.query-processor
              [annotate :as annotate]
-             [interface :as i]]
+             [interface :as i]
+             [store :as qp.store]]
             [metabase.util :as u]
             [metabase.util.date :as du]
             [monger
@@ -19,8 +20,7 @@
              [operators :refer :all]])
   (:import java.sql.Timestamp
            [java.util Date TimeZone]
-           [metabase.query_processor.interface AgFieldRef DateTimeField DateTimeValue Field FieldLiteral
-            RelativeDateTimeValue Value]
+           [metabase.query_processor.interface AgFieldRef DateTimeField DateTimeValue Field FieldLiteral RelativeDateTimeValue Value]
            org.bson.types.ObjectId
            org.joda.time.DateTime))
 
@@ -37,6 +37,7 @@
 
 ;; # DRIVER QP INTERFACE
 
+;; TODO - We already have a *query* dynamic var in metabase.query-processor.interface. Do we need this one too?
 (def ^:dynamic ^:private *query* nil)
 
 (defn- log-monger-form [form]
@@ -505,16 +506,16 @@
 
 (defn mbql->native
   "Process and run an MBQL query."
-  [{database :database, {{source-table-name :name} :source-table} :query, :as query}]
-  {:pre [(map? database)
-         (string? source-table-name)]}
-  (binding [*query* query]
-    (let [{proj :projections, generated-pipeline :query} (generate-aggregation-pipeline (:query query))]
-      (log-monger-form generated-pipeline)
-      {:projections proj
-       :query       generated-pipeline
-       :collection  source-table-name
-       :mbql?       true})))
+  [{database :database, {source-table-id :source-table} :query, :as query}]
+  {:pre [(map? database)]}
+  (let [{source-table-name :name} (qp.store/table source-table-id)]
+    (binding [*query* query]
+      (let [{proj :projections, generated-pipeline :query} (generate-aggregation-pipeline (:query query))]
+        (log-monger-form generated-pipeline)
+        {:projections proj
+         :query       generated-pipeline
+         :collection  source-table-name
+         :mbql?       true}))))
 
 (defn execute-query
   "Process and run a native MongoDB query."
diff --git a/src/metabase/driver/sparksql.clj b/src/metabase/driver/sparksql.clj
index 30287b96a4a398c4f6b6f5acd6e933096b1cb3cc..75486d54fac0a0ddd5742d1c22bb4cbe6c2b08fd 100644
--- a/src/metabase/driver/sparksql.clj
+++ b/src/metabase/driver/sparksql.clj
@@ -15,8 +15,10 @@
              [generic-sql :as sql]
              [hive-like :as hive-like]]
             [metabase.driver.generic-sql.query-processor :as sqlqp]
-            [metabase.models.table :refer [Table]]
-            [metabase.query-processor.util :as qputil]
+            [metabase.mbql.util :as mbql.u]
+            [metabase.query-processor
+             [store :as qp.store]
+             [util :as qputil]]
             [metabase.util
              [honeysql-extensions :as hx]
              [i18n :refer [trs tru]]])
@@ -34,13 +36,8 @@
 
 (def ^:private source-table-alias "t1")
 
-(defn- find-source-table [query]
-  (first (qputil/postwalk-collect #(instance? (type Table) %)
-                                  identity
-                                  query)))
-
 (defn- resolve-table-alias [{:keys [schema-name table-name special-type field-name] :as field}]
-  (let [source-table (find-source-table sqlqp/*query*)]
+  (let [source-table (qp.store/table (mbql.u/query->source-table-id sqlqp/*query*))]
     (if (and (= schema-name (:schema source-table))
              (= table-name (:name source-table)))
       (-> (assoc field :schema-name nil)
@@ -63,12 +60,12 @@
       :else                                               field)))
 
 (defn- apply-join-tables
-  [honeysql-form {join-tables :join-tables, {source-table-name :name, source-schema :schema} :source-table}]
+  [honeysql-form {join-tables :join-tables}]
   (loop [honeysql-form honeysql-form, [{:keys [table-name pk-field source-field schema join-alias]} & more] join-tables]
     (let [honeysql-form (h/merge-left-join honeysql-form
                           [(hx/qualify-and-escape-dots schema table-name) (keyword join-alias)]
                           [:= (hx/qualify-and-escape-dots source-table-alias (:field-name source-field))
-                              (hx/qualify-and-escape-dots join-alias         (:field-name pk-field))])]
+                           (hx/qualify-and-escape-dots join-alias         (:field-name pk-field))])]
       (if (seq more)
         (recur honeysql-form more)
         honeysql-form))))
@@ -91,9 +88,9 @@
             (h/limit items))))))
 
 (defn- apply-source-table
-  [honeysql-form {{table-name :name, schema :schema} :source-table}]
-  {:pre [table-name]}
-  (h/from honeysql-form [(hx/qualify-and-escape-dots schema table-name) source-table-alias]))
+  [honeysql-form {source-table-id :source-table}]
+  (let [{table-name :name, schema :schema} (qp.store/table source-table-id)]
+    (h/from honeysql-form [(hx/qualify-and-escape-dots schema table-name) source-table-alias])))
 
 
 ;;; ------------------------------------------- Other Driver Method Impls --------------------------------------------
diff --git a/src/metabase/events/activity_feed.clj b/src/metabase/events/activity_feed.clj
index c7cd977d409b738d17db3eb59d173ff2175503af..0b6aaae0db20e824436ad40b07a6003dc87296a2 100644
--- a/src/metabase/events/activity_feed.clj
+++ b/src/metabase/events/activity_feed.clj
@@ -5,6 +5,7 @@
              [events :as events]
              [query-processor :as qp]
              [util :as u]]
+            [metabase.mbql.util :as mbql.u]
             [metabase.models
              [activity :as activity :refer [Activity]]
              [card :refer [Card]]
@@ -41,20 +42,12 @@
 
 ;;; ------------------------------------------------ EVENT PROCESSING ------------------------------------------------
 
-(defn- inner-query->source-table-id
-  "Recurse through INNER-QUERY source-queries as needed until we can return the ID of this query's source-table."
-  [{:keys [source-table source-query]}]
-  (or (when source-table
-        (u/get-id source-table))
-      (when source-query
-        (recur source-query))))
-
 (defn- process-card-activity! [topic object]
   (let [details-fn  #(select-keys % [:name :description])
         query       (u/ignore-exceptions (qp/expand (:dataset_query object)))
         database-id (when-let [database (:database query)]
                       (u/get-id database))
-        table-id    (inner-query->source-table-id (:query query))]
+        table-id    (mbql.u/query->source-table-id query)]
     (activity/record-activity!
       :topic       topic
       :object      object
diff --git a/src/metabase/mbql/util.clj b/src/metabase/mbql/util.clj
index 7ee6f40756b0a9512ac926bada7c07e361fd1bc3..cf281a610fdd950ec1bd682e9005b426acc9c178 100644
--- a/src/metabase/mbql/util.clj
+++ b/src/metabase/mbql/util.clj
@@ -140,3 +140,25 @@
   (if-not new-clause
     outer-query
     (update-in outer-query [:query :filter] combine-filter-clauses new-clause)))
+
+
+(defn query->source-table-id
+  "Return the source Table ID associated with `query`, if applicable; handles nested queries as well."
+  {:argslists '([outer-query])}
+  [{{source-table-id :source-table, source-query :source-query} :query, query-type :type, :as query}]
+  (cond
+    ;; for native queries, there's no source table to resolve
+    (not= query-type :query)
+    nil
+
+    ;; for MBQL queries with a *native* source query, it's the same story
+    (and (nil? source-table-id) source-query (:native source-query))
+    nil
+
+    ;; for MBQL queries with an MBQL source query, recurse on the source query and try again
+    (and (nil? source-table-id) source-query)
+    (recur (assoc query :query source-query))
+
+    ;; otherwise resolve the source Table
+    :else
+    source-table-id))
diff --git a/src/metabase/models/query/permissions.clj b/src/metabase/models/query/permissions.clj
index e8f7f5896d8eb995c45e72ba09a077070066c296..7b68dc1b750b8406084e4fa3d80a28e95d9bf084 100644
--- a/src/metabase/models/query/permissions.clj
+++ b/src/metabase/models/query/permissions.clj
@@ -5,7 +5,8 @@
   (:require [clojure.tools.logging :as log]
             [metabase.models
              [interface :as i]
-             [permissions :as perms]]
+             [permissions :as perms]
+             [table :refer [Table]]]
             [metabase.query-processor.util :as qputil]
             [metabase.util :as u]
             [metabase.util
@@ -32,7 +33,7 @@
 ;;          tables->permissions-path-set   source-card-read-perms
 
 (defn- query->source-and-join-tables
-  "Return a sequence of all Tables (as TableInstance maps) referenced by QUERY."
+  "Return a sequence of all Tables (as TableInstance maps, or IDs) referenced by `query`."
   [{:keys [source-table join-tables source-query native], :as query}]
   (cond
     ;; if we come across a native query just put a placeholder (`::native`) there so we know we need to add native
@@ -46,14 +47,24 @@
 (s/defn ^:private tables->permissions-path-set :- #{perms/ObjectPath}
   "Given a sequence of `tables` referenced by a query, return a set of required permissions."
   [database-or-id tables]
-  (set (for [table tables]
-         (if (= ::native table)
-           ;; Any `::native` placeholders from above mean we need native ad-hoc query permissions for this DATABASE
-           (perms/adhoc-native-query-path database-or-id)
-           ;; anything else (i.e., a normal table) just gets normal table permissions
-           (perms/object-path (u/get-id database-or-id)
-                              (:schema table)
-                              (or (:id table) (:table-id table)))))))
+  (let [table-ids        (filter integer? tables)
+        table-id->schema (when (seq table-ids)
+                           (db/select-id->field :schema Table :id [:in table-ids]))]
+    (set (for [table tables]
+           (cond
+             ;; Any `::native` placeholders from above mean we need native ad-hoc query permissions for this DATABASE
+             (= ::native table)
+             (perms/adhoc-native-query-path database-or-id)
+
+             ;; If Table is an ID then fetch its schema from the DB and require normal table perms
+             (integer? table)
+             (perms/object-path (u/get-id database-or-id) (table-id->schema table) table)
+
+             ;; for a TableInstance require normal table perms
+             :else
+             (perms/object-path (u/get-id database-or-id)
+                                (:schema table)
+                                (or (:id table) (:table-id table))))))))
 
 (s/defn ^:private source-card-read-perms :- #{perms/ObjectPath}
   "Calculate the permissions needed to run an ad-hoc query that uses a Card with `source-card-id` as its source
diff --git a/src/metabase/query_processor.clj b/src/metabase/query_processor.clj
index 67733ddef24d7ce23f5b549b8faf08c18bff7416..1445e3d0f778a12bcb066f6625a93f3844a34eb1 100644
--- a/src/metabase/query_processor.clj
+++ b/src/metabase/query_processor.clj
@@ -36,6 +36,7 @@
              [resolve-driver :as resolve-driver]
              [results-metadata :as results-metadata]
              [source-table :as source-table]
+             [store :as store]
              [validate :as validate]]
             [metabase.query-processor.util :as qputil]
             [metabase.util
@@ -94,7 +95,6 @@
       mbql-to-native/mbql->native                      ; ▲▲▲ NATIVE-ONLY POINT ▲▲▲ Query converted from MBQL to native here; all functions *above* will only see the native query
       annotate-and-sort/annotate-and-sort
       perms/check-query-permissions
-      log-query/log-expanded-query
       dev/check-results-format
       limit/limit
       cumulative-ags/handle-cumulative-aggregations
@@ -104,8 +104,8 @@
       resolve/resolve-middleware
       add-dim/add-remapping
       implicit-clauses/add-implicit-clauses
-      source-table/resolve-source-table-middleware
       expand/expand-middleware                         ; ▲▲▲ QUERY EXPANSION POINT  ▲▲▲ All functions *above* will see EXPANDED query during PRE-PROCESSING
+      source-table/resolve-source-table-middleware
       row-count-and-status/add-row-count-and-status    ; ▼▼▼ RESULTS WRAPPING POINT ▼▼▼ All functions *below* will see results WRAPPED in `:data` during POST-PROCESSING
       parameters/substitute-parameters
       expand-macros/expand-macros
@@ -114,7 +114,9 @@
       resolve-driver/resolve-driver                    ; ▲▲▲ DRIVER RESOLUTION POINT ▲▲▲ All functions *above* will have access to the driver during PRE- *and* POST-PROCESSING
       bind-timezone/bind-effective-timezone
       fetch-source-query/fetch-source-query
+      store/initialize-store
       log-query/log-initial-query
+      ;; TODO - bind *query* here ?
       cache/maybe-return-cached-results
       log-query/log-results-metadata
       validate/validate-query
diff --git a/src/metabase/query_processor/interface.clj b/src/metabase/query_processor/interface.clj
index 4c5b1518c58bf822b2e53d5222a4a3931ff2a3f5..e6630b4fa0eb30beb96d33704afdc11fb9e10c62 100644
--- a/src/metabase/query_processor/interface.clj
+++ b/src/metabase/query_processor/interface.clj
@@ -38,6 +38,9 @@
    Not neccesarily bound when using various functions like `fk->` in the REPL."
   nil)
 
+
+;;; ------------------------------------------------------ ETC -------------------------------------------------------
+
 (defn ^:deprecated driver-supports?
   "Does the currently bound `*driver*` support FEATURE?
    (This returns `nil` if `*driver*` is unbound. `*driver*` is always bound when running queries the normal way,
diff --git a/src/metabase/query_processor/middleware/add_implicit_clauses.clj b/src/metabase/query_processor/middleware/add_implicit_clauses.clj
index 820409c2e09ae0d7f7cf88ba86f7418f1a0c51eb..8f76c283f373f023100fc88d3ddb60840208eefa 100644
--- a/src/metabase/query_processor/middleware/add_implicit_clauses.clj
+++ b/src/metabase/query_processor/middleware/add_implicit_clauses.clj
@@ -5,6 +5,7 @@
             [metabase.query-processor
              [interface :as i]
              [sort :as sort]
+             [store :as qp.store]
              [util :as qputil]]
             [metabase.query-processor.middleware.resolve :as resolve]
             [toucan
@@ -26,16 +27,17 @@
 
 (defn- fields-for-source-table
   "Return the all fields for SOURCE-TABLE, for use as an implicit `:fields` clause."
-  [{{source-table-id :id, :as source-table} :source-table, :as inner-query}]
-  ;; Sort the implicit FIELDS so the SQL (or other native query) that gets generated (mostly) approximates the 'magic' sorting
-  ;; we do on the results. This is done so when the outer query we generate is a `SELECT *` the order doesn't change
-  (for [field (sort/sort-fields inner-query (fetch-fields-for-souce-table-id source-table-id))
-        :let  [field (-> field
-                         resolve/convert-db-field
-                         (resolve/resolve-table {[nil source-table-id] source-table}))]]
-    (if (qputil/datetime-field? field)
-      (i/map->DateTimeField {:field field, :unit :default})
-      field)))
+  [{source-table-id :source-table, :as inner-query}]
+  (let [{source-table-id :id, :as source-table} (qp.store/table source-table-id)]
+    ;; Sort the implicit FIELDS so the SQL (or other native query) that gets generated (mostly) approximates the 'magic' sorting
+    ;; we do on the results. This is done so when the outer query we generate is a `SELECT *` the order doesn't change
+    (for [field (sort/sort-fields inner-query (fetch-fields-for-souce-table-id source-table-id))
+          :let  [field (-> field
+                           resolve/convert-db-field
+                           (resolve/resolve-table {[nil source-table-id] source-table}))]]
+      (if (qputil/datetime-field? field)
+        (i/map->DateTimeField {:field field, :unit :default})
+        field))))
 
 (defn- should-add-implicit-fields? [{:keys [fields breakout source-table], aggregations :aggregation}]
   (and source-table ; if query is using another query as its source then there will be no table to add nested fields for
diff --git a/src/metabase/query_processor/middleware/log.clj b/src/metabase/query_processor/middleware/log.clj
index 183668ba9705e625ba3cd1ae21c80d3a404efdc1..10d437d48823c021b9bb13cc18d928ed7c83a662 100644
--- a/src/metabase/query_processor/middleware/log.clj
+++ b/src/metabase/query_processor/middleware/log.clj
@@ -2,36 +2,10 @@
   "Middleware for logging a query before it is processed.
    (Various other middleware functions log the query as well in different stages.)"
   (:require [clojure.tools.logging :as log]
-            [clojure.walk :as walk]
             [medley.core :as m]
-            [metabase.query-processor
-             [interface :as i]
-             [util :as qputil]]
+            [metabase.query-processor.interface :as i]
             [metabase.util :as u]))
 
-(defn- log-expanded-query* [query]
-  (u/prog1 query
-    (when (and (qputil/mbql-query? query)
-               (not i/*disable-qp-logging*))
-      (log/debug (u/format-color 'magenta "\nPREPROCESSED/EXPANDED: %s\n%s"
-                   (u/emoji "😻")
-                   (u/pprint-to-str
-                    ;; Remove empty kv pairs because otherwise expanded query is HUGE
-                    (walk/prewalk
-                     (fn [f]
-                       (if-not (map? f) f
-                               (m/filter-vals (complement nil?) (into {} f))))
-                     ;; obscure DB details when logging. Just log the name of driver because we don't care about its properties
-                     (-> query
-                         (assoc-in [:database :details] (u/emoji "😋 ")) ; :yum:
-                         (update :driver name)))))))))
-
-(defn log-expanded-query
-  "Middleware for logging a query after it is expanded, but before it is processed."
-  [qp]
-  (comp qp log-expanded-query*))
-
-
 (defn- log-initial-query* [query]
   (u/prog1 query
     (when-not i/*disable-qp-logging*
diff --git a/src/metabase/query_processor/middleware/mbql_to_native.clj b/src/metabase/query_processor/middleware/mbql_to_native.clj
index 2179256597c55e5526d57a5f2674f7b9a85f5a49..31270a4fc4fd57bff4c84ef9934b6d60d5af6c7c 100644
--- a/src/metabase/query_processor/middleware/mbql_to_native.clj
+++ b/src/metabase/query_processor/middleware/mbql_to_native.clj
@@ -5,14 +5,12 @@
             [metabase
              [driver :as driver]
              [util :as u]]
-            [metabase.query-processor
-             [interface :as i]
-             [util :as qputil]]))
+            [metabase.query-processor.interface :as i]))
 
 (defn- query->native-form
   "Return a `:native` query form for QUERY, converting it from MBQL if needed."
-  [query]
-  (u/prog1 (if-not (qputil/mbql-query? query)
+  [{query-type :type, :as query}]
+  (u/prog1 (if-not (= :query query-type)
              (:native query)
              (driver/mbql->native (:driver query) query))
     (when-not i/*disable-qp-logging*
@@ -22,9 +20,9 @@
   "Middleware that handles conversion of MBQL queries to native (by calling driver QP methods) so the queries
    can be executed. For queries that are already native, this function is effectively a no-op."
   [qp]
-  (fn [query]
+  (fn [{query-type :type, :as query}]
     (let [native-form  (query->native-form query)
-          native-query (if-not (qputil/mbql-query? query)
+          native-query (if-not (= query-type :query)
                          query
                          (assoc query :native native-form))
           results      (qp native-query)]
diff --git a/src/metabase/query_processor/middleware/resolve.clj b/src/metabase/query_processor/middleware/resolve.clj
index d26ddb8a57195f9e25d60d092606ed5d238ba178..2663b840855c5a34587f17e600b85f9a397fc8f5 100644
--- a/src/metabase/query_processor/middleware/resolve.clj
+++ b/src/metabase/query_processor/middleware/resolve.clj
@@ -10,7 +10,6 @@
             [metabase
              [db :as mdb]
              [util :as u]]
-            [metabase.util.date :as du]
             [metabase.models
              [database :refer [Database]]
              [field :as field]
@@ -18,6 +17,7 @@
              [table :refer [Table]]]
             [metabase.query-processor
              [interface :as i]
+             [store :as qp.store]
              [util :as qputil]]
             [metabase.util.date :as du]
             [schema.core :as s]
@@ -25,8 +25,8 @@
              [db :as db]
              [hydrate :refer [hydrate]]])
   (:import java.util.TimeZone
-           [metabase.query_processor.interface DateTimeField DateTimeValue ExpressionRef Field FieldLiteral FieldPlaceholder
-            RelativeDatetime RelativeDateTimeValue TimeField TimeValue Value ValuePlaceholder]))
+           [metabase.query_processor.interface DateTimeField DateTimeValue ExpressionRef Field FieldLiteral
+            FieldPlaceholder RelativeDatetime RelativeDateTimeValue TimeField TimeValue Value ValuePlaceholder]))
 
 ;;; ---------------------------------------------------- UTIL FNS ----------------------------------------------------
 
@@ -406,31 +406,32 @@
   (if (:native source-query)
     expanded-query-dict
     (let [ ;; Resolve the nested query as if it were a top level query
-          {nested-q :query :as nested-qd} (resolve-tables (assoc expanded-query-dict :query source-query))
-          nested-source-table             (get-in nested-qd [:query :source-table])
+          {nested-inner :query, :as nested-outer} (resolve-tables (assoc expanded-query-dict :query source-query))
+          nested-source-table-id                  (:source-table nested-inner)
           ;; Build a list of join tables found from the newly resolved nested query
-          nested-joined-tables            (fk-field-ids->joined-tables (:id nested-source-table)
-                                                                       (:fk-field-ids nested-qd))
+          nested-joined-tables                    (fk-field-ids->joined-tables nested-source-table-id
+                                                                               (:fk-field-ids nested-outer))
           ;; Create the map of fk to table info from the resolved nested query
-          fk-id+table-id->table           (create-fk-id+table-id->table nested-source-table nested-joined-tables)
+          fk-id+table-id->table                   (create-fk-id+table-id->table (some-> nested-source-table-id qp.store/table)
+                                                                                nested-joined-tables)
           ;; Resolve the top level (original) breakout fields with the join information from the resolved nested query
-          resolved-breakout               (for [breakout (get-in expanded-query-dict [:query :breakout])]
-                                            (resolve-table breakout fk-id+table-id->table))]
+          resolved-breakout                       (for [breakout (get-in expanded-query-dict [:query :breakout])]
+                                                    (resolve-table breakout fk-id+table-id->table))]
       (assoc-in expanded-query-dict [:query :source-query]
-                (if (and (contains? nested-q :fields)
+                (if (and (contains? nested-inner :fields)
                          (seq resolved-breakout))
-                  (update nested-q :fields append-new-fields resolved-breakout)
-                  nested-q)))))
+                  (update nested-inner :fields append-new-fields resolved-breakout)
+                  nested-inner)))))
 
 (defn- resolve-tables
   "Resolve the `Tables` in an EXPANDED-QUERY-DICT."
-  [{:keys [fk-field-ids], {{source-table-id :id :as source-table} :source-table} :query, :as expanded-query-dict}]
+  [{:keys [fk-field-ids], {source-table-id :source-table} :query, :as expanded-query-dict}]
   (if-not source-table-id
     ;; if we have a `source-query`, recurse and resolve tables in that
     (resolve-tables-in-nested-query expanded-query-dict)
     ;; otherwise we can resolve tables in the (current) top-level
     (let [joined-tables         (fk-field-ids->joined-tables source-table-id fk-field-ids)
-          fk-id+table-id->table (create-fk-id+table-id->table source-table joined-tables)]
+          fk-id+table-id->table (create-fk-id+table-id->table (qp.store/table source-table-id) joined-tables)]
       (as-> expanded-query-dict <>
         (assoc-in <> [:query :join-tables]  joined-tables)
         (walk/postwalk #(resolve-table % fk-id+table-id->table) <>)))))
diff --git a/src/metabase/query_processor/middleware/source_table.clj b/src/metabase/query_processor/middleware/source_table.clj
index 262de2f3517519115c03823f9088df780c59d70f..dfd77808c5fe0fb58f691810f97ea3402465bc99 100644
--- a/src/metabase/query_processor/middleware/source_table.clj
+++ b/src/metabase/query_processor/middleware/source_table.clj
@@ -1,25 +1,16 @@
 (ns metabase.query-processor.middleware.source-table
-  (:require [metabase.models.table :refer [Table]]
+  (:require [metabase.mbql.util :as mbql.u]
+            [metabase.models.table :refer [Table]]
+            [metabase.query-processor.store :as qp.store]
+            [metabase.util.i18n :refer [trs]]
             [toucan.db :as db]))
 
-(defn- resolve-source-table
-  [{{source-table-id :source-table} :query, query-type :type, :as expanded-query-dict}]
-  (cond
-    (not= query-type :query)
-    expanded-query-dict
-
-    (nil? source-table-id)
-    (update-in expanded-query-dict [:query :source-query] (fn [source-query]
-                                                            (if (:native source-query)
-                                                              source-query
-                                                              (:query (resolve-source-table (assoc expanded-query-dict
-                                                                                              :query source-query))))))
-
-    :else
+(defn- resolve-source-table [query]
+  (when-let [source-table-id (mbql.u/query->source-table-id query)]
     (let [source-table (or (db/select-one [Table :schema :name :id], :id source-table-id)
-                           (throw (Exception. (format "Query expansion failed: could not find source table %d."
-                                                      source-table-id))))]
-      (assoc-in expanded-query-dict [:query :source-table] source-table))))
+                           (throw (Exception. (str (trs "Cannot run query: could not find source table {0}." source-table-id)))))]
+      (qp.store/store-table! source-table)))
+  query)
 
 (defn resolve-source-table-middleware
   "Middleware that will take the source-table (an integer) and hydrate that source table from the the database and
diff --git a/src/metabase/query_processor/middleware/store.clj b/src/metabase/query_processor/middleware/store.clj
new file mode 100644
index 0000000000000000000000000000000000000000..68eb79b74fdfd2517477a0a23cffb4186c8fc092
--- /dev/null
+++ b/src/metabase/query_processor/middleware/store.clj
@@ -0,0 +1,11 @@
+(ns metabase.query-processor.middleware.store
+  "The store middleware is responsible for initializing a fresh QP Store, which caches resolved objects for the duration
+  of a query execution. See `metabase.query-processor.store` for more details."
+  (:require [metabase.query-processor.store :as qp.store]))
+
+(defn initialize-store
+  "Initialize the QP Store (resolved objects cache) for this query execution."
+  [qp]
+  (fn [query]
+    (qp.store/with-store
+      (qp query))))
diff --git a/src/metabase/query_processor/store.clj b/src/metabase/query_processor/store.clj
new file mode 100644
index 0000000000000000000000000000000000000000..2899160499510d4684129c79c2a086f815025f01
--- /dev/null
+++ b/src/metabase/query_processor/store.clj
@@ -0,0 +1,90 @@
+(ns metabase.query-processor.store
+  "The Query Processor Store caches resolved Tables and Fields for the duration of a query execution. Certain middleware
+  handles resolving things like the query's source Table and any Fields that are referenced in a query, and saves the
+  referenced objects in the store; other middleware and driver-specific query processor implementations use functions
+  in the store to fetch those objects as needed.
+
+  For example, a driver might be converting a Field ID clause (e.g. `[:field-id 10]`) to its native query language. It
+  can fetch the underlying Metabase FieldInstance by calling `field`:
+
+    (qp.store/field 10) ;; get Field 10
+
+   Of course, it would be entirely possible to call `(Field 10)` every time you needed information about that Field,
+  but fetching all Fields in a single pass and storing them for reuse is dramatically more efficient than fetching
+  those Fields potentially dozens of times in a single query execution."
+  (:require [schema.core :as s]
+            [metabase.models
+             [field :refer [Field]]
+             [table :refer [Table]]]
+            [metabase.util :as u]
+            [metabase.util.schema :as su]))
+
+;;; ---------------------------------------------- Setting up the Store ----------------------------------------------
+
+(def ^:private ^:dynamic *store*
+  "Dynamic var used as the QP store for a given query execution."
+  (atom nil))
+
+(defn do-with-store
+  "Execute `f` with a freshly-bound `*store*`."
+  [f]
+  (binding [*store* (atom {})]
+    (f)))
+
+(defmacro with-store
+  "Execute `body` with a freshly-bound QP `*store*`. The `store` middleware takes care of setting up a fresh store for
+  each query execution; you should have no need to use this macro yourself outside of that namespace."
+  {:style/indent 0}
+  [& body]
+  `(do-with-store (fn [] ~@body)))
+
+;; TODO - DATABASE ??
+
+(def ^:private TableInstanceWithRequiredStoreKeys
+  "Schema for Tables stored in the QP store (must be a `TableInstance` with at least the `:id`, `:schema`, and `:name`
+  keys)."
+  (s/both
+   (class Table)
+   {:id     su/IntGreaterThanZero ; TODO - what's the point of storing ID if it's already the key?
+    :schema (s/maybe s/Str)
+    :name   su/NonBlankString
+    s/Any s/Any}))
+
+
+;;; ------------------------------------------ Saving objects in the Store -------------------------------------------
+
+(s/defn store-table!
+  "Store a `table` in the QP Store for the duration of the current query execution. Throws an Exception if Table is
+  invalid or doesn't have all required keys."
+  [table :- TableInstanceWithRequiredStoreKeys]
+  (swap! *store* assoc-in [:tables (u/get-id table)] table))
+
+(s/defn store-field!
+  "Store a `field` in the QP Store for the duration of the current query execution. Throws an Exception if Field is
+  invalid or doesn't have all required keys."
+  [field :- (class Field)]
+  (swap! *store* assoc-in [:fields (u/get-id field)] field))
+
+(s/defn store-field-literal!
+  "Like `store-field!`, but designed for storing Fields referred to by `:field-literal` clauses, i.e. by name instead of
+  by ID."
+  [field-literal :- su/NonBlankString, field :- (class Field)]
+  (swap! *store* assoc-in [:field-literals field-literal] field))
+
+
+;;; ---------------------------------------- Fetching objects from the Store -----------------------------------------
+
+(s/defn table :- TableInstanceWithRequiredStoreKeys
+  "Fetch Table with `table-id` from the QP Store. Throws an Exception if valid item is not returned."
+  [table-id :- su/IntGreaterThanZero]
+  (get-in @*store* [:tables table-id]))
+
+(s/defn field :- (class Field)
+  "Fetch Field with `field-id` from the QP Store. Throws an Exception if valid item is not returned."
+  [field-id :- su/IntGreaterThanZero]
+  (get-in @*store* [:fields field-id]))
+
+(s/defn field-liteal :- (class Field)
+  "Fetch Field named by `field-literal` from the QP Store. Throws an Exception if valid item is not returned."
+  [field-literal :- su/NonBlankString]
+  (get-in @*store* [:field-literals field-literal]))
diff --git a/test/metabase/driver/googleanalytics_test.clj b/test/metabase/driver/googleanalytics_test.clj
index a68299ca2100bd983b077302b0b752edb270e1c7..a81582266b29468dc45a2fbf849ebd40a41b252d 100644
--- a/test/metabase/driver/googleanalytics_test.clj
+++ b/test/metabase/driver/googleanalytics_test.clj
@@ -7,7 +7,9 @@
              [database :refer [Database]]
              [field :refer [Field]]
              [table :refer [Table]]]
-            [metabase.query-processor.interface :as qpi]
+            [metabase.query-processor
+             [interface :as qpi]
+             [store :as qp.store]]
             [metabase.test.data.users :as users]
             [metabase.util :as u]
             [metabase.util.date :as du]
@@ -70,7 +72,10 @@
    :mbql? true})
 
 (defn- mbql->native [query]
-  (qp/mbql->native (update query :query (partial merge {:source-table {:name "0123456"}}))))
+  (binding [qp.store/*store* (atom {:tables {1 #metabase.models.table.TableInstance{:name   "0123456"
+                                                                                    :schema nil
+                                                                                    :id     1}}})]
+    (qp/mbql->native (update query :query (partial merge {:source-table 1})))))
 
 ;; just check that a basic almost-empty MBQL query can be compiled
 (expect
diff --git a/test/metabase/query_processor/middleware/fetch_source_query_test.clj b/test/metabase/query_processor/middleware/fetch_source_query_test.clj
index 2eb3b6a050a73673fdafa91408be50fb83740196..a64ef58d9bc5d1fecee1ed83c03c74d9cfa33a6b 100644
--- a/test/metabase/query_processor/middleware/fetch_source_query_test.clj
+++ b/test/metabase/query_processor/middleware/fetch_source_query_test.clj
@@ -66,7 +66,7 @@
 ;; `fetch-source-query`)
 (expect
   (default-expanded-results
-   {:source-query {:source-table {:schema "PUBLIC", :name "VENUES", :id (data/id :venues)}
+   {:source-query {:source-table (data/id :venues)
                    :join-tables  nil}})
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :query
@@ -78,7 +78,8 @@
 (expect
   (let [date-field-literal {:field-name "date", :base-type :type/Date, :binning-strategy nil, :binning-param nil, :fingerprint nil}]
     (default-expanded-results
-     {:source-query {:source-table {:schema "PUBLIC" :name "CHECKINS" :id (data/id :checkins)}, :join-tables nil}
+     {:source-query {:source-table (data/id :checkins)
+                     :join-tables  nil}
       :filter       {:filter-type :between,
                      :field       date-field-literal
                      :min-val     {:value (tcoerce/to-timestamp (du/str->date-time "2015-01-01"))
@@ -115,7 +116,7 @@
   (default-expanded-results
    {:limit        25
     :source-query {:limit 50
-                   :source-query {:source-table {:schema "PUBLIC", :name "VENUES", :id (data/id :venues)}
+                   :source-query {:source-table (data/id :venues)
                                   :limit        100
                                   :join-tables  nil}}})
   (tt/with-temp* [Card [card-1 {:dataset_query {:database (data/id)