diff --git a/.gitignore b/.gitignore
index a45c2e8a8167fb7f89916af744378f5b3058171c..f82f949b1705dec2e57dcb4152c14fdeaaf0eb2a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,4 +52,5 @@ bin/release/aws-eb/metabase-aws-eb.zip
 coverage-summary.json
 .DS_Store
 bin/node_modules/
-*.log
\ No newline at end of file
+*.log
+*.trace.db
diff --git a/frontend/test/__runner__/test_db_fixture.db.h2.db b/frontend/test/__runner__/test_db_fixture.db.h2.db
index 66876709fb7c85c5d9f9dbd49d36cadd87dccce6..cbf0d4ceebfc04b4746082ba533ee6767757e201 100644
Binary files a/frontend/test/__runner__/test_db_fixture.db.h2.db and b/frontend/test/__runner__/test_db_fixture.db.h2.db differ
diff --git a/frontend/test/__support__/integrated_tests.js b/frontend/test/__support__/integrated_tests.js
index ae0cefb8d525196cbbcea37e2d0859d2bfa2ebf5..2ad825f2c9820bd08ce21d83e3d681604aaac029 100644
--- a/frontend/test/__support__/integrated_tests.js
+++ b/frontend/test/__support__/integrated_tests.js
@@ -11,7 +11,13 @@ import "./mocks";
 import { format as urlFormat } from "url";
 import api from "metabase/lib/api";
 import { defer } from "metabase/lib/promise";
-import { DashboardApi, SessionApi } from "metabase/services";
+import {
+  DashboardApi,
+  SessionApi,
+  CardApi,
+  MetricApi,
+  SegmentApi,
+} from "metabase/services";
 import { METABASE_SESSION_COOKIE } from "metabase/lib/cookies";
 import normalReducers from "metabase/reducers-main";
 import publicReducers from "metabase/reducers-public";
@@ -487,6 +493,43 @@ export async function withApiMocks(mocks, test) {
   }
 }
 
+// to help tests cleanup after themselves, since integration tests don't use
+// isolated environments, e.x.
+//
+// beforeAll(async () => {
+//   cleanup.metric(await MetricApi.create({ ... }))
+// })
+// afterAll(cleanup);
+//
+export const cleanup = () => {
+  useSharedAdminLogin();
+  Promise.all(
+    cleanup.actions.splice(0, cleanup.actions.length).map(action => action()),
+  );
+};
+cleanup.actions = [];
+cleanup.fn = action => cleanup.actions.push(action);
+cleanup.metric = metric => cleanup.fn(() => deleteMetric(metric));
+cleanup.segment = segment => cleanup.fn(() => deleteSegment(segment));
+cleanup.question = question => cleanup.fn(() => deleteQuestion(question));
+
+export const deleteQuestion = question =>
+  CardApi.delete({ cardId: getId(question) });
+export const deleteSegment = segment =>
+  SegmentApi.delete({ segmentId: getId(segment), revision_message: "Please" });
+export const deleteMetric = metric =>
+  MetricApi.delete({ metricId: getId(metric), revision_message: "Please" });
+
+const getId = o =>
+  typeof o === "object" && o != null
+    ? typeof o.id === "function" ? o.id() : o.id
+    : o;
+
+export const deleteAllSegments = async () =>
+  Promise.all((await SegmentApi.list()).map(deleteSegment));
+export const deleteAllMetrics = async () =>
+  Promise.all((await MetricApi.list()).map(deleteMetric));
+
 let pendingRequests = 0;
 let pendingRequestsDeferred = null;
 
diff --git a/frontend/test/admin/datamodel/FieldApp.integ.spec.js b/frontend/test/admin/datamodel/FieldApp.integ.spec.js
index 97f9c22f3c13c8e6959b55d26d7872c1ad0860a4..a99ef2c49e7d53d1c17d3031bd8257c361c28605 100644
--- a/frontend/test/admin/datamodel/FieldApp.integ.spec.js
+++ b/frontend/test/admin/datamodel/FieldApp.integ.spec.js
@@ -189,6 +189,7 @@ describe("FieldApp", () => {
       const { store, fieldApp } = await initFieldApp({
         fieldId: CREATED_AT_ID,
       });
+
       const picker = fieldApp.find(SpecialTypeAndTargetPicker);
       const typeSelect = picker.find(Select).at(0);
       click(typeSelect);
@@ -269,7 +270,7 @@ describe("FieldApp", () => {
       await store.dispatch(
         updateField({
           ...createdAtField,
-          special_type: null,
+          special_type: "type/CreationTimestamp",
           fk_target_field_id: null,
         }),
       );
diff --git a/frontend/test/admin/datamodel/datamodel.integ.spec.js b/frontend/test/admin/datamodel/datamodel.integ.spec.js
index e030d5cc3ddb7cc1456c61b44420f99ae4b08757..6bdbaf11694cbfb4b6e7813c7fef5ad8b06d704a 100644
--- a/frontend/test/admin/datamodel/datamodel.integ.spec.js
+++ b/frontend/test/admin/datamodel/datamodel.integ.spec.js
@@ -2,6 +2,8 @@
 import {
   useSharedAdminLogin,
   createTestStore,
+  deleteAllSegments,
+  deleteAllMetrics,
 } from "__support__/integrated_tests";
 import { click, clickButton, setInputValue } from "__support__/enzyme_utils";
 import { mount } from "enzyme";
@@ -199,14 +201,18 @@ describe("admin/datamodel", () => {
       ).toEqual("User countCount");
     });
 
-    afterAll(async () => {
-      await MetabaseApi.table_update({ id: 1, visibility_type: null }); // Sample Dataset
-      await MetabaseApi.field_update({
-        id: 8,
-        visibility_type: "normal",
-        special_type: null,
-      }); // Address
-      await MetabaseApi.field_update({ id: 9, visibility_type: "normal" }); // Address
-    });
+    afterAll(() =>
+      Promise.all([
+        MetabaseApi.table_update({ id: 1, visibility_type: null }), // Sample Dataset
+        MetabaseApi.field_update({
+          id: 8,
+          visibility_type: "normal",
+          special_type: null,
+        }), // Address
+        MetabaseApi.field_update({ id: 9, visibility_type: "normal" }), // Address
+        deleteAllSegments(),
+        deleteAllMetrics(),
+      ]),
+    );
   });
 });
diff --git a/frontend/test/home/HomepageApp.integ.spec.js b/frontend/test/home/HomepageApp.integ.spec.js
index 17248a700d6db02682f8d31d0d9ace920b633f32..71f5658e673e05db685549e246dac7a940c9cc35 100644
--- a/frontend/test/home/HomepageApp.integ.spec.js
+++ b/frontend/test/home/HomepageApp.integ.spec.js
@@ -2,6 +2,7 @@ import {
   useSharedAdminLogin,
   createTestStore,
   createSavedQuestion,
+  cleanup,
 } from "__support__/integrated_tests";
 import { click } from "__support__/enzyme_utils";
 
@@ -22,38 +23,24 @@ import Activity from "metabase/home/components/Activity";
 import ActivityItem from "metabase/home/components/ActivityItem";
 import ActivityStory from "metabase/home/components/ActivityStory";
 import Scalar from "metabase/visualizations/visualizations/Scalar";
