diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj
index 02ba3e9dfa2dc63a2dde0d3145884979ab995686..2d3c1582a9cc07cdc28ff283c2b6e128c5d7fda4 100644
--- a/src/metabase/api/card.clj
+++ b/src/metabase/api/card.clj
@@ -324,7 +324,10 @@
 
 ;;; ------------------------------------------------------------ Running a Query ------------------------------------------------------------
 
-(defn- run-query-for-card [card-id parameters constraints]
+(defn run-query-for-card
+  "Run the query for Card with PARAMETERS and CONSTRAINTS, and return results in the usual format."
+  [card-id & {:keys [parameters constraints]
+              :or   {constraints dataset-api/default-query-constraints}}]
   {:pre [(u/maybe? sequential? parameters)]}
   (let [card    (read-check Card card-id)
         query   (assoc (:dataset_query card)
@@ -337,19 +340,19 @@
 (defendpoint POST "/:card-id/query"
   "Run the query associated with a Card."
   [card-id :as {{:keys [parameters]} :body}]
-  (run-query-for-card card-id parameters dataset-api/query-constraints))
+  (run-query-for-card card-id, :parameters parameters))
 
 (defendpoint POST "/:card-id/query/csv"
   "Run the query associated with a Card, and return its results as CSV. Note that this expects the parameters as serialized JSON in the 'parameters' parameter"
   [card-id parameters]
   {parameters (s/maybe su/JSONString)}
-  (dataset-api/as-csv (run-query-for-card card-id (json/parse-string parameters keyword) nil)))
+  (dataset-api/as-csv (run-query-for-card card-id, :parameters (json/parse-string parameters keyword), :constraints nil)))
 
 (defendpoint POST "/:card-id/query/json"
   "Run the query associated with a Card, and return its results as JSON. Note that this expects the parameters as serialized JSON in the 'parameters' parameter"
   [card-id parameters]
   {parameters (s/maybe su/JSONString)}
-  (dataset-api/as-json (run-query-for-card card-id (json/parse-string parameters keyword) nil)))
+  (dataset-api/as-json (run-query-for-card card-id, :parameters (json/parse-string parameters keyword), :constraints nil)))
 
 
 (define-routes)
diff --git a/src/metabase/api/common.clj b/src/metabase/api/common.clj
index c6597276982e4f154398bd6bfd85e66f7336155d..9ece0a037128038dcc00afc2d65282e10847130c 100644
--- a/src/metabase/api/common.clj
+++ b/src/metabase/api/common.clj
@@ -64,9 +64,11 @@
          (recur (first rest-args) (second rest-args) (drop 2 rest-args))))))
 
 (defn check-exists?
-  "Check that object with ID exists in the DB, or throw a 404."
-  [entity id]
-  (check-404 (db/exists? entity, :id id)))
+  "Check that object with ID (or other key/values) exists in the DB, or throw a 404."
+  ([entity id]
+   (check-exists? entity :id id))
+  ([entity k v & more]
+   (check-404 (apply db/exists? entity k v more))))
 
 (defn check-superuser
   "Check that `*current-user*` is a superuser or throw a 403. This doesn't require a DB call."
@@ -210,6 +212,7 @@
 
 ;;; ------------------------------------------------------------ DEFENDPOINT AND RELATED FUNCTIONS ------------------------------------------------------------
 
+;; TODO - several of the things `defendpoint` does could and should just be done by custom Ring middleware instead
 (defmacro defendpoint
   "Define an API function.
    This automatically does several things:
diff --git a/src/metabase/api/dataset.clj b/src/metabase/api/dataset.clj
index 9fc602ee412f4f90230cee28a86dd270a648c334..bed6de0afbef4ae75f2373adbb551039b860e493 100644
--- a/src/metabase/api/dataset.clj
+++ b/src/metabase/api/dataset.clj
@@ -21,7 +21,7 @@
   "General maximum number of rows to return from an API query."
   10000)
 
-(def ^:const query-constraints
+(def ^:const default-query-constraints
   "Default map of constraints that we apply on dataset queries executed by the api."
   {:max-results           max-results
    :max-results-bare-rows max-results-bare-rows})
@@ -31,7 +31,7 @@
   [:as {{:keys [database] :as body} :body}]
   (read-check Database database)
   ;; add sensible constraints for results limits on our query
-  (let [query (assoc body :constraints query-constraints)]
+  (let [query (assoc body :constraints default-query-constraints)]
     (qp/dataset-query query {:executed-by *current-user-id*})))
 
 (defendpoint POST "/duration"
@@ -39,7 +39,7 @@
   [:as {{:keys [database] :as query} :body}]
   (read-check Database database)
   ;; add sensible constraints for results limits on our query
-  (let [query         (assoc query :constraints query-constraints)
+  (let [query         (assoc query :constraints default-query-constraints)
         running-times (db/select-field :running_time QueryExecution
                         :query_hash (hash query)
                         {:order-by [[:started_at :desc]]
diff --git a/src/metabase/public_settings.clj b/src/metabase/public_settings.clj
index afc5bc5b03e6521e962111f5712bccfd6c91d00b..c176840ef4891fa45bbe431d0719dcbb19b25c8f 100644
--- a/src/metabase/public_settings.clj
+++ b/src/metabase/public_settings.clj
@@ -68,20 +68,20 @@
 (defn public-settings
   "Return a simple map of key/value pairs which represent the public settings (`MetabaseBootstrap`) for the front-end application."
   []
-  {:ga_code               "UA-60817802-1"
-   :password_complexity   password/active-password-complexity
-   :timezones             common/timezones
-   :version               config/mb-version-info
-   :engines               ((resolve 'metabase.driver/available-drivers))
-   :setup_token           ((resolve 'metabase.setup/token-value))
+  {:admin_email           (admin-email)
    :anon_tracking_enabled (anon-tracking-enabled)
-   :site_name             (site-name)
+   :custom_geojson        (setting/get :custom-geojson)
    :email_configured      ((resolve 'metabase.email/email-configured?))
-   :admin_email           (admin-email)
-   :report_timezone       (setting/get :report-timezone)
-   :timezone_short        (short-timezone-name (setting/get :report-timezone))
-   :has_sample_dataset    (db/exists? 'Database, :is_sample true)
+   :engines               ((resolve 'metabase.driver/available-drivers))
+   :ga_code               "UA-60817802-1"
    :google_auth_client_id (setting/get :google-auth-client-id)
+   :has_sample_dataset    (db/exists? 'Database, :is_sample true)
    :map_tile_server_url   (map-tile-server-url)
-   :custom_geojson        (setting/get :custom-geojson)
-   :types                 (types/types->parents)})
+   :password_complexity   password/active-password-complexity
+   :report_timezone       (setting/get :report-timezone)
+   :setup_token           ((resolve 'metabase.setup/token-value))
+   :site_name             (site-name)
+   :timezone_short        (short-timezone-name (setting/get :report-timezone))
+   :timezones             common/timezones
+   :types                 (types/types->parents)
+   :version               config/mb-version-info})
diff --git a/src/metabase/query_processor.clj b/src/metabase/query_processor.clj
index 3d2e62ce5479dd65ee80e7f49994039f77769dd0..531c161fb3d3d01fe85f77056ba70b520922c7d0 100644
--- a/src/metabase/query_processor.clj
+++ b/src/metabase/query_processor.clj
@@ -508,65 +508,17 @@
 ;;; |                                     DATASET-QUERY PUBLIC API                                       |
 ;;; +----------------------------------------------------------------------------------------------------+
 
-(declare query-fail query-complete save-query-execution!)
-
-(defn- assert-valid-query-result [query-result]
-  (when-not (contains? query-result :status)
-    (throw (Exception. "invalid response from database driver. no :status provided")))
-  (when (= :failed (:status query-result))
-    (log/error (u/pprint-to-str 'red query-result))
-    (throw (Exception. (str (get query-result :error "general error"))))))
-
-(defn dataset-query
-  "Process and run a json based dataset query and return results.
-
-  Takes 2 arguments:
-
-  1.  the json query as a dictionary
-  2.  query execution options specified in a dictionary
-
-  Depending on the database specified in the query this function will delegate to a driver specific implementation.
-  For the purposes of tracking we record each call to this function as a QueryExecution in the database.
-
-  Possible caller-options include:
-
-    :executed-by [int]  (User ID of caller)
-    :card-id     [int]  (ID of Card associated with this execution)"
-  {:arglists '([query options])}
-  [query {:keys [executed-by card-id]}]
-  {:pre [(integer? executed-by) (u/maybe? integer? card-id)]}
-  (let [query-uuid      (str (java.util.UUID/randomUUID))
-        query-hash      (hash query)
-        query-execution {:uuid              query-uuid
-                         :executor_id       executed-by
-                         :json_query        query
-                         :query_hash        query-hash
-                         :version           0
-                         :status            :starting
-                         :error             ""
-                         :started_at        (u/new-sql-timestamp)
-                         :finished_at       (u/new-sql-timestamp)
-                         :running_time      0
-                         :result_rows       0
-                         :result_file       ""
-                         :result_data       "{}"
-                         :raw_query         ""
-                         :additional_info   ""
-                         :start_time_millis (System/currentTimeMillis)}
-        query           (assoc query :info {:executed-by executed-by
-                                            :card-id     card-id
-                                            :uuid        query-uuid
-                                            :query-hash  query-hash
-                                            :query-type  (if (mbql-query? query) "MBQL" "native")})]
-    (try
-      (let [result (process-query query)]
-        (assert-valid-query-result result)
-        (query-complete query-execution result))
-      (catch Throwable e
-        (log/error (u/format-color 'red "Query failure: %s\n%s" (.getMessage e) (u/pprint-to-str (u/filtered-stacktrace e))))
-        (query-fail query-execution (.getMessage e))))))
+(defn- save-query-execution!
+  "Save (or update) a `QueryExecution`."
+  [{:keys [id], :as query-execution}]
+  (if id
+    ;; execution has already been saved, so update it
+    (u/prog1 query-execution
+      (db/update! QueryExecution id query-execution))
+    ;; first time saving execution, so insert it
+    (db/insert! QueryExecution query-execution)))
 
-(defn- query-fail
+(defn- save-and-return-failed-query!
   "Save QueryExecution state and construct a failed query response"
   [query-execution error-message]
   (let [updates {:status       :failed
@@ -586,7 +538,7 @@
                            :cols    []
                            :columns []}))))
 
-(defn- query-complete
+(defn- save-and-return-successful-query!
   "Save QueryExecution state and construct a completed (successful) query response"
   [query-execution query-result]
   ;; record our query execution and format response
@@ -602,12 +554,78 @@
       (dissoc :error :raw_query :result_rows :version)
       (merge query-result)))
 
-(defn- save-query-execution!
-  "Save (or update) a `QueryExecution`."
-  [{:keys [id], :as query-execution}]
-  (if id
-    ;; execution has already been saved, so update it
-    (u/prog1 query-execution
-      (db/update! QueryExecution id query-execution))
-    ;; first time saving execution, so insert it
-    (db/insert! QueryExecution query-execution)))
+
+(defn- assert-query-status-successful
+  "Make sure QUERY-RESULT `:status` is something other than `nil`or `:failed`, or throw an Exception."
+  [query-result]
+  (when-not (contains? query-result :status)
+    (throw (Exception. "invalid response from database driver. no :status provided")))
+  (when (= :failed (:status query-result))
+    (log/error (u/pprint-to-str 'red query-result))
+    (throw (Exception. (str (get query-result :error "general error"))))))
+
+(def ^:dynamic ^Boolean *allow-queries-with-no-executor-id*
+  "Should we allow running queries (via `dataset-query`) without specifying the `executed-by` User ID?
+   By default this is `false`, but this constraint can be disabled for running queries not executed by a specific user
+   (e.g., public Cards)."
+  false)
+
+(defn- query-execution-info
+  "Return the info for the `QueryExecution` entry for this QUERY."
+  [{{:keys [uuid executed-by query-hash]} :info, :as query}]
+  {:uuid              (or uuid (throw (Exception. "Missing query UUID!")))
+   :executor_id       executed-by
+   :json_query        (dissoc query :info)
+   :query_hash        (or query-hash (throw (Exception. "Missing query hash!")))
+   :version           0
+   :error             ""
+   :started_at        (u/new-sql-timestamp)
+   :finished_at       (u/new-sql-timestamp)
+   :running_time      0
+   :result_rows       0
+   :result_file       ""
+   :result_data       "{}"
+   :raw_query         ""
+   :additional_info   ""
+   :start_time_millis (System/currentTimeMillis)})
+
+(defn- run-and-save-query!
+  "Run QUERY and save appropriate `QueryExecution` info, and then return results (or an error message) in the usual format."
+  [query]
+  (let [query-execution (query-execution-info query)]
+    (try
+      (let [result (process-query query)]
+        (assert-query-status-successful result)
+        (save-and-return-successful-query! query-execution result))
+      (catch Throwable e
+        (log/error (u/format-color 'red "Query failure: %s\n%s" (.getMessage e) (u/pprint-to-str (u/filtered-stacktrace e))))
+        (save-and-return-failed-query! query-execution (.getMessage e))))))
+
+(defn dataset-query
+  "Process and run a json based dataset query and return results.
+
+  Takes 2 arguments:
+
+  1.  the json query as a dictionary
+  2.  query execution options specified in a dictionary
+
+  Depending on the database specified in the query this function will delegate to a driver specific implementation.
+  For the purposes of tracking we record each call to this function as a QueryExecution in the database.
+
+  Possible caller-options include:
+
+    :executed-by [int]  (User ID of caller)
+    :card-id     [int]  (ID of Card associated with this execution)"
+  {:arglists '([query options])}
+  [query {:keys [executed-by card-id]}]
+  {:pre [(or (integer? executed-by)
+             *allow-queries-with-no-executor-id*)
+         (u/maybe? integer? card-id)]}
+  (let [query-uuid (str (java.util.UUID/randomUUID))
+        query-hash (hash query)
+        query      (assoc query :info {:executed-by executed-by
+                                       :card-id     card-id
+                                       :uuid        query-uuid
+                                       :query-hash  query-hash
+                                       :query-type  (if (mbql-query? query) "MBQL" "native")})]
+    (run-and-save-query! query)))
diff --git a/test/metabase/api/dataset_test.clj b/test/metabase/api/dataset_test.clj
index c6f2459457da028576d2a26ab9d4dea8fac4e6b8..36221ffa2febb6b3b69d3589a9237a6b80d171e7 100644
--- a/test/metabase/api/dataset_test.clj
+++ b/test/metabase/api/dataset_test.clj
@@ -2,7 +2,7 @@
   "Unit tests for /api/dataset endpoints."
   (:require [clojure.string :as s]
             [expectations :refer :all]
-            [metabase.api.dataset :refer [query-constraints]]
+            [metabase.api.dataset :refer [default-query-constraints]]
             [metabase.db :as db]
             (metabase.models [card :refer [Card]]
                              [query-execution :refer [QueryExecution]])
@@ -65,7 +65,7 @@
                                (ql/aggregation (ql/count))))
                       (assoc :type "query")
                       (assoc-in [:query :aggregation] [{:aggregation-type "count", :custom-name nil}])
-                      (assoc :constraints query-constraints))
+                      (assoc :constraints default-query-constraints))
     :started_at   true
     :finished_at  true
     :running_time true}
@@ -81,7 +81,7 @@
                                (ql/aggregation (ql/count))))
                       (assoc :type "query")
                       (assoc-in [:query :aggregation] [{:aggregation-type "count", :custom-name nil}])
-                      (assoc :constraints query-constraints))
+                      (assoc :constraints default-query-constraints))
     :started_at   true
     :finished_at  true
     :running_time true
@@ -108,7 +108,7 @@
                 :json_query   {:database    (id)
                                :type        "native"
                                :native      {:query "foobar"}
-                               :constraints query-constraints}
+                               :constraints default-query-constraints}
                 :started_at   true
                 :finished_at  true
                 :running_time true}]