From c6fbc1ff677449c1c33de3240f043aa4727959a0 Mon Sep 17 00:00:00 2001
From: Ngoc Khuat <qn.khuat@gmail.com>
Date: Thu, 9 Mar 2023 22:59:08 +0700
Subject: [PATCH] Adds `metabase_field.database_auto_incremented` (#28942)

---
 .../test/metabase/driver/snowflake_test.clj   |  2 +
 .../test/metabase/driver/sqlite_test.clj      |  3 +-
 resources/migrations/000_migrations.yaml      | 16 +++++
 .../driver/sql_jdbc/sync/describe_table.clj   | 25 ++++---
 src/metabase/models/field.clj                 |  1 -
 src/metabase/sync/interface.clj               | 29 ++++----
 .../sync_metadata/fields/fetch_metadata.clj   | 27 ++++----
 .../sync_metadata/fields/sync_instances.clj   | 33 ++++-----
 .../sync_metadata/fields/sync_metadata.clj    | 36 ++++++----
 test/metabase/api/database_test.clj           |  6 +-
 test/metabase/api/field_test.clj              |  4 +-
 test/metabase/api/table_test.clj              | 54 +++++++++------
 test/metabase/driver/postgres_test.clj        | 35 +++++-----
 .../sql_jdbc/sync/describe_table_test.clj     | 49 ++++++++++++--
 test/metabase/driver/sql_jdbc_test.clj        | 18 +++--
 test/metabase/sync/sync_dynamic_test.clj      |  6 +-
 .../fields/fetch_metadata_test.clj            | 22 ++++--
 .../fields/sync_metadata_test.clj             | 67 ++++++++++++++++---
 test/metabase/sync_test.clj                   |  7 ++
 test/metabase/test/mock/toucanery.clj         | 20 +++++-
 test/metabase/test/mock/util.clj              |  1 +
 21 files changed, 316 insertions(+), 145 deletions(-)

diff --git a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj
index 1ea9689f1d2..de86971dab3 100644
--- a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj
+++ b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj
@@ -129,11 +129,13 @@
                          :base-type         :type/Number
                          :pk?               true
                          :database-position 0
+                         :database-is-auto-increment true
                          :database-required false}
                         {:name              "name"
                          :database-type     "VARCHAR"
                          :base-type         :type/Text
                          :database-position 1
+                         :database-is-auto-increment false
                          :database-required true}}}
              (driver/describe-table :snowflake (assoc (mt/db) :name "ABC") (db/select-one Table :id (mt/id :categories))))))))
 
diff --git a/modules/drivers/sqlite/test/metabase/driver/sqlite_test.clj b/modules/drivers/sqlite/test/metabase/driver/sqlite_test.clj
index fc3f37e3eaf..721ed950462 100644
--- a/modules/drivers/sqlite/test/metabase/driver/sqlite_test.clj
+++ b/modules/drivers/sqlite/test/metabase/driver/sqlite_test.clj
@@ -147,7 +147,8 @@
                                    :database-type "TIMESTAMP"
                                    :base-type :type/DateTime
                                    :database-position 0
-                                   :database-required false}}}
+                                   :database-required false
+                                   :database-is-auto-increment false}}}
                        (driver/describe-table driver db (db/select-one Table :id (mt/id :timestamp_table)))))))))))))
 
 (deftest select-query-datetime
diff --git a/resources/migrations/000_migrations.yaml b/resources/migrations/000_migrations.yaml
index f0b44a3c74b..26abef3f221 100644
--- a/resources/migrations/000_migrations.yaml
+++ b/resources/migrations/000_migrations.yaml
@@ -14300,6 +14300,22 @@ databaseChangeLog:
             constraintName: fk_action_model_id
             onDelete: CASCADE
 
+  - changeSet:
+      id: v47.00-004
+      author: qnkhuat
+      comment: Added 0.47.0 - Add auto_incremented to metabase_field
+      changes:
+        - addColumn:
+            tableName: metabase_field
+            columns:
+              - column:
+                  name: database_is_auto_increment
+                  type: boolean
+                  remarks: Indicates this field is auto incremented
+                  defaultValueBoolean: false
+                  constraints:
+                    nullable: false
+
 # >>>>>>>>>> DO NOT ADD NEW MIGRATIONS BELOW THIS LINE! ADD THEM ABOVE <<<<<<<<<<
 
 ########################################################################################################################
diff --git a/src/metabase/driver/sql_jdbc/sync/describe_table.clj b/src/metabase/driver/sql_jdbc/sync/describe_table.clj
index ce61f5d887c..49720daafdc 100644
--- a/src/metabase/driver/sql_jdbc/sync/describe_table.clj
+++ b/src/metabase/driver/sql_jdbc/sync/describe_table.clj
@@ -98,18 +98,21 @@
                   nil)
     (fn [^ResultSet rs]
       ;; https://docs.oracle.com/javase/7/docs/api/java/sql/DatabaseMetaData.html#getColumns(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String)
-      #(let [default (.getString rs "COLUMN_DEF")
-             no-default? (contains? #{nil "NULL" "null"} default)
-             nullable (.getInt rs "NULLABLE")
-             not-nullable? (= 0 nullable)
-             auto-increment (.getString rs "IS_AUTOINCREMENT")
+      #(let [default            (.getString rs "COLUMN_DEF")
+             no-default?        (contains? #{nil "NULL" "null"} default)
+             nullable           (.getInt rs "NULLABLE")
+             not-nullable?      (= 0 nullable)
+             ;; IS_AUTOINCREMENT could return nil
+             auto-increment     (.getString rs "IS_AUTOINCREMENT")
+             auto-increment?    (= "YES" auto-increment)
              no-auto-increment? (= "NO" auto-increment)
-             column-name (.getString rs "COLUMN_NAME")
-             required? (and no-default? not-nullable? no-auto-increment?)]
+             column-name        (.getString rs "COLUMN_NAME")
+             required?          (and no-default? not-nullable? no-auto-increment?)]
          (merge
-           {:name              column-name
-            :database-type     (.getString rs "TYPE_NAME")
-            :database-required required?}
+           {:name                      column-name
+            :database-type             (.getString rs "TYPE_NAME")
+            :database-is-auto-increment auto-increment?
+            :database-required         required?}
            (when-let [remarks (.getString rs "REMARKS")]
              (when-not (str/blank? remarks)
                {:field-comment remarks})))))))
