Skip to content

Commit

Permalink
dev/rollback! n migrations (#40488)
Browse files Browse the repository at this point in the history
  • Loading branch information
qnkhuat committed Jun 18, 2024
1 parent 62e8739 commit 4d907fe
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 26 deletions.
14 changes: 11 additions & 3 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -665,9 +665,17 @@
:extra-paths ["dev/src"]
:main-opts ["-m" "dev.liquibase"]}

;; the following aliases are convenience for running tests against certain parts of the codebase.
;;
;; clj -X:dev:ee:ee-dev:test:test/mbql
;; Migrate CLI:
;; clojure -M:migrate <command>
;; E.g.
;; clojure -M:migrate up ;; migrate up to the latest
;; clojure -M:migrate rollback count 2 ;; rollback 2 migrations
;; clojure -M:migrate rollback id "v40.00.001" ;; rollback to a specific migration with id
;; clojure -M:migrate status ;; print the latest migration id
:migrate
{:extra-deps {io.github.camsaul/humane-are {:mvn/version "1.0.2"}}
:extra-paths ["dev/src"]
:main-opts ["-m" "dev.migrate"]}

;; run tests against MLv2. Very fast since this is almost all ^:parallel
:test/mlv2
Expand Down
12 changes: 4 additions & 8 deletions dev/src/dev.clj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
[clojure.test]
[dev.debug-qp :as debug-qp]
[dev.explain :as dev.explain]
[dev.migrate :as dev.migrate]
[dev.model-tracking :as model-tracking]
[hashp.core :as hashp]
[honey.sql :as sql]
Expand Down Expand Up @@ -101,6 +102,9 @@
pprint-sql]
[dev.explain
explain-query]
[dev.migrate
migrate!
rollback!]
[model-tracking
track!
untrack!
Expand Down Expand Up @@ -258,14 +262,6 @@
(a/>!! canceled-chan :cancel)
(throw e)))))

(defn migrate!
"Run migrations for the Metabase application database. Possible directions are `:up` (default), `:force`, `:down`, and
`:release-locks`. When migrating `:down` pass along a version to migrate to (44+)."
([]
(migrate! :up))
([direction & [version]]
(mdb/migrate! (mdb/data-source) direction version)))

