From caaf963ff56e8a52934478498012541b0aa742b6 Mon Sep 17 00:00:00 2001
From: lbrdnk <lbrdnk@users.noreply.github.com>
Date: Wed, 18 Oct 2023 16:41:48 +0200
Subject: [PATCH] Add test checking driver defmultis are mentioned in changelog
 (#32262)

* Add `:added` metadata to driver multimethods

Commit which introduced defmulti was determined
with `git log -L<line>,+1:<file>. Then first version it appeared in
was extracted from `git tag --contains <sha>`.

* Add driver methods changelog test

* Add `:changelog-test/ignore` to various defmultis

Multiple defmultis added after 42 incl. are missing entries from
changelog. Marking them this way makes it possible to add those
entries retrospectively without having failing tests, one by one.
---
 .../bigquery_cloud_sdk/query_processor.clj    |  2 +-
 .../metabase/driver/mongo/query_processor.clj |  4 +-
 src/metabase/driver.clj                       | 42 +++++------
 src/metabase/driver/ddl/interface.clj         |  8 +--
 src/metabase/driver/sql.clj                   |  4 +-
 .../driver/sql/parameters/substitution.clj    |  4 +-
 src/metabase/driver/sql/query_processor.clj   | 24 +++----
 src/metabase/driver/sql/util/unprepare.clj    |  4 +-
 src/metabase/driver/sql_jdbc/actions.clj      | 10 +--
 src/metabase/driver/sql_jdbc/connection.clj   |  6 +-
 src/metabase/driver/sql_jdbc/execute.clj      |  5 +-
 .../driver/sql_jdbc/execute/old_impl.clj      |  6 +-
 .../driver/sql_jdbc/sync/describe_table.clj   |  3 +-
 .../driver/sql_jdbc/sync/interface.clj        | 27 +++----
 test/metabase/driver/impl_test.clj            | 71 ++++++++++++++++++-
 15 files changed, 146 insertions(+), 74 deletions(-)

diff --git a/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj b/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj
index cf24ea6850c..d140be94139 100644
--- a/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj
+++ b/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj
@@ -74,7 +74,7 @@
 
 (defmulti parse-result-of-type
   "Parse the values that come back in results of a BigQuery query based on their column type."
-  {:arglists '([column-type column-mode timezone-id v])}
+  {:added "0.41.0" :arglists '([column-type column-mode timezone-id v])}
   (fn [column-type _ _ _] column-type))
 
 (defn- parse-value
diff --git a/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj b/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj
index a57080548ff..9b3ecac630f 100644
--- a/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj
+++ b/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj
@@ -646,7 +646,7 @@
 
 (defmulti datetime-diff
   "Helper function for ->rvalue for `datetime-diff` clauses."
-  {:arglists '([x y unit])}
+  {:added "0.46.0" :arglists '([x y unit])}
   (fn [_ _ unit] unit))
 
 (defmethod datetime-diff :year
@@ -705,7 +705,7 @@
 (defmulti compile-filter
   "Compile an mbql filter clause to datastructures suitable to query mongo. Note this is not the whole query but just
   compiling the \"where\" clause equivalent."
-  {:arglists '([clause])}
+  {:added "0.39.0" :arglists '([clause])}
   mbql.u/dispatch-by-clause-name-or-class)
 
 (defmethod compile-filter :between
diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj
index eb7572a1565..97aa825fd67 100644
--- a/src/metabase/driver.clj
+++ b/src/metabase/driver.clj
@@ -224,7 +224,7 @@
 
   If you do need to implement this method yourself, you do not need to call parent implementations. We'll take care of
   that for you."
-  {:arglists '([driver])}
+  {:added "0.32.0" :arglists '([driver])}
   dispatch-on-uninitialized-driver)
   ;; VERY IMPORTANT: Unlike all other driver multimethods, we DO NOT use the driver hierarchy for dispatch here. Why?
   ;; We do not want a driver to inherit parent drivers' implementations and have those implementations end up getting
@@ -244,7 +244,7 @@
   way, like SQLite), you do not need to implement this method; instead, specifiy it in your plugin manifest, and
   `lazy-loaded-driver` will create an implementation for you. Probably best if we only have one place where we set
   values for this."
-  {:arglists '([driver])}
+  {:added "0.32.0" :arglists '([driver])}
   dispatch-on-uninitialized-driver
   :hierarchy #'hierarchy)
 
@@ -253,7 +253,7 @@
 
 (defmulti contact-info
   "The contact information for the driver"
-  {:added "0.43.0" :arglists '([driver])}
+  {:changelog-test/ignore true :added "0.43.0" :arglists '([driver])}
   dispatch-on-uninitialized-driver
   :hierarchy #'hierarchy)
 
@@ -281,7 +281,7 @@
   can be made successfully, otherwise it should return falsey or throw an appropriate Exception. Exceptions if a
   connection cannot be made. Throw an `ex-info` containing a truthy `::can-connect-message?` in `ex-data`
   in order to suppress logging expected driver validation messages during setup."
-  {:arglists '([driver details])}
+  {:added "0.32.0" :arglists '([driver details])}
   dispatch-on-initialized-driver-safe-keys
   :hierarchy #'hierarchy)
 
@@ -289,7 +289,7 @@
   "Return a map containing information that describes the version of the DBMS. This typically includes a
   `:version` containing the (semantic) version of the DBMS as a string and potentially a `:flavor`
   specifying the flavor like `MySQL` or `MariaDB`."
-  {:arglists '([driver database])}
+  {:changelog-test/ignore true :added "0.46.0" :arglists '([driver database])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -302,7 +302,7 @@
   "Return a map containing information that describes all of the tables in a `database`, an instance of the `Database`
   model. It is expected that this function will be peformant and avoid draining meaningful resources of the database.
   Results should match the [[metabase.sync.interface/DatabaseMetadata]] schema."
-  {:arglists '([driver database])}
+  {:added "0.32.0" :arglists '([driver database])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -311,7 +311,7 @@
   therein). `database` will be an instance of the `Database` model; and `table`, an instance of the `Table` model. It
   is expected that this function will be peformant and avoid draining meaningful resources of the database. Results
   should match the [[metabase.sync.interface/TableMetadata]] schema."
-  {:arglists '([driver database table])}
+  {:added "0.32.0" :arglists '([driver database table])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -329,7 +329,7 @@
 (defmulti describe-table-fks
   "Return information about the foreign keys in a `table`. Required for drivers that support `:foreign-keys`. Results
   should match the [[metabase.sync.interface/FKMetadata]] schema."
-  {:arglists '([driver database table])}
+  {:added "0.32.0" :arglists '([driver database table])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -386,7 +386,7 @@
 
   Like `display-name`, lazy-loaded drivers should specify this in their plugin manifest; `lazy-loaded-driver` will
   automatically create an implementation for you."
-  {:arglists '([driver])}
+  {:added "0.32.0" :arglists '([driver])}
   dispatch-on-uninitialized-driver
   :hierarchy #'hierarchy)
 
@@ -541,7 +541,7 @@
     (supports? :postgres :set-timezone) ; -> true
 
   DEPRECATED — [[database-supports?]] should be used instead. This function will be removed in Metabase version 0.50.0."
-  {:arglists '([driver feature]), :deprecated "0.47.0"}
+  {:added "0.32.0", :arglists '([driver feature]), :deprecated "0.47.0"}
   (fn [driver feature]
     (when-not (driver-features feature)
       (throw (Exception. (tru "Invalid driver feature: {0}" feature))))
@@ -615,7 +615,7 @@
   users to the erroneous input fields.
   Error messages can also be strings, or localized strings, as returned by [[metabase.util.i18n/trs]] and
   `metabase.util.i18n/tru`."
-  {:arglists '([this message])}
+  {:added "0.32.0" :arglists '([this message])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -637,7 +637,7 @@
 
     {:query \"-- Metabase card: 10 user: 5
               SELECT * FROM my_table\"}"
-  {:arglists '([driver query]), :style/indent 1}
+  {:added "0.32.0", :arglists '([driver query]), :style/indent 1}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -663,7 +663,7 @@
 
   For databases that do not feature concepts like 'prepared statements', this method need not be implemented; the
   default implementation is an identity function."
-  {:arglists '([driver query]), :style/indent 1}
+  {:added "0.32.0", :arglists '([driver query]), :style/indent 1}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -679,7 +679,7 @@
   "Notify the driver that the attributes of a `database` have changed, or that `database was deleted. This is
   specifically relevant in the event that the driver was doing some caching or connection pooling; the driver should
   release ALL related resources when this is called."
-  {:arglists '([driver database])}
+  {:added "0.32.0" :arglists '([driver database])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -693,7 +693,7 @@
     (defn sync-in-context [driver database f]
       (with-connection [_ database]
         (f)))"
-  {:arglists '([driver database f]), :style/indent 2}
+  {:added "0.32.0", :arglists '([driver database f]), :style/indent 2}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -707,7 +707,7 @@
 
   This method is currently only used by the H2 driver to load the Sample Database, so it is not neccesary for any other
   drivers to implement it at this time."
-  {:arglists '([driver database table])}
+  {:added "0.32.0" :arglists '([driver database table])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -750,7 +750,7 @@
   `metabase.driver.common.parameters.*` namespaces. See the `:sql` and `:mongo` drivers for sample implementations of
   this method.`Driver-agnostic end-to-end native parameter tests live in
   [[metabase.query-processor-test.parameters-test]] and other namespaces."
-  {:arglists '([driver inner-query])}
+  {:added "0.34.0" :arglists '([driver inner-query])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -821,7 +821,7 @@
 (defmulti execute-write-query!
   "Execute a writeback query e.g. one powering a custom `QueryAction` (see [[metabase.models.action]]).
   Drivers that support `:actions/custom` must implement this method."
-  {:added "0.44.0", :arglists '([driver query])}
+  {:changelog-test/ignore true, :added "0.44.0", :arglists '([driver query])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -842,7 +842,7 @@
 (defmulti set-role!
   "Sets the database role used on a connection. Called prior to query execution for drivers that support connection
   impersonation (an EE-only feature)."
-  {:arglists '([driver conn role])}
+  {:added "0.47.0" :arglists '([driver conn role])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -853,7 +853,7 @@
 
 (defmulti table-name-length-limit
   "Return the maximum number of characters allowed in a table name, or `nil` if there is no limit."
-  {:added "0.47.0", :arglists '([driver])}
+  {:changelog-test/ignore true, :added "0.47.0", :arglists '([driver])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
@@ -891,7 +891,7 @@
   - [:bigint]
   - [[:varchar 255]]
   - [:generated-always :as :identity :primary-key]"
-  {:added "0.47.0", :arglists '([driver upload-type])}
+  {:changelog-test/ignore true, :added "0.47.0", :arglists '([driver upload-type])}
   dispatch-on-initialized-driver
   :hierarchy #'hierarchy)
 
diff --git a/src/metabase/driver/ddl/interface.clj b/src/metabase/driver/ddl/interface.clj
index f55d69708e9..f56a6457f21 100644
--- a/src/metabase/driver/ddl/interface.clj
+++ b/src/metabase/driver/ddl/interface.clj
@@ -31,7 +31,7 @@
   This is actually ultimately used to format any name that comes back
   from [[metabase.test.data.sql/qualified-name-components]] -- so if you include the Database name there, it will get
   formatted by this as well."
-  {:arglists '([driver table-or-field-name])}
+  {:changelog-test/ignore true :added "0.44.0" :arglists '([driver table-or-field-name])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -47,7 +47,7 @@
   - [false :persist.check/create-table]
   - [false :persist.check/read-table]
   - [false :persist.check/delete-table]"
-  {:arglists '([database])}
+  {:changelog-test/ignore true :added "0.44.0" :arglists '([database])}
   (fn [database] (driver/dispatch-on-initialized-driver (:engine database)))
   :hierarchy #'driver/hierarchy)
 
@@ -90,12 +90,12 @@
   database. Assumes that the destination schema is populated and permissions are correct. This should all be true
   if `(driver/database-supports engine :persisted-models database)` returns true. Returns a map with :state that
   is :success or :error. If :state is :error, includes a key :error with a string message."
-  {:arglists '([driver database definition dataset-query])}
+  {:changelog-test/ignore true :added "0.44.0" :arglists '([driver database definition dataset-query])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
 (defmulti unpersist!
   "Unpersist a persisted model. Responsible for removing the persisted table."
-  {:arglists '([driver database persisted-info])}
+  {:changelog-test/ignore true :added "0.44.0" :arglists '([driver database persisted-info])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
diff --git a/src/metabase/driver/sql.clj b/src/metabase/driver/sql.clj
index 59c0dd4e7a1..a899219399c 100644
--- a/src/metabase/driver/sql.clj
+++ b/src/metabase/driver/sql.clj
@@ -72,7 +72,7 @@
 
 (defmulti set-role-statement
   "SQL for setting the active role for a connection, such as USE ROLE or equivalent, for the given driver."
-  {:arglists '([driver role])}
+  {:added "0.47.0" :arglists '([driver role])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -83,7 +83,7 @@
 (defmulti default-database-role
   "The name of the default role for a given database, used for queries that do not have custom user
   impersonation rules configured for them. This must be implemented for each driver that supports user impersonation."
-  {:arglists '(^String [driver database])}
+  {:added "0.47.0" :arglists '(^String [driver database])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
diff --git a/src/metabase/driver/sql/parameters/substitution.clj b/src/metabase/driver/sql/parameters/substitution.clj
index a9c6a3d7426..015f4037c8b 100644
--- a/src/metabase/driver/sql/parameters/substitution.clj
+++ b/src/metabase/driver/sql/parameters/substitution.clj
@@ -40,7 +40,7 @@
   "Returns a `PreparedStatementSubstitution` (see schema below) for `x` and the given driver. This allows driver
   specific parameters and SQL replacement text (usually just ?). The param value is already prepared and ready for
   inlcusion in the query, such as what's needed for SQLite and timestamps."
-  {:arglists '([driver x])}
+  {:added "0.34.0" :arglists '([driver x])}
   (fn [driver x] [(driver/dispatch-on-initialized-driver driver) (class x)])
   :hierarchy #'driver/hierarchy)
 
@@ -106,7 +106,7 @@
   `:prepared-statement-args`.
 
     (->replacement-snippet-info :h2 \"ABC\") -> {:replacement-snippet \"?\", :prepared-statement-args \"ABC\"}"
-  {:arglists '([driver value])}
+  {:added "0.33.4" :arglists '([driver value])}
   (fn [driver v] [(driver/the-initialized-driver driver) (class v)])
   :hierarchy #'driver/hierarchy)
 
diff --git a/src/metabase/driver/sql/query_processor.clj b/src/metabase/driver/sql/query_processor.clj
index 11bbc814835..44cc1e8b3bf 100644
--- a/src/metabase/driver/sql/query_processor.clj
+++ b/src/metabase/driver/sql/query_processor.clj
@@ -158,7 +158,7 @@
 
 (defmulti ->integer
   "Cast to integer"
-  {:arglists '([driver honeysql-expr])}
+  {:changelog-test/ignore true :added "0.45.0" :arglists '([driver honeysql-expr])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -168,7 +168,7 @@
 
 (defmulti ->float
   "Cast to float."
-  {:arglists '([driver honeysql-expr])}
+  {:changelog-test/ignore true :added "0.45.0" :arglists '([driver honeysql-expr])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -194,7 +194,7 @@
 (defmulti ->honeysql
   "Return an appropriate HoneySQL form for an object. Dispatches off both driver and either clause name or object class
   making this easy to override in any places needed for a given driver."
-  {:arglists '([driver mbql-expr-or-object])}
+  {:added "0.37.0" :arglists '([driver mbql-expr-or-object])}
   (fn [driver x]
     [(driver/dispatch-on-initialized-driver driver) (mbql.u/dispatch-by-clause-name-or-class x)])
   :hierarchy #'driver/hierarchy)
@@ -221,7 +221,7 @@
 (defmulti current-datetime-honeysql-form
   "HoneySQL form that should be used to get the current `datetime` (or equivalent). Defaults to `:%now`. Should ideally
   include the database type info on the form (ex: via [[hx/with-type-info]])."
-  {:arglists '([driver])}
+  {:added "0.34.2" :arglists '([driver])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -235,7 +235,7 @@
   component.
 
   `honeysql-expr` is already compiled to Honey SQL, so DO NOT call [[->honeysql]] on it."
-  {:arglists '([driver unit honeysql-expr])}
+  {:added "0.32.0" :arglists '([driver unit honeysql-expr])}
   (fn [driver unit _] [(driver/dispatch-on-initialized-driver driver) unit])
   :hierarchy #'driver/hierarchy)
 
@@ -340,7 +340,7 @@
     (add-interval-honeysql-form :my-driver hsql-form 1 :day) -> (hx/call :date_add hsql-form 1 (hx/literal 'day'))
 
   `amount` is usually an integer, but can be floating-point for units like seconds."
-  {:arglists '([driver hsql-form amount unit])}
+  {:added "0.34.2" :arglists '([driver hsql-form amount unit])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -400,7 +400,7 @@
   method name is a bit of a misnomer!
 
   TODO -- we should update this method name to better reflect its usage in Honey SQL 2."
-  {:arglists '([driver])}
+  {:added "0.32.0" :arglists '([driver])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -457,7 +457,7 @@
   of `honeysql-form`. Most drivers can use the default implementations for all of these methods, but some may need to
   override one or more (e.g. SQL Server needs to override this method for the `:limit` clause, since T-SQL uses `TOP`
   instead of `LIMIT`)."
-  {:arglists '([driver top-level-clause honeysql-form query]), :style/indent 2}
+  {:added "0.32.0", :arglists '([driver top-level-clause honeysql-form query]), :style/indent 2}
   (fn [driver top-level-clause _ _]
     [(driver/dispatch-on-initialized-driver driver) top-level-clause])
   :hierarchy #'driver/hierarchy)
@@ -471,7 +471,7 @@
 
   Lots of SQL DB's have denormalized JSON fields and they all have some sort of special syntax for dealing with
   indexing into it. Implement the special syntax in this multimethod."
-  {:arglists '([driver identifier json-field]), :added "0.43.1"}
+  {:changelog-test/ignore true, :arglists '([driver identifier json-field]), :added "0.43.1"}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -1211,13 +1211,13 @@
 
 (defmulti join->honeysql
   "Compile a single MBQL `join` to HoneySQL."
-  {:arglists '([driver join])}
+  {:added "0.32.9" :arglists '([driver join])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
 (defmulti join-source
   "Generate HoneySQL for a table or query to be joined."
-  {:arglists '([driver join])}
+  {:added "0.32.9" :arglists '([driver join])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -1506,7 +1506,7 @@
   "Do miscellaneous transformations to the MBQL before compiling the query. These changes are idempotent, so it is safe
   to use this function in your own implementations of [[driver/mbql->native]], if you want to apply changes to the
   same version of the query that we will ultimately be compiling."
-  {:arglists '([driver inner-query]), :added "0.42.0"}
+  {:changelog-test/ignore true, :arglists '([driver inner-query]), :added "0.42.0"}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
diff --git a/src/metabase/driver/sql/util/unprepare.clj b/src/metabase/driver/sql/util/unprepare.clj
index c01f4f4c103..cf1de78eb5d 100644
--- a/src/metabase/driver/sql/util/unprepare.clj
+++ b/src/metabase/driver/sql/util/unprepare.clj
@@ -20,7 +20,7 @@
 (defmulti unprepare-value
   "Convert a single argument to appropriate raw SQL for splicing directly into a SQL query. Dispatches on both driver
   and the class of `value`."
-  {:arglists '(^String [driver value])}
+  {:added "0.32.0" :arglists '(^String [driver value])}
   (fn [driver value]
     [(driver/the-initialized-driver driver) (class value)])
   :hierarchy #'driver/hierarchy)
@@ -86,7 +86,7 @@
 
   Drivers likely do not need to implement this method themselves -- instead, you should only need to provide
   implementations of `unprepare-value` for the cases where it is needed."
-  {:arglists '([driver [sql & args]]), :style/indent 1}
+  {:added "0.32.0", :arglists '([driver [sql & args]]), :style/indent 1}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
diff --git a/src/metabase/driver/sql_jdbc/actions.clj b/src/metabase/driver/sql_jdbc/actions.clj
index 446467114f8..c6438c746d1 100644
--- a/src/metabase/driver/sql_jdbc/actions.clj
+++ b/src/metabase/driver/sql_jdbc/actions.clj
@@ -41,7 +41,7 @@
     If non per-column error is available, returns an empty map.
 
   Or return `nil` if the parser doesn't match."
-  {:arglists '([driver error-type database action-type error-message]), :added "0.48.0"}
+  {:changelog-test/ignore true, :arglists '([driver error-type database action-type error-message]), :added "0.48.0"}
   (fn [driver error-type _database _action-type _error-message]
    [(driver/dispatch-on-initialized-driver driver) error-type])
   :hierarchy #'driver/hierarchy)
@@ -99,7 +99,7 @@
   "Return a map of [[metabase.types]] type to SQL string type name. Used for casting. Looks like we're just copypasting
   this from implementations of [[metabase.test.data.sql/field-base-type->sql-type]] so go find that stuff if you need
   to write more implementations for this."
-  {:arglists '([driver]), :added "0.44.0"}
+  {:changelog-test/ignore true, :arglists '([driver]), :added "0.44.0"}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -190,7 +190,7 @@
   "Multimethod for preparing a honeysql query `hsql-query` for a given action type `action`.
   `action` is a keyword like `:row/create` or `:bulk/create`; `hsql-query` is a generic
   query of the type corresponding to `action`."
-  {:arglists '([driver action hsql-query]), :added "0.46.0"}
+  {:changelog-test/ignore true, :arglists '([driver action hsql-query]), :added "0.46.0"}
   (fn [driver action _]
     [(driver/dispatch-on-initialized-driver driver)
      (keyword action)])
@@ -249,7 +249,7 @@
   `create-hsql` is the honeysql query used to insert the new row,
   `conn` is the DB connection used to insert the new row and
   `result` is the value returned by the insert command."
-  {:arglists '([driver create-hsql conn result]), :added "0.46.0"}
+  {:changelog-test/ignore true, :arglists '([driver create-hsql conn result]), :added "0.46.0"}
   (fn [driver _ _ _]
     (driver/dispatch-on-initialized-driver driver))
   :hierarchy #'driver/hierarchy)
@@ -311,7 +311,7 @@
 
   So the point of using nested transactions is that if 2 is done inside a nested transaction we can rollback the
   nested transaction which allows the top-level transaction to proceed even tho part of it errored."
-  {:arglists '([driver ^java.sql.Connection connection thunk]), :added "0.44.0"}
+  {:changelog-test/ignore true :arglists '([driver ^java.sql.Connection connection thunk]), :added "0.44.0"}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
diff --git a/src/metabase/driver/sql_jdbc/connection.clj b/src/metabase/driver/sql_jdbc/connection.clj
index f3c0d2742ca..b0f6803b3f4 100644
--- a/src/metabase/driver/sql_jdbc/connection.clj
+++ b/src/metabase/driver/sql_jdbc/connection.clj
@@ -38,7 +38,7 @@
   DO NOT USE THIS METHOD DIRECTLY UNLESS YOU KNOW WHAT YOU ARE DOING! THIS RETURNS AN UNPOOLED CONNECTION SPEC! IF YOU
   WANT A CONNECTION SPEC FOR RUNNING QUERIES USE [[db->pooled-connection-spec]] INSTEAD WHICH WILL RETURN A *POOLED*
   CONNECTION SPEC."
-  {:arglists '([driver details-map])}
+  {:added "0.32.0" :arglists '([driver details-map])}
   driver/dispatch-on-initialized-driver-safe-keys
   :hierarchy #'driver/hierarchy)
 
@@ -57,7 +57,7 @@
   powering Cards and the sync process, which are less sensitive to overhead than something like the application DB.
 
   Drivers that need to override the default properties below can provide custom implementations of this method."
-  {:arglists '([driver database])}
+  {:added "0.33.4" :arglists '([driver database])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -67,7 +67,7 @@
 
   The default method uses the first non-nil value of the keys `:db`, `:dbname`, `:sid`, or `:catalog`; implement a new
   method if your driver does not have any of these keys in its details."
-  {:arglists '([driver details]), :added "0.45.0"}
+  {:changelog-test/ignore true, :arglists '([driver details]), :added "0.45.0"}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
diff --git a/src/metabase/driver/sql_jdbc/execute.clj b/src/metabase/driver/sql_jdbc/execute.clj
index 3f86d591fb8..5172740f93f 100644
--- a/src/metabase/driver/sql_jdbc/execute.clj
+++ b/src/metabase/driver/sql_jdbc/execute.clj
@@ -118,7 +118,7 @@
   "Set the `PreparedStatement` parameter at index `i` to `object`. Dispatches on driver and class of `object`. By
   default, this calls `.setObject`, but drivers can override this method to convert the object to a different class or
   set it with a different intended JDBC type as needed."
-  {:arglists '([driver prepared-statement i object])}
+  {:added "0.34.0" :arglists '([driver prepared-statement i object])}
   (fn [driver _ _ object]
     [(driver/dispatch-on-initialized-driver driver) (class object)])
   :hierarchy #'driver/hierarchy)
@@ -131,7 +131,8 @@
   "Create a PreparedStatement with `sql` query, and set any `params`. You shouldn't need to override the default
   implementation for this method; if you do, take care to set options to maximize result set read performance (e.g.
   `ResultSet/TYPE_FORWARD_ONLY`); refer to the default implementation."
-  {:added "0.35.0", :arglists '(^java.sql.PreparedStatement [driver ^java.sql.Connection connection ^String sql params])}
+  {:added "0.35.0",
+   :arglists '(^java.sql.PreparedStatement [driver ^java.sql.Connection connection ^String sql params])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
diff --git a/src/metabase/driver/sql_jdbc/execute/old_impl.clj b/src/metabase/driver/sql_jdbc/execute/old_impl.clj
index 8f8d94afe71..20ccbcb8b5b 100644
--- a/src/metabase/driver/sql_jdbc/execute/old_impl.clj
+++ b/src/metabase/driver/sql_jdbc/execute/old_impl.clj
@@ -9,9 +9,7 @@
 (defmulti connection-with-timezone
   "Deprecated in Metabase 47. Implement [[metabase.driver.sql-jdbc.execute/do-with-connection-with-options]] instead.
   This method will be removed in or after Metabase 50."
-  {:added      "0.35.0"
-   :deprecated "0.47.0"
-   :arglists   '(^java.sql.Connection [driver database ^String timezone-id])}
+  {:added "0.35.0", :deprecated "0.47.0", :arglists '(^java.sql.Connection [driver database ^String timezone-id])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -25,7 +23,7 @@
   This method is only called for drivers using the default implementation
   of [[metabase.driver.sql-jdbc.execute/do-with-connection-with-options]]; it should be considered deprecated in
   favor of implementing [[metabase.driver.sql-jdbc.execute/do-with-connection-with-options]] directly."
-  {:deprecated "0.35.0", :arglists '([driver])}
+  {:added "0.35.0", :deprecated "0.35.0", :arglists '([driver])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
diff --git a/src/metabase/driver/sql_jdbc/sync/describe_table.clj b/src/metabase/driver/sql_jdbc/sync/describe_table.clj
index 1917c4feef3..347e5920bf7 100644
--- a/src/metabase/driver/sql_jdbc/sync/describe_table.clj
+++ b/src/metabase/driver/sql_jdbc/sync/describe_table.clj
@@ -197,7 +197,8 @@
   Ref: https://docs.oracle.com/javase/8/docs/api/java/sql/DatabaseMetaData.html#getPrimaryKeys-java.lang.String-java.lang.String-java.lang.String-
 
   Note: If db-name, schema, and table-name are not passed, this may return _all_ pks that the metadata's connection can access."
-  {:added    "0.45.0"
+  {:changelog-test/ignore true
+   :added    "0.45.0"
    :arglists '([driver ^Connection conn db-name-or-nil table])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
diff --git a/src/metabase/driver/sql_jdbc/sync/interface.clj b/src/metabase/driver/sql_jdbc/sync/interface.clj
index cd418461012..efece085f6f 100644
--- a/src/metabase/driver/sql_jdbc/sync/interface.clj
+++ b/src/metabase/driver/sql_jdbc/sync/interface.clj
@@ -12,7 +12,8 @@
   functions for more details on the differences.
 
   `metabase` is an instance of `DatabaseMetaData`."
-  {:arglists '([driver
+  {:added "0.37.1"
+   :arglists '([driver
                 ^java.sql.Connection connection
                 ^String schema-inclusion-filters
                 ^String schema-exclusion-filters])}
@@ -21,7 +22,7 @@
 
 (defmulti excluded-schemas
   "Return set of string names of schemas to skip syncing tables from."
-  {:arglists '([driver])}
+  {:added "0.37.1" :arglists '([driver])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -29,7 +30,7 @@
   "Check if we have SELECT privileges for given `table`.
 
   Default impl is in [[metabase.driver.sql-jdbc.sync.describe-database]]."
-  {:arglists '([driver ^java.sql.Connection connection ^String table-schema ^String table-name])}
+  {:added "0.37.1" :arglists '([driver ^java.sql.Connection connection ^String table-schema ^String table-name])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -39,11 +40,13 @@
   a sequence of all schema names from the JDBC database metadata and filter out any schemas in `excluded-schemas`, along
   with any that shouldn't be included based on the given inclusion and exclusion patterns (see the
   `metabase.driver.sync` namespace for full explanation)."
-  {:added "0.43.0", :arglists '([driver
-                                 ^java.sql.Connection connection
-                                 ^java.sql.DatabaseMetaData metadata
-                                 ^String schema-inclusion-patterns
-                                 ^String schema-exclusion-patterns])}
+  {:changelog-test/ignore true
+   :added "0.43.0"
+   :arglists '([driver
+                ^java.sql.Connection connection
+                ^java.sql.DatabaseMetaData metadata
+                ^String schema-inclusion-patterns
+                ^String schema-exclusion-patterns])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -51,7 +54,7 @@
   "Given a native DB column type (as a keyword), return the corresponding `Field` `base-type`, which should derive from
   `:type/*`. You can use `pattern-based-database-type->base-type` in this namespace to implement this using regex
   patterns."
-  {:arglists '([driver database-type])}
+  {:added "0.37.1" :arglists '([driver database-type])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -60,7 +63,7 @@
   driver can mark Postgres JSON type columns as `:type/SerializedJSON` semantic type.
 
   `database-type` and `column-name` will be strings."
-  {:arglists '([driver database-type column-name])}
+  {:added "0.37.1" :arglists '([driver database-type column-name])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -71,7 +74,7 @@
 
     (fallback-metadata-query :postgres \"public\" \"my_table\")
     ;; -> [\"SELECT * FROM public.my_table WHERE 1 <> 1 LIMIT 0\"]"
-  {:arglists '([driver schema table])}
+  {:added "0.37.1" :arglists '([driver schema table])}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
@@ -86,7 +89,7 @@
   DEPRECATED: you can implement [[metabase.driver/db-default-timezone]] directly;
   use [[metabase.driver.sql-jdbc.execute/do-with-connection-with-options]] to get a `java.sql.Connection` for a
   Database."
-  {:arglists '([driver jdbc-spec]), :deprecated "0.48.0"}
+  {:added "0.38.0", :arglists '([driver jdbc-spec]), :deprecated "0.48.0"}
   driver/dispatch-on-initialized-driver
   :hierarchy #'driver/hierarchy)
 
diff --git a/test/metabase/driver/impl_test.clj b/test/metabase/driver/impl_test.clj
index 78f10362c05..f752c884045 100644
--- a/test/metabase/driver/impl_test.clj
+++ b/test/metabase/driver/impl_test.clj
@@ -1,10 +1,16 @@
 (ns metabase.driver.impl-test
   (:require
    [clojure.core.async :as a]
+   [clojure.java.io :as io]
    [clojure.test :refer :all]
    [metabase.driver :as driver]
    [metabase.driver.impl :as driver.impl]
-   [metabase.test.util.async :as tu.async]))
+   [metabase.test.util.async :as tu.async]
+   [metabase.util :as u])
+  (:import
+   (com.vladsch.flexmark.ast Heading)
+   (com.vladsch.flexmark.parser Parser)
+   (com.vladsch.flexmark.util.ast Document Node)))
 
 (set! *warn-on-reflection* true)
 
@@ -36,3 +42,66 @@
                  (driver/the-initialized-driver ::race-condition-test)))
           (is (= true
                  @finished-loading)))))))
+
+;;;; [[driver-multimethods-in-changelog-test]]
+
+(defn- parse-drivers-changelog
+  "Create a mapping of version to appropriate changelog file section.
+  All level 2 headings containing version and sections following are collected. This approach could handle changes from
+  version 0.42.0 onwards, as prior to this version, this information was stored at github wiki. Output has a following
+  shape {\"0.47.0\" \"...insert-into!...\" ...}."
+  []
+  (let [changelog     (slurp (io/file "docs/developers-guide/driver-changelog.md"))
+        parser        (.build (Parser/builder))
+        document      (.parse ^Parser parser ^String changelog)]
+    (loop [[child & children] (.getChildren ^Document document)
+           version->text      {}
+           last-version       nil]
+      (cond (nil? child)
+            version->text
+
+            (and (instance? Heading child)
+                 (= 2 (.getLevel ^Heading child)))
+            (let [heading-str      (str (.getChars ^Node child))
+                  new-last-version (re-find #"(?<=## Metabase )\d+\.\d+\.\d+" heading-str)]
+              (if (some? new-last-version)
+                (recur children version->text new-last-version)
+                (recur children version->text nil)))
+
+            (some? last-version)
+            (recur children
+                   (update version->text last-version str (.getChars ^Node child))
+                   last-version)
+
+            :else
+            (recur children version->text last-version)))))
+
+(defn- collect-metadatas
+  "List metadata for all defmultis of driver namespaces."
+  []
+  (let [nss (filter #(re-find #"^metabase\.driver" (name %)) u/metabase-namespace-symbols)]
+    (apply require nss)
+    (->> (map ns-publics nss)
+         (mapcat vals)
+         (filter #(instance? clojure.lang.MultiFn (deref %)))
+         (map meta))))
+
+(defn- older-than-42?
+  [version]
+  (when-let [version (drop 1 (re-find #"(\d+)\.(\d+)\.(\d+)" (str version)))]
+    (< (compare (mapv #(Integer/parseInt %) version)
+                [0 42 0])
+       0)))
+
+(deftest driver-multimethods-in-changelog-test
+  (let [metadatas             (collect-metadatas)
+        version->section-text (parse-drivers-changelog)]
+    (doseq [m metadatas]
+      (when-not (:changelog-test/ignore m)
+        (let [method (str (:ns m) "/" (:name m))]
+          (testing (str method " has `:added` metadata set")
+            (is (contains? m :added)))
+          (when-not (older-than-42? (:added m))
+            (testing (str method " is mentioned in changelog for version " (:added m))
+              (is (re-find (re-pattern (str "\\Q" (:name m) "\\E"))
+                           (get version->section-text (:added m) ""))))))))))
-- 
GitLab