@@ -153,7 +156,7 @@
   (map-indexed (fn [i {:keys [database-type], column-name :name, :as col}]
                  (let [semantic-type (calculated-semantic-type driver column-name database-type)]
                    (merge
-                    (u/select-non-nil-keys col [:name :database-type :field-comment :database-required])
+                    (u/select-non-nil-keys col [:name :database-type :field-comment :database-required :database-is-auto-increment])
                     {:base-type         (database-type->base-type-or-warn driver database-type)
                      :database-position i}
                     (when semantic-type
diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj
index 28bcf773b3b..a835e451f13 100644
--- a/src/metabase/models/field.clj
+++ b/src/metabase/models/field.clj
@@ -183,7 +183,6 @@
   :in  mi/json-in
   :out (comp update-semantic-numeric-values mi/json-out-with-keywordization))
 
-
 (mi/define-methods
  Field
  {:hydration-keys (constantly [:destination :field :origin :human_readable_field])
diff --git a/src/metabase/sync/interface.clj b/src/metabase/sync/interface.clj
index 7146c6e7743..b9f1b5c6dad 100644
--- a/src/metabase/sync/interface.clj
+++ b/src/metabase/sync/interface.clj
@@ -22,21 +22,22 @@
 
 (def TableMetadataField
   "Schema for a given Field as provided in `describe-table`."
-  {:name                               su/NonBlankString
-   :database-type                      (s/maybe su/NonBlankString) ; blank if the Field is all NULL & untyped, i.e. in Mongo
-   :base-type                          su/FieldType
-   :database-position                  su/IntGreaterThanOrEqualToZero
-   (s/optional-key :semantic-type)     (s/maybe su/FieldSemanticOrRelationType)
-   (s/optional-key :effective-type)    (s/maybe su/FieldType)
-   (s/optional-key :coercion-strategy) (s/maybe su/CoercionStrategy)
-   (s/optional-key :field-comment)     (s/maybe su/NonBlankString)
-   (s/optional-key :pk?)               s/Bool
-   (s/optional-key :nested-fields)     #{(s/recursive #'TableMetadataField)}
-   (s/optional-key :nfc-path)          [s/Any]
-   (s/optional-key :custom)            {s/Any s/Any}
-   (s/optional-key :database-required) s/Bool
+  {:name                                        su/NonBlankString
+   :database-type                               (s/maybe su/NonBlankString) ; blank if the Field is all NULL & untyped, i.e. in Mongo
+   :base-type                                   su/FieldType
+   :database-position                           su/IntGreaterThanOrEqualToZero
+   (s/optional-key :semantic-type)              (s/maybe su/FieldSemanticOrRelationType)
+   (s/optional-key :effective-type)             (s/maybe su/FieldType)
+   (s/optional-key :coercion-strategy)          (s/maybe su/CoercionStrategy)
+   (s/optional-key :field-comment)              (s/maybe su/NonBlankString)
+   (s/optional-key :pk?)                        s/Bool
+   (s/optional-key :nested-fields)              #{(s/recursive #'TableMetadataField)}
+   (s/optional-key :nfc-path)                   [s/Any]
+   (s/optional-key :custom)                     {s/Any s/Any}
+   (s/optional-key :database-is-auto-increment) s/Bool
+   (s/optional-key :database-required)          s/Bool
    ;; for future backwards compatability, when adding things
-   s/Keyword                           s/Any})
+   s/Keyword                                    s/Any})
 
 (def TableMetadata
   "Schema for the expected output of `describe-table`."
diff --git a/src/metabase/sync/sync_metadata/fields/fetch_metadata.clj b/src/metabase/sync/sync_metadata/fields/fetch_metadata.clj
index cc4f34576ac..023106fc63b 100644
--- a/src/metabase/sync/sync_metadata/fields/fetch_metadata.clj
+++ b/src/metabase/sync/sync_metadata/fields/fetch_metadata.clj
@@ -23,18 +23,19 @@
 (s/defn ^:private fields->parent-id->fields :- {common/ParentID #{common/TableMetadataFieldWithID}}
   [fields :- (s/maybe [i/FieldInstance])]
   (->> (for [field fields]
-         {:parent-id         (:parent_id field)
-          :id                (:id field)
-          :name              (:name field)
-          :database-type     (:database_type field)
-          :effective-type    (:effective_type field)
-          :coercion-strategy (:coercion_strategy field)
-          :base-type         (:base_type field)
-          :semantic-type     (:semantic_type field)
-          :pk?               (isa? (:semantic_type field) :type/PK)
-          :field-comment     (:description field)
-          :database-position (:database_position field)
-          :database-required (:database_required field)})
+         {:parent-id                 (:parent_id field)
+          :id                        (:id field)
+          :name                      (:name field)
+          :database-type             (:database_type field)
+          :effective-type            (:effective_type field)
+          :coercion-strategy         (:coercion_strategy field)
+          :base-type                 (:base_type field)
+          :semantic-type             (:semantic_type field)
+          :pk?                       (isa? (:semantic_type field) :type/PK)
+          :field-comment             (:description field)
+          :database-is-auto-increment (:database_is_auto_increment field)
+          :database-position         (:database_position field)
+          :database-required         (:database_required field)})
        ;; make a map of parent-id -> set of child Fields
        (group-by :parent-id)
        ;; remove the parent ID because the Metadata from `describe-table` won't have it. Save the results as a set
@@ -68,7 +69,7 @@
   "Fetch active Fields from the Metabase application database for a given `table`."
   [table :- i/TableInstance]
  (db/select [Field :name :database_type :base_type :effective_type :coercion_strategy :semantic_type
-             :parent_id :id :description :database_position :nfc_path :database_required]
+             :parent_id :id :description :database_position :nfc_path :database_is_auto_increment :database_required]
      :table_id  (u/the-id table)
      :active    true
      {:order-by table/field-order-rule}))
diff --git a/src/metabase/sync/sync_metadata/fields/sync_instances.clj b/src/metabase/sync/sync_metadata/fields/sync_instances.clj
index 929c9db0f14..f92add2f16b 100644
--- a/src/metabase/sync/sync_metadata/fields/sync_instances.clj
+++ b/src/metabase/sync/sync_metadata/fields/sync_instances.clj
@@ -41,7 +41,7 @@
   [table :- i/TableInstance, new-field-metadatas :- [i/TableMetadataField], parent-id :- common/ParentID]
   (when (seq new-field-metadatas)
     (db/insert-many! Field
-      (for [{:keys [database-type database-required base-type effective-type coercion-strategy
+      (for [{:keys [database-type database-is-auto-increment database-required base-type effective-type coercion-strategy
                     field-comment database-position nfc-path visibility-type], field-name :name :as field} new-field-metadatas]
         (do
          (when (and effective-type
@@ -55,22 +55,23 @@
                                      field-name
                                      effective-type
                                      base-type)))
-         {:table_id          (u/the-id table)
-          :name              field-name
-          :display_name      (humanization/name->human-readable-name field-name)
-          :database_type     (or database-type "NULL") ; placeholder for Fields w/ no type info (e.g. Mongo) & all NULL
-          :base_type         base-type
+         {:table_id                  (u/the-id table)
+          :name                      field-name
+          :display_name              (humanization/name->human-readable-name field-name)
+          :database_type             (or database-type "NULL") ; placeholder for Fields w/ no type info (e.g. Mongo) & all NULL
+          :base_type                 base-type
           ;; todo test this?
-          :effective_type    (if (and effective-type coercion-strategy) effective-type base-type)
-          :coercion_strategy (when effective-type coercion-strategy)
-          :semantic_type     (common/semantic-type field)
-          :parent_id         parent-id
-          :nfc_path          nfc-path
-          :description       field-comment
-          :position          database-position
-          :database_position database-position
-          :database_required (or database-required false)
-          :visibility_type   (or visibility-type :normal)})))))
+          :effective_type            (if (and effective-type coercion-strategy) effective-type base-type)
+          :coercion_strategy         (when effective-type coercion-strategy)
+          :semantic_type             (common/semantic-type field)
+          :parent_id                 parent-id
+          :nfc_path                  nfc-path
+          :description               field-comment
+          :position                  database-position
+          :database_position         database-position
+          :database_is_auto_increment (or database-is-auto-increment false)
+          :database_required         (or database-required false)
+          :visibility_type           (or visibility-type :normal)})))))
 
 (s/defn ^:private create-or-reactivate-fields! :- (s/maybe [i/FieldInstance])
   "Create (or reactivate) Metabase Field object(s) for any Fields in `new-field-metadatas`. Does *NOT* recursively
diff --git a/src/metabase/sync/sync_metadata/fields/sync_metadata.clj b/src/metabase/sync/sync_metadata/fields/sync_metadata.clj
index fdab400b4b5..540a5b7ed3c 100644
--- a/src/metabase/sync/sync_metadata/fields/sync_metadata.clj
+++ b/src/metabase/sync/sync_metadata/fields/sync_metadata.clj
@@ -19,19 +19,22 @@
   "Update the metadata for a Metabase Field as needed if any of the info coming back from the DB has changed. Syncs
   base type, database type, semantic type, and comments/remarks; returns `1` if the Field was updated; `0` otherwise."
   [table :- i/TableInstance, field-metadata :- i/TableMetadataField, metabase-field :- common/TableMetadataFieldWithID]
-  (let [{old-database-type     :database-type
-         old-base-type         :base-type
-         old-field-comment     :field-comment
-         old-semantic-type     :semantic-type
-         old-database-position :database-position
-         old-database-name     :name
-         old-db-required       :database-required} metabase-field
-        {new-database-type     :database-type
-         new-base-type         :base-type
-         new-field-comment     :field-comment
-         new-database-position :database-position
-         new-database-name     :name
-         new-db-required       :database-required} field-metadata
+  (let [{old-database-type              :database-type
+         old-base-type                  :base-type
+         old-field-comment              :field-comment
+         old-semantic-type              :semantic-type
+         old-database-position          :database-position
+         old-database-name              :name
+         old-database-is-auto-increment :database-is-auto-increment
+         old-db-required                :database-required} metabase-field
+        {new-database-type              :database-type
+         new-base-type                  :base-type
+         new-field-comment              :field-comment
+         new-database-position          :database-position
+         new-database-name              :name
+         new-database-is-auto-increment :database-is-auto-increment
+         new-db-required                :database-required} field-metadata
+        new-database-is-auto-increment             (boolean new-database-is-auto-increment)
         new-db-required                            (boolean new-db-required)
         new-database-type                          (or new-database-type "NULL")
         new-semantic-type                          (common/semantic-type field-metadata)
@@ -58,6 +61,7 @@
         ;; different they have the same canonical representation (lower-casing at the moment).
         new-name? (not= old-database-name new-database-name)
 
+        new-db-auto-incremented? (not= old-database-is-auto-increment new-database-is-auto-increment)
         new-db-required? (not= old-db-required new-db-required)
 
         ;; calculate combined updates
@@ -97,6 +101,12 @@
                           old-database-name
                           new-database-name))
            {:name new-database-name})
+         (when new-db-auto-incremented?
+           (log/info (trs "Database auto incremented of {0} has changed from ''{1}'' to ''{2}''."
+                          (common/field-metadata-name-for-logging table metabase-field)
+                          old-database-is-auto-increment
+                          new-database-is-auto-increment))
+           {:database_is_auto_increment new-database-is-auto-increment})
          (when new-db-required?
            (log/info (trs "Database required of {0} has changed from ''{1}'' to ''{2}''."
                           (common/field-metadata-name-for-logging table metabase-field)
diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj
index 54f222658fb..615171a36db 100644
--- a/test/metabase/api/database_test.clj
+++ b/test/metabase/api/database_test.clj
@@ -369,7 +369,8 @@
                                                              :visibility_type   "normal"
                                                              :has_field_values  "none"
                                                              :database_position 0
-                                                             :database_required false})
+                                                             :database_required false
+                                                             :database_is_auto_increment true})
                                                            (merge
                                                             (field-details (db/select-one Field :id (mt/id :categories :name)))
                                                             {:table_id          (mt/id :categories)
@@ -382,7 +383,8 @@
                                                              :visibility_type   "normal"
                                                              :has_field_values  "list"
                                                              :database_position 1
-                                                             :database_required true})]
+                                                             :database_required true
+                                                             :database_is_auto_increment false})]
                                      :segments     []
                                      :metrics      []
                                      :id           (mt/id :categories)