(methodical/defmethod t2.connection/do-with-connection :model/Database
"Support running arbitrary queries against data warehouse DBs for easy REPL debugging. Only works for SQL+JDBC drivers
right now!
Expand Down
104 changes: 104 additions & 0 deletions dev/src/dev/migrate.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
(ns dev.migrate
(:gen-class)
(:require
[metabase.db :as mdb]
[metabase.db.liquibase :as liquibase]
[metabase.util.malli :as mu]
[toucan2.core :as t2])
(:import
(liquibase Liquibase)))

(set! *warn-on-reflection* true)

(defn- latest-migration
[]
((juxt :id :comments)
(t2/query-one {:select [:id :comments]
:from [:databasechangelog]
:order-by [[:orderexecuted :desc]]
:limit 1})))
(defn migrate!
"Run migrations for the Metabase application database. Possible directions are `:up` (default), `:force`, `:down`, and
`:release-locks`. When migrating `:down` pass along a version to migrate to (44+)."
([]
(migrate! :up))
;; do we really use this in dev?
([direction & [version]]
(mdb/migrate! (mdb/db-type) (mdb/data-source)
direction version)
#_{:clj-kondo/ignore [:discouraged-var]}
(println "Migrated up. Latest migration:" (latest-migration))))

(defn- rollback-n-migrations!
[^Integer n]
(with-open [conn (.getConnection (mdb/data-source))]
(liquibase/with-liquibase [^Liquibase liquibase conn]
(liquibase/with-scope-locked liquibase
(.rollback liquibase n "")))))

(defn- migration-since
[id]
(->> (t2/query-one {:select [[:%count.* :count]]
:from [:databasechangelog]
:where [:> :orderexecuted {:select [:orderexecuted]
:from [:databasechangelog]
:where [:like :id (format "%s%%" id)]
:order-by [:orderexecuted :desc]
:limit 1}]
:limit 1})
:count
;; includes the selected id
inc))

(defn- maybe-parse-long
[x]
(cond-> x
(string? x)
parse-long))

(mu/defn rollback!
"Rollback helper, can take a number of migrations to rollback or a specific migration ID(inclusive).
;; Rollback 2 migrations:
(rollback! :count 2)
;; rollback to \"v50.2024-03-18T16:00:00\" (inclusive)
(rollback! :id \"v50.2024-03-18T16:00:00\")"
[k :- [:enum :id :count "id" "count"]
target]
(let [n (case (keyword k)
:id (migration-since target)
:count (maybe-parse-long target))]
(rollback-n-migrations! n)
#_{:clj-kondo/ignore [:discouraged-var]}
(println (format "Rollbacked %d migrations. Latest migration: %s" n (latest-migration)))))

(defn migration-status
"Print the latest migration ID."
[]
#_{:clj-kondo/ignore [:discouraged-var]}
(println "Current migration:" (latest-migration)))

(defn -main
"Migrations helpers
Usage:
clojure -M:migrate up ;; migrate up to the latest
clojure -M:migrate rollback count 2 ;; rollback 2 migrations
clojure -M:migrate rollback id \"v40.00.001\" ;; rollback to a specific migration with id
clojure -M:migrate status ;; print the latest migration id"

[& args]
(let [[cmd & migration-args] args]
(case cmd
"rollback"
(apply rollback! migration-args)

"up"
(apply migrate! migration-args)

"status"
(migration-status)

(throw (ex-info "Invalid command" {:command cmd
:args args})))))
38 changes: 23 additions & 15 deletions dev/src/user.clj
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
(ns user
(:require
[environ.core :as env]
[humane-are.core :as humane-are]
[mb.hawk.assert-exprs]
[metabase.bootstrap]
[metabase.test-runner.assert-exprs]
[pjstadig.humane-test-output :as humane-test-output]))
[metabase.plugins.classloader :as classloader]
[metabase.util :as u]))

;; Initialize Humane Test Output if it's not already initialized. Don't enable humane-test-output when running tests
;; from the CLI, it breaks diffs. This uses [[env/env]] rather than [[metabase.config]] so we don't load that namespace
;; before we load [[metabase.bootstrap]]
(when-not (= (env/env :mb-run-mode) "test")
(humane-test-output/activate!))
;; Wrap these with ignore-exceptions to reduce the "required" deps of this namespace
;; We sometimes need to run cmd stuffs like `clojure -M:migrate rollback n 3` and these
;; libraries might not be available in the classpath
(u/ignore-exceptions
;; make sure stuff like `=?` and what not are loaded
(classloader/require 'mb.hawk.assert-exprs))

;;; Same for https://github.com/camsaul/humane-are
(humane-are/install!)
(u/ignore-exceptions
(classloader/require 'metabase.test-runner.assert-exprs))

(comment metabase.bootstrap/keep-me
;; make sure stuff like `=?` and what not are loaded
mb.hawk.assert-exprs/keep-me
metabase.test-runner.assert-exprs/keep-me)
(u/ignore-exceptions
(classloader/require 'humane-are.core)
((resolve 'humane-are.core/install!)))

(u/ignore-exceptions
(classloader/require 'pjstadig.humane-test-output)
;; Initialize Humane Test Output if it's not already initialized. Don't enable humane-test-output when running tests
;; from the CLI, it breaks diffs. This uses [[env/env]] rather than [[metabase.config]] so we don't load that namespace
;; before we load [[metabase.bootstrap]]
(when-not (= (env/env :mb-run-mode) "test")
((resolve 'pjstadig.humane-test-output/activate!))))

(comment metabase.bootstrap/keep-me)

(defn dev
"Load and switch to the 'dev' namespace."
Expand Down

0 comments on commit 4d907fe

Please sign in to comment.