diff --git a/deps.edn b/deps.edn index 689860c9e437f9d33f023017d70e2074c9337a99..f85e9d825344bfff4de398e1180930220769da05 100644 --- a/deps.edn +++ b/deps.edn @@ -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 diff --git a/dev/src/dev.clj b/dev/src/dev.clj index fa9abd8302f01877afb920b8e5488347afd4fdbd..494a8e20b07562905b89d92e6228798b90180961 100644 --- a/dev/src/dev.clj +++ b/dev/src/dev.clj @@ -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] @@ -101,6 +102,9 @@ pprint-sql] [dev.explain explain-query] + [dev.migrate + migrate! + rollback!] [model-tracking track! untrack! @@ -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! diff --git a/dev/src/dev/migrate.clj b/dev/src/dev/migrate.clj new file mode 100644 index 0000000000000000000000000000000000000000..3e773a2d43aa56e0178298cc458204a0f211b842 --- /dev/null +++ b/dev/src/dev/migrate.clj @@ -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}))))) diff --git a/dev/src/user.clj b/dev/src/user.clj index a2378e5f55fb8c0dee1a50b13d5fa73143d9cd1e..6f30abc0c058418da463d525b1b847c5608b6aaa 100644 --- a/dev/src/user.clj +++ b/dev/src/user.clj @@ -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."