diff --git a/test/metabase/api/field_test.clj b/test/metabase/api/field_test.clj
index 071af85d35d..f9dd032eb7f 100644
--- a/test/metabase/api/field_test.clj
+++ b/test/metabase/api/field_test.clj
@@ -33,7 +33,8 @@
   (testing "GET /api/field/:id"
     (is (= (-> (merge
                 (mt/object-defaults Field)
-                (db/select-one [Field :created_at :updated_at :last_analyzed :fingerprint :fingerprint_version :database_position :database_required]
+                (db/select-one [Field :created_at :updated_at :last_analyzed :fingerprint :fingerprint_version
+                                :database_position :database_required :database_is_auto_increment]
                   :id (mt/id :users :name))
                 {:table_id         (mt/id :users)
                  :table            (merge
@@ -63,6 +64,7 @@
                  :effective_type   "type/Text"
                  :has_field_values "list"
                  :database_required false
+                 :database_is_auto_increment false
                  :dimensions       []
                  :name_field       nil})
                (m/dissoc-in [:table :db :updated_at] [:table :db :created_at] [:table :db :timezone]))
diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj
index 931d2e13a4e..32e4168392a 100644
--- a/test/metabase/api/table_test.clj
+++ b/test/metabase/api/table_test.clj
@@ -74,7 +74,8 @@
    (field-defaults)
    (select-keys
     field
-    [:created_at :fingerprint :fingerprint_version :fk_target_field_id :id :last_analyzed :updated_at :database_required])))
+    [:created_at :fingerprint :fingerprint_version :fk_target_field_id :id :last_analyzed :updated_at
+     :database_required :database_is_auto_increment])))
 
 (defn- fk-field-details [field]
   (-> (field-details field)
@@ -156,7 +157,8 @@
                                      :effective_type   "type/BigInteger"
                                      :visibility_type  "normal"
                                      :has_field_values "none"
-                                     :database_required false)
+                                     :database_required false
+                                     :database_is_auto_increment true)
                               (assoc (field-details (db/select-one Field :id (mt/id :users :name)))
                                      :semantic_type             "type/Name"
                                      :table_id                 (mt/id :users)
@@ -171,7 +173,8 @@
                                      :has_field_values         "list"
                                      :position                 1
                                      :database_position        1
-                                     :database_required        false)
+                                     :database_required        false
+                                     :database_is_auto_increment false)
                               (assoc (field-details (db/select-one Field :id (mt/id :users :last_login)))
                                      :table_id                 (mt/id :users)
                                      :name                     "LAST_LOGIN"
@@ -185,7 +188,8 @@
                                      :has_field_values         "none"
                                      :position                 2
                                      :database_position        2
-                                     :database_required        false)
+                                     :database_required        false
+                                     :database_is_auto_increment false)
                               (assoc (field-details (db/select-one Field :table_id (mt/id :users), :name "PASSWORD"))
                                      :semantic_type     "type/Category"
                                      :table_id         (mt/id :users)
@@ -198,7 +202,8 @@
                                      :has_field_values "list"
                                      :position          3
                                      :database_position 3
-                                     :database_required false)]
+                                     :database_required false
+                                     :database_is_auto_increment false)]
                :id           (mt/id :users)})
              (mt/user-http-request :rasta :get 200 (format "table/%d/query_metadata?include_sensitive_fields=true" (mt/id :users))))
           "Make sure that getting the User table *does* include info about the password field, but not actual values themselves"))))