-import { CardApi, MetricApi, SegmentApi } from "metabase/services";
+import { MetricApi, SegmentApi } from "metabase/services";
 
 describe("HomepageApp", () => {
-  let questionId = null;
-  let segmentId = null;
-  let metricId = null;
-
   beforeAll(async () => {
     useSharedAdminLogin();
 
     // Create some entities that will show up in the top of activity feed
     // This test doesn't care if there already are existing items in the feed or not
     // Delays are required for having separable creation times for each entity
-    questionId = (await createSavedQuestion(unsavedOrderCountQuestion)).id();
+    cleanup.question(await createSavedQuestion(unsavedOrderCountQuestion));
     await delay(100);
-    segmentId = (await SegmentApi.create(orders_past_300_days_segment)).id;
+    cleanup.segment(await SegmentApi.create(orders_past_300_days_segment));
     await delay(100);
-    metricId = (await MetricApi.create(vendor_count_metric)).id;
+    cleanup.metric(await MetricApi.create(vendor_count_metric));
     await delay(100);
   });
 
-  afterAll(async () => {
-    await MetricApi.delete({
-      metricId,
-      revision_message: "Let's exterminate this metric",
-    });
-    await SegmentApi.delete({
-      segmentId,
-      revision_message: "Let's exterminate this segment",
-    });
-    await CardApi.delete({ cardId: questionId });
-  });
+  afterAll(cleanup);
 
   describe("activity feed", async () => {
     it("shows the expected list of activity", async () => {
diff --git a/frontend/test/metabase-bootstrap.js b/frontend/test/metabase-bootstrap.js
index fad39e0091cb16aba9fcc8ca56097b3224319073..f1716c1a11cf0ed445f3ad777f0a7ebf6246cf5f 100644
--- a/frontend/test/metabase-bootstrap.js
+++ b/frontend/test/metabase-bootstrap.js
@@ -17,43 +17,67 @@ window.MetabaseBootstrap = {
   ],
   available_locales: [["en", "English"]],
   types: {
-    "type/Address": ["type/*"],
-    "type/Array": ["type/Collection"],
-    "type/AvatarURL": ["type/URL"],
+    "type/DruidHyperUnique": ["type/*"],
+    "type/Longitude": ["type/Coordinate"],
+    "type/IPAddress": ["type/TextLike"],
+    "type/URL": ["type/Text"],
     "type/BigInteger": ["type/Integer"],
-    "type/Boolean": ["type/*"],
     "type/Category": ["type/Special"],
-    "type/City": ["type/Category", "type/Address", "type/Text"],
-    "type/Collection": ["type/*"],
-    "type/Coordinate": ["type/Float"],
-    "type/Country": ["type/Category", "type/Address", "type/Text"],
-    "type/Date": ["type/DateTime"],
-    "type/DateTime": ["type/*"],
-    "type/Decimal": ["type/Float"],
-    "type/Description": ["type/Text"],
-    "type/Dictionary": ["type/Collection"],
-    "type/Email": ["type/Text"],
-    "type/FK": ["type/Special"],
-    "type/Float": ["type/Number"],
-    "type/IPAddress": ["type/TextLike"],
-    "type/ImageURL": ["type/URL"],
+    "type/Owner": ["type/User"],
+    "type/TextLike": ["type/*"],
+    "type/Discount": ["type/Number"],
+    "type/UNIXTimestampSeconds": ["type/UNIXTimestamp"],
+    "type/PostgresEnum": ["type/Text"],
+    "type/Time": ["type/DateTime"],
     "type/Integer": ["type/Number"],
-    "type/Latitude": ["type/Coordinate"],
-    "type/Longitude": ["type/Coordinate"],
-    "type/Name": ["type/Category", "type/Text"],
+    "type/Author": ["type/User"],
+    "type/Cost": ["type/Number"],
+    "type/Quantity": ["type/Integer"],
     "type/Number": ["type/*"],
-    "type/PK": ["type/Special"],
-    "type/SerializedJSON": ["type/Text", "type/Collection"],
-    "type/Special": ["type/*"],
+    "type/JoinTimestamp": ["type/DateTime"],
+    "type/Subscription": ["type/Category"],
     "type/State": ["type/Category", "type/Address", "type/Text"],
+    "type/Address": ["type/*"],
+    "type/Source": ["type/Category"],
+    "type/Name": ["type/Category", "type/Text"],
+    "type/Decimal": ["type/Float"],
+    "type/Date": ["type/DateTime"],
     "type/Text": ["type/*"],
-    "type/TextLike": ["type/*"],
-    "type/Time": ["type/DateTime"],
-    "type/UNIXTimestamp": ["type/Integer", "type/DateTime"],
-    "type/UNIXTimestampMilliseconds": ["type/UNIXTimestamp"],
-    "type/UNIXTimestampSeconds": ["type/UNIXTimestamp"],
-    "type/URL": ["type/Text"],
+    "type/FK": ["type/Special"],
+    "type/SerializedJSON": ["type/Text", "type/Collection"],
+    "type/MongoBSONID": ["type/TextLike"],
+    "type/Duration": ["type/Number"],
+    "type/Float": ["type/Number"],
+    "type/CreationTimestamp": ["type/DateTime"],
+    "type/Email": ["type/Text"],
+    "type/City": ["type/Category", "type/Address", "type/Text"],
+    "type/Title": ["type/Category", "type/Text"],
+    "type/Special": ["type/*"],
+    "type/Dictionary": ["type/Collection"],
+    "type/Description": ["type/Text"],
+    "type/Company": ["type/Category"],
+    "type/PK": ["type/Special"],
+    "type/Latitude": ["type/Coordinate"],
+    "type/Coordinate": ["type/Float"],
     "type/UUID": ["type/Text"],
+    "type/Country": ["type/Category", "type/Address", "type/Text"],
+    "type/Boolean": ["type/Category", "type/*"],
+    "type/GrossMargin": ["type/Number"],
+    "type/AvatarURL": ["type/URL"],
+    "type/Share": ["type/Float"],
+    "type/Product": ["type/Category"],
+    "type/ImageURL": ["type/URL"],
+    "type/Price": ["type/Number"],
+    "type/UNIXTimestampMilliseconds": ["type/UNIXTimestamp"],
+    "type/Collection": ["type/*"],
+    "type/User": ["type/*"],
+    "type/Array": ["type/Collection"],
+    "type/Income": ["type/Number"],
+    "type/Comment": ["type/Text"],
+    "type/Score": ["type/Number"],
     "type/ZipCode": ["type/Integer", "type/Address"],
+    "type/DateTime": ["type/*"],
+    "type/UNIXTimestamp": ["type/Integer", "type/DateTime"],
+    "type/Enum": ["type/Category", "type/*"],
   },
 };
diff --git a/frontend/test/parameters/parameters.integ.spec.js b/frontend/test/parameters/parameters.integ.spec.js
index 697217469733d60ed05a636caa753300f52a4169..4f00a026885382baaf3c692814fedc4e2315945a 100644
--- a/frontend/test/parameters/parameters.integ.spec.js
+++ b/frontend/test/parameters/parameters.integ.spec.js
@@ -10,6 +10,7 @@ import {
   logout,
   waitForRequestToComplete,
   waitForAllRequestsToComplete,
+  cleanup,
 } from "__support__/integrated_tests";
 
 import jwt from "jsonwebtoken";
@@ -49,7 +50,15 @@ describe("parameters", () => {
 
     // enable public sharing
     await SettingsApi.put({ key: "enable-public-sharing", value: true });
+    cleanup.fn(() =>
+      SettingsApi.put({ key: "enable-public-sharing", value: false }),
+    );
+
     await SettingsApi.put({ key: "enable-embedding", value: true });
+    cleanup.fn(() =>
+      SettingsApi.put({ key: "enable-embedding", value: false }),
+    );
+
     await SettingsApi.put({
       key: "embedding-secret-key",
       value: METABASE_SECRET_KEY,
@@ -61,6 +70,27 @@ describe("parameters", () => {
       name: "User ID",
       human_readable_field_id: PEOPLE_NAME_FIELD_ID,
     });
+    cleanup.fn(() =>
+      MetabaseApi.field_dimension_delete({
+        fieldId: ORDER_USER_ID_FIELD_ID,
+      }),
+    );
+
+    // set each of these fields to have "has_field_values" = "search"
+    for (const fieldId of [
+      ORDER_USER_ID_FIELD_ID,
+      PEOPLE_ID_FIELD_ID,
+      PEOPLE_NAME_FIELD_ID,
+    ]) {
+      const field = await MetabaseApi.field_get({
+        fieldId: fieldId,
+      });
+      await MetabaseApi.field_update({
+        id: fieldId,
+        has_field_values: "search",
+      });
+      cleanup.fn(() => MetabaseApi.field_update(field));
+    }
 
     const store = await createTestStore();
     await store.dispatch(fetchTableMetadata(1));
@@ -116,6 +146,12 @@ describe("parameters", () => {
       .setDisplay("scalar")
       .setDisplayName("Test Question");
     question = await createSavedQuestion(unsavedQuestion);
+    cleanup.fn(() =>
+      CardApi.update({
+        id: question.id(),
+        archived: true,
+      }),
+    );
 
     // create a dashboard
     dashboard = await createDashboard({
@@ -128,6 +164,13 @@ describe("parameters", () => {
         { name: "User", slug: "user_id", id: "4", type: "id" },
       ],
     });
+    cleanup.fn(() =>
+      DashboardApi.update({
+        id: dashboard.id,
+        archived: true,
+      }),
+    );
+
     const dashcard = await DashboardApi.addcard({
       dashId: dashboard.id,
       cardId: question.id(),
@@ -356,25 +399,7 @@ describe("parameters", () => {
     sharedParametersTests(() => ({ app, store }));
   });
 
-  afterAll(async () => {
-    useSharedAdminLogin();
-    // archive the dash so we don't impact other tests
-    await DashboardApi.update({
-      id: dashboard.id,
-      archived: true,
-    });
-    // archive the card so we don't impact other tests
-    await CardApi.update({
-      id: question.id(),
-      archived: true,
-    });
-    // do some cleanup so that we don't impact other tests
-    await SettingsApi.put({ key: "enable-public-sharing", value: false });
-    await SettingsApi.put({ key: "enable-embedding", value: false });
-    await MetabaseApi.field_dimension_delete({
-      fieldId: ORDER_USER_ID_FIELD_ID,
-    });
-  });
+  afterAll(cleanup);
 });
 
 async function sharedParametersTests(getAppAndStore) {
diff --git a/frontend/test/query_builder/components/FieldList.integ.spec.js b/frontend/test/query_builder/components/FieldList.integ.spec.js
index b73bdb6ec01c8f32fa16d77067f0f42e3ce7ec0a..a0264d65741f0e52c0f54f5332229750d2c1a336 100644
--- a/frontend/test/query_builder/components/FieldList.integ.spec.js
+++ b/frontend/test/query_builder/components/FieldList.integ.spec.js
@@ -4,6 +4,7 @@ jest.mock("metabase/hoc/Remapped");
 import {
   createTestStore,
   useSharedAdminLogin,
+  cleanup,
 } from "__support__/integrated_tests";
 
 import React from "react";
@@ -43,6 +44,7 @@ describe("FieldList", () => {
   beforeAll(async () => {
     useSharedAdminLogin();
   });
+  afterAll(cleanup);
 
   it("should allow using expression as aggregation dimension", async () => {
     const store = await createTestStore();
@@ -73,6 +75,7 @@ describe("FieldList", () => {
     // TODO Atte Keinänen 6/27/17: Check why the result is wrapped in a promise that needs to be resolved manually
     const segment = await (await createSegment(orders_past_300_days_segment))
       .payload;
+    cleanup.segment(segment);
 
     const store = await createTestStore();
     await store.dispatch(fetchDatabases());
diff --git a/frontend/test/query_builder/components/dataref/MetricPane.integ.spec.js b/frontend/test/query_builder/components/dataref/MetricPane.integ.spec.js
index d75b8bf68f6d72241b8d5d277aa675ad0bbeaa49..ef4e546a230899fc514dd5c299a167eeeeebe06d 100644
--- a/frontend/test/query_builder/components/dataref/MetricPane.integ.spec.js
+++ b/frontend/test/query_builder/components/dataref/MetricPane.integ.spec.js
@@ -1,6 +1,7 @@
 import {
   useSharedAdminLogin,
   createTestStore,
+  cleanup,
 } from "__support__/integrated_tests";
 import { click } from "__support__/enzyme_utils";
 
@@ -27,11 +28,10 @@ import { MetricApi } from "metabase/services";
 describe("MetricPane", () => {
   let store = null;
   let queryBuilder = null;
-  let metricId = null;
 
   beforeAll(async () => {
     useSharedAdminLogin();
-    metricId = (await MetricApi.create(vendor_count_metric)).id;
+    cleanup.metric(await MetricApi.create(vendor_count_metric));
     store = await createTestStore();
 
     store.pushPath(Urls.plainQuestion());
@@ -39,12 +39,8 @@ describe("MetricPane", () => {
     await store.waitForActions([INITIALIZE_QB]);
   });
 
-  afterAll(async () => {
-    await MetricApi.delete({
-      metricId,
-      revision_message: "Let's exterminate this metric",
-    });
-  });
+  afterAll(cleanup);
+
   // NOTE: These test cases are intentionally stateful
   // (doing the whole app rendering thing in every single test case would probably slow things down)
 
diff --git a/frontend/test/query_builder/components/dataref/SegmentPane.integ.spec.js b/frontend/test/query_builder/components/dataref/SegmentPane.integ.spec.js
index fe7db890dfd1a342d13233f793a2569e9d4e25ee..335485bed1c10d3982959a9996b7ef183223e759 100644
--- a/frontend/test/query_builder/components/dataref/SegmentPane.integ.spec.js
+++ b/frontend/test/query_builder/components/dataref/SegmentPane.integ.spec.js
@@ -1,6 +1,7 @@
 import {
   useSharedAdminLogin,
   createTestStore,
+  cleanup,
 } from "__support__/integrated_tests";
 import { click } from "__support__/enzyme_utils";
 
@@ -31,11 +32,10 @@ import * as Urls from "metabase/lib/urls";
 describe("SegmentPane", () => {
   let store = null;
   let queryBuilder = null;
-  let segment = null;
 
   beforeAll(async () => {
     useSharedAdminLogin();
-    segment = await SegmentApi.create(orders_past_300_days_segment);
+    cleanup.segment(await SegmentApi.create(orders_past_300_days_segment));
     store = await createTestStore();
 
     store.pushPath(Urls.plainQuestion());
@@ -43,12 +43,7 @@ describe("SegmentPane", () => {
     await store.waitForActions([INITIALIZE_QB]);
   });
 
-  afterAll(async () => {
-    await SegmentApi.delete({
-      segmentId: segment.id,
-      revision_message: "Please",
-    });
-  });
+  afterAll(cleanup);
 
   // NOTE: These test cases are intentionally stateful
   // (doing the whole app rendering thing in every single test case would probably slow things down)
diff --git a/frontend/test/services/MetabaseApi.integ.spec.js b/frontend/test/services/MetabaseApi.integ.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b3385a38086811b6abec39291ef69da4326634da
--- /dev/null
+++ b/frontend/test/services/MetabaseApi.integ.spec.js
@@ -0,0 +1,33 @@
+import { useSharedAdminLogin } from "__support__/integrated_tests";
+
+import { MetabaseApi } from "metabase/services";
+
+describe("MetabaseApi", () => {
+  beforeAll(() => useSharedAdminLogin());
+  describe("table_query_metadata", () => {
+    // these table IDs correspond to the sample dataset in the fixture db
+    [1, 2, 3, 4].map(tableId =>
+      it(`should have the correct metadata for table ${tableId}`, async () => {
+        expect(
+          stripKeys(await MetabaseApi.table_query_metadata({ tableId })),
+        ).toMatchSnapshot();
+      }),
+    );
+  });
+});
+
+function stripKeys(object) {
+  // handles both arrays and objects
+  if (object && typeof object === "object") {
+    for (const key in object) {
+      if (
+        /^((updated|created)_at|last_analyzed|timezone|is_on_demand)$/.test(key)
+      ) {
+        delete object[key];
+      } else {
+        stripKeys(object[key]);
+      }
+    }
+  }
+  return object;
+}
diff --git a/frontend/test/services/__snapshots__/MetabaseApi.integ.spec.js.snap b/frontend/test/services/__snapshots__/MetabaseApi.integ.spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..3072cd50c4d474e96f26f57702fbcf6e50c98758
--- /dev/null
+++ b/frontend/test/services/__snapshots__/MetabaseApi.integ.spec.js.snap
@@ -0,0 +1,3888 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MetabaseApi table_query_metadata should have the correct metadata for table 1 1`] = `
+Object {
+  "active": true,
+  "caveats": null,
+  "db": Object {
+    "cache_field_values_schedule": "0 0 0 * * ? *",
+    "caveats": null,
+    "description": null,
+    "engine": "h2",
+    "features": Array [
+      "native-query-params",
+      "basic-aggregations",
+      "standard-deviation-aggregations",
+      "expression-aggregations",
+      "foreign-keys",
+      "native-parameters",
+      "nested-queries",
+      "expressions",
+      "binning",
+    ],
+    "id": 1,
+    "is_full_sync": true,
+    "is_sample": true,
+    "metadata_sync_schedule": "0 0 * * * ? *",
+    "name": "Sample Dataset",
+    "options": null,
+    "points_of_interest": null,
+  },
+  "db_id": 1,
+  "description": "This is a confirmed order for a product from a user.",
+  "dimension_options": Object {
+    "0": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "minute",
+      ],
+      "name": "Minute",
+      "type": "type/DateTime",
+    },
+    "1": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "hour",
+      ],
+      "name": "Hour",
+      "type": "type/DateTime",
+    },
+    "10": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-month",
+      ],
+      "name": "Day of Month",
+      "type": "type/DateTime",
+    },
+    "11": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-year",
+      ],
+      "name": "Day of Year",
+      "type": "type/DateTime",
+    },
+    "12": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "week-of-year",
+      ],
+      "name": "Week of Year",
+      "type": "type/DateTime",
+    },
+    "13": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "month-of-year",
+      ],
+      "name": "Month of Year",
+      "type": "type/DateTime",
+    },
+    "14": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "quarter-of-year",
+      ],
+      "name": "Quarter of Year",
+      "type": "type/DateTime",
+    },
+    "15": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "default",
+      ],
+      "name": "Auto bin",
+      "type": "type/Number",
+    },
+    "16": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        10,
+      ],
+      "name": "10 bins",
+      "type": "type/Number",
+    },
+    "17": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        50,
+      ],
+      "name": "50 bins",
+      "type": "type/Number",
+    },
+    "18": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        100,
+      ],
+      "name": "100 bins",
+      "type": "type/Number",
+    },
+    "19": Object {
+      "mbql": null,
+      "name": "Don't bin",
+      "type": "type/Number",
+    },
+    "2": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day",
+      ],
+      "name": "Day",
+      "type": "type/DateTime",
+    },
+    "20": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "default",
+      ],
+      "name": "Auto bin",
+      "type": "type/Coordinate",
+    },
+    "21": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        1,
+      ],
+      "name": "Bin every 1 degree",
+      "type": "type/Coordinate",
+    },
+    "22": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        10,
+      ],
+      "name": "Bin every 10 degrees",
+      "type": "type/Coordinate",
+    },
+    "23": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        20,
+      ],
+      "name": "Bin every 20 degrees",
+      "type": "type/Coordinate",
+    },
+    "24": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        50,
+      ],
+      "name": "Bin every 50 degrees",
+      "type": "type/Coordinate",
+    },
+    "25": Object {
+      "mbql": null,
+      "name": "Don't bin",
+      "type": "type/Coordinate",
+    },
+    "3": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "week",
+      ],
+      "name": "Week",
+      "type": "type/DateTime",
+    },
+    "4": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "month",
+      ],
+      "name": "Month",
+      "type": "type/DateTime",
+    },
+    "5": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "quarter",
+      ],
+      "name": "Quarter",
+      "type": "type/DateTime",
+    },
+    "6": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "year",
+      ],
+      "name": "Year",
+      "type": "type/DateTime",
+    },
+    "7": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "minute-of-hour",
+      ],
+      "name": "Minute of Hour",
+      "type": "type/DateTime",
+    },
+    "8": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "hour-of-day",
+      ],
+      "name": "Hour of Day",
+      "type": "type/DateTime",
+    },
+    "9": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-week",
+      ],
+      "name": "Day of Week",
+      "type": "type/DateTime",
+    },
+  },
+  "display_name": "Orders",
+  "entity_name": null,
+  "entity_type": "entity/TransactionTable",
+  "fields": Array [
+    Object {
+      "active": true,
+      "base_type": "type/DateTime",
+      "caveats": null,
+      "database_type": "TIMESTAMP",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "datetime-field",
+          null,
+          "day",
+        ],
+        "name": "Day",
+        "type": "type/DateTime",
+      },
+      "description": "The date and time an order was submitted.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute",
+          ],
+          "name": "Minute",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour",
+          ],
+          "name": "Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day",
+          ],
+          "name": "Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week",
+          ],
+          "name": "Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month",
+          ],
+          "name": "Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter",
+          ],
+          "name": "Quarter",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "year",
+          ],
+          "name": "Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute-of-hour",
+          ],
+          "name": "Minute of Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour-of-day",
+          ],
+          "name": "Hour of Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-week",
+          ],
+          "name": "Day of Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-month",
+          ],
+          "name": "Day of Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-year",
+          ],
+          "name": "Day of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week-of-year",
+          ],
+          "name": "Week of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month-of-year",
+          ],
+          "name": "Month of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter-of-year",
+          ],
+          "name": "Quarter of Year",
+          "type": "type/DateTime",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Created At",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 1381,
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 1,
+      "max_value": null,
+      "min_value": null,
+      "name": "CREATED_AT",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 9,
+      "special_type": "type/CreationTimestamp",
+      "table_id": 1,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Float",
+      "caveats": null,
+      "database_type": "DOUBLE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Number",
+      },
+      "description": "Discount amount.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            10,
+          ],
+          "name": "10 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            50,
+          ],
+          "name": "50 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            100,
+          ],
+          "name": "100 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Number",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Discount",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 1022,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 5.382008821780497,
+            "max": 70.30699128866013,
+            "min": 0.21249216890034361,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 35,
+      "max_value": null,
+      "min_value": null,
+      "name": "DISCOUNT",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": null,
+      "special_type": "type/Discount",
+      "table_id": 1,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/BigInteger",
+      "caveats": null,
+      "database_type": "BIGINT",
+      "default_dimension_option": null,
+      "description": "This is a unique ID for the product. It is also called the “Invoice number” or “Confirmation number” in customer facing emails and screens.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "ID",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 10000,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 5000.5,
+            "max": 10000,
+            "min": 1,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 2,
+      "max_value": 17624,
+      "min_value": 1,
+      "name": "ID",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 10,
+      "special_type": "type/PK",
+      "table_id": 1,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Integer",
+      "caveats": null,
+      "database_type": "INTEGER",
+      "default_dimension_option": null,
+      "description": "The product ID. This is an internal identifier for the product, NOT the SKU.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Product ID",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 200,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 100.0589,
+            "max": 200,
+            "min": 1,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": 24,
+      "has_field_values": "none",
+      "id": 3,
+      "max_value": 200,
+      "min_value": 1,
+      "name": "PRODUCT_ID",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 11,
+      "special_type": "type/FK",
+      "table_id": 1,
+      "target": Object {
+        "active": true,
+        "base_type": "type/BigInteger",
+        "caveats": null,
+        "database_type": "BIGINT",
+        "description": "The numerical product number. Only used internally. All external communication should use the title or EAN.",
+        "display_name": "ID",
+        "fingerprint": Object {
+          "global": Object {
+            "distinct-count": 200,
+          },
+          "type": Object {
+            "type/Number": Object {
+              "avg": 100.5,
+              "max": 200,
+              "min": 1,
+            },
+          },
+        },
+        "fingerprint_version": 1,
+        "fk_target_field_id": null,
+        "has_field_values": "none",
+        "id": 24,
+        "max_value": 200,
+        "min_value": 1,
+        "name": "ID",
+        "parent_id": null,
+        "points_of_interest": null,
+        "position": 0,
+        "preview_display": true,
+        "raw_column_id": 4,
+        "special_type": "type/PK",
+        "table_id": 3,
+        "visibility_type": "normal",
+      },
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Integer",
+      "caveats": null,
+      "database_type": "INTEGER",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Number",
+      },
+      "description": "Number of products bought.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            10,
+          ],
+          "name": "10 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            50,
+          ],
+          "name": "50 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            100,
+          ],
+          "name": "100 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Number",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Quantity",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 62,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 4.0585,
+            "max": 87,
+            "min": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "list",
+      "id": 36,
+      "max_value": null,
+      "min_value": null,
+      "name": "QUANTITY",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": null,
+      "special_type": "type/Quantity",
+      "table_id": 1,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Float",
+      "caveats": null,
+      "database_type": "DOUBLE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Number",
+      },
+      "description": "The raw, pre-tax cost of the order. Note that this might be different in the future from the product price due to promotions, credits, etc.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            10,
+          ],
+          "name": "10 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            50,
+          ],
+          "name": "50 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            100,
+          ],
+          "name": "100 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Number",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Subtotal",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 400,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 79.59262891828064,
+            "max": 129.06849213878442,
+            "min": 31.478481306013133,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 4,
+      "max_value": 99.37,
+      "min_value": 12.02,
+      "name": "SUBTOTAL",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 12,
+      "special_type": null,
+      "table_id": 1,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Float",
+      "caveats": null,
+      "database_type": "DOUBLE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Number",
+      },
+      "description": "This is the amount of local and federal taxes that are collected on the purchase. Note that other governmental fees on some products are not included here, but instead are accounted for in the subtotal.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            10,
+          ],
+          "name": "10 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            50,
+          ],
+          "name": "50 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            100,
+          ],
+          "name": "100 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Number",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Tax",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 761,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 3.263451999999993,
+            "max": 9.68,
+            "min": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 5,
+      "max_value": 7.45,
+      "min_value": 0,
+      "name": "TAX",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 13,
+      "special_type": null,
+      "table_id": 1,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Float",
+      "caveats": null,
+      "database_type": "DOUBLE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Number",
+      },
+      "description": "The total billed amount.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            10,
+          ],
+          "name": "10 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            50,
+          ],
+          "name": "50 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            100,
+          ],
+          "name": "100 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Number",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Total",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 10000,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 85.6977993498081,
+            "max": 249.05982750877476,
+            "min": 12.377464066307697,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 6,
+      "max_value": 106.82000000000001,
+      "min_value": 12.02,
+      "name": "TOTAL",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 14,
+      "special_type": "type/Income",
+      "table_id": 1,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Integer",
+      "caveats": null,
+      "database_type": "INTEGER",
+      "default_dimension_option": null,
+      "description": "The id of the user who made this order. Note that in some cases where an order was created on behalf of a customer who phoned the order in, this might be the employee who handled the request.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "User ID",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 1787,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 979.7903,
+            "max": 1952,
+            "min": 1,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": 13,
+      "has_field_values": "none",
+      "id": 7,
+      "max_value": 2498,
+      "min_value": 2,
+      "name": "USER_ID",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 15,
+      "special_type": "type/FK",
+      "table_id": 1,
+      "target": Object {
+        "active": true,
+        "base_type": "type/BigInteger",
+        "caveats": null,
+        "database_type": "BIGINT",
+        "description": "A unique identifier given to each user.",
+        "display_name": "ID",
+        "fingerprint": Object {
+          "global": Object {
+            "distinct-count": 2500,
+          },
+          "type": Object {
+            "type/Number": Object {
+              "avg": 1250.5,
+              "max": 2500,
+              "min": 1,
+            },
+          },
+        },
+        "fingerprint_version": 1,
+        "fk_target_field_id": null,
+        "has_field_values": "none",
+        "id": 13,
+        "max_value": 2500,
+        "min_value": 1,
+        "name": "ID",
+        "parent_id": null,
+        "points_of_interest": null,
+        "position": 0,
+        "preview_display": true,
+        "raw_column_id": 21,
+        "special_type": "type/PK",
+        "table_id": 2,
+        "visibility_type": "normal",
+      },
+      "visibility_type": "normal",
+    },
+  ],
+  "id": 1,
+  "metrics": Array [],
+  "name": "ORDERS",
+  "points_of_interest": null,
+  "raw_table_id": 2,
+  "rows": 12805,
+  "schema": "PUBLIC",
+  "segments": Array [],
+  "show_in_getting_started": false,
+  "visibility_type": null,
+}
+`;
+
+exports[`MetabaseApi table_query_metadata should have the correct metadata for table 2 1`] = `
+Object {
+  "active": true,
+  "caveats": null,
+  "db": Object {
+    "cache_field_values_schedule": "0 0 0 * * ? *",
+    "caveats": null,
+    "description": null,
+    "engine": "h2",
+    "features": Array [
+      "native-query-params",
+      "basic-aggregations",
+      "standard-deviation-aggregations",
+      "expression-aggregations",
+      "foreign-keys",
+      "native-parameters",
+      "nested-queries",
+      "expressions",
+      "binning",
+    ],
+    "id": 1,
+    "is_full_sync": true,
+    "is_sample": true,
+    "metadata_sync_schedule": "0 0 * * * ? *",
+    "name": "Sample Dataset",
+    "options": null,
+    "points_of_interest": null,
+  },
+  "db_id": 1,
+  "description": "This is a user account. Note that employees and customer support staff will have accounts.",
+  "dimension_options": Object {
+    "0": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "minute",
+      ],
+      "name": "Minute",
+      "type": "type/DateTime",
+    },
+    "1": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "hour",
+      ],
+      "name": "Hour",
+      "type": "type/DateTime",
+    },
+    "10": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-month",
+      ],
+      "name": "Day of Month",
+      "type": "type/DateTime",
+    },
+    "11": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-year",
+      ],
+      "name": "Day of Year",
+      "type": "type/DateTime",
+    },
+    "12": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "week-of-year",
+      ],
+      "name": "Week of Year",
+      "type": "type/DateTime",
+    },
+    "13": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "month-of-year",
+      ],
+      "name": "Month of Year",
+      "type": "type/DateTime",
+    },
+    "14": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "quarter-of-year",
+      ],
+      "name": "Quarter of Year",
+      "type": "type/DateTime",
+    },
+    "15": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "default",
+      ],
+      "name": "Auto bin",
+      "type": "type/Number",
+    },
+    "16": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        10,
+      ],
+      "name": "10 bins",
+      "type": "type/Number",
+    },
+    "17": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        50,
+      ],
+      "name": "50 bins",
+      "type": "type/Number",
+    },
+    "18": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        100,
+      ],
+      "name": "100 bins",
+      "type": "type/Number",
+    },
+    "19": Object {
+      "mbql": null,
+      "name": "Don't bin",
+      "type": "type/Number",
+    },
+    "2": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day",
+      ],
+      "name": "Day",
+      "type": "type/DateTime",
+    },
+    "20": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "default",
+      ],
+      "name": "Auto bin",
+      "type": "type/Coordinate",
+    },
+    "21": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        1,
+      ],
+      "name": "Bin every 1 degree",
+      "type": "type/Coordinate",
+    },
+    "22": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        10,
+      ],
+      "name": "Bin every 10 degrees",
+      "type": "type/Coordinate",
+    },
+    "23": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        20,
+      ],
+      "name": "Bin every 20 degrees",
+      "type": "type/Coordinate",
+    },
+    "24": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        50,
+      ],
+      "name": "Bin every 50 degrees",
+      "type": "type/Coordinate",
+    },
+    "25": Object {
+      "mbql": null,
+      "name": "Don't bin",
+      "type": "type/Coordinate",
+    },
+    "3": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "week",
+      ],
+      "name": "Week",
+      "type": "type/DateTime",
+    },
+    "4": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "month",
+      ],
+      "name": "Month",
+      "type": "type/DateTime",
+    },
+    "5": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "quarter",
+      ],
+      "name": "Quarter",
+      "type": "type/DateTime",
+    },
+    "6": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "year",
+      ],
+      "name": "Year",
+      "type": "type/DateTime",
+    },
+    "7": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "minute-of-hour",
+      ],
+      "name": "Minute of Hour",
+      "type": "type/DateTime",
+    },
+    "8": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "hour-of-day",
+      ],
+      "name": "Hour of Day",
+      "type": "type/DateTime",
+    },
+    "9": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-week",
+      ],
+      "name": "Day of Week",
+      "type": "type/DateTime",
+    },
+  },
+  "display_name": "People",
+  "entity_name": null,
+  "entity_type": "entity/UserTable",
+  "fields": Array [
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The street address of the account’s billing address",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Address",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2500,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 17.9912,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 8,
+      "max_value": null,
+      "min_value": null,
+      "name": "ADDRESS",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 16,
+      "special_type": null,
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Date",
+      "caveats": null,
+      "database_type": "DATE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "datetime-field",
+          null,
+          "day",
+        ],
+        "name": "Day",
+        "type": "type/DateTime",
+      },
+      "description": "The date of birth of the user",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute",
+          ],
+          "name": "Minute",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour",
+          ],
+          "name": "Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day",
+          ],
+          "name": "Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week",
+          ],
+          "name": "Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month",
+          ],
+          "name": "Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter",
+          ],
+          "name": "Quarter",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "year",
+          ],
+          "name": "Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute-of-hour",
+          ],
+          "name": "Minute of Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour-of-day",
+          ],
+          "name": "Hour of Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-week",
+          ],
+          "name": "Day of Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-month",
+          ],
+          "name": "Day of Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-year",
+          ],
+          "name": "Day of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week-of-year",
+          ],
+          "name": "Week of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month-of-year",
+          ],
+          "name": "Month of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter-of-year",
+          ],
+          "name": "Quarter of Year",
+          "type": "type/DateTime",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Birth Date",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2293,
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 9,
+      "max_value": null,
+      "min_value": null,
+      "name": "BIRTH_DATE",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 17,
+      "special_type": null,
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The city of the account’s billing address",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "City",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2469,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 12.1504,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 10,
+      "max_value": null,
+      "min_value": null,
+      "name": "CITY",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 18,
+      "special_type": "type/City",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/DateTime",
+      "caveats": null,
+      "database_type": "TIMESTAMP",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "datetime-field",
+          null,
+          "day",
+        ],
+        "name": "Day",
+        "type": "type/DateTime",
+      },
+      "description": "The date the user record was created. Also referred to as the user’s \\"join date\\"",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute",
+          ],
+          "name": "Minute",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour",
+          ],
+          "name": "Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day",
+          ],
+          "name": "Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week",
+          ],
+          "name": "Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month",
+          ],
+          "name": "Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter",
+          ],
+          "name": "Quarter",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "year",
+          ],
+          "name": "Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute-of-hour",
+          ],
+          "name": "Minute of Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour-of-day",
+          ],
+          "name": "Hour of Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-week",
+          ],
+          "name": "Day of Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-month",
+          ],
+          "name": "Day of Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-year",
+          ],
+          "name": "Day of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week-of-year",
+          ],
+          "name": "Week of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month-of-year",
+          ],
+          "name": "Month of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter-of-year",
+          ],
+          "name": "Quarter of Year",
+          "type": "type/DateTime",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Created At",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 980,
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 11,
+      "max_value": null,
+      "min_value": null,
+      "name": "CREATED_AT",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 19,
+      "special_type": "type/CreationTimestamp",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The contact email for the account.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Email",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2500,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 24.1664,
+            "percent-email": 1,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 12,
+      "max_value": null,
+      "min_value": null,
+      "name": "EMAIL",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 20,
+      "special_type": "type/Email",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/BigInteger",
+      "caveats": null,
+      "database_type": "BIGINT",
+      "default_dimension_option": null,
+      "description": "A unique identifier given to each user.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "ID",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2500,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 1250.5,
+            "max": 2500,
+            "min": 1,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 13,
+      "max_value": 2500,
+      "min_value": 1,
+      "name": "ID",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 21,
+      "special_type": "type/PK",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Float",
+      "caveats": null,
+      "database_type": "DOUBLE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Coordinate",
+      },
+      "description": "This is the latitude of the user on sign-up. It might be updated in the future to the last seen location.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "bin-width",
+            1,
+          ],
+          "name": "Bin every 1 degree",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "bin-width",
+            10,
+          ],
+          "name": "Bin every 10 degrees",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "bin-width",
+            20,
+          ],
+          "name": "Bin every 20 degrees",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "bin-width",
+            50,
+          ],
+          "name": "Bin every 50 degrees",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Coordinate",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Latitude",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2500,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 0.2000029228840675,
+            "max": 89.9420089232147,
+            "min": -89.62556830684696,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 14,
+      "max_value": 89.98241873383432,
+      "min_value": -89.96310010740648,
+      "name": "LATITUDE",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 22,
+      "special_type": "type/Latitude",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Float",
+      "caveats": null,
+      "database_type": "DOUBLE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Coordinate",
+      },
+      "description": "This is the longitude of the user on sign-up. It might be updated in the future to the last seen location.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "bin-width",
+            1,
+          ],
+          "name": "Bin every 1 degree",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "bin-width",
+            10,
+          ],
+          "name": "Bin every 10 degrees",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "bin-width",
+            20,
+          ],
+          "name": "Bin every 20 degrees",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "bin-width",
+            50,
+          ],
+          "name": "Bin every 50 degrees",
+          "type": "type/Coordinate",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Coordinate",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Longitude",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2500,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 0.7039410787134972,
+            "max": 179.9427208287961,
+            "min": -179.693556788217,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 15,
+      "max_value": 179.73650520575882,
+      "min_value": -179.30480334715446,
+      "name": "LONGITUDE",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 23,
+      "special_type": "type/Longitude",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The name of the user who owns an account",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Name",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2500,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 13.516,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 16,
+      "max_value": null,
+      "min_value": null,
+      "name": "NAME",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 24,
+      "special_type": "type/Name",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "This is the salted password of the user. It should not be visible",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Password",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2500,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 36,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 17,
+      "max_value": null,
+      "min_value": null,
+      "name": "PASSWORD",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 25,
+      "special_type": null,
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The channel through which we acquired this user. Valid values include: Affiliate, Facebook, Google, Organic and Twitter",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Source",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 5,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 7.4096,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "list",
+      "id": 18,
+      "max_value": null,
+      "min_value": null,
+      "name": "SOURCE",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 26,
+      "special_type": "type/Source",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "CHAR",
+      "default_dimension_option": null,
+      "description": "The state or province of the account’s billing address",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "State",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 62,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 2,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "list",
+      "id": 19,
+      "max_value": null,
+      "min_value": null,
+      "name": "STATE",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 27,
+      "special_type": "type/State",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "CHAR",
+      "default_dimension_option": null,
+      "description": "The postal code of the account’s billing address",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Zip",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 2467,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 5,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 20,
+      "max_value": null,
+      "min_value": null,
+      "name": "ZIP",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 28,
+      "special_type": "type/ZipCode",
+      "table_id": 2,
+      "target": null,
+      "visibility_type": "normal",
+    },
+  ],
+  "id": 2,
+  "metrics": Array [],
+  "name": "PEOPLE",
+  "points_of_interest": null,
+  "raw_table_id": 3,
+  "rows": 2500,
+  "schema": "PUBLIC",
+  "segments": Array [],
+  "show_in_getting_started": false,
+  "visibility_type": null,
+}
+`;
+
+exports[`MetabaseApi table_query_metadata should have the correct metadata for table 3 1`] = `
+Object {
+  "active": true,
+  "caveats": null,
+  "db": Object {
+    "cache_field_values_schedule": "0 0 0 * * ? *",
+    "caveats": null,
+    "description": null,
+    "engine": "h2",
+    "features": Array [
+      "native-query-params",
+      "basic-aggregations",
+      "standard-deviation-aggregations",
+      "expression-aggregations",
+      "foreign-keys",
+      "native-parameters",
+      "nested-queries",
+      "expressions",
+      "binning",
+    ],
+    "id": 1,
+    "is_full_sync": true,
+    "is_sample": true,
+    "metadata_sync_schedule": "0 0 * * * ? *",
+    "name": "Sample Dataset",
+    "options": null,
+    "points_of_interest": null,
+  },
+  "db_id": 1,
+  "description": "This is our product catalog. It includes all products ever sold by the Sample Company.",
+  "dimension_options": Object {
+    "0": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "minute",
+      ],
+      "name": "Minute",
+      "type": "type/DateTime",
+    },
+    "1": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "hour",
+      ],
+      "name": "Hour",
+      "type": "type/DateTime",
+    },
+    "10": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-month",
+      ],
+      "name": "Day of Month",
+      "type": "type/DateTime",
+    },
+    "11": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-year",
+      ],
+      "name": "Day of Year",
+      "type": "type/DateTime",
+    },
+    "12": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "week-of-year",
+      ],
+      "name": "Week of Year",
+      "type": "type/DateTime",
+    },
+    "13": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "month-of-year",
+      ],
+      "name": "Month of Year",
+      "type": "type/DateTime",
+    },
+    "14": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "quarter-of-year",
+      ],
+      "name": "Quarter of Year",
+      "type": "type/DateTime",
+    },
+    "15": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "default",
+      ],
+      "name": "Auto bin",
+      "type": "type/Number",
+    },
+    "16": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        10,
+      ],
+      "name": "10 bins",
+      "type": "type/Number",
+    },
+    "17": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        50,
+      ],
+      "name": "50 bins",
+      "type": "type/Number",
+    },
+    "18": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        100,
+      ],
+      "name": "100 bins",
+      "type": "type/Number",
+    },
+    "19": Object {
+      "mbql": null,
+      "name": "Don't bin",
+      "type": "type/Number",
+    },
+    "2": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day",
+      ],
+      "name": "Day",
+      "type": "type/DateTime",
+    },
+    "20": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "default",
+      ],
+      "name": "Auto bin",
+      "type": "type/Coordinate",
+    },
+    "21": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        1,
+      ],
+      "name": "Bin every 1 degree",
+      "type": "type/Coordinate",
+    },
+    "22": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        10,
+      ],
+      "name": "Bin every 10 degrees",
+      "type": "type/Coordinate",
+    },
+    "23": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        20,
+      ],
+      "name": "Bin every 20 degrees",
+      "type": "type/Coordinate",
+    },
+    "24": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        50,
+      ],
+      "name": "Bin every 50 degrees",
+      "type": "type/Coordinate",
+    },
+    "25": Object {
+      "mbql": null,
+      "name": "Don't bin",
+      "type": "type/Coordinate",
+    },
+    "3": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "week",
+      ],
+      "name": "Week",
+      "type": "type/DateTime",
+    },
+    "4": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "month",
+      ],
+      "name": "Month",
+      "type": "type/DateTime",
+    },
+    "5": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "quarter",
+      ],
+      "name": "Quarter",
+      "type": "type/DateTime",
+    },
+    "6": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "year",
+      ],
+      "name": "Year",
+      "type": "type/DateTime",
+    },
+    "7": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "minute-of-hour",
+      ],
+      "name": "Minute of Hour",
+      "type": "type/DateTime",
+    },
+    "8": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "hour-of-day",
+      ],
+      "name": "Hour of Day",
+      "type": "type/DateTime",
+    },
+    "9": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-week",
+      ],
+      "name": "Day of Week",
+      "type": "type/DateTime",
+    },
+  },
+  "display_name": "Products",
+  "entity_name": null,
+  "entity_type": "entity/ProductTable",
+  "fields": Array [
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The type of product, valid values include: Doohicky, Gadget, Gizmo and Widget",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Category",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 4,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 6.515,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "list",
+      "id": 21,
+      "max_value": null,
+      "min_value": null,
+      "name": "CATEGORY",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 1,
+      "special_type": "type/Category",
+      "table_id": 3,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/DateTime",
+      "caveats": null,
+      "database_type": "TIMESTAMP",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "datetime-field",
+          null,
+          "day",
+        ],
+        "name": "Day",
+        "type": "type/DateTime",
+      },
+      "description": "The date the product was added to our catalog.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute",
+          ],
+          "name": "Minute",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour",
+          ],
+          "name": "Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day",
+          ],
+          "name": "Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week",
+          ],
+          "name": "Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month",
+          ],
+          "name": "Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter",
+          ],
+          "name": "Quarter",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "year",
+          ],
+          "name": "Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute-of-hour",
+          ],
+          "name": "Minute of Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour-of-day",
+          ],
+          "name": "Hour of Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-week",
+          ],
+          "name": "Day of Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-month",
+          ],
+          "name": "Day of Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-year",
+          ],
+          "name": "Day of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week-of-year",
+          ],
+          "name": "Week of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month-of-year",
+          ],
+          "name": "Month of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter-of-year",
+          ],
+          "name": "Quarter of Year",
+          "type": "type/DateTime",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Created At",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 178,
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 22,
+      "max_value": null,
+      "min_value": null,
+      "name": "CREATED_AT",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 2,
+      "special_type": "type/CreationTimestamp",
+      "table_id": 3,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "CHAR",
+      "default_dimension_option": null,
+      "description": "The international article number. A 13 digit number uniquely identifying the product.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Ean",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 200,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 13,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 23,
+      "max_value": null,
+      "min_value": null,
+      "name": "EAN",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 3,
+      "special_type": null,
+      "table_id": 3,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/BigInteger",
+      "caveats": null,
+      "database_type": "BIGINT",
+      "default_dimension_option": null,
+      "description": "The numerical product number. Only used internally. All external communication should use the title or EAN.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "ID",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 200,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 100.5,
+            "max": 200,
+            "min": 1,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 24,
+      "max_value": 200,
+      "min_value": 1,
+      "name": "ID",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 4,
+      "special_type": "type/PK",
+      "table_id": 3,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Float",
+      "caveats": null,
+      "database_type": "DOUBLE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Number",
+      },
+      "description": "The list price of the product. Note that this is not always the price the product sold for due to discounts, promotions, etc.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            10,
+          ],
+          "name": "10 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            50,
+          ],
+          "name": "50 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            100,
+          ],
+          "name": "100 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Number",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Price",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 200,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 60.18227577428187,
+            "max": 86.04566142585628,
+            "min": 31.478481306013133,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 25,
+      "max_value": 99.37,
+      "min_value": 12.02,
+      "name": "PRICE",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 5,
+      "special_type": null,
+      "table_id": 3,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Float",
+      "caveats": null,
+      "database_type": "DOUBLE",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Number",
+      },
+      "description": "The average rating users have given the product. This ranges from 1 - 5",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            10,
+          ],
+          "name": "10 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            50,
+          ],
+          "name": "50 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            100,
+          ],
+          "name": "100 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Number",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Rating",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 23,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 3.928500000000003,
+            "max": 5,
+            "min": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "list",
+      "id": 26,
+      "max_value": 5,
+      "min_value": 0,
+      "name": "RATING",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 6,
+      "special_type": "type/Score",
+      "table_id": 3,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The name of the product as it should be displayed to customers.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Title",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 198,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 21.305,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 27,
+      "max_value": null,
+      "min_value": null,
+      "name": "TITLE",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 7,
+      "special_type": "type/Title",
+      "table_id": 3,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The source of the product.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Vendor",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 200,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 19.915,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 28,
+      "max_value": null,
+      "min_value": null,
+      "name": "VENDOR",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 8,
+      "special_type": "type/Company",
+      "table_id": 3,
+      "target": null,
+      "visibility_type": "normal",
+    },
+  ],
+  "id": 3,
+  "metrics": Array [],
+  "name": "PRODUCTS",
+  "points_of_interest": null,
+  "raw_table_id": 1,
+  "rows": 200,
+  "schema": "PUBLIC",
+  "segments": Array [],
+  "show_in_getting_started": false,
+  "visibility_type": null,
+}
+`;
+
+exports[`MetabaseApi table_query_metadata should have the correct metadata for table 4 1`] = `
+Object {
+  "active": true,
+  "caveats": null,
+  "db": Object {
+    "cache_field_values_schedule": "0 0 0 * * ? *",
+    "caveats": null,
+    "description": null,
+    "engine": "h2",
+    "features": Array [
+      "native-query-params",
+      "basic-aggregations",
+      "standard-deviation-aggregations",
+      "expression-aggregations",
+      "foreign-keys",
+      "native-parameters",
+      "nested-queries",
+      "expressions",
+      "binning",
+    ],
+    "id": 1,
+    "is_full_sync": true,
+    "is_sample": true,
+    "metadata_sync_schedule": "0 0 * * * ? *",
+    "name": "Sample Dataset",
+    "options": null,
+    "points_of_interest": null,
+  },
+  "db_id": 1,
+  "description": "These are reviews our customers have left on products. Note that these are not tied to orders so it is possible people have reviewed products they did not purchase from us.",
+  "dimension_options": Object {
+    "0": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "minute",
+      ],
+      "name": "Minute",
+      "type": "type/DateTime",
+    },
+    "1": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "hour",
+      ],
+      "name": "Hour",
+      "type": "type/DateTime",
+    },
+    "10": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-month",
+      ],
+      "name": "Day of Month",
+      "type": "type/DateTime",
+    },
+    "11": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-year",
+      ],
+      "name": "Day of Year",
+      "type": "type/DateTime",
+    },
+    "12": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "week-of-year",
+      ],
+      "name": "Week of Year",
+      "type": "type/DateTime",
+    },
+    "13": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "month-of-year",
+      ],
+      "name": "Month of Year",
+      "type": "type/DateTime",
+    },
+    "14": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "quarter-of-year",
+      ],
+      "name": "Quarter of Year",
+      "type": "type/DateTime",
+    },
+    "15": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "default",
+      ],
+      "name": "Auto bin",
+      "type": "type/Number",
+    },
+    "16": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        10,
+      ],
+      "name": "10 bins",
+      "type": "type/Number",
+    },
+    "17": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        50,
+      ],
+      "name": "50 bins",
+      "type": "type/Number",
+    },
+    "18": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "num-bins",
+        100,
+      ],
+      "name": "100 bins",
+      "type": "type/Number",
+    },
+    "19": Object {
+      "mbql": null,
+      "name": "Don't bin",
+      "type": "type/Number",
+    },
+    "2": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day",
+      ],
+      "name": "Day",
+      "type": "type/DateTime",
+    },
+    "20": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "default",
+      ],
+      "name": "Auto bin",
+      "type": "type/Coordinate",
+    },
+    "21": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        1,
+      ],
+      "name": "Bin every 1 degree",
+      "type": "type/Coordinate",
+    },
+    "22": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        10,
+      ],
+      "name": "Bin every 10 degrees",
+      "type": "type/Coordinate",
+    },
+    "23": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        20,
+      ],
+      "name": "Bin every 20 degrees",
+      "type": "type/Coordinate",
+    },
+    "24": Object {
+      "mbql": Array [
+        "binning-strategy",
+        null,
+        "bin-width",
+        50,
+      ],
+      "name": "Bin every 50 degrees",
+      "type": "type/Coordinate",
+    },
+    "25": Object {
+      "mbql": null,
+      "name": "Don't bin",
+      "type": "type/Coordinate",
+    },
+    "3": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "week",
+      ],
+      "name": "Week",
+      "type": "type/DateTime",
+    },
+    "4": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "month",
+      ],
+      "name": "Month",
+      "type": "type/DateTime",
+    },
+    "5": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "quarter",
+      ],
+      "name": "Quarter",
+      "type": "type/DateTime",
+    },
+    "6": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "year",
+      ],
+      "name": "Year",
+      "type": "type/DateTime",
+    },
+    "7": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "minute-of-hour",
+      ],
+      "name": "Minute of Hour",
+      "type": "type/DateTime",
+    },
+    "8": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "hour-of-day",
+      ],
+      "name": "Hour of Day",
+      "type": "type/DateTime",
+    },
+    "9": Object {
+      "mbql": Array [
+        "datetime-field",
+        null,
+        "day-of-week",
+      ],
+      "name": "Day of Week",
+      "type": "type/DateTime",
+    },
+  },
+  "display_name": "Reviews",
+  "entity_name": null,
+  "entity_type": "entity/GenericTable",
+  "fields": Array [
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "CLOB",
+      "default_dimension_option": null,
+      "description": "The review the user left. Limited to 2000 characters.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Body",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 984,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 178.66158536585365,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 29,
+      "max_value": null,
+      "min_value": null,
+      "name": "BODY",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": false,
+      "raw_column_id": 31,
+      "special_type": "type/Description",
+      "table_id": 4,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/DateTime",
+      "caveats": null,
+      "database_type": "TIMESTAMP",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "datetime-field",
+          null,
+          "day",
+        ],
+        "name": "Day",
+        "type": "type/DateTime",
+      },
+      "description": "The day and time a review was written by a user.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute",
+          ],
+          "name": "Minute",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour",
+          ],
+          "name": "Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day",
+          ],
+          "name": "Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week",
+          ],
+          "name": "Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month",
+          ],
+          "name": "Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter",
+          ],
+          "name": "Quarter",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "year",
+          ],
+          "name": "Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "minute-of-hour",
+          ],
+          "name": "Minute of Hour",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "hour-of-day",
+          ],
+          "name": "Hour of Day",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-week",
+          ],
+          "name": "Day of Week",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-month",
+          ],
+          "name": "Day of Month",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "day-of-year",
+          ],
+          "name": "Day of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "week-of-year",
+          ],
+          "name": "Week of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "month-of-year",
+          ],
+          "name": "Month of Year",
+          "type": "type/DateTime",
+        },
+        Object {
+          "mbql": Array [
+            "datetime-field",
+            null,
+            "quarter-of-year",
+          ],
+          "name": "Quarter of Year",
+          "type": "type/DateTime",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Created At",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 624,
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 30,
+      "max_value": null,
+      "min_value": null,
+      "name": "CREATED_AT",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 32,
+      "special_type": "type/CreationTimestamp",
+      "table_id": 4,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/BigInteger",
+      "caveats": null,
+      "database_type": "BIGINT",
+      "default_dimension_option": null,
+      "description": "A unique internal identifier for the review. Should not be used externally.",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "ID",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 984,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 492.5,
+            "max": 984,
+            "min": 1,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "none",
+      "id": 31,
+      "max_value": 1078,
+      "min_value": 1,
+      "name": "ID",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 33,
+      "special_type": "type/PK",
+      "table_id": 4,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Integer",
+      "caveats": null,
+      "database_type": "INTEGER",
+      "default_dimension_option": null,
+      "description": "The product the review was for",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Product ID",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 195,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 100.0670731707317,
+            "max": 200,
+            "min": 1,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": 24,
+      "has_field_values": "none",
+      "id": 32,
+      "max_value": 200,
+      "min_value": 1,
+      "name": "PRODUCT_ID",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 34,
+      "special_type": "type/FK",
+      "table_id": 4,
+      "target": Object {
+        "active": true,
+        "base_type": "type/BigInteger",
+        "caveats": null,
+        "database_type": "BIGINT",
+        "description": "The numerical product number. Only used internally. All external communication should use the title or EAN.",
+        "display_name": "ID",
+        "fingerprint": Object {
+          "global": Object {
+            "distinct-count": 200,
+          },
+          "type": Object {
+            "type/Number": Object {
+              "avg": 100.5,
+              "max": 200,
+              "min": 1,
+            },
+          },
+        },
+        "fingerprint_version": 1,
+        "fk_target_field_id": null,
+        "has_field_values": "none",
+        "id": 24,
+        "max_value": 200,
+        "min_value": 1,
+        "name": "ID",
+        "parent_id": null,
+        "points_of_interest": null,
+        "position": 0,
+        "preview_display": true,
+        "raw_column_id": 4,
+        "special_type": "type/PK",
+        "table_id": 3,
+        "visibility_type": "normal",
+      },
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Integer",
+      "caveats": null,
+      "database_type": "SMALLINT",
+      "default_dimension_option": Object {
+        "mbql": Array [
+          "binning-strategy",
+          null,
+          "default",
+        ],
+        "name": "Auto bin",
+        "type": "type/Number",
+      },
+      "description": "The rating (on a scale of 1-5) the user left.",
+      "dimension_options": Array [
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "default",
+          ],
+          "name": "Auto bin",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            10,
+          ],
+          "name": "10 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            50,
+          ],
+          "name": "50 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": Array [
+            "binning-strategy",
+            null,
+            "num-bins",
+            100,
+          ],
+          "name": "100 bins",
+          "type": "type/Number",
+        },
+        Object {
+          "mbql": null,
+          "name": "Don't bin",
+          "type": "type/Number",
+        },
+      ],
+      "dimensions": Array [],
+      "display_name": "Rating",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 5,
+        },
+        "type": Object {
+          "type/Number": Object {
+            "avg": 4.0477642276422765,
+            "max": 5,
+            "min": 1,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "list",
+      "id": 33,
+      "max_value": 5,
+      "min_value": 1,
+      "name": "RATING",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 35,
+      "special_type": "type/Score",
+      "table_id": 4,
+      "target": null,
+      "visibility_type": "normal",
+    },
+    Object {
+      "active": true,
+      "base_type": "type/Text",
+      "caveats": null,
+      "database_type": "VARCHAR",
+      "default_dimension_option": null,
+      "description": "The user who left the review",
+      "dimension_options": Array [],
+      "dimensions": Array [],
+      "display_name": "Reviewer",
+      "fingerprint": Object {
+        "global": Object {
+          "distinct-count": 936,
+        },
+        "type": Object {
+          "type/Text": Object {
+            "average-length": 9.570121951219512,
+            "percent-email": 0,
+            "percent-json": 0,
+            "percent-url": 0,
+          },
+        },
+      },
+      "fingerprint_version": 1,
+      "fk_target_field_id": null,
+      "has_field_values": "search",
+      "id": 34,
+      "max_value": null,
+      "min_value": null,
+      "name": "REVIEWER",
+      "parent_id": null,
+      "points_of_interest": null,
+      "position": 0,
+      "preview_display": true,
+      "raw_column_id": 36,
+      "special_type": null,
+      "table_id": 4,
+      "target": null,
+      "visibility_type": "normal",
+    },
+  ],
+  "id": 4,
+  "metrics": Array [],
+  "name": "REVIEWS",
+  "points_of_interest": null,
+  "raw_table_id": 5,
+  "rows": 984,
+  "schema": "PUBLIC",
+  "segments": Array [],
+  "show_in_getting_started": false,
+  "visibility_type": null,
+}
+`;
diff --git a/src/metabase/api/database.clj b/src/metabase/api/database.clj
index f8f5b70b5e3e0064220d2f257a863b7edfcab865..cc974a20ad6c3518e0894e9cc94a9075b53be792 100644
--- a/src/metabase/api/database.clj
+++ b/src/metabase/api/database.clj
@@ -198,7 +198,7 @@
 
 (defn- db-metadata [id]
   (-> (api/read-check Database id)
-      (hydrate [:tables [:fields :target :has_field_values] :segments :metrics])
+      (hydrate [:tables [:fields [:target :has_field_values] :has_field_values] :segments :metrics])
       (update :tables (fn [tables]
                         (for [table tables
                               :when (mi/can-read? table)]
diff --git a/src/metabase/api/field.clj b/src/metabase/api/field.clj
index c987aeba8abc9b4754b6ae4666b188f2508f8348..f87294f097870f6ac5e97df8b9022d0a8f787324 100644
--- a/src/metabase/api/field.clj
+++ b/src/metabase/api/field.clj
@@ -211,8 +211,8 @@
   {value-pairs [[(s/one s/Num "value") (s/optional su/NonBlankString "human readable value")]]}
   (let [field (api/write-check Field id)]
     (api/check (field-values/field-should-have-field-values? field)
-      [400 (str "You can only update the human readable values of a mapped values of a Field whose 'special_type' "
-                "is 'category'/'city'/'state'/'country' or whose 'base_type' is 'type/Boolean'.")])
+      [400 (str "You can only update the human readable values of a mapped values of a Field whose value of "
+                "`has_field_values` is `list` or whose 'base_type' is 'type/Boolean'.")])
     (if-let [field-value-id (db/select-one-id FieldValues, :field_id id)]
       (update-field-values! field-value-id value-pairs)
       (create-field-values! field value-pairs)))
diff --git a/src/metabase/api/table.clj b/src/metabase/api/table.clj
index 9ee39dbc73104a80997b1887fe451c4adbebf230..ff4d17143192dda30fdea49a449c5c56580fba19 100644
--- a/src/metabase/api/table.clj
+++ b/src/metabase/api/table.clj
@@ -212,7 +212,7 @@
                 field)))))
 
 (api/defendpoint GET "/:id/query_metadata"
-  "Get metadata about a `Table` useful for running queries.
+  "Get metadata about a `Table` us eful for running queries.
    Returns DB, fields, field FKs, and field values.
 
   By passing `include_sensitive_fields=true`, information *about* sensitive `Fields` will be returned; in no case will
@@ -222,7 +222,7 @@
   (let [table (api/read-check Table id)
         driver (driver/database-id->driver (:db_id table))]
     (-> table
-        (hydrate :db [:fields :target :dimensions :has_field_values] :segments :metrics)
+        (hydrate :db [:fields [:target :has_field_values] :dimensions :has_field_values] :segments :metrics)
         (m/dissoc-in [:db :details])
         (assoc-dimension-options driver)
         format-fields-for-response
@@ -244,8 +244,8 @@
           (assoc
               :table_id     (str "card__" card-id)
               :id           [:field-literal (:name col) (or (:base_type col) :type/*)]
-              ;; don't return :special_type if it's a PK or FK because it confuses the frontend since it can't actually be
-              ;; used that way IRL
+              ;; don't return :special_type if it's a PK or FK because it confuses the frontend since it can't
+              ;; actually be used that way IRL
               :special_type (when-let [special-type (keyword (:special_type col))]
                               (when-not (or (isa? special-type :type/PK)
                                             (isa? special-type :type/FK))
@@ -263,12 +263,15 @@
              :display_name (:name card)
              :schema       (get-in card [:collection :name] "Everything else")
              :description  (:description card)}
-      include-fields? (assoc :fields (card-result-metadata->virtual-fields (u/get-id card) database_id (:result_metadata card))))))
+      include-fields? (assoc :fields (card-result-metadata->virtual-fields (u/get-id card)
+                                                                           database_id
+                                                                           (:result_metadata card))))))
 
 (api/defendpoint GET "/card__:id/query_metadata"
   "Return metadata for the 'virtual' table for a Card."
   [id]
-  (let [{:keys [database_id] :as card } (db/select-one [Card :id :dataset_query :result_metadata :name :description :collection_id :database_id]
+  (let [{:keys [database_id] :as card } (db/select-one [Card :id :dataset_query :result_metadata :name :description
+                                                        :collection_id :database_id]
                                           :id id)]
     (-> card
         api/read-check
diff --git a/src/metabase/cmd.clj b/src/metabase/cmd.clj
index 92a17ce113c73dc822f802372b51531924f224fc..0efcf920bfabc48fa23a1e24ad5a23ee95728eb1 100644
--- a/src/metabase/cmd.clj
+++ b/src/metabase/cmd.clj
@@ -40,7 +40,7 @@
   []
   ;; override env var that would normally make Jetty block forever
   (require 'environ.core)
-  (intern 'environ.core 'env (assoc environ.core/env :mb-jetty-join "false"))
+  (intern 'environ.core 'env (assoc @(resolve 'environ.core/env) :mb-jetty-join "false"))
   (u/profile "start-normally" ((resolve 'metabase.core/start-normally))))
 
 (defn ^:command reset-password
@@ -49,6 +49,12 @@
   (require 'metabase.cmd.reset-password)
   ((resolve 'metabase.cmd.reset-password/reset-password!) email-address))
 
+(defn ^:command refresh-integration-test-db-metadata
+  "Re-sync the frontend integration test DB's metadata for the Sample Dataset."
+  []
+  (require 'metabase.cmd.refresh-integration-test-db-metadata)
+  ((resolve 'metabase.cmd.refresh-integration-test-db-metadata/refresh-integration-test-db-metadata)))
+
 (defn ^:command help
   "Show this help message listing valid Metabase commands."
   []
diff --git a/src/metabase/cmd/refresh_integration_test_db_metadata.clj b/src/metabase/cmd/refresh_integration_test_db_metadata.clj
new file mode 100644
index 0000000000000000000000000000000000000000..19d4a819e13e4552ea86e1887edb95f3b2757887
--- /dev/null
+++ b/src/metabase/cmd/refresh_integration_test_db_metadata.clj
@@ -0,0 +1,44 @@
+(ns metabase.cmd.refresh-integration-test-db-metadata
+  (:require [clojure.java.io :as io]
+            [environ.core :refer [env]]
+            [metabase
+             [db :as mdb]
+             [util :as u]]
+            [metabase.models
+             [database :refer [Database]]
+             [field :refer [Field]]
+             [table :refer [Table]]]
+            [metabase.sample-data :as sample-data]
+            [metabase.sync :as sync]
+            [toucan.db :as db]))
+
+(defn- test-fixture-db-path
+  "Get the path to the test fixture DB that we'll use for `MB_DB_FILE`. Throw an Exception if the file doesn't exist."
+  []
+  (let [path (str (System/getProperty "user.dir") "/frontend/test/__runner__/test_db_fixture.db")]
+    (when-not (or (.exists (io/file (str path ".h2.db")))
+                  (.exists (io/file (str path ".mv.db"))))
+      (throw (Exception. (str "Could not find frontend integration test DB at path: " path ".h2.db (or .mv.db)"))))
+    path))
+
+(defn ^:command refresh-integration-test-db-metadata
+  "Re-sync the frontend integration test DB's metadata for the Sample Dataset."
+  []
+  (let [db-path (test-fixture-db-path)]
+    ;; now set the path at MB_DB_FILE
+    (intern 'environ.core 'env (assoc env :mb-db-type "h2", :mb-db-file db-path))
+    ;; set up the DB, make sure sample dataset is added
+    (mdb/setup-db!)
+    (sample-data/add-sample-dataset!)
+    (sample-data/update-sample-dataset-if-needed!)
+    ;; clear out all Fingerprints so we force analysis to run again. Clear out special type and has_field_values as
+    ;; well so we can be sure those will be set to the correct values
+    (db/debug-print-queries
+      (db/update! Field {:set {:fingerprint_version 0
+                               :special_type        nil
+                               :has_field_values    nil
+                               :fk_target_field_id  nil}}))
+    ;; now re-run sync
+    (sync/sync-database! (Database :is_sample true))
+    ;; done!
+    (println "Finished.")))
diff --git a/src/metabase/db/metadata_queries.clj b/src/metabase/db/metadata_queries.clj
index 8f7b9d412ca89804f6b4979bb6f7953beae47841..caae266a05e7b0e6b30295ec7c1d21dd1e25143a 100644
--- a/src/metabase/db/metadata_queries.clj
+++ b/src/metabase/db/metadata_queries.clj
@@ -48,7 +48,7 @@
   ([field]
    ;; fetch up to one more value than allowed for FieldValues. e.g. if the max is 100 distinct values fetch up to 101.
    ;; That way we will know if we're over the limit
-   (field-distinct-values field (inc field-values/low-cardinality-threshold)))
+   (field-distinct-values field (inc field-values/list-cardinality-threshold)))
   ([field max-results]
    {:pre [(integer? max-results)]}
    (mapv first (field-query field (-> {}
diff --git a/src/metabase/db/migrations.clj b/src/metabase/db/migrations.clj
index e6077ffb556f32d0af8d23675b8dff26df0f29ca..a8640fb425b1c6ce2127734208f76747e887b5bf 100644
--- a/src/metabase/db/migrations.clj
+++ b/src/metabase/db/migrations.clj
@@ -10,6 +10,7 @@
             [clojure.string :as str]
             [clojure.tools.logging :as log]
             [metabase
+             [db :as mdb]
              [config :as config]
              [driver :as driver]
              [public-settings :as public-settings]
@@ -369,8 +370,22 @@
 ;; `pre-update` implementation.
 ;;
 ;; Caching these permissions will prevent 1000+ DB call API calls. See https://github.com/metabase/metabase/issues/6889
-(defmigration ^{:author "camsaul", :added "0.28.2"} populate-card-read-permissions
+(defmigration ^{:author "camsaul", :added "0.29.0"} populate-card-read-permissions
   (run!
    (fn [card]
      (db/update! Card (u/get-id card) {}))
    (db/select-reducible Card :archived false, :read_permissions nil)))
+
+;; Starting in version 0.29.0 we switched the way we decide which Fields should get FieldValues. Prior to 29, Fields
+;; would be marked as special type Category if they should have FieldValues. In 29+, the Category special type no
+;; longer has any meaning as far as the backend is concerned. Instead, we use the new `has_field_values` column to
+;; keep track of these things. Fields whose value for `has_field_values` is `list` is the equiavalent of the old
+;; meaning of the Category special type.
+;;
+;; Since the meanings of things has changed we'll want to make sure we mark all Category fields as `list` as well so
+;; their behavior doesn't suddenly change.
+(defmigration ^{:author "camsaul", :added "0.29.0"} mark-category-fields-as-list
+  (db/update-where! Field {:has_field_values nil
+                           :special_type     (mdb/isa :type/Category)
+                           :active           true}
+    :has_field_values "list"))
diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj
index 3f6a85c2b11d0b954fb955620902cbc625c7f224..51cdcbb016cf0985f94ed7f11fbe87ea4509f70a 100644
--- a/src/metabase/models/field.clj
+++ b/src/metabase/models/field.clj
@@ -93,11 +93,12 @@
   models/IModel
   (merge models/IModelDefaults
          {:hydration-keys (constantly [:destination :field :origin :human_readable_field])
-          :types          (constantly {:base_type       :keyword
-                                       :special_type    :keyword
-                                       :visibility_type :keyword
-                                       :description     :clob
-                                       :fingerprint     :json})
+          :types          (constantly {:base_type        :keyword
+                                       :special_type     :keyword
+                                       :visibility_type  :keyword
+                                       :description      :clob
+                                       :has_field_values :clob
+                                       :fingerprint      :json})
           :properties     (constantly {:timestamped? true})
           :pre-insert     pre-insert
           :pre-update     pre-update
@@ -165,6 +166,15 @@
     (for [field fields]
       (assoc field :dimensions (get id->dimensions (:id field) [])))))
 
+(defn- is-searchable?
+  "Is this `field` a Field that you should be presented with a search widget for (to search its values)? If so, we can
+  give it a `has_field_values` value of `search`."
+  [{base-type :base_type}]
+  ;; For the time being we will consider something to be "searchable" if it's a text Field since the `starts-with`
+  ;; filter that powers the search queries (see `metabase.api.field/search-values`) doesn't work on anything else
+  (or (isa? base-type :type/Text)
+      (isa? base-type :type/TextLike)))
+
 (defn with-has-field-values
   "Infer what the value of the `has_field_values` should be for Fields where it's not set. Admins can set this to one
   of the values below, but if it's `nil` in the DB we'll infer it automatically.
@@ -179,12 +189,16 @@
                                                    (:id field)))
         fields-with-fieldvalues-ids         (when (seq fields-without-has-field-values-ids)
                                               (db/select-field :field_id FieldValues
-                                                :field_id [:in fields-without-has-field-values-ids]))]
+                                                               :field_id [:in fields-without-has-field-values-ids]))]
     (for [field fields]
-      (assoc field :has_field_values (or (:has_field_values field)
-                                         (if (contains? fields-with-fieldvalues-ids (u/get-id field))
-                                           :list
-                                           :search))))))
+      (when field
+        (assoc field
+          :has_field_values (or
+                             (:has_field_values field)
+                             (cond
+                               (contains? fields-with-fieldvalues-ids (u/get-id field)) :list
+                               (is-searchable? field)                                   :search
+                               :else                                                    :none)))))))
 
 (defn readable-fields-only
   "Efficiently checks if each field is readable and returns only readable fields"
diff --git a/src/metabase/models/field_values.clj b/src/metabase/models/field_values.clj
index f5ba4694ad3d0c342d2687a1debf9477805c122e..703435d1293ea62eaf164dfe65034aed42bb5ce7 100644
--- a/src/metabase/models/field_values.clj
+++ b/src/metabase/models/field_values.clj
@@ -5,17 +5,24 @@
              [db :as db]
              [models :as models]]))
 
-(def ^:const ^Integer low-cardinality-threshold
-  "Fields with less than this many distinct values should automatically be given a special type of `:type/Category`."
-  300)
+(def ^:const ^Integer category-cardinality-threshold
+  "Fields with less than this many distinct values should automatically be given a special type of `:type/Category`.
+  This no longer has any meaning whatsoever as far as the backend code is concerned; it is used purely to inform
+  frontend behavior such as widget choices."
+  (int 30))
+
+(def ^:const ^Integer list-cardinality-threshold
+  "Fields with less than this many distincy values should be given a `has_field_values` value of `list`, which means the
+  Field should have FieldValues."
+  (int 100))
 
 (def ^:private ^:const ^Integer entry-max-length
   "The maximum character length for a stored `FieldValues` entry."
-  100)
+  (int 100))
 
 (def ^:private ^:const ^Integer total-max-length
   "Maximum total length for a `FieldValues` entry (combined length of all values for the field)."
-  (* low-cardinality-threshold entry-max-length))
+  (int (* list-cardinality-threshold entry-max-length)))
 
 
 ;; ## Entity + DB Multimethods
@@ -35,20 +42,18 @@
 (defn field-should-have-field-values?
   "Should this `Field` be backed by a corresponding `FieldValues` object?"
   {:arglists '([field])}
-  [{:keys [base_type special_type visibility_type] :as field}]
-  {:pre [visibility_type
+  [{base-type :base_type, visibility-type :visibility_type, has-field-values :has_field_values, :as field}]
+  {:pre [visibility-type
          (contains? field :base_type)
-         (contains? field :special_type)]}
-  (and (not (contains? #{:retired :sensitive :hidden :details-only} (keyword visibility_type)))
-       (not (isa? (keyword base_type) :type/DateTime))
-       (or (isa? (keyword base_type) :type/Boolean)
-           (isa? (keyword special_type) :type/Category)
-           (isa? (keyword special_type) :type/Enum))))
+         (contains? field :has_field_values)]}
+  (and (not (contains? #{:retired :sensitive :hidden :details-only} (keyword visibility-type)))
+       (not (isa? (keyword base-type) :type/DateTime))
+       (= has-field-values "list")))
 
 
 (defn- values-less-than-total-max-length?
-  "`true` if the combined length of all the values in DISTINCT-VALUES is below the
-   threshold for what we'll allow in a FieldValues entry. Does some logging as well."
+  "`true` if the combined length of all the values in DISTINCT-VALUES is below the threshold for what we'll allow in a
+  FieldValues entry. Does some logging as well."
   [distinct-values]
   (let [total-length (reduce + (map (comp count str)
                                     distinct-values))]
@@ -59,19 +64,21 @@
                    "FieldValues are NOT allowed for this Field.")))))
 
 (defn- cardinality-less-than-threshold?
-  "`true` if the number of DISTINCT-VALUES is less that `low-cardinality-threshold`.
+  "`true` if the number of DISTINCT-VALUES is less that `list-cardinality-threshold`.
    Does some logging as well."
   [distinct-values]
   (let [num-values (count distinct-values)]
-    (u/prog1 (<= num-values low-cardinality-threshold)
+    (u/prog1 (<= num-values list-cardinality-threshold)
       (log/debug (if <>
-                   (format "Field has %d distinct values (max %d). FieldValues are allowed for this Field." num-values low-cardinality-threshold)
-                   (format "Field has over %d values. FieldValues are NOT allowed for this Field." low-cardinality-threshold))))))
+                   (format "Field has %d distinct values (max %d). FieldValues are allowed for this Field."
+                           num-values list-cardinality-threshold)
+                   (format "Field has over %d values. FieldValues are NOT allowed for this Field."
+                           list-cardinality-threshold))))))
 
 
 (defn- distinct-values
-  "Fetch a sequence of distinct values for FIELD that are below the `total-max-length` threshold.
-   If the values are past the threshold, this returns `nil`."
+  "Fetch a sequence of distinct values for FIELD that are below the `total-max-length` threshold. If the values are past
+  the threshold, this returns `nil`."
   [field]
   (require 'metabase.db.metadata-queries)
   (let [values ((resolve 'metabase.db.metadata-queries/field-distinct-values) field)]
@@ -80,11 +87,9 @@
         values))))
 
 (defn- fixup-human-readable-values
-  "Field values and human readable values are lists that are zipped
-  together. If the field values have changes, the human readable
-  values will need to change too. This function reconstructs the
-  human_readable_values to reflect `NEW-VALUES`. If a new field value
-  is found, a string version of that is used"
+  "Field values and human readable values are lists that are zipped together. If the field values have changes, the
+  human readable values will need to change too. This function reconstructs the human_readable_values to reflect
+  `NEW-VALUES`. If a new field value is found, a string version of that is used"
   [{old-values :values, old-hrv :human_readable_values} new-values]
   (when (seq old-hrv)
     (let [orig-remappings (zipmap old-values old-hrv)]
@@ -120,7 +125,7 @@
 
 (defn field-values->pairs
   "Returns a list of pairs (or single element vectors if there are no human_readable_values) for the given
-   `FIELD-VALUES` instance."
+  `FIELD-VALUES` instance."
   [{:keys [values human_readable_values] :as field-values}]
   (if (seq human_readable_values)
     (map vector values human_readable_values)
@@ -152,8 +157,8 @@
 
 (defn- table-ids->table-id->is-on-demand?
   "Given a collection of TABLE-IDS return a map of Table ID to whether or not its Database is subject to 'On Demand'
-   FieldValues updating. This means the FieldValues for any Fields belonging to the Database should be updated only
-   when they are used in new Dashboard or Card parameters."
+  FieldValues updating. This means the FieldValues for any Fields belonging to the Database should be updated only
+  when they are used in new Dashboard or Card parameters."
   [table-ids]
   (let [table-ids            (set table-ids)
         table-id->db-id      (when (seq table-ids)
@@ -166,11 +171,12 @@
 
 (defn update-field-values-for-on-demand-dbs!
   "Update the FieldValues for any Fields with FIELD-IDS if the Field should have FieldValues and it belongs to a
-   Database that is set to do 'On-Demand' syncing."
+  Database that is set to do 'On-Demand' syncing."
   [field-ids]
   (let [fields (when (seq field-ids)
                  (filter field-should-have-field-values?
-                         (db/select ['Field :name :id :base_type :special_type :visibility_type :table_id]
+                         (db/select ['Field :name :id :base_type :special_type :visibility_type :table_id
+                                     :has_field_values]
                            :id [:in field-ids])))
         table-id->is-on-demand? (table-ids->table-id->is-on-demand? (map :table_id fields))]
     (doseq [{table-id :table_id, :as field} fields]
diff --git a/src/metabase/models/interface.clj b/src/metabase/models/interface.clj
index b938bed4f8dd4bf53234119f41ee8e665f1fc510..3b3dc7eb8ae41e0b5d9eb8975e8b97017267f66f 100644
--- a/src/metabase/models/interface.clj
+++ b/src/metabase/models/interface.clj
@@ -10,7 +10,9 @@
             [toucan.models :as models])
   (:import java.sql.Blob))
 
-;;; ------------------------------------------------------------ Toucan Extensions ------------------------------------------------------------
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                               Toucan Extensions                                                |
+;;; +----------------------------------------------------------------------------------------------------------------+
 
 (models/set-root-namespace! 'metabase.models)
 
@@ -46,7 +48,8 @@
 (def ^:private encrypted-json-out (comp json-out encryption/maybe-decrypt))
 
 ;; cache the decryption/JSON parsing because it's somewhat slow (~500µs vs ~100µs on a *fast* computer)
-(def ^:private cached-encrypted-json-out (memoize/ttl encrypted-json-out :ttl/threshold (* 60 60 1000))) ; cache decrypted JSON for one hour
+;; cache the decrypted JSON for one hour
+(def ^:private cached-encrypted-json-out (memoize/ttl encrypted-json-out :ttl/threshold (* 60 60 1000)))
 
 (models/add-type! :encrypted-json
   :in  encrypted-json-in
@@ -94,14 +97,18 @@
   :update add-updated-at-timestamp)
 
 
-;;; ------------------------------------------------------------ New Permissions Stuff ------------------------------------------------------------
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                             New Permissions Stuff                                              |
+;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defprotocol IObjectPermissions
   "Methods for determining whether the current user has read/write permissions for a given object."
 
   (perms-objects-set [this, ^clojure.lang.Keyword read-or-write]
-    "Return a set of permissions object paths that a user must have access to in order to access this object. This should be something like #{\"/db/1/schema/public/table/20/\"}.
-     READ-OR-WRITE will be either `:read` or `:write`, depending on which permissions set we're fetching (these will be the same sets for most models; they can ignore this param).")
+    "Return a set of permissions object paths that a user must have access to in order to access this object. This
+    should be something like #{\"/db/1/schema/public/table/20/\"}. READ-OR-WRITE will be either `:read` or `:write`,
+    depending on which permissions set we're fetching (these will be the same sets for most models; they can ignore
+    this param).")
 
   (can-read? ^Boolean [instance], ^Boolean [entity, ^Integer id]
     "Return whether `*current-user*` has *read* permissions for an object. You should use one of these implmentations:
@@ -109,7 +116,8 @@
      *  `(constantly true)`
      *  `superuser?`
      *  `(partial current-user-has-full-permissions? :read)` (you must also implement `perms-objects-set` to use this)
-     *  `(partial current-user-has-partial-permissions? :read)` (you must also implement `perms-objects-set` to use this)")
+     *  `(partial current-user-has-partial-permissions? :read)` (you must also implement `perms-objects-set` to use
+        this)")
 
   (^{:hydrate :can_write} can-write? ^Boolean [instance], ^Boolean [entity, ^Integer id]
    "Return whether `*current-user*` has *write* permissions for an object. You should use one of these implmentations:
@@ -117,7 +125,8 @@
      *  `(constantly true)`
      *  `superuser?`
      *  `(partial current-user-has-full-permissions? :write)` (you must also implement `perms-objects-set` to use this)
-     *  `(partial current-user-has-partial-permissions? :write)` (you must also implement `perms-objects-set` to use this)"))
+     *  `(partial current-user-has-partial-permissions? :write)` (you must also implement `perms-objects-set` to use
+        this)"))
 
 (def IObjectPermissionsDefaults
   "Default implementations for `IObjectPermissions`."
@@ -147,14 +156,14 @@
 
 (def ^{:arglists '([read-or-write entity object-id] [read-or-write object])}
   ^Boolean current-user-has-full-permissions?
-  "Implementation of `can-read?`/`can-write?` for the new permissions system.
-   `true` if the current user has *full* permissions for the paths returned by its implementation of `perms-objects-set`.
-   (READ-OR-WRITE is either `:read` or `:write` and passed to `perms-objects-set`; you'll usually want to partially bind it in the implementation map)."
+  "Implementation of `can-read?`/`can-write?` for the new permissions system. `true` if the current user has *full*
+  permissions for the paths returned by its implementation of `perms-objects-set`. (READ-OR-WRITE is either `:read` or
+  `:write` and passed to `perms-objects-set`; you'll usually want to partially bind it in the implementation map)."
   (make-perms-check-fn 'metabase.models.permissions/set-has-full-permissions-for-set?))
 
 (def ^{:arglists '([read-or-write entity object-id] [read-or-write object])}
   ^Boolean current-user-has-partial-permissions?
-  "Implementation of `can-read?`/`can-write?` for the new permissions system.
-   `true` if the current user has *partial* permissions for the paths returned by its implementation of `perms-objects-set`.
-   (READ-OR-WRITE is either `:read` or `:write` and passed to `perms-objects-set`; you'll usually want to partially bind it in the implementation map)."
+  "Implementation of `can-read?`/`can-write?` for the new permissions system. `true` if the current user has *partial*
+  permissions for the paths returned by its implementation of `perms-objects-set`. (READ-OR-WRITE is either `:read` or
+  `:write` and passed to `perms-objects-set`; you'll usually want to partially bind it in the implementation map)."
   (make-perms-check-fn 'metabase.models.permissions/set-has-partial-permissions-for-set?))
diff --git a/src/metabase/models/params.clj b/src/metabase/models/params.clj
index 1a31f251125858badaeb2deaa47851e7358311f8..3fae1874079acbb88f055ced75ba9d5525c9d8f0 100644
--- a/src/metabase/models/params.clj
+++ b/src/metabase/models/params.clj
@@ -62,7 +62,7 @@
   just the column identifiers, perhaps for use with something like `select-keys`. Clutch!
 
     (db/select Field:params-columns-only)"
-  ['Field :id :table_id :display_name :base_type :special_type])
+  ['Field :id :table_id :display_name :base_type :special_type :has_field_values])
 
 (defn- fields->table-id->name-field
   "Given a sequence of `fields,` return a map of Table ID -> to a `:type/Name` Field in that Table, if one exists. In
diff --git a/src/metabase/models/table.clj b/src/metabase/models/table.clj
index f5eab443db20e7cca9d5c7272e5011d419f673a9..be920669af127833390d1298625ce9c9d8f8352f 100644
--- a/src/metabase/models/table.clj
+++ b/src/metabase/models/table.clj
@@ -113,7 +113,7 @@
   [tables]
   (with-objects :segments
     (fn [table-ids]
-      (db/select Segment :table_id [:in table-ids], {:order-by [[:name :asc]]}))
+      (db/select Segment :table_id [:in table-ids], :is_active true, {:order-by [[:name :asc]]}))
     tables))
 
 (defn with-metrics
@@ -122,7 +122,7 @@
   [tables]
   (with-objects :metrics
     (fn [table-ids]
-      (db/select Metric :table_id [:in table-ids], {:order-by [[:name :asc]]}))
+      (db/select Metric :table_id [:in table-ids], :is_active true, {:order-by [[:name :asc]]}))
     tables))
 
 (defn with-fields
diff --git a/src/metabase/sync/analyze.clj b/src/metabase/sync/analyze.clj
index e23be8b52a800ab855f75c30f7e636950c4b73c0..e9d9e81cf6e005fdb1452df87f542349ebeacf4a 100644
--- a/src/metabase/sync/analyze.clj
+++ b/src/metabase/sync/analyze.clj
@@ -11,7 +11,7 @@
             [metabase.sync.analyze
              [classify :as classify]
              [fingerprint :as fingerprint]
-             [table-row-count :as table-row-count]]
+             #_[table-row-count :as table-row-count]]
             [metabase.util :as u]
             [schema.core :as s]
             [toucan.db :as db]))
@@ -68,7 +68,8 @@
 (s/defn analyze-table!
   "Perform in-depth analysis for a TABLE."
   [table :- i/TableInstance]
-  (table-row-count/update-row-count! table)
+  ;; Table row count disabled for now because of performance issues
+  #_(table-row-count/update-row-count! table)
   (fingerprint/fingerprint-fields! table)
   (classify/classify-fields! table)
   (classify/classify-table! table)
diff --git a/src/metabase/sync/analyze/classifiers/category.clj b/src/metabase/sync/analyze/classifiers/category.clj
index 1cd48a98de143a32f60cb5082ecd0a654cfc9a5a..705d6268cc8a8c37d306bb6af8a35b38ee14b1b5 100644
--- a/src/metabase/sync/analyze/classifiers/category.clj
+++ b/src/metabase/sync/analyze/classifiers/category.clj
@@ -1,5 +1,15 @@
 (ns metabase.sync.analyze.classifiers.category
-  "Classifier that determines whether a Field should be marked as a `:type/Category` based on the number of distinct values it has."
+  "Classifier that determines whether a Field should be marked as a `:type/Category` and/or as a `list` Field based on
+  the number of distinct values it has.
+
+  As of Metabase v0.29, the Category now longer has any use inside of the Metabase backend; it is used
+  only for frontend purposes (e.g. deciding which widget to show). Previously, makring something as a Category meant
+  that its values should be cached and saved in a FieldValues object. With the changes in v0.29, this is instead
+  managed by a column called `has_field_values`.
+
+  A value of `list` now means the values should be cached. Deciding whether a Field should be a `list` Field is still
+  determined by the cardinality of the Field, like Category status. Thus it is entirely possibly for a Field to be
+  both a Category and a `list` Field."
   (:require [clojure.tools.logging :as log]
             [metabase.models.field-values :as field-values]
             [metabase.sync
@@ -9,22 +19,44 @@
             [schema.core :as s]))
 
 
-(s/defn ^:private cannot-be-category? :- s/Bool
-  [base-type :- su/FieldType]
-  (or (isa? base-type :type/DateTime)
-      (isa? base-type :type/Collection)))
+(s/defn ^:private cannot-be-category-or-list? :- s/Bool
+  [base-type :- su/FieldType, special-type :- (s/maybe su/FieldType)]
+  (or (isa? base-type    :type/DateTime)
+      (isa? base-type    :type/Collection)
+      ;; Don't let IDs become list Fields (they already can't become categories, because they already have a special
+      ;; type). It just doesn't make sense to cache a sequence of numbers since they aren't inherently meaningful
+      (isa? special-type :type/PK)
+      (isa? special-type :type/FK)))
+
+(defn- field-should-be-category? [distinct-count field]
+  ;; only mark a Field as a Category if it doesn't already have a special type
+  (when-not (:special_type field)
+    (when (<= distinct-count field-values/category-cardinality-threshold)
+      (log/debug (format "%s has %d distinct values. Since that is less than %d, we're marking it as a category."
+                         (sync-util/name-for-logging field)
+                         distinct-count
+                         field-values/category-cardinality-threshold))
+      true)))
 
-(s/defn infer-is-category :- (s/maybe i/FieldInstance)
+(defn- field-should-be-list? [distinct-count field]
+  {:pre [(contains? field :has_field_values)]}
+  ;; only update has_field_values if it hasn't been set yet. If it's already been set then it was probably done so
+  ;; manually by an admin, and we don't want to stomp over their choices.
+  (when (nil? (:has_field_values field))
+    (when (<= distinct-count field-values/list-cardinality-threshold)
+      (log/debug (format "%s has %d distinct values. Since that is less than %d, it should have cached FieldValues."
+                         (sync-util/name-for-logging field)
+                         distinct-count
+                         field-values/list-cardinality-threshold))
+      true)))
+
+
+(s/defn infer-is-category-or-list :- (s/maybe i/FieldInstance)
   "Classifier that attempts to determine whether FIELD ought to be marked as a Category based on its distinct count."
   [field :- i/FieldInstance, fingerprint :- (s/maybe i/Fingerprint)]
-  (when-not (:special_type field)
-    (when fingerprint
-      (when-not (cannot-be-category? (:base_type field))
-        (when-let [distinct-count (get-in fingerprint [:global :distinct-count])]
-          (when (< distinct-count field-values/low-cardinality-threshold)
-            (log/debug (format "%s has %d distinct values. Since that is less than %d, we're marking it as a category."
-                               (sync-util/name-for-logging field)
-                               distinct-count
-                               field-values/low-cardinality-threshold))
-            (assoc field
-              :special_type :type/Category)))))))
+  (when fingerprint
+    (when-not (cannot-be-category-or-list? (:base_type field) (:special_type field))
+      (when-let [distinct-count (get-in fingerprint [:global :distinct-count])]
+        (cond-> field
+          (field-should-be-category? distinct-count field) (assoc :special_type :type/Category)
+          (field-should-be-list?     distinct-count field) (assoc :has_field_values "list"))))))
diff --git a/src/metabase/sync/analyze/classify.clj b/src/metabase/sync/analyze/classify.clj
index 19da675453529dc8d136e43c724cf310f3f22b1e..2c9ca3bb5f67ba05e2c7a21920348e6600b1131c 100644
--- a/src/metabase/sync/analyze/classify.clj
+++ b/src/metabase/sync/analyze/classify.clj
@@ -1,21 +1,21 @@
 (ns metabase.sync.analyze.classify
   "Analysis sub-step that takes a fingerprint for a Field and infers and saves appropriate information like special
-   type. Each 'classifier' takes the information available to it and decides whether or not to run.
-   We currently have the following classifiers:
-
-   1.  `name`: Looks at the name of a Field and infers a special type if possible
-   2.  `no-preview-display`: Looks at average length of text Field recorded in fingerprint and decides whether or not
-        we should hide this Field
-   3.  `category`: Looks at the number of distinct values of Field and determines whether it can be a Category
-   4.  `text-fingerprint`: Looks at percentages recorded in a text Fields' TextFingerprint and infers a special type
-        if possible
-
-   All classifier functions take two arguments, a `FieldInstance` and a possibly `nil` `Fingerprint`, and should
-   return the Field with any appropriate changes (such as a new special type). If no changes are appropriate, a
-   classifier may return nil. Error handling is handled by `run-classifiers` below, so individual classiers do not
-   need to handle errors themselves.
-
-   In the future, we plan to add more classifiers, including ML ones that run offline."
+  type. Each 'classifier' takes the information available to it and decides whether or not to run. We currently have
+  the following classifiers:
+
+  1.  `name`: Looks at the name of a Field and infers a special type if possible
+  2.  `no-preview-display`: Looks at average length of text Field recorded in fingerprint and decides whether or not we
+      should hide this Field
+  3.  `category`: Looks at the number of distinct values of Field and determines whether it can be a Category
+  4.  `text-fingerprint`: Looks at percentages recorded in a text Fields' TextFingerprint and infers a special type if
+      possible
+
+  All classifier functions take two arguments, a `FieldInstance` and a possibly `nil` `Fingerprint`, and should return
+  the Field with any appropriate changes (such as a new special type). If no changes are appropriate, a classifier may
+  return nil. Error handling is handled by `run-classifiers` below, so individual classiers do not need to handle
+  errors themselves.
+
+  In the future, we plan to add more classifiers, including ML ones that run offline."
   (:require [clojure.data :as data]
             [clojure.tools.logging :as log]
             [metabase.models
@@ -39,7 +39,7 @@
 
 (def ^:private values-that-can-be-set
   "Columns of Field that classifiers are allowed to set."
-  #{:special_type :preview_display :entity_type})
+  #{:special_type :preview_display :has_field_values :entity_type})
 
 (def ^:private FieldOrTableInstance (s/either i/FieldInstance i/TableInstance))
 
@@ -65,21 +65,22 @@
 
 (def ^:private classifiers
   "Various classifier functions available. These should all take two args, a `FieldInstance` and a possibly `nil`
-   `Fingerprint`, and return `FieldInstance` with any inferred property changes, or `nil` if none could be inferred.
-   Order is important!"
+  `Fingerprint`, and return `FieldInstance` with any inferred property changes, or `nil` if none could be inferred.
+  Order is important!"
   [name/infer-special-type
-   category/infer-is-category
+   category/infer-is-category-or-list
    no-preview-display/infer-no-preview-display
    text-fingerprint/infer-special-type])
 
 (s/defn ^:private run-classifiers :- i/FieldInstance
   "Run all the available `classifiers` against FIELD and FINGERPRINT, and return the resulting FIELD with changes
-   decided upon by the classifiers."
+  decided upon by the classifiers."
   [field :- i/FieldInstance, fingerprint :- (s/maybe i/Fingerprint)]
   (loop [field field, [classifier & more] classifiers]
     (if-not classifier
       field
-      (recur (or (sync-util/with-error-handling (format "Error running classifier on %s" (sync-util/name-for-logging field))
+      (recur (or (sync-util/with-error-handling (format "Error running classifier on %s"
+                                                        (sync-util/name-for-logging field))
                    (classifier field fingerprint))
                  field)
              more))))
@@ -102,8 +103,8 @@
 ;;; +------------------------------------------------------------------------------------------------------------------+
 
 (s/defn ^:private fields-to-classify :- (s/maybe [i/FieldInstance])
-  "Return a sequences of Fields belonging to TABLE for which we should attempt to determine special type.
-   This should include Fields that have the latest fingerprint, but have not yet *completed* analysis."
+  "Return a sequences of Fields belonging to TABLE for which we should attempt to determine special type. This should
+  include Fields that have the latest fingerprint, but have not yet *completed* analysis."
   [table :- i/TableInstance]
   (seq (db/select Field
          :table_id            (u/get-id table)
@@ -111,9 +112,8 @@
          :last_analyzed       nil)))
 
 (s/defn classify-fields!
-  "Run various classifiers on the appropriate FIELDS in a TABLE that have not been previously analyzed.
-   These do things like inferring (and setting) the special types and preview display status for Fields
-   belonging to TABLE."
+  "Run various classifiers on the appropriate FIELDS in a TABLE that have not been previously analyzed. These do things
+  like inferring (and setting) the special types and preview display status for Fields belonging to TABLE."
   [table :- i/TableInstance]
   (when-let [fields (fields-to-classify table)]
     (doseq [field fields]
diff --git a/src/metabase/sync/analyze/fingerprint/global.clj b/src/metabase/sync/analyze/fingerprint/global.clj
index 24c0ed7bb91b10a0e88992cd2dbbc8f65018bcb3..3fa55fd999ce3cf03b3f9346c86d9358553d2449 100644
--- a/src/metabase/sync/analyze/fingerprint/global.clj
+++ b/src/metabase/sync/analyze/fingerprint/global.clj
@@ -7,7 +7,7 @@
   "Generate a fingerprint of global information for Fields of all types."
   [values :- i/FieldSample]
   ;; TODO - this logic isn't as nice as the old logic that actually called the DB
-  ;; We used to do (queries/field-distinct-count field field-values/low-cardinality-threshold)
+  ;; We used to do (queries/field-distinct-count field field-values/list-cardinality-threshold)
   ;; Consider whether we are so married to the idea of only generating fingerprints from samples that we
   ;; are ok with inaccurate counts like the one we'll surely be getting here
   {:distinct-count (count (distinct values))})
diff --git a/src/metabase/sync/field_values.clj b/src/metabase/sync/field_values.clj
index ce85aab842a09dc874a098d44da075e852d1b2f9..f894bc7f350772475460941078f116e84fc238b0 100644
--- a/src/metabase/sync/field_values.clj
+++ b/src/metabase/sync/field_values.clj
@@ -13,8 +13,8 @@
 
 (s/defn ^:private clear-field-values-for-field! [field :- i/FieldInstance]
   (when (db/exists? FieldValues :field_id (u/get-id field))
-    (log/debug (format "Based on type info, %s should no longer have field values.\n" (sync-util/name-for-logging field))
-               (format "(base type: %s, special type: %s, visibility type: %s)\n" (:base_type field) (:special_type field) (:visibility_type field))
+    (log/debug (format "Based on cardinality and/or type information, %s should no longer have field values.\n"
+                       (sync-util/name-for-logging field))
                "Deleting FieldValues...")
     (db/delete! FieldValues :field_id (u/get-id field))))
 
diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj
index 24ec87ef93f30bc52b84c9840c35e588b6783086..0b27c853768a83a5ddac7378a3929d967a1146cf 100644
--- a/test/metabase/api/database_test.clj
+++ b/test/metabase/api/database_test.clj
@@ -15,7 +15,7 @@
              [field-values :as field-values]
              [sync-metadata :as sync-metadata]]
             [metabase.test
-             [data :as data :refer :all]
+             [data :as data]
              [util :as tu :refer [match-$]]]
             [metabase.test.data
              [datasets :as datasets]
@@ -72,9 +72,9 @@
    :timezone                    nil})
 
 (defn- db-details
-  "Return default column values for a database (either the test database, via `(db)`, or optionally passed in)."
+  "Return default column values for a database (either the test database, via `(data/db)`, or optionally passed in)."
   ([]
-   (db-details (db)))
+   (db-details (data/db)))
   ([db]
    (merge default-db-details
           (match-$ db
@@ -102,12 +102,12 @@
 ;; regular users *should not* see DB details
 (expect
   (add-schedules (dissoc (db-details) :details))
-  ((user->client :rasta) :get 200 (format "database/%d" (id))))
+  ((user->client :rasta) :get 200 (format "database/%d" (data/id))))
 
 ;; superusers *should* see DB details
 (expect
   (add-schedules (db-details))
-  ((user->client :crowberto) :get 200 (format "database/%d" (id))))
+  ((user->client :crowberto) :get 200 (format "database/%d" (data/id))))
 
 ;; ## POST /api/database
 ;; Check that we can create a Database
@@ -185,7 +185,7 @@
   (let [ids-to-skip (into (set skip)
                           (for [engine datasets/all-valid-engines
                                 :let   [id (datasets/when-testing-engine engine
-                                             (:id (get-or-create-test-data-db! (driver/engine->driver engine))))]
+                                             (:id (data/get-or-create-test-data-db! (driver/engine->driver engine))))]
                                 :when  id]
                             id))]
     (when-let [dbs (seq (db/select [Database :name :engine :id] :id [:not-in ids-to-skip]))]
@@ -204,7 +204,7 @@
   (set (filter identity (conj (for [engine datasets/all-valid-engines]
                                 (datasets/when-testing-engine engine
                                   (merge default-db-details
-                                         (match-$ (get-or-create-test-data-db! (driver/engine->driver engine))
+                                         (match-$ (data/get-or-create-test-data-db! (driver/engine->driver engine))
                                            {:created_at         $
                                             :engine             (name $engine)
                                             :id                 $
@@ -244,7 +244,7 @@
                        :features           (map name (driver/features (driver/engine->driver :postgres)))}))
              (filter identity (for [engine datasets/all-valid-engines]
                                 (datasets/when-testing-engine engine
-                                  (let [database (get-or-create-test-data-db! (driver/engine->driver engine))]
+                                  (let [database (data/get-or-create-test-data-db! (driver/engine->driver engine))]
                                     (merge default-db-details
                                            (match-$ database
                                              {:created_at         $
@@ -287,7 +287,7 @@
 ;; ## GET /api/database/:id/metadata
 (expect
   (merge default-db-details
-         (match-$ (db)
+         (match-$ (data/db)
            {:created_at $
             :engine     "h2"
             :id         $
@@ -296,21 +296,21 @@
             :timezone   $
             :features   (mapv name (driver/features (driver/engine->driver :h2)))
             :tables     [(merge default-table-details
-                                (match-$ (Table (id :categories))
+                                (match-$ (Table (data/id :categories))
                                   {:schema       "PUBLIC"
                                    :name         "CATEGORIES"
                                    :display_name "Categories"
-                                   :fields       [(assoc (field-details (Field (id :categories :id)))
-                                                    :table_id         (id :categories)
+                                   :fields       [(assoc (field-details (Field (data/id :categories :id)))
+                                                    :table_id         (data/id :categories)
                                                     :special_type     "type/PK"
                                                     :name             "ID"
                                                     :display_name     "ID"
                                                     :database_type    "BIGINT"
                                                     :base_type        "type/BigInteger"
                                                     :visibility_type  "normal"
-                                                    :has_field_values "search")
-                                                  (assoc (field-details (Field (id :categories :name)))
-                                                    :table_id         (id :categories)
+                                                    :has_field_values "none")
+                                                  (assoc (field-details (Field (data/id :categories :name)))
+                                                    :table_id         (data/id :categories)
                                                     :special_type     "type/Name"
                                                     :name             "NAME"
                                                     :display_name     "Name"
@@ -320,13 +320,13 @@
                                                     :has_field_values "list")]
                                    :segments     []
                                    :metrics      []
-                                   :rows         75
+                                   :rows         nil
                                    :updated_at   $
-                                   :id           (id :categories)
+                                   :id           (data/id :categories)
                                    :raw_table_id $
-                                   :db_id        (id)
+                                   :db_id        (data/id)
                                    :created_at   $}))]}))
-  (let [resp ((user->client :rasta) :get 200 (format "database/%d/metadata" (id)))]
+  (let [resp ((user->client :rasta) :get 200 (format "database/%d/metadata" (data/id)))]
     (assoc resp :tables (filter #(= "CATEGORIES" (:name %)) (:tables resp)))))
 
 
@@ -335,18 +335,18 @@
 (expect
   [["USERS" "Table"]
    ["USER_ID" "CHECKINS :type/Integer :type/FK"]]
-  ((user->client :rasta) :get 200 (format "database/%d/autocomplete_suggestions" (id)) :prefix "u"))
+  ((user->client :rasta) :get 200 (format "database/%d/autocomplete_suggestions" (data/id)) :prefix "u"))
 
 (expect
   [["CATEGORIES" "Table"]
    ["CHECKINS" "Table"]
    ["CATEGORY_ID" "VENUES :type/Integer :type/FK"]]
-  ((user->client :rasta) :get 200 (format "database/%d/autocomplete_suggestions" (id)) :prefix "c"))
+  ((user->client :rasta) :get 200 (format "database/%d/autocomplete_suggestions" (data/id)) :prefix "c"))
 
 (expect
   [["CATEGORIES" "Table"]
    ["CATEGORY_ID" "VENUES :type/Integer :type/FK"]]
-  ((user->client :rasta) :get 200 (format "database/%d/autocomplete_suggestions" (id)) :prefix "cat"))
+  ((user->client :rasta) :get 200 (format "database/%d/autocomplete_suggestions" (data/id)) :prefix "cat"))
 
 
 ;;; GET /api/database?include_cards=true
diff --git a/test/metabase/api/field_test.clj b/test/metabase/api/field_test.clj
index 93ef6297650bb2ea3ebee0f014e538ee7a30d26f..8c7101f01068cc0ce286743cf10cb0668fa169d4 100644
--- a/test/metabase/api/field_test.clj
+++ b/test/metabase/api/field_test.clj
@@ -1,4 +1,5 @@
 (ns metabase.api.field-test
+  "Tests for `/api/field` endpoints."
   (:require [expectations :refer :all]
             [metabase
              [driver :as driver]
@@ -56,7 +57,7 @@
                              :schema                  "PUBLIC"
                              :name                    "USERS"
                              :display_name            "Users"
-                             :rows                    15
+                             :rows                    nil
                              :updated_at              $
                              :entity_name             nil
                              :active                  true
@@ -167,7 +168,7 @@
   (:id (field->field-values table-key field-key)))
 
 ;; ## GET /api/field/:id/values
-;; Should return something useful for a field that has special_type :type/Category
+;; Should return something useful for a field whose `has_field_values` is `list`
 (expect
   {:values [[1] [2] [3] [4]], :field_id (data/id :venues :price)}
   (do
@@ -177,7 +178,7 @@
     ;; now update the values via the API
     ((user->client :rasta) :get 200 (format "field/%d/values" (data/id :venues :price)))))
 
-;; Should return nothing for a field whose special_type is *not* :type/Category
+;; Should return nothing for a field whose `has_field_values` is not `list`
 (expect
   {:values [], :field_id (data/id :venues :id)}
   ((user->client :rasta) :get 200 (format "field/%d/values" (data/id :venues :id))))
@@ -187,7 +188,7 @@
   {:values [], :field_id (data/id :users :password)}
   ((user->client :rasta) :get 200 (format "field/%d/values" (data/id :users :password))))
 
-(def ^:private category-field {:name "Field Test" :base_type :type/Integer :special_type :type/Category})
+(def ^:private list-field {:name "Field Test", :base_type :type/Integer, :has_field_values "list"})
 
 ;; ## POST /api/field/:id/values
 
@@ -196,7 +197,7 @@
   [{:values [[5] [6] [7] [8] [9]], :field_id true}
    {:status "success"}
    {:values [[1] [2] [3] [4]], :field_id true}]
-  (tt/with-temp* [Field [{field-id :id} category-field]
+  (tt/with-temp* [Field       [{field-id :id}       list-field]
                   FieldValues [{field-value-id :id} {:values (range 5 10), :field_id field-id}]]
     (mapv tu/boolean-ids-and-timestamps
           [((user->client :crowberto) :get 200 (format "field/%d/values" field-id))
@@ -209,7 +210,7 @@
   [{:values [[1] [2] [3] [4]], :field_id true}
    {:status "success"}
    {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true}]
-  (tt/with-temp* [Field [{field-id :id} category-field]
+  (tt/with-temp* [Field [{field-id :id} list-field]
                   FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id}]]
     (mapv tu/boolean-ids-and-timestamps
           [((user->client :crowberto) :get 200 (format "field/%d/values" field-id))
@@ -222,7 +223,7 @@
   [{:values [], :field_id true}
    {:status "success"}
    {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true}]
-  (tt/with-temp* [Field [{field-id :id} category-field]]
+  (tt/with-temp* [Field [{field-id :id} list-field]]
     (mapv tu/boolean-ids-and-timestamps
           [((user->client :crowberto) :get 200 (format "field/%d/values" field-id))
            ((user->client :crowberto) :post 200 (format "field/%d/values" field-id)
@@ -234,7 +235,7 @@
   [{:values [[1] [2] [3] [4]], :field_id true}
    {:status "success"}
    {:values [], :field_id true}]
-  (tt/with-temp* [Field [{field-id :id} category-field]
+  (tt/with-temp* [Field       [{field-id :id}       list-field]
                   FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id}]]
     (mapv tu/boolean-ids-and-timestamps
           [((user->client :crowberto) :get 200 (format "field/%d/values" field-id))
@@ -247,7 +248,7 @@
   [{:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true}
    {:status "success"}
    {:values [[1] [2] [3] [4]], :field_id true}]
-  (tt/with-temp* [Field [{field-id :id} category-field]
+  (tt/with-temp* [Field       [{field-id :id}       list-field]
                   FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id
                                                      :human_readable_values ["$" "$$" "$$$" "$$$$"]}]]
     (mapv tu/boolean-ids-and-timestamps
@@ -259,18 +260,18 @@
 ;; Should throw when human readable values are present but not for every value
 (expect
   "If remapped values are specified, they must be specified for all field values"
-  (tt/with-temp* [Field [{field-id :id} {:name "Field Test" :base_type :type/Integer :special_type :type/Category}]]
+  (tt/with-temp* [Field [{field-id :id} {:name "Field Test", :base_type :type/Integer, :has_field_values "list"}]]
     ((user->client :crowberto) :post 400 (format "field/%d/values" field-id)
      {:values [[1 "$"] [2 "$$"] [3] [4]]})))
-, :field_id true
-;; ## PUT /api/field/:id/dimensio, :field_id truen
+
+;; ## PUT /api/field/:id/dimension
 
 (defn- dimension-for-field [field-id]
   (-> (Field :id field-id)
       (hydrate :dimensions)
       :dimensions))
 
-(defn- create-dimension-via-API! [field-id map-to-post]
+(defn- create-dimension-via-API! {:style/indent 1} [field-id map-to-post]
   ((user->client :crowberto) :post 200 (format "field/%d/dimension" field-id) map-to-post))
 
 ;; test that we can do basic field update work, including unsetting some fields such as special-type
@@ -320,7 +321,8 @@
   (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}]
                   Field [{field-id-2 :id} {:name "Field Test 2"}]]
     (let [before-creation (dimension-for-field field-id-1)
-          _               (create-dimension-via-API! field-id-1 {:name "some dimension name", :type "external" :human_readable_field_id field-id-2})
+          _               (create-dimension-via-API! field-id-1
+                            {:name "some dimension name", :type "external" :human_readable_field_id field-id-2})
           new-dim         (dimension-for-field field-id-1)]
       [before-creation
        (tu/boolean-ids-and-timestamps new-dim)])))
@@ -330,12 +332,13 @@
   clojure.lang.ExceptionInfo
   (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}]]
     (create-dimension-via-API! field-id-1 {:name "some dimension name", :type "external"})))
-, :field_id true
+
 ;; Non-admin users can't update dimension, :field_id trues
 (expect
   "You don't have permissions to do that."
   (tt/with-temp* [Field [{field-id :id} {:name "Field Test 1"}]]
-    ((user->client :rasta) :post 403 (format "field/%d/dimension" field-id) {:name "some dimension name", :type "external"})))
+    ((user->client :rasta) :post 403 (format "field/%d/dimension" field-id)
+     {:name "some dimension name", :type "external"})))
 
 ;; Ensure we can delete a dimension
 (expect
@@ -375,9 +378,10 @@
   (tt/with-temp* [Field [{field-id-1 :id} {:name         "Field Test 1"
                                            :special_type :type/FK}]
                   Field [{field-id-2 :id} {:name "Field Test 2"}]]
-
-    (create-dimension-via-API! field-id-1 {:name "fk-remove-dimension", :type "external" :human_readable_field_id field-id-2})
-
+    ;; create the Dimension
+    (create-dimension-via-API! field-id-1
+      {:name "fk-remove-dimension", :type "external" :human_readable_field_id field-id-2})
+    ;; not remove the special type (!) TODO
     (let [new-dim          (dimension-for-field field-id-1)
           _                ((user->client :crowberto) :put 200 (format "field/%d" field-id-1) {:special_type nil})
           dim-after-update (dimension-for-field field-id-1)]
@@ -396,11 +400,13 @@
   (tt/with-temp* [Field [{field-id-1 :id} {:name         "Field Test 1"
                                            :special_type :type/FK}]
                   Field [{field-id-2 :id} {:name "Field Test 2"}]]
-
-    (create-dimension-via-API! field-id-1 {:name "fk-remove-dimension", :type "external" :human_readable_field_id field-id-2})
-
+    ;; create the Dimension
+    (create-dimension-via-API! field-id-1
+      {:name "fk-remove-dimension", :type "external" :human_readable_field_id field-id-2})
+    ;; now change something unrelated: description
     (let [new-dim          (dimension-for-field field-id-1)
-          _                ((user->client :crowberto) :put 200 (format "field/%d" field-id-1) {:description "something diffrent"})
+          _                ((user->client :crowberto) :put 200 (format "field/%d" field-id-1)
+                            {:description "something diffrent"})
           dim-after-update (dimension-for-field field-id-1)]
       [(tu/boolean-ids-and-timestamps new-dim)
        (tu/boolean-ids-and-timestamps dim-after-update)])))
@@ -539,7 +545,7 @@
                                          :base_type "type/Integer"}]]
     (create-dimension-via-API! field-id {:name "some dimension name", :type "internal"})
     (let [new-dim (dimension-for-field field-id)]
-      ((user->client :crowberto) :put 200 (format "field/%d" field-id) {:special_type "type/Category"})
+      ((user->client :crowberto) :put 200 (format "field/%d" field-id) {:has_field_values "list"})
       [(tu/boolean-ids-and-timestamps new-dim)
        (tu/boolean-ids-and-timestamps (dimension-for-field field-id))])))
 
diff --git a/test/metabase/api/public_test.clj b/test/metabase/api/public_test.clj
index 3b91394e49bc347b3d6e087223524f21f0a6ddff..bc919af463324437de3755af140b2dac3758087e 100644
--- a/test/metabase/api/public_test.clj
+++ b/test/metabase/api/public_test.clj
@@ -410,6 +410,7 @@
                                    :type         :dimension
                                    :dimension    [:field-id (data/id :venues :name)]}}}}})
 
+
 ;;; ------------------------------------------- card->referenced-field-ids -------------------------------------------
 
 (expect
diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj
index 11e7cf2854a0dcf0e36341eab91d8fbaa9959a95..6f1b2ea84cff4699817ea4b4465274e0a95c9ccd 100644
--- a/test/metabase/api/table_test.clj
+++ b/test/metabase/api/table_test.clj
@@ -113,22 +113,22 @@
 (expect
   #{{:name         (data/format-name "categories")
      :display_name "Categories"
-     :rows         75
+     :rows         0
      :id           (data/id :categories)
      :entity_type  "entity/GenericTable"}
     {:name         (data/format-name "checkins")
      :display_name "Checkins"
-     :rows         1000
+     :rows         0
      :id           (data/id :checkins)
      :entity_type  "entity/EventTable"}
     {:name         (data/format-name "users")
      :display_name "Users"
-     :rows         15
+     :rows         0
      :id           (data/id :users)
      :entity_type  "entity/UserTable"}
     {:name         (data/format-name "venues")
      :display_name "Venues"
-     :rows         100
+     :rows         0
      :id           (data/id :venues)
      :entity_type  "entity/GenericTable"}}
   (->> ((user->client :rasta) :get 200 "table")
@@ -146,7 +146,7 @@
            {:schema       "PUBLIC"
             :name         "VENUES"
             :display_name "Venues"
-            :rows         100
+            :rows         nil
             :updated_at   $
             :pk_field     (#'table/pk-field-id $$)
             :id           (data/id :venues)
@@ -186,7 +186,7 @@
                              :display_name     "ID"
                              :database_type    "BIGINT"
                              :base_type        "type/BigInteger"
-                             :has_field_values "search")
+                             :has_field_values "none")
                            (assoc (field-details (Field (data/id :categories :name)))
                              :table_id                 (data/id :categories)
                              :special_type             "type/Name"
@@ -197,7 +197,7 @@
                              :dimension_options        []
                              :default_dimension_option nil
                              :has_field_values         "list")]
-            :rows         75
+            :rows         nil
             :updated_at   $
             :id           (data/id :categories)
             :raw_table_id $
@@ -240,7 +240,7 @@
                              :database_type    "BIGINT"
                              :base_type        "type/BigInteger"
                              :visibility_type  "normal"
-                             :has_field_values "search")
+                             :has_field_values "none")
                            (assoc (field-details (Field (data/id :users :last_login)))
                              :table_id                 (data/id :users)
                              :name                     "LAST_LOGIN"
@@ -250,7 +250,7 @@
                              :visibility_type          "normal"
                              :dimension_options        (var-get #'table-api/datetime-dimension-indexes)
                              :default_dimension_option (var-get #'table-api/date-default-index)
-                             :has_field_values         "search")
+                             :has_field_values         "none")
                            (assoc (field-details (Field (data/id :users :name)))
                              :special_type             "type/Name"
                              :table_id                 (data/id :users)
@@ -271,7 +271,7 @@
                              :base_type        "type/Text"
                              :visibility_type  "sensitive"
                              :has_field_values "list")]
-            :rows         15
+            :rows         nil
             :updated_at   $
             :id           (data/id :users)
             :raw_table_id $
@@ -294,7 +294,7 @@
                              :display_name     "ID"
                              :database_type    "BIGINT"
                              :base_type        "type/BigInteger"
-                             :has_field_values "search")
+                             :has_field_values "none")
                            (assoc (field-details (Field (data/id :users :last_login)))
                              :table_id                 (data/id :users)
                              :name                     "LAST_LOGIN"
@@ -303,7 +303,7 @@
                              :base_type                "type/DateTime"
                              :dimension_options        (var-get #'table-api/datetime-dimension-indexes)
                              :default_dimension_option (var-get #'table-api/date-default-index)
-                             :has_field_values         "search")
+                             :has_field_values         "none")
                            (assoc (field-details (Field (data/id :users :name)))
                              :table_id         (data/id :users)
                              :special_type     "type/Name"
@@ -312,7 +312,7 @@
                              :database_type    "VARCHAR"
                              :base_type        "type/Text"
                              :has_field_values "list")]
-            :rows         15
+            :rows         nil
             :updated_at   $
             :id           (data/id :users)
             :raw_table_id $
@@ -340,7 +340,7 @@
 
 
 ;; ## PUT /api/table/:id
-(tt/expect-with-temp [Table [table {:rows 15}]]
+(tt/expect-with-temp [Table [table]]
   (merge (-> (table-defaults)
              (dissoc :segments :field_values :metrics)
              (assoc-in [:db :details] {:db "mem:test-data;USER=GUEST;PASSWORD=guest"}))
@@ -350,7 +350,7 @@
             :visibility_type "hidden"
             :schema          $
             :name            $
-            :rows            15
+            :rows            nil
             :display_name    "Userz"
             :pk_field        (#'table/pk-field-id $$)
             :id              $
@@ -363,7 +363,9 @@
       (dissoc ((user->client :crowberto) :get 200 (format "table/%d" (:id table)))
               :updated_at)))
 
-(tt/expect-with-temp [Table [table {:rows 15}]]
+;; see how many times sync-table! gets called when we call the PUT endpoint. It should happen when you switch from
+;; hidden -> not hidden at the spots marked below, twice total
+(tt/expect-with-temp [Table [table]]
   2
   (let [original-sync-table! sync/sync-table!
         called (atom 0)
@@ -375,11 +377,11 @@
                                                                                           :visibility_type state
                                                                                           :description     "What a nice table!"})))]
     (do (test-fun "hidden")
-        (test-fun nil)
+        (test-fun nil)         ; <- should get synced
         (test-fun "hidden")
         (test-fun "cruft")
         (test-fun "technical")
-        (test-fun nil)
+        (test-fun nil)         ; <- should get synced again
         (test-fun "technical")
         @called)))
 
@@ -406,7 +408,7 @@
                                                           :name         "CHECKINS"
                                                           :display_name "Checkins"
                                                           :entity_type  "entity/EventTable"
-                                                          :rows         1000
+                                                          :rows         nil
                                                           :updated_at   $
                                                           :id           $
                                                           :raw_table_id $
@@ -419,17 +421,17 @@
                                  :base_type     "type/BigInteger"
                                  :database_type "BIGINT"
                                  :special_type  "type/PK"
-                                 :table        (merge (dissoc (table-defaults) :db :segments :field_values :metrics)
-                                                      (match-$ (Table (data/id :users))
-                                                        {:schema       "PUBLIC"
-                                                         :name         "USERS"
-                                                         :display_name "Users"
-                                                         :entity_type  "entity/UserTable"
-                                                         :rows         15
-                                                         :updated_at   $
-                                                         :id           $
-                                                         :raw_table_id $
-                                                         :created_at   $}))))}])
+                                 :table         (merge (dissoc (table-defaults) :db :segments :field_values :metrics)
+                                                       (match-$ (Table (data/id :users))
+                                                         {:schema       "PUBLIC"
+                                                          :name         "USERS"
+                                                          :display_name "Users"
+                                                          :entity_type  "entity/UserTable"
+                                                          :rows         nil
+                                                          :updated_at   $
+                                                          :id           $
+                                                          :raw_table_id $
+                                                          :created_at   $}))))}])
   ((user->client :rasta) :get 200 (format "table/%d/fks" (data/id :users))))
 
 ;; Make sure metadata for 'virtual' tables comes back as expected from GET /api/table/:id/query_metadata
diff --git a/test/metabase/models/field_test.clj b/test/metabase/models/field_test.clj
index 42d75f1de2f85dae1657f49097e6485e7122e5b0..a2981731b288c28c72bf1f86749927da339bf164 100644
--- a/test/metabase/models/field_test.clj
+++ b/test/metabase/models/field_test.clj
@@ -1,72 +1,8 @@
 (ns metabase.models.field-test
+  "Tests for specific behavior related to the Field model."
   (:require [expectations :refer :all]
-            [metabase.models.field-values :refer :all]
             [metabase.sync.analyze.classifiers.name :as name]))
 
-;; field-should-have-field-values?
-
-;; retired/sensitive/hidden/details-only fields should always be excluded
-(expect false (field-should-have-field-values? {:base_type       :type/Boolean
-                                                :special_type    :type/Category
-                                                :visibility_type :retired}))
-(expect false (field-should-have-field-values? {:base_type       :type/Boolean
-                                                :special_type    :type/Category
-                                                :visibility_type :sensitive}))
-(expect false (field-should-have-field-values? {:base_type       :type/Boolean
-                                                :special_type    :type/Category
-                                                :visibility_type :hidden}))
-(expect false (field-should-have-field-values? {:base_type       :type/Boolean
-                                                :special_type    :type/Category
-                                                :visibility_type :details-only}))
-;; date/time based fields should always be excluded
-(expect false (field-should-have-field-values? {:base_type       :type/Date
-                                                :special_type    :type/Category
-                                                :visibility_type :normal}))
-(expect false (field-should-have-field-values? {:base_type       :type/DateTime
-                                                :special_type    :type/Category
-                                                :visibility_type :normal}))
-(expect false (field-should-have-field-values? {:base_type       :type/Time
-                                                :special_type    :type/Category
-                                                :visibility_type :normal}))
-;; most special types should be excluded
-(expect false (field-should-have-field-values? {:base_type       :type/Text
-                                                :special_type    :type/ImageURL
-                                                :visibility_type :normal}))
-(expect false (field-should-have-field-values? {:base_type       :type/Text
-                                                :special_type    :id
-                                                :visibility_type :normal}))
-(expect false (field-should-have-field-values? {:base_type       :type/Text
-                                                :special_type    :type/FK
-                                                :visibility_type :normal}))
-(expect false (field-should-have-field-values? {:base_type       :type/Text
-                                                :special_type    :type/Latitude
-                                                :visibility_type :normal}))
-(expect false (field-should-have-field-values? {:base_type       :type/Text
-                                                :special_type    :type/Number
-                                                :visibility_type :normal}))
-(expect false (field-should-have-field-values? {:base_type       :type/Text
-                                                :special_type    :type/UNIXTimestampMilliseconds
-                                                :visibility_type :normal}))
-;; boolean fields + category/city/state/country fields are g2g
-(expect true (field-should-have-field-values? {:base_type       :type/Boolean
-                                               :special_type    :type/Number
-                                               :visibility_type :normal}))
-(expect true (field-should-have-field-values? {:base_type       :type/Text
-                                               :special_type    :type/Category
-                                               :visibility_type :normal}))
-(expect true (field-should-have-field-values? {:base_type       :type/Text
-                                               :special_type    :type/City
-                                               :visibility_type :normal}))
-(expect true (field-should-have-field-values? {:base_type       :type/Text
-                                               :special_type    :type/State
-                                               :visibility_type :normal}))
-(expect true (field-should-have-field-values? {:base_type       :type/Text
-                                               :special_type    :type/Country
-                                               :visibility_type :normal}))
-(expect true (field-should-have-field-values? {:base_type       :type/Text
-                                               :special_type    :type/Name
-                                               :visibility_type :normal}))
-
 
 ;;; infer-field-special-type
 (expect :type/PK       (#'name/special-type-for-name-and-base-type "id"      :type/Integer))
diff --git a/test/metabase/models/field_values_test.clj b/test/metabase/models/field_values_test.clj
index 2b93f6ef1416d61dfba5134894bfab72abf68389..fb1e94c0d67a97b88a2664ccbd79cc0974ef82a7 100644
--- a/test/metabase/models/field_values_test.clj
+++ b/test/metabase/models/field_values_test.clj
@@ -1,4 +1,5 @@
 (ns metabase.models.field-values-test
+  "Tests for specific behavior related to FieldValues and functions in the `metabase.models.field-values` namespace."
   (:require [clojure.java.jdbc :as jdbc]
             [expectations :refer :all]
             [metabase
@@ -13,36 +14,70 @@
             [toucan.db :as db]
             [toucan.util.test :as tt]))
 
-;; ## TESTS FOR FIELD-SHOULD-HAVE-FIELD-VALUES?
+;;; ---------------------------------------- field-should-have-field-values? -----------------------------------------
 
-(expect (field-should-have-field-values? {:special_type    :type/Category
-                                          :visibility_type :normal
-                                          :base_type       :type/Text}))
+(expect (field-should-have-field-values? {:has_field_values "list"
+                                          :visibility_type  :normal
+                                          :base_type        :type/Text}))
 
-(expect false (field-should-have-field-values? {:special_type    :type/Category
-                                                :visibility_type :sensitive
-                                                :base_type       :type/Text}))
+(expect false (field-should-have-field-values? {:has_field_values "list"
+                                                :visibility_type  :sensitive
+                                                :base_type        :type/Text}))
 
-(expect false (field-should-have-field-values? {:special_type    :type/Category
-                                                :visibility_type :hidden
-                                                :base_type       :type/Text}))
+(expect false (field-should-have-field-values? {:has_field_values "list"
+                                                :visibility_type  :hidden
+                                                :base_type        :type/Text}))
 
-(expect false (field-should-have-field-values? {:special_type :type/Category
-                                                :visibility_type          :details-only
-                                                :base_type                :type/Text}))
+(expect false (field-should-have-field-values? {:has_field_values "list"
+                                                :visibility_type  :details-only
+                                                :base_type        :type/Text}))
 
-(expect false (field-should-have-field-values? {:special_type    nil
+(expect false (field-should-have-field-values? {:has_field_values nil
                                                 :visibility_type :normal
                                                 :base_type       :type/Text}))
 
-(expect (field-should-have-field-values? {:special_type    "type/Country"
-                                          :visibility_type :normal
-                                          :base_type       :type/Text}))
+(expect (field-should-have-field-values? {:has_field_values "list"
+                                          :visibility_type  :normal
+                                          :base_type        :type/Text}))
+
+(expect (field-should-have-field-values? {:has_field_values "list"
+                                          :special_type     :type/Category
+                                          :visibility_type  :normal
+                                          :base_type        "type/Boolean"}))
+
+
+;; retired/sensitive/hidden/details-only fields should always be excluded
+(expect false (field-should-have-field-values? {:base_type        :type/Boolean
+                                                :has_field_values "list"
+                                                :visibility_type  :retired}))
+
+(expect false (field-should-have-field-values? {:base_type        :type/Boolean
+                                                :has_field_values "list"
+                                                :visibility_type  :sensitive}))
+
+(expect false (field-should-have-field-values? {:base_type        :type/Boolean
+                                                :has_field_values "list"
+                                                :visibility_type  :hidden}))
+
+(expect false (field-should-have-field-values? {:base_type        :type/Boolean
+                                                :has_field_values "list"
+                                                :visibility_type  :details-only}))
+
+;; date/time based fields should always be excluded
+(expect false (field-should-have-field-values? {:base_type        :type/Date
+                                                :has_field_values "list"
+                                                :visibility_type  :normal}))
+
+(expect false (field-should-have-field-values? {:base_type        :type/DateTime
+                                                :has_field_values "list"
+                                                :visibility_type  :normal}))
+
+(expect false (field-should-have-field-values? {:base_type        :type/Time
+                                                :has_field_values "list"
+                                                :visibility_type  :normal}))
 
-(expect (field-should-have-field-values? {:special_type    nil
-                                          :visibility_type :normal
-                                          :base_type       "type/Boolean"}))
 
+;;; ------------------------------------------------ everything else -------------------------------------------------
 
 (expect
   [[1 2 3]
diff --git a/test/metabase/models/on_demand_test.clj b/test/metabase/models/on_demand_test.clj
index 9a7b17c8343e40d5fa34821570280b057b6f14a2..3626cd7e80e7f143538ab150ef1c8b5c8b215cd2 100644
--- a/test/metabase/models/on_demand_test.clj
+++ b/test/metabase/models/on_demand_test.clj
@@ -14,8 +14,8 @@
             [toucan.util.test :as tt]))
 
 (defn- do-with-mocked-field-values-updating
-  "Run F the function responsible for updating FieldValues bound to a mock function that instead just records
-   the names of Fields that should have been updated. Returns the set of updated Field names."
+  "Run F the function responsible for updating FieldValues bound to a mock function that instead just records the names
+  of Fields that should have been updated. Returns the set of updated Field names."
   {:style/indent 0}
   [f]
   (let [updated-field-names (atom #{})]
@@ -44,7 +44,7 @@
   (tt/with-temp* [Database [db    (:db options)]
                   Table    [table (merge {:db_id (u/get-id db)}
                                          (:table options))]
-                  Field    [field (merge {:table_id (u/get-id table), :special_type "type/Category"}
+                  Field    [field (merge {:table_id (u/get-id table), :has_field_values "list"}
                                          (:field options))]]
     (do-with-mocked-field-values-updating
       (fn [updated-field-names]
@@ -90,7 +90,7 @@
       ;; clear out the list of updated field names
       (reset! updated-field-names #{})
       ;; now Change the Field that is referenced by the Card's SQL param
-      (tt/with-temp Field [new-field {:table_id (u/get-id table), :special_type "type/Category", :name "New Field"}]
+      (tt/with-temp Field [new-field {:table_id (u/get-id table), :has_field_values "list", :name "New Field"}]
         (db/update! Card (u/get-id card)
           :dataset_query (native-query-with-template-tag new-field))))))
 
@@ -133,7 +133,7 @@
   (do-with-updated-fields-for-card {:db {:is_on_demand false}}
     (fn [{:keys [table card]}]
       ;; change the query to one referencing a different Field. Field should not get values since DB is not On-Demand
-      (tt/with-temp Field [new-field {:table_id (u/get-id table), :special_type "type/Category", :name "New Field"}]
+      (tt/with-temp Field [new-field {:table_id (u/get-id table), :has_field_values "list", :name "New Field"}]
         (db/update! Card (u/get-id card)
           :dataset_query (native-query-with-template-tag new-field))))))
 
@@ -193,9 +193,9 @@
   (do-with-updated-fields-for-dashboard {:db {:is_on_demand true}}
     (fn [{:keys [table field card dash dashcard updated-field-names]}]
       ;; create a Dashboard and add a DashboardCard with a param mapping
-      (tt/with-temp Field [new-field {:table_id     (u/get-id table)
-                                      :name         "New Field"
-                                      :special_type "type/Category"}]
+      (tt/with-temp Field [new-field {:table_id         (u/get-id table)
+                                      :name             "New Field"
+                                      :has_field_values "list"}]
         ;; clear out the list of updated Field Names
         (reset! updated-field-names #{})
         ;; ok, now update the parameter mapping to the new field. The new Field should get new values
@@ -219,6 +219,6 @@
   #{}
   (do-with-updated-fields-for-dashboard {:db {:is_on_demand false}}
     (fn [{:keys [table field card dash dashcard updated-field-names]}]
-      (tt/with-temp Field [new-field {:table_id (u/get-id table), :special_type "type/Category"}]
+      (tt/with-temp Field [new-field {:table_id (u/get-id table), :has_field_values "list"}]
         (dashboard/update-dashcards! dash
           [(assoc dashcard :parameter_mappings (parameter-mappings-for-card-and-field card new-field))])))))
diff --git a/test/metabase/models/params_test.clj b/test/metabase/models/params_test.clj
index 399178146663bc8d3983454cb5653740e6dc18c4..743fd0d451850336c5cc42c2d4d1348975c3f0b5 100644
--- a/test/metabase/models/params_test.clj
+++ b/test/metabase/models/params_test.clj
@@ -19,11 +19,12 @@
   {:name         "ID"
    :table_id     (data/id :venues)
    :special_type :type/PK
-   :name_field   {:id           (data/id :venues :name)
-                  :table_id     (data/id :venues)
-                  :display_name "Name"
-                  :base_type    :type/Text
-                  :special_type :type/Name}}
+   :name_field   {:id               (data/id :venues :name)
+                  :table_id         (data/id :venues)
+                  :display_name     "Name"
+                  :base_type        :type/Text
+                  :special_type     :type/Name
+                  :has_field_values "list"}}
   (-> (db/select-one [Field :name :table_id :special_type], :id (data/id :venues :id))
       (hydrate :name_field)))
 
@@ -58,26 +59,27 @@
 
 ;; check that we can hydrate param_fields for a Card
 (expect
- {(data/id :venues :id) {:id               (data/id :venues :id)
-                         :table_id         (data/id :venues)
-                         :display_name     "ID"
-                         :base_type        :type/BigInteger
-                         :special_type     :type/PK
-                         :has_field_values :search
-                         :name_field       {:id           (data/id :venues :name)
-                                            :table_id     (data/id :venues)
-                                            :display_name "Name"
-                                            :base_type    :type/Text
-                                            :special_type :type/Name}
-                         :dimensions       []}}
- (tt/with-temp Card [card {:dataset_query
-                           {:database (data/id)
-                            :type     :native
-                            :native   {:query         "SELECT COUNT(*) FROM VENUES WHERE {{x}}"
-                                       :template_tags {:name {:name         :name
-                                                              :display_name "Name"
-                                                              :type         :dimension
-                                                              :dimension    [:field-id (data/id :venues :id)]}}}}}]
+  {(data/id :venues :id) {:id               (data/id :venues :id)
+                          :table_id         (data/id :venues)
+                          :display_name     "ID"
+                          :base_type        :type/BigInteger
+                          :special_type     :type/PK
+                          :has_field_values :none
+                          :name_field       {:id               (data/id :venues :name)
+                                             :table_id         (data/id :venues)
+                                             :display_name     "Name"
+                                             :base_type        :type/Text
+                                             :special_type     :type/Name
+                                             :has_field_values "list"}
+                          :dimensions       []}}
+  (tt/with-temp Card [card {:dataset_query
+                            {:database (data/id)
+                             :type     :native
+                             :native   {:query         "SELECT COUNT(*) FROM VENUES WHERE {{x}}"
+                                        :template_tags {:name {:name         :name
+                                                               :display_name "Name"
+                                                               :type         :dimension
+                                                               :dimension    [:field-id (data/id :venues :id)]}}}}}]
    (-> (hydrate card :param_fields)
        :param_fields)))
 
@@ -88,12 +90,13 @@
                           :display_name     "ID"
                           :base_type        :type/BigInteger
                           :special_type     :type/PK
-                          :has_field_values :search
-                          :name_field       {:id           (data/id :venues :name)
-                                             :table_id     (data/id :venues)
-                                             :display_name "Name"
-                                             :base_type    :type/Text
-                                             :special_type :type/Name}
+                          :has_field_values :none
+                          :name_field       {:id               (data/id :venues :name)
+                                             :table_id         (data/id :venues)
+                                             :display_name     "Name"
+                                             :base_type        :type/Text
+                                             :special_type     :type/Name
+                                             :has_field_values "list"}
                           :dimensions       []}}
   (public-test/with-sharing-enabled-and-temp-dashcard-referencing :venues :id [dashboard]
     (-> (hydrate dashboard :param_fields)
diff --git a/test/metabase/sample_dataset_test.clj b/test/metabase/sample_dataset_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..72ad087c4d8d8d12dcc8d401d1dca95f093ee867
--- /dev/null
+++ b/test/metabase/sample_dataset_test.clj
@@ -0,0 +1,71 @@
+(ns metabase.sample-dataset-test
+  "Tests to make sure the Sample Dataset syncs the way we would expect."
+  (:require [expectations :refer :all]
+            [metabase.models
+             [database :refer [Database]]
+             [field :refer [Field]]
+             [table :refer [Table]]]
+            [metabase.sample-data :as sample-data]
+            [metabase.sync :as sync]
+            [metabase.util :as u]
+            [toucan.db :as db]
+            [toucan.hydrate :refer [hydrate]]
+            [toucan.util.test :as tt]))
+
+;;; ---------------------------------------------------- Tooling -----------------------------------------------------
+
+;; These tools are pretty sophisticated for the amount of
+
+(defn- sample-dataset-db []
+  {:details (#'sample-data/db-details)
+   :engine  :h2
+   :name    "Sample Dataset"})
+
+(defmacro ^:private with-temp-sample-dataset-db
+  "Execute `body` with a temporary Sample Dataset DB bound to `db-binding`."
+  {:style/indent 1}
+  [[db-binding] & body]
+  `(tt/with-temp Database [db# (sample-dataset-db)]
+     (sync/sync-database! db#)
+     (let [~db-binding db#]
+       ~@body)))
+
+(defn- table
+  "Get the Table in a `db` with `table-name`."
+  [db table-name]
+  (db/select-one Table :name table-name, :db_id (u/get-id db)))
+
+(defn- field
+  "Get the Field in a `db` with `table-name` and `field-name.`"
+  [db table-name field-name]
+  (db/select-one Field :name field-name, :table_id (u/get-id (table db table-name))))
+
+
+;;; ----------------------------------------------------- Tests ------------------------------------------------------
+
+;; Make sure the Sample Dataset is getting synced correctly. For example PEOPLE.NAME should be has_field_values = search
+;; instead of `list`.
+(expect
+  {:description      "The name of the user who owns an account"
+   :database_type    "VARCHAR"
+   :special_type     :type/Name
+   :name             "NAME"
+   :has_field_values :search
+   :active           true
+   :visibility_type  :normal
+   :preview_display  true
+   :display_name     "Name"
+   :fingerprint      {:global {:distinct-count 2500}
+                      :type   {:type/Text {:percent-json   0.0
+                                           :percent-url    0.0
+                                           :percent-email  0.0
+                                           :average-length 13.516}}}
+   :base_type        :type/Text}
+  (with-temp-sample-dataset-db [db]
+    (-> (field db "PEOPLE" "NAME")
+        ;; it should be `nil` after sync but get set to `search` by the auto-inference. We only set `list` in sync and
+        ;; setting anything else is reserved for admins, however we fill in what we think should be the appropiate value
+        ;; with the hydration fn
+        (hydrate :has_field_values)
+        (select-keys [:name :description :database_type :special_type :has_field_values :active :visibility_type
+                      :preview_display :display_name :fingerprint :base_type]))))
diff --git a/test/metabase/sync/analyze/classifiers/category_test.clj b/test/metabase/sync/analyze/classifiers/category_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..f258bdbd2a2246558794220c1f7d1f2ac1880cfa
--- /dev/null
+++ b/test/metabase/sync/analyze/classifiers/category_test.clj
@@ -0,0 +1,27 @@
+(ns metabase.sync.analyze.classifiers.category-test
+  "Tests for the category classifier."
+  (:require [expectations :refer :all]
+            [metabase.sync.analyze.classifiers.category :as category-classifier]))
+
+;; make sure the logic for deciding whether a Field should be a list works as expected
+(expect
+  nil
+  (#'category-classifier/field-should-be-list?
+   2500
+   {:database_type       "VARCHAR"
+    :special_type        :type/Name
+    :name                "NAME"
+    :fingerprint_version 1
+    :has_field_values    nil
+    :active              true
+    :visibility_type     :normal
+    :preview_display     true
+    :display_name        "Name"
+    :fingerprint         {:global {:distinct-count 2500}
+                          :type
+                          {:type/Text
+                           {:percent-json   0.0
+                            :percent-url    0.0
+                            :percent-email  0.0
+                            :average-length 13.516}}}
+    :base_type           :type/Text}))
diff --git a/test/metabase/sync_database_test.clj b/test/metabase/sync_database_test.clj
index 396de7105463b42f50bc41c9dc262297732618d8..f104585db99e614621cb181b385f2cbfa3bebbec 100644
--- a/test/metabase/sync_database_test.clj
+++ b/test/metabase/sync_database_test.clj
@@ -16,7 +16,7 @@
              [field-values :as field-values :refer [FieldValues]]
              [table :refer [Table]]]
             [metabase.test
-             [data :refer :all]
+             [data :as data]
              [util :as tu]]
             [metabase.test.mock.util :as mock-util]
             [toucan.db :as db]
@@ -103,7 +103,7 @@
    :entity_type             :entity/GenericTable
    :entity_name             nil
    :visibility_type         nil
-   :rows                    1000
+   :rows                    nil
    :active                  true
    :created_at              true
    :updated_at              true})
