From 6331c3b13076d00819eee78fb29b9c0431d35a92 Mon Sep 17 00:00:00 2001
From: Howon Lee <hlee.howon@gmail.com>
Date: Tue, 3 Aug 2021 13:59:58 -0700
Subject: [PATCH] Make the field search do limits (whacks #17228) (#17283)

We're playing this whackamole because we added the middleware to universally sorta stomp on any `limit` or `offset` params, and they got stomped on. Which, in turn, led to my PR to make the default not limited. Which, in turn, led to the other issue this PR solves which makes the search things we relied upon to not have a limit, have a limit.
---
 .../add-card-sidebar/QuestionList.jsx         |  3 +++
 .../src/metabase/nav/components/SearchBar.jsx |  3 ++-
 .../components/data-search/SearchResults.jsx  |  3 +++
 src/metabase/api/field.clj                    | 17 ++++++++++------
 test/metabase/api/field_test.clj              | 20 +++++++++++++++----
 test/metabase/api/search_test.clj             |  6 ++++++
 6 files changed, 41 insertions(+), 11 deletions(-)

diff --git a/frontend/src/metabase/dashboard/components/add-card-sidebar/QuestionList.jsx b/frontend/src/metabase/dashboard/components/add-card-sidebar/QuestionList.jsx
index 470a3eeca91..0395a9e4ddc 100644
--- a/frontend/src/metabase/dashboard/components/add-card-sidebar/QuestionList.jsx
+++ b/frontend/src/metabase/dashboard/components/add-card-sidebar/QuestionList.jsx
@@ -15,6 +15,8 @@ QuestionList.propTypes = {
   hasCollections: PropTypes.bool,
 };
 
+const SEARCH_LIMIT = 1000;
+
 export function QuestionList({
   searchText,
   collectionId,
@@ -35,6 +37,7 @@ export function QuestionList({
   query = {
     ...query,
     models: "card",
+    limit: SEARCH_LIMIT,
   };
 
   return (
diff --git a/frontend/src/metabase/nav/components/SearchBar.jsx b/frontend/src/metabase/nav/components/SearchBar.jsx
index 6437003376f..35943215357 100644
--- a/frontend/src/metabase/nav/components/SearchBar.jsx
+++ b/frontend/src/metabase/nav/components/SearchBar.jsx
@@ -52,6 +52,7 @@ const SearchInput = styled.input`
 `;
 
 const ALLOWED_SEARCH_FOCUS_ELEMENTS = new Set(["BODY", "A"]);
+const SEARCH_LIMIT = 50;
 
 export default class SearchBar extends React.Component {
   state = {
@@ -152,7 +153,7 @@ export default class SearchBar extends React.Component {
                   py={1}
                 >
                   <Search.ListLoader
-                    query={{ q: searchText.trim() }}
+                    query={{ q: searchText.trim(), limit: SEARCH_LIMIT }}
                     wrapped
                     reload
                     debounced
diff --git a/frontend/src/metabase/query_builder/components/data-search/SearchResults.jsx b/frontend/src/metabase/query_builder/components/data-search/SearchResults.jsx
index 60e31ff0114..e5973072fb2 100644
--- a/frontend/src/metabase/query_builder/components/data-search/SearchResults.jsx
+++ b/frontend/src/metabase/query_builder/components/data-search/SearchResults.jsx
@@ -18,6 +18,8 @@ const propTypes = {
   ),
 };
 
+const SEARCH_LIMIT = 1000;
+
 export function SearchResults({
   searchQuery,
   onSelect,
@@ -27,6 +29,7 @@ export function SearchResults({
   const query = {
     q: searchQuery,
     models: searchModels,
+    limit: SEARCH_LIMIT,
   };
 
   if (databaseId) {
diff --git a/src/metabase/api/field.clj b/src/metabase/api/field.clj
index d92c02cbf35..71209a9e3a3 100644
--- a/src/metabase/api/field.clj
+++ b/src/metabase/api/field.clj
@@ -12,6 +12,7 @@
             [metabase.models.table :as table :refer [Table]]
             [metabase.query-processor :as qp]
             [metabase.related :as related]
+            [metabase.server.middleware.offset-paging :as offset-paging]
             [metabase.types :as types]
             [metabase.util :as u]
             [metabase.util.i18n :refer [trs]]
@@ -21,8 +22,11 @@
             [toucan.hydrate :refer [hydrate]])
   (:import java.text.NumberFormat))
 
+
 ;;; --------------------------------------------- Basic CRUD Operations ----------------------------------------------
 
+(def ^:private default-max-field-search-limit 1000)
+
 (def ^:private FieldVisibilityType
   "Schema for a valid `Field` visibility type."
   (apply s/enum (map name field/visibility-types)))
@@ -137,7 +141,6 @@
     ;; return updated field
     (hydrate (Field id) :dimensions)))
 
-
 ;;; ------------------------------------------------- Field Metadata -------------------------------------------------
 
 (api/defendpoint GET "/:id/summary"
@@ -297,6 +300,7 @@
     (db/select-one Field :id fk-target-field-id)
     field))
 
+
 (defn- search-values-query
   "Generate the MBQL query used to power FieldValues search in `search-values` below. The actual query generated differs
   slightly based on whether the two Fields are the same Field."
@@ -326,9 +330,10 @@
       ;; -> ((14 \"Marilyne Mohr\")
              (36 \"Margot Farrell\")
              (48 \"Maryam Douglas\"))"
-  [field search-field value & [limit]]
+  [field search-field value maybe-limit]
   (try
     (let [field   (follow-fks field)
+          limit   (or maybe-limit default-max-field-search-limit)
           results (qp/process-query (search-values-query field search-field value limit))
           rows    (get-in results [:data :rows])]
       ;; if the two Fields are different, we'll get results like [[v1 v2] [v1 v2]]. That is the expected format and we can
@@ -344,17 +349,17 @@
       (log/debug e (trs "Error searching field values"))
       nil)))
 
+
 (api/defendpoint GET "/:id/search/:search-id"
   "Search for values of a Field with `search-id` that start with `value`. See docstring for
   `metabase.api.field/search-values` for a more detailed explanation."
-  [id search-id value limit]
-  {value su/NonBlankString
-   limit (s/maybe su/IntStringGreaterThanZero)}
+  [id search-id value]
+  {value su/NonBlankString}
   (let [field        (api/check-404 (Field id))
         search-field (api/check-404 (Field search-id))]
     (throw-if-no-read-or-segmented-perms field)
     (throw-if-no-read-or-segmented-perms search-field)
-    (search-values field search-field value (when limit (Integer/parseInt limit)))))
+    (search-values field search-field value offset-paging/*limit*)))
 
 (defn remapped-value
   "Search for one specific remapping where the value of `field` exactly matches `value`. Returns a pair like
diff --git a/test/metabase/api/field_test.clj b/test/metabase/api/field_test.clj
index 53543135c87..00235007c8d 100644
--- a/test/metabase/api/field_test.clj
+++ b/test/metabase/api/field_test.clj
@@ -595,7 +595,8 @@
              (mt/format-rows-by [int str]
                (field-api/search-values (Field (mt/id :venues :id))
                                         (Field (mt/id :venues :name))
-                                        "Red")))))
+                                        "Red"
+                                        nil)))))
     (tqp.test/test-timeseries-drivers
       (is (= [["139" "Red Medicine"]
               ["148" "Fred 62"]
@@ -608,7 +609,16 @@
               ["977" "Fred 62"]]
              (field-api/search-values (Field (mt/id :checkins :id))
                                       (Field (mt/id :checkins :venue_name))
-                                      "Red"))))))
+                                      "Red"
+                                      nil)))))
+  (testing "make sure limit works"
+    (mt/test-drivers (mt/normal-drivers)
+      (is (= [[1 "Red Medicine"]]
+             (mt/format-rows-by [int str]
+                                (field-api/search-values (Field (mt/id :venues :id))
+                                                         (Field (mt/id :venues :name))
+                                                         "Red"
+                                                         1)))))))
 
 (deftest search-values-with-field-same-as-search-field-test
   (testing "make sure it also works if you use the same Field twice"
@@ -616,9 +626,11 @@
       (is (= [["Fred 62" "Fred 62"] ["Red Medicine" "Red Medicine"]]
              (field-api/search-values (Field (mt/id :venues :name))
                                       (Field (mt/id :venues :name))
-                                      "Red"))))
+                                      "Red"
+                                      nil))))
     (tqp.test/test-timeseries-drivers
       (is (= [["Fred 62" "Fred 62"] ["Red Medicine" "Red Medicine"]]
              (field-api/search-values (Field (mt/id :checkins :venue_name))
                                       (Field (mt/id :checkins :venue_name))
-                                      "Red"))))))
+                                      "Red"
+                                      nil))))))
diff --git a/test/metabase/api/search_test.clj b/test/metabase/api/search_test.clj
index 1b4f0776356..ad072e50023 100644
--- a/test/metabase/api/search_test.clj
+++ b/test/metabase/api/search_test.clj
@@ -222,6 +222,12 @@
   (testing "It offsets matches properly"
     (with-search-items-in-root-collection "test"
       (is (<= 4 (count (search-request-data :crowberto :q "test" :limit "100" :offset "2"))))))
+  (testing "It offsets without limit properly"
+    (with-search-items-in-root-collection "test"
+      (is (= 4 (count (search-request-data :crowberto :q "test" :offset "2"))))))
+  (testing "It limits without offset properly"
+    (with-search-items-in-root-collection "test"
+      (is (= 2 (count (search-request-data :crowberto :q "test" :limit "2"))))))
   (testing "It subsets matches for model"
     (with-search-items-in-root-collection "test"
       (is (= 0 (count (search-request-data :crowberto :q "test" :models "database"))))
-- 
GitLab