@@ -222,7 +227,8 @@
                                      :base_type        "type/BigInteger"
                                      :effective_type   "type/BigInteger"
                                      :has_field_values "none"
-                                     :database_required false)
+                                     :database_required false
+                                     :database_is_auto_increment true)
                               (assoc (field-details (db/select-one Field :id (mt/id :users :name)))
                                      :table_id         (mt/id :users)
                                      :semantic_type     "type/Name"
@@ -234,7 +240,8 @@
                                      :has_field_values "list"
                                      :position          1
                                      :database_position 1
-                                     :database_required false)
+                                     :database_required false
+                                     :database_is_auto_increment false)
                               (assoc (field-details (db/select-one Field :id (mt/id :users :last_login)))
                                      :table_id                 (mt/id :users)
                                      :name                     "LAST_LOGIN"
@@ -247,7 +254,8 @@
                                      :has_field_values         "none"
                                      :position                 2
                                      :database_position        2
-                                     :database_required        false)]
+                                     :database_required        false
+                                     :database_is_auto_increment false)]
                :id           (mt/id :users)})
              (mt/user-http-request :rasta :get 200 (format "table/%d/query_metadata" (mt/id :users))))
           "Make sure that getting the User table does *not* include password info"))))
@@ -447,22 +455,24 @@
                               :base_type         "type/BigInteger"
                               :effective_type    "type/BigInteger"
                               :has_field_values  "none"
-                              :database_required false})
+                              :database_required false
+                              :database_is_auto_increment true})
                             (merge
                              (field-details (db/select-one Field :id (mt/id :categories :name)))
-                             {:table_id                 (mt/id :categories)
-                              :semantic_type            "type/Name"
-                              :name                     "NAME"
-                              :display_name             "Name"
-                              :database_type            "CHARACTER VARYING"
-                              :base_type                "type/Text"
-                              :effective_type           "type/Text"
-                              :dimension_options        []
-                              :default_dimension_option nil
-                              :has_field_values         "list"
-                              :database_position        1
-                              :position                 1
-                              :database_required        true})]
+                             {:table_id                  (mt/id :categories)
+                              :semantic_type             "type/Name"
+                              :name                      "NAME"
+                              :display_name              "Name"
+                              :database_type             "CHARACTER VARYING"
+                              :base_type                 "type/Text"
+                              :effective_type            "type/Text"
+                              :dimension_options         []
+                              :default_dimension_option  nil
+                              :has_field_values          "list"
+                              :database_position         1
+                              :position                  1
+                              :database_required         true
+                              :database_is_auto_increment false})]
              :id           (mt/id :categories)})
            (mt/user-http-request :rasta :get 200 (format "table/%d/query_metadata" (mt/id :categories)))))))
 
diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj
index 8c30a3fe173..afc9d19cc65 100644
--- a/test/metabase/driver/postgres_test.clj
+++ b/test/metabase/driver/postgres_test.clj
@@ -807,22 +807,25 @@
 
         (testing "check that describe-table properly describes the database & base types of the enum fields"
           (is (= {:name   "birds"
-                  :fields #{{:name              "name"
-                             :database-type     "varchar"
-                             :base-type         :type/Text
-                             :pk?               true
-                             :database-position 0
-                             :database-required true}
-                            {:name              "status"
-                             :database-type     "bird_status"
-                             :base-type         :type/PostgresEnum
-                             :database-position 1
-                             :database-required true}
-                            {:name              "type"
-                             :database-type     "bird type"
-                             :base-type         :type/PostgresEnum
-                             :database-position 2
-                             :database-required true}}}
+                  :fields #{{:name                      "name"
+                             :database-type             "varchar"
+                             :base-type                 :type/Text
+                             :pk?                       true
+                             :database-position         0
+                             :database-required         true
+                             :database-is-auto-increment false}
+                            {:name                      "status"
+                             :database-type             "bird_status"
+                             :base-type                 :type/PostgresEnum
+                             :database-position         1
+                             :database-required         true
+                             :database-is-auto-increment false}
+                            {:name                      "type"
+                             :database-type             "bird type"
+                             :base-type                 :type/PostgresEnum
+                             :database-position         2
+                             :database-required         true
+                             :database-is-auto-increment false}}}
                  (driver/describe-table :postgres db {:name "birds"}))))
 
         (testing "check that when syncing the DB the enum types get recorded appropriately"
diff --git a/test/metabase/driver/sql_jdbc/sync/describe_table_test.clj b/test/metabase/driver/sql_jdbc/sync/describe_table_test.clj
index 63f66675916..d144c9ff772 100644
--- a/test/metabase/driver/sql_jdbc/sync/describe_table_test.clj
+++ b/test/metabase/driver/sql_jdbc/sync/describe_table_test.clj
@@ -7,7 +7,9 @@
     :as sql-jdbc.describe-table]
    [metabase.driver.sql-jdbc.sync.interface :as sql-jdbc.sync.interface]
    [metabase.models.table :refer [Table]]
+   [metabase.sync :as sync]
    [metabase.test :as mt]
+   [metabase.test.data.one-off-dbs :as one-off-dbs]
    [metabase.util :as u]
    [toucan.db :as db]))
 
@@ -23,14 +25,49 @@
 (deftest describe-table-test
   (is (= {:name "VENUES",
           :fields
-          #{{:name "ID", :database-type "BIGINT", :base-type :type/BigInteger, :database-position 0, :pk? true :database-required false}
-            {:name "NAME", :database-type "CHARACTER VARYING", :base-type :type/Text, :database-position 1 :database-required false}
-            {:name "CATEGORY_ID", :database-type "INTEGER", :base-type :type/Integer, :database-position 2 :database-required false}
-            {:name "LATITUDE", :database-type "DOUBLE PRECISION", :base-type :type/Float, :database-position 3 :database-required false}
-            {:name "LONGITUDE", :database-type "DOUBLE PRECISION", :base-type :type/Float, :database-position 4 :database-required false}
-            {:name "PRICE", :database-type "INTEGER", :base-type :type/Integer, :database-position 5 :database-required false}}}
+          #{{:name "ID", :database-type "BIGINT", :base-type :type/BigInteger, :database-position 0, :pk? true :database-required false :database-is-auto-increment true}
+            {:name "NAME", :database-type "CHARACTER VARYING", :base-type :type/Text, :database-position 1 :database-required false :database-is-auto-increment false}
+            {:name "CATEGORY_ID", :database-type "INTEGER", :base-type :type/Integer, :database-position 2 :database-required false :database-is-auto-increment false}
+            {:name "LATITUDE", :database-type "DOUBLE PRECISION", :base-type :type/Float, :database-position 3 :database-required false :database-is-auto-increment false}
+            {:name "LONGITUDE", :database-type "DOUBLE PRECISION", :base-type :type/Float, :database-position 4 :database-required false :database-is-auto-increment false}
+            {:name "PRICE", :database-type "INTEGER", :base-type :type/Integer, :database-position 5 :database-required false :database-is-auto-increment false}}}
          (sql-jdbc.describe-table/describe-table :h2 (mt/id) {:name "VENUES"}))))
 