@@ -170,8 +170,8 @@
                                   :base_type     :type/Text})]})]
   (tt/with-temp Database [db {:engine :sync-test}]
     (sync-database! db)
-    ;; we are purposely running the sync twice to test for possible logic issues which only manifest
-    ;; on resync of a database, such as adding tables that already exist or duplicating fields
+    ;; we are purposely running the sync twice to test for possible logic issues which only manifest on resync of a
+    ;; database, such as adding tables that already exist or duplicating fields
     (sync-database! db)
     (mapv table-details (db/select Table, :db_id (u/get-id db), {:order-by [:name]}))))
 
@@ -253,8 +253,8 @@
      @calls-to-describe-database)))
 
 
-;; Test that we will remove field-values when they aren't appropriate.  Calling `sync-database!` below should cause
-;; them to get removed since the Field doesn't have an appropriate special type
+;; Test that we will remove field-values when they aren't appropriate. Calling `sync-database!` below should cause
+;; them to get removed since the Field isn't `has_field_values` = `list`
 (expect
   [[1 2 3]
    nil]
@@ -278,41 +278,41 @@
          :type/PK
          :type/Latitude
          :type/PK]
-  (let [get-special-type (fn [] (db/select-one-field :special_type Field, :id (id :venues :id)))]
+  (let [get-special-type (fn [] (db/select-one-field :special_type Field, :id (data/id :venues :id)))]
     [;; Special type should be :id to begin with
      (get-special-type)
      ;; Clear out the special type
-     (do (db/update! Field (id :venues :id), :special_type nil)
+     (do (db/update! Field (data/id :venues :id), :special_type nil)
          (get-special-type))
      ;; Calling sync-table! should set the special type again
-     (do (sync-table! (Table (id :venues)))
+     (do (sync-table! (Table (data/id :venues)))
          (get-special-type))
      ;; sync-table! should *not* change the special type of fields that are marked with a different type
-     (do (db/update! Field (id :venues :id), :special_type :type/Latitude)
+     (do (db/update! Field (data/id :venues :id), :special_type :type/Latitude)
          (get-special-type))
      ;; Make sure that sync-table runs set-table-pks-if-needed!
-     (do (db/update! Field (id :venues :id), :special_type nil)
-         (sync-table! (Table (id :venues)))
+     (do (db/update! Field (data/id :venues :id), :special_type nil)
+         (sync-table! (Table (data/id :venues)))
          (get-special-type))]))
 
 ;; ## FK SYNCING
 
 ;; Check that Foreign Key relationships were created on sync as we expect
 
-(expect (id :venues :id)
-  (db/select-one-field :fk_target_field_id Field, :id (id :checkins :venue_id)))
+(expect (data/id :venues :id)
+  (db/select-one-field :fk_target_field_id Field, :id (data/id :checkins :venue_id)))
 
-(expect (id :users :id)
-  (db/select-one-field :fk_target_field_id Field, :id (id :checkins :user_id)))
+(expect (data/id :users :id)
+  (db/select-one-field :fk_target_field_id Field, :id (data/id :checkins :user_id)))
 
-(expect (id :categories :id)
-  (db/select-one-field :fk_target_field_id Field, :id (id :venues :category_id)))
+(expect (data/id :categories :id)
+  (db/select-one-field :fk_target_field_id Field, :id (data/id :venues :category_id)))
 
 ;; Check that sync-table! causes FKs to be set like we'd expect
 (expect [{:special_type :type/FK, :fk_target_field_id true}
-         {:special_type nil, :fk_target_field_id false}
+         {:special_type nil,      :fk_target_field_id false}
          {:special_type :type/FK, :fk_target_field_id true}]
-  (let [field-id (id :checkins :user_id)
+  (let [field-id (data/id :checkins :user_id)
         get-special-type-and-fk-exists? (fn []
                                           (into {} (-> (db/select-one [Field :special_type :fk_target_field_id],
                                                          :id field-id)
@@ -323,15 +323,15 @@
      (do (db/update! Field field-id, :special_type nil, :fk_target_field_id nil)
          (get-special-type-and-fk-exists?))
      ;; Run sync-table and they should be set again
-     (let [table (Table (id :checkins))]
+     (let [table (Table (data/id :checkins))]
        (sync-table! table)
        (get-special-type-and-fk-exists?))]))
 
 
 ;;; ## FieldValues Syncing
 
-(let [get-field-values    (fn [] (db/select-one-field :values FieldValues, :field_id (id :venues :price)))
-      get-field-values-id (fn [] (db/select-one-id FieldValues, :field_id (id :venues :price)))]
+(let [get-field-values    (fn [] (db/select-one-field :values FieldValues, :field_id (data/id :venues :price)))
+      get-field-values-id (fn [] (db/select-one-id FieldValues, :field_id (data/id :venues :price)))]
   ;; Test that when we delete FieldValues syncing the Table again will cause them to be re-created
   (expect
     [[1 2 3 4]  ; 1
@@ -343,7 +343,7 @@
      (do (db/delete! FieldValues :id (get-field-values-id))
          (get-field-values))
      ;; 3. Now re-sync the table and make sure they're back
-     (do (sync-table! (Table (id :venues)))
+     (do (sync-table! (Table (data/id :venues)))
          (get-field-values))])
 
   ;; Test that syncing will cause FieldValues to be updated
@@ -357,12 +357,14 @@
      (do (db/update! FieldValues (get-field-values-id), :values [1 2 3])
          (get-field-values))
      ;; 3. Now re-sync the table and make sure the value is back
-     (do (sync-table! (Table (id :venues)))
+     (do (sync-table! (Table (data/id :venues)))
          (get-field-values))]))
 
-;; Make sure that if a Field's cardinality passes `low-cardinality-threshold` (currently 300)
-;; the corresponding FieldValues entry will be deleted (#3215)
-(defn- insert-range-sql [rang]
+;; Make sure that if a Field's cardinality passes `list-cardinality-threshold` (currently 100) the corresponding
+;; FieldValues entry will be deleted (#3215)
+(defn- insert-range-sql
+  "Generate SQL to insert a row for each number in `rang`."
+  [rang]
   (str "INSERT INTO blueberries_consumed (num) VALUES "
        (str/join ", " (for [n rang]
                         (str "(" n ")")))))
@@ -375,18 +377,18 @@
         (jdbc/with-db-connection [conn (sql/connection-details->spec (driver/engine->driver :h2) details)]
           (let [exec! #(doseq [statement %]
                          (jdbc/execute! conn [statement]))]
-            ;; create the `blueberries_consumed` table and insert a 100 values
+            ;; create the `blueberries_consumed` table and insert 50 values
             (exec! ["CREATE TABLE blueberries_consumed (num INTEGER NOT NULL);"
-                    (insert-range-sql (range 100))])
+                    (insert-range-sql (range 50))])
             (sync-database! db)
             (let [table-id (db/select-one-id Table :db_id (u/get-id db))
                   field-id (db/select-one-id Field :table_id table-id)]
               ;; field values should exist...
               (assert (= (count (db/select-one-field :values FieldValues :field_id field-id))
-                         100))
-              ;; ok, now insert enough rows to push the field past the `low-cardinality-threshold` and sync again,
+                         50))
+              ;; ok, now insert enough rows to push the field past the `list-cardinality-threshold` and sync again,
               ;; there should be no more field values
-              (exec! [(insert-range-sql (range 100 (+ 100 field-values/low-cardinality-threshold)))])
+              (exec! [(insert-range-sql (range 50 (+ 100 field-values/list-cardinality-threshold)))])
               (sync-database! db)
               (db/exists? FieldValues :field_id field-id))))))))
 
@@ -400,9 +402,9 @@
 (expect
   [{:min -165.374 :max -73.9533}
    {:min 10.0646 :max 40.7794}]
-  (tt/with-temp* [Database [database {:details (:details (Database (id))), :engine :h2}]
+  (tt/with-temp* [Database [database {:details (:details (Database (data/id))), :engine :h2}]
                   Table    [table    {:db_id (u/get-id database), :name "VENUES"}]]
     (sync-table! table)
     (map narrow-to-min-max
-         [(db/select-one-field :fingerprint Field, :id (id :venues :longitude))
-          (db/select-one-field :fingerprint Field, :id (id :venues :latitude))])))
+         [(db/select-one-field :fingerprint Field, :id (data/id :venues :longitude))
+          (db/select-one-field :fingerprint Field, :id (data/id :venues :latitude))])))