+(deftest describe-auto-increment-on-non-pk-field-test
+  (testing "a non-pk field with auto-increment should be have metabase_field.database_is_auto_increment=true"
+    (one-off-dbs/with-blank-db
+      (doseq [statement [;; H2 needs that 'guest' user for QP purposes. Set that up
+                         "CREATE USER IF NOT EXISTS GUEST PASSWORD 'guest';"
+                         ;; Keep DB open until we say otherwise :)
+                         "SET DB_CLOSE_DELAY -1;"
+                         ;; create table & load data
+                         "DROP TABLE IF EXISTS \"birds\";"
+                         "CREATE TABLE \"employee_counter\" (\"id\" INTEGER AUTO_INCREMENT PRIMARY KEY, \"count\" INTEGER AUTO_INCREMENT, \"rank\" INTEGER NOT NULL)"
+                         "GRANT ALL ON \"employee_counter\" TO GUEST;"]]
+        (jdbc/execute! one-off-dbs/*conn* [statement]))
+      (sync/sync-database! (mt/db))
+      (is (= {:fields #{{:base-type                 :type/Integer
+                         :database-is-auto-increment true
+                         :database-position         0
+                         :database-required         false
+                         :database-type             "INTEGER"
+                         :name                      "id"
+                         :pk?                       true}
+                        {:base-type                 :type/Integer
+                         :database-is-auto-increment true
+                         :database-position         1
+                         :database-required         false
+                         :database-type             "INTEGER"
+                         :name                      "count"}
+                        {:base-type                 :type/Integer
+                         :database-is-auto-increment false
+                         :database-position         2
+                         :database-required         true
+                         :database-type             "INTEGER"
+                         :name                      "rank"}}
+              :name "employee_counter"}
+             (sql-jdbc.describe-table/describe-table :h2 (mt/id) {:name "employee_counter"}))))))
+
 (deftest describe-table-fks-test
   (is (= #{{:fk-column-name   "CATEGORY_ID"
             :dest-table       {:name "CATEGORIES", :schema "PUBLIC"}
diff --git a/test/metabase/driver/sql_jdbc_test.clj b/test/metabase/driver/sql_jdbc_test.clj
index d6457227404..c32201c9e75 100644
--- a/test/metabase/driver/sql_jdbc_test.clj
+++ b/test/metabase/driver/sql_jdbc_test.clj
@@ -26,32 +26,38 @@
                      :base-type         :type/BigInteger
                      :pk?               true
                      :database-position 0
-                     :database-required false}
+                     :database-required false
+                     :database-is-auto-increment true}
                     {:name              "NAME"
                      :database-type     "CHARACTER VARYING"
                      :base-type         :type/Text
                      :database-position 1
-                     :database-required false}
+                     :database-required false
+                     :database-is-auto-increment false}
                     {:name              "CATEGORY_ID"
                      :database-type     "INTEGER"
                      :base-type         :type/Integer
                      :database-position 2
-                     :database-required false}
+                     :database-required false
+                     :database-is-auto-increment false}
                     {:name              "LATITUDE"
                      :database-type     "DOUBLE PRECISION"
                      :base-type         :type/Float
                      :database-position 3
-                     :database-required false}
+                     :database-required false
+                     :database-is-auto-increment false}
                     {:name              "LONGITUDE"
                      :database-type     "DOUBLE PRECISION"
                      :base-type         :type/Float
                      :database-position 4
-                     :database-required false}
+                     :database-required false
+                     :database-is-auto-increment false}
                     {:name              "PRICE"
                      :database-type     "INTEGER"
                      :base-type         :type/Integer
                      :database-position 5
-                     :database-required false}}}
+                     :database-required false
+                     :database-is-auto-increment false}}}
          (driver/describe-table :h2 (mt/db) (db/select-one Table :id (mt/id :venues))))))
 
 (deftest ^:parallel describe-table-fks-test
diff --git a/test/metabase/sync/sync_dynamic_test.clj b/test/metabase/sync/sync_dynamic_test.clj
index 28ca86e4c5f..97d89acb148 100644
--- a/test/metabase/sync/sync_dynamic_test.clj
+++ b/test/metabase/sync/sync_dynamic_test.clj
@@ -22,7 +22,7 @@
                            (for [field fields]
                              (u/select-non-nil-keys
                               field
-                              [:table_id :name :fk_target_field_id :parent_id :base_type :database_type]))))))))
+                              [:table_id :name :fk_target_field_id :parent_id :base_type :database_type :database_is_auto_increment]))))))))
 
 (defn- get-tables [database-or-id]
   (->> (hydrate (db/select Table, :db_id (u/the-id database-or-id), {:order-by [:id]}) :fields)
@@ -32,5 +32,5 @@
   (testing "basic test to make sure syncing nested fields works. This is sort of a higher-level test."
     (mt/with-temp Database [db {:engine ::toucanery/toucanery}]
       (sync/sync-database! db)
-      (is (= (remove-nonsense toucanery/toucanery-tables-and-fields)
-             (remove-nonsense (get-tables db)))))))
+     (is (= (remove-nonsense toucanery/toucanery-tables-and-fields)
+            (remove-nonsense (get-tables db)))))))
diff --git a/test/metabase/sync/sync_metadata/fields/fetch_metadata_test.clj b/test/metabase/sync/sync_metadata/fields/fetch_metadata_test.clj
index 8cde628a4b9..6924dc32717 100644
--- a/test/metabase/sync/sync_metadata/fields/fetch_metadata_test.clj
+++ b/test/metabase/sync/sync_metadata/fields/fetch_metadata_test.clj
@@ -22,31 +22,36 @@
               :effective-type    :type/Integer
               :semantic-type     :type/PK
               :pk?               true
-              :database-required false}
+              :database-required false
+              :database-is-auto-increment true}
              {:name              "buyer"
               :database-type     "OBJECT"
               :base-type         :type/Dictionary
               :effective-type    :type/Dictionary
               :pk?               false
               :database-required false
+              :database-is-auto-increment false
               :nested-fields     #{{:name              "name"
                                     :database-type     "VARCHAR"
                                     :base-type         :type/Text
                                     :effective-type    :type/Text
                                     :pk?               false
-                                    :database-required false}
+                                    :database-required false
+                                    :database-is-auto-increment false}
                                    {:name              "cc"
                                     :database-type     "VARCHAR"
                                     :base-type         :type/Text
                                     :effective-type    :type/Text
                                     :pk?               false
-                                    :database-required false}}}
+                                    :database-required false
+                                    :database-is-auto-increment false}}}
              {:name              "ts"
               :database-type     "BIGINT"
               :base-type         :type/BigInteger
               :effective-type    :type/DateTime
               :coercion-strategy :Coercion/UNIXMilliSeconds->DateTime
               :pk?               false
+              :database-is-auto-increment false
               :database-required false}
              {:name              "toucan"
               :database-type     "OBJECT"
@@ -54,31 +59,36 @@
               :effective-type    :type/Dictionary
               :pk?               false
               :database-required false
+              :database-is-auto-increment false
               :nested-fields     #{{:name              "name"
                                     :database-type     "VARCHAR"
                                     :base-type         :type/Text
                                     :effective-type    :type/Text
                                     :pk?               false
-                                    :database-required false}
+                                    :database-required false
+                                    :database-is-auto-increment false}
                                    {:name              "details"
                                     :database-type     "OBJECT"
                                     :base-type         :type/Dictionary
                                     :effective-type    :type/Dictionary
                                     :pk?               false
                                     :database-required false
+                                    :database-is-auto-increment false
                                     :nested-fields     #{{:name              "weight"
                                                           :database-type     "DECIMAL"
                                                           :base-type         :type/Decimal
                                                           :effective-type    :type/Decimal
                                                           :semantic-type     :type/Category
                                                           :pk?               false
-                                                          :database-required false}
+                                                          :database-required false
+                                                          :database-is-auto-increment false}
                                                          {:name              "age"
                                                           :database-type     "INT"
                                                           :base-type         :type/Integer
                                                           :effective-type    :type/Integer
                                                           :pk?               false
-                                                          :database-required false}}}}}}
+                                                          :database-required false
+                                                          :database-is-auto-increment false}}}}}}
 
            (let [transactions-table-id   (u/the-id (db/select-one-id Table :db_id (u/the-id db), :name "transactions"))
                  remove-ids-and-nil-vals (partial walk/postwalk #(if-not (map? %)
diff --git a/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj b/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj
index c9bcde0f6fc..d1d7c3e7291 100644
--- a/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj
+++ b/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj
@@ -25,13 +25,15 @@
              :database-type     "Integer"
              :base-type         :type/Integer
              :database-position 0
-             :database-required false}
+             :database-required false
+             :database-is-auto-increment false}
             {:name              "My Field"
              :database-type     "NULL"
              :base-type         :type/Integer
              :id                1
              :database-position 0
-             :database-required false})))))
+             :database-required false
+             :database-is-auto-increment false})))))
 
 (deftest database-required-changed-test
   (testing "test that if database-required changes we will update it in the DB"
@@ -41,13 +43,48 @@
              :database-type     "Integer"
              :base-type         :type/Integer
              :database-position 0
+             :database-required false
+             :database-is-auto-increment false}
+            {:name              "My Field"
+             :database-type     "Integer"
+             :base-type         :type/Integer
+             :id                1
+             :database-position 0
+             :database-required true
+             :database-is-auto-increment false})))))
+
+(deftest database-is-auto-increment-changed-test
+  (testing "test that if database-required changes we will update it in the DB"
+    (is (= [["Field" 1 {:database_is_auto_increment true}]]
+           (updates-that-will-be-performed
+            {:name              "My Field"
+             :database-type     "Integer"
+             :base-type         :type/Integer
+             :database-position 0
+             :database-required false
+             :database-is-auto-increment true}
+            {:name              "My Field"
+             :database-type     "Integer"
+             :base-type         :type/Integer
+             :id                1
+             :database-position 0
+             :database-required false
+             :database-is-auto-increment false})))
+    (is (= [["Field" 1 {:database_is_auto_increment false}]]
+           (updates-that-will-be-performed
+            {:name              "My Field"
+             :database-type     "Integer"
+             :base-type         :type/Integer
+             :database-position 0
+             ;; no :database-is-auto-increment key to test case where describe-table does not not return it
              :database-required false}
             {:name              "My Field"
              :database-type     "Integer"
              :base-type         :type/Integer
              :id                1
              :database-position 0
-             :database-required true})))))
+             :database-required false
+             :database-is-auto-increment true})))))
 
 (deftest no-op-test
   (testing "no changes should be made (i.e., no calls to `update!`) if nothing changes"
@@ -57,13 +94,15 @@
              :database-type     "Integer"
              :base-type         :type/Integer
              :database-position 0
-             :database-required false}
+             :database-required false
+             :database-is-auto-increment true}
             {:name              "My Field"
              :database-type     "Integer"
              :base-type         :type/Integer
              :id                1
              :database-position 0
-             :database-required false})))))
+             :database-required false
+             :database-is-auto-increment true})))))
 
 (deftest nil-database-type-test
   (testing (str "test that if `database-type` comes back as `nil` in the metadata from the sync process, we won't try "
@@ -75,13 +114,15 @@
              :database-type     nil
              :base-type         :type/Integer
              :database-position 0
-             :database-required false}
+             :database-required false
+             :database-is-auto-increment false}
             {:name              "My Field"
              :database-type     "Integer"
              :base-type         :type/Integer
              :database-position 0
              :id                1
-             :database-required false}))))
+             :database-required false
+             :database-is-auto-increment false}))))
 
   (testing (str "if `database-type` comes back as `nil` and was already saved in application DB as `NULL` no changes "
                 "should be made")
@@ -91,13 +132,15 @@
              :database-type     nil
              :base-type         :type/Integer
              :database-position 0
-             :database-required false}
+             :database-required false
+             :database-is-auto-increment false}
             {:name              "My Field"
              :database-type     "NULL"
              :base-type         :type/Integer
              :id                1
              :database-position 0
-             :database-required false})))))
+             :database-required false
+             :database-is-auto-increment false})))))
 
 (deftest dont-overwrite-semantic-type-test
   (testing "We should not override non-nil `semantic_type`s"
@@ -108,11 +151,13 @@
              :base-type         :type/Integer
              :semantic-type     nil
              :database-position 0
-             :database-required false}
+             :database-required false
+             :database-is-auto-increment false}
             {:name              "My Field"
              :database-type     "Integer"
              :base-type         :type/Integer
              :semantic-type     :type/Price
              :id                1
              :database-position 0
-             :database-required false})))))
+             :database-required false
+             :database-is-auto-increment false})))))
diff --git a/test/metabase/sync_test.clj b/test/metabase/sync_test.clj
index a63bc30cfec..3162049afcf 100644
--- a/test/metabase/sync_test.clj
+++ b/test/metabase/sync_test.clj
@@ -31,15 +31,18 @@
                         :database-type     "SERIAL"
                         :base-type         :type/Integer
                         :semantic-type     :type/PK
+                        :database-is-auto-increment true
                         :database-position 0}
                        {:name              "title"
                         :database-type     "VARCHAR"
                         :base-type         :type/Text
                         :semantic-type     :type/Title
+                        :database-is-auto-increment false
                         :database-position 1}
                        {:name              "studio"
                         :database-type     "VARCHAR"
                         :base-type         :type/Text
+                        :database-is-auto-increment false
                         :database-position 2}}
              :description nil}
    "studio" {:name   "studio"
@@ -48,10 +51,12 @@
                         :database-type     "VARCHAR"
                         :base-type         :type/Text
                         :semantic-type     :type/PK
+                        :database-is-auto-increment false
                         :database-position 0}
                        {:name              "name"
                         :database-type     "VARCHAR"
                         :base-type         :type/Text
+                        :database-is-auto-increment false
                         :database-position 1}}
              :description ""}})
 
@@ -110,6 +115,7 @@
     :fingerprint         false
     :fingerprint_version false
     :fk_target_field_id  false
+    :database_is_auto_increment false
     :id                  true
     :last_analyzed       false
     :parent_id           false
@@ -133,6 +139,7 @@
     :effective_type    :type/Integer
     :semantic_type     :type/PK
     :database_position 0
+    :database_is_auto_increment true
     :position          0}))
 
 (defn- field:movie-studio []
diff --git a/test/metabase/test/mock/toucanery.clj b/test/metabase/test/mock/toucanery.clj
index 0aa475e4e6c..f45f5ab01ba 100644
--- a/test/metabase/test/mock/toucanery.clj
+++ b/test/metabase/test/mock/toucanery.clj
@@ -12,44 +12,56 @@
                    :fields #{{:name          "id"
                               :pk?           true
                               :database-type "SERIAL"
-                              :base-type     :type/Integer}
+                              :base-type     :type/Integer
+                              :database-is-auto-increment true}
                              {:name          "ts"
                               :database-type "BIGINT"
                               :base-type     :type/BigInteger
                               :effective-type :type/DateTime
-                              :coercion-strategy :Coercion/UNIXMilliSeconds->DateTime}
+                              :coercion-strategy :Coercion/UNIXMilliSeconds->DateTime
+                              :database-is-auto-increment false}
                              {:name          "toucan"
                               :database-type "OBJECT"
                               :base-type     :type/Dictionary
+                              :database-is-auto-increment false
                               :nested-fields #{{:name          "name"
                                                 :database-type "VARCHAR"
-                                                :base-type     :type/Text}
+                                                :base-type     :type/Text
+                                                :database-is-auto-increment false}
                                                {:name          "details"
                                                 :database-type "OBJECT"
                                                 :base-type     :type/Dictionary
+                                                :database-is-auto-increment false
                                                 :nested-fields #{{:name          "age"
                                                                   :database-type "INT"
+                                                                  :database-is-auto-increment false
                                                                   :base-type     :type/Integer}
                                                                  {:name          "weight"
                                                                   :database-type "DECIMAL"
+                                                                  :database-is-auto-increment false
                                                                   :semantic-type :type/Category
                                                                   :base-type     :type/Decimal}}}}}
                              {:name          "buyer"
                               :database-type "OBJECT"
+                              :database-is-auto-increment false
                               :base-type     :type/Dictionary
                               :nested-fields #{{:name          "name"
                                                 :database-type "VARCHAR"
+                                                :database-is-auto-increment false
                                                 :base-type     :type/Text}
                                                {:name          "cc"
                                                 :database-type "VARCHAR"
+                                                :database-is-auto-increment false
                                                 :base-type     :type/Text}}}}}
    "employees"    {:name   "employees"
                    :schema nil
                    :fields #{{:name          "id"
                               :database-type "SERIAL"
+                              :database-is-auto-increment true
                               :base-type     :type/Integer}
                              {:name          "name"
                               :database-type "VARCHAR"
+                              :database-is-auto-increment false
                               :base-type     :type/Text}}}})
 
 (driver/register! ::toucanery, :abstract? true)
@@ -105,6 +117,7 @@
                                   :display_name  "ID"
                                   :database_type "SERIAL"
                                   :base_type     :type/Integer
+                                  :database_is_auto_increment true
                                   :semantic_type :type/PK})]
            :display_name "Employees"})
    (merge mock.util/table-defaults
@@ -120,6 +133,7 @@
                                  {:name          "id"
                                   :display_name  "ID"
                                   :database_type "SERIAL"
+                                  :database_is_auto_increment true
                                   :base_type     :type/Integer})
                           (merge mock.util/field-defaults
                                  {:name          "buyer"
diff --git a/test/metabase/test/mock/util.clj b/test/metabase/test/mock/util.clj
index 26701fa629f..309635b0d54 100644
--- a/test/metabase/test/mock/util.clj
+++ b/test/metabase/test/mock/util.clj
@@ -22,6 +22,7 @@
    :caveats            nil
    :points_of_interest nil
    :fk_target_field_id false
+   :database_is_auto_increment false
    :updated_at         true
    :active             true
    :nfc_path           nil
-- 
GitLab