├── src ├── drift │ ├── drift_version.clj │ ├── version.clj │ ├── listener_protocol.clj │ ├── destroyer.clj │ ├── Drift.clj │ ├── execute.clj │ ├── builder.clj │ ├── args.clj │ ├── config.clj │ ├── generator.clj │ ├── runner.clj │ └── core.clj └── leiningen │ ├── migrate.clj │ └── create_migration.clj ├── test ├── migrations │ ├── 002_test_update.clj │ └── 001_create_tests.clj ├── drift │ ├── test_destroyer.clj │ ├── test_version.clj │ ├── test_builder.clj │ ├── test_generator.clj │ ├── test_args.clj │ ├── test_execute.clj │ ├── test_config.clj │ ├── test_runner.clj │ └── test_core.clj ├── config │ ├── dynamic_config.clj │ ├── migrate_config.clj │ └── finished_config.clj └── test_helper.clj ├── .gitignore ├── project.clj ├── sample_migrate_config.clj └── README.md /src/drift/drift_version.clj: -------------------------------------------------------------------------------- 1 | (ns drift.drift-version) 2 | 3 | (def version "1.5.4-SNAPSHOT") -------------------------------------------------------------------------------- /test/migrations/002_test_update.clj: -------------------------------------------------------------------------------- 1 | (ns migrations.002-test-update) 2 | 3 | (defn up []) 4 | 5 | (defn down []) -------------------------------------------------------------------------------- /test/migrations/001_create_tests.clj: -------------------------------------------------------------------------------- 1 | (ns migrations.001-create-tests) 2 | 3 | (defn up []) 4 | 5 | (defn down []) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | .project 4 | .classpath 5 | lib 6 | bin 7 | .lein-failures 8 | .pmd 9 | classes 10 | .lein-deps-sum 11 | target/ 12 | *.clj~ 13 | -------------------------------------------------------------------------------- /test/drift/test_destroyer.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-destroyer 2 | (:use clojure.test 3 | drift.destroyer 4 | test-helper)) 5 | 6 | (deftest test-migration-usage 7 | (migration-usage)) -------------------------------------------------------------------------------- /test/config/dynamic_config.clj: -------------------------------------------------------------------------------- 1 | (ns config.dynamic-config 2 | (use clojure.test)) 3 | 4 | (defn config [] 5 | 6 | {:init (fn [args] 7 | (is (= args ["bloop" "blargh"])) 8 | {:more-config 42})}) 9 | -------------------------------------------------------------------------------- /src/drift/version.clj: -------------------------------------------------------------------------------- 1 | (ns drift.version 2 | (:require [drift.config :as config])) 3 | 4 | (defn 5 | #^{ :doc "Returns the current db version." } 6 | current-db-version [] 7 | ((config/current-version-fn))) 8 | 9 | (defn 10 | #^{ :doc "Sets the db version." } 11 | update-db-version [new-version] 12 | ((config/update-version-fn) new-version) 13 | new-version) 14 | -------------------------------------------------------------------------------- /test/drift/test_version.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-version 2 | (:use clojure.test 3 | drift.version) 4 | (:require [test-helper :as test-helper])) 5 | 6 | (deftest test-current-db-version 7 | (is (= (current-db-version) 0))) 8 | 9 | (deftest test-update-db-version 10 | (is (= (current-db-version) 0)) 11 | (update-db-version 1) 12 | (is (= (current-db-version) 1)) 13 | (update-db-version 0) 14 | (is (= (current-db-version) 0))) -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | ;; IMPORTANT: When bumping the version number here, be sure to bump it also in 2 | ;; src/drift/drift_version.clj! 3 | (defproject drift "1.5.4-SNAPSHOT" 4 | :description "Drift is a rails like migration framework for Clojure." 5 | :dependencies [[clojure-tools "1.1.2"] 6 | [org.clojure/tools.logging "0.2.3"]] 7 | :profiles {:dev {:dependencies [[log4j/log4j "1.2.16"]]}} 8 | 9 | :aot [drift.listener-protocol drift.Drift]) -------------------------------------------------------------------------------- /src/leiningen/migrate.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.migrate 2 | "Run drift migration scripts." 3 | (:require [drift.drift-version :as drift-version]) 4 | (:use [leiningen.core.eval :only (eval-in-project)])) 5 | 6 | (defn migrate [project & args] 7 | "Run migration scripts." 8 | (eval-in-project 9 | (update-in project [:dependencies] 10 | conj ['drift drift-version/version]) 11 | `(drift.execute/run '~args) 12 | '(require 'drift.execute))) 13 | -------------------------------------------------------------------------------- /src/drift/listener_protocol.clj: -------------------------------------------------------------------------------- 1 | (ns drift.listener-protocol) 2 | 3 | (defprotocol ListenerProtocol 4 | (start [this namespaces ^Boolean up?] "Called before drift starts executing migrations. The given namespaces is a list of all namespaces of the migrations to run.") 5 | (running [this ^String namespace ^Boolean up?] "Called for each migration just before it is run. The given namespace is the namespace of the migration to run.") 6 | (end [this] "Called after all migrations have completed.")) -------------------------------------------------------------------------------- /src/leiningen/create_migration.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.create-migration 2 | "Create a new versioned migration script." 3 | (:require [drift.drift-version :as drift-version]) 4 | (:use [leiningen.core.eval :only (eval-in-project)])) 5 | 6 | (defn create-migration [project & args] 7 | "Create a new migration file." 8 | (eval-in-project 9 | (update-in project [:dependencies] 10 | conj ['drift drift-version/version]) 11 | `(drift.generator/generate-migration-file-cmdline '~args) 12 | '(require 'drift.generator))) 13 | -------------------------------------------------------------------------------- /test/config/migrate_config.clj: -------------------------------------------------------------------------------- 1 | (ns config.migrate-config 2 | (:require [drift.builder :as builder])) 3 | 4 | (def version (atom nil)) 5 | (def init-run? (atom false)) 6 | 7 | (defn memory-current-version [] 8 | (or @version 0)) 9 | 10 | (defn memory-update-version [new-version] 11 | (swap! version #(identity %2) new-version)) 12 | 13 | (defn init [args] 14 | (compare-and-set! init-run? false true)) 15 | 16 | (defn migrate-config [] 17 | { :directory "/test/migrations" 18 | :current-version memory-current-version 19 | :update-version memory-update-version 20 | :init init 21 | :ns-content "\n (:use clojure.contrib.sql)" 22 | :migration-number-generator builder/incremental-migration-number-generator }) -------------------------------------------------------------------------------- /test/config/finished_config.clj: -------------------------------------------------------------------------------- 1 | (ns config.finished-config 2 | (:require [drift.builder :as builder])) 3 | 4 | (def version (atom nil)) 5 | (def init-run? (atom false)) 6 | (def finished-run? (atom false)) 7 | 8 | (defn memory-current-version [] 9 | (or @version 0)) 10 | 11 | (defn memory-update-version [new-version] 12 | (swap! version #(identity %2) new-version)) 13 | 14 | (defn init [args] 15 | (compare-and-set! init-run? false true)) 16 | 17 | (defn finished [] 18 | (compare-and-set! finished-run? false true)) 19 | 20 | (defn migrate-config [] 21 | { :directory "/test/migrations" 22 | :current-version memory-current-version 23 | :update-version memory-update-version 24 | :init init 25 | :finished finished 26 | :ns-content "\n (:use clojure.contrib.sql)" 27 | :migration-number-generator builder/incremental-migration-number-generator }) 28 | -------------------------------------------------------------------------------- /test/test_helper.clj: -------------------------------------------------------------------------------- 1 | (ns test-helper 2 | (:import [java.io File] 3 | [org.apache.log4j ConsoleAppender Level Logger PatternLayout] 4 | [org.apache.log4j.varia LevelRangeFilter]) 5 | (:use clojure.test)) 6 | 7 | (def output-pattern (new PatternLayout "%-5p [%c]: %m%n")) 8 | 9 | (def console-appender (new ConsoleAppender output-pattern)) 10 | (.addFilter console-appender 11 | (doto (new LevelRangeFilter) 12 | (.setLevelMin Level/WARN))) 13 | 14 | (doto (Logger/getRootLogger) 15 | (.setLevel Level/ALL) 16 | (.addAppender console-appender)) 17 | 18 | (defn 19 | #^{:doc "Verifies the given file is not nil, is an instance of File, and has the given name."} 20 | test-file [file expected-file-name] 21 | (is file) 22 | (is (instance? File file)) 23 | (when file 24 | (is (= expected-file-name (.getName file))))) 25 | 26 | (defn 27 | #^{:doc "Simply calls test-file on the given directory and name."} 28 | test-directory [directory expected-directory-name] 29 | (test-file directory expected-directory-name)) -------------------------------------------------------------------------------- /src/drift/destroyer.clj: -------------------------------------------------------------------------------- 1 | (ns drift.destroyer 2 | (:import [java.io File]) 3 | (:require [clojure.tools.logging :as logging] 4 | [drift.core :as core])) 5 | 6 | (defn 7 | #^{:doc "Prints out how to use the destroy migration command."} 8 | migration-usage [] 9 | (println "You must supply a migration name (Like migration-name).") 10 | (println "Usage: ./run.sh script/destroy.clj migration ")) 11 | 12 | (defn 13 | #^{:doc "Creates the migration file from the given migration-name."} 14 | destroy-migration-file [migration-name] 15 | (if migration-name 16 | (if-let [migrate-directory (core/find-migrate-directory)] 17 | (if-let [migration-file (core/find-migration-file migrate-directory migration-name)] 18 | (let [is-deleted (.delete migration-file)] 19 | (logging/info (str "File " (.getPath migration-file) (if is-deleted " deleted." " not deleted.") ))) 20 | (logging/error (str "Could not find migration file for " migration-name))) 21 | (logging/error "Could not find db directory.")) 22 | (migration-usage))) -------------------------------------------------------------------------------- /test/drift/test_builder.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-builder 2 | (:use clojure.test 3 | drift.builder 4 | test-helper) 5 | (:require [drift.core :as core] 6 | [clojure.tools.logging :as logging])) 7 | 8 | (deftest test-find-or-create-migrate-directory 9 | (let [migrate-directory (find-or-create-migrate-directory (core/migrate-directory))] 10 | (test-directory migrate-directory "migrations")) 11 | (test-directory (find-or-create-migrate-directory) "migrations") 12 | (is (nil? (find-or-create-migrate-directory nil)))) 13 | 14 | (deftest test-create-migration-file 15 | (let [migrate-directory (find-or-create-migrate-directory) 16 | migration-file (create-migration-file migrate-directory "builder-test")] 17 | (test-file migration-file "003_builder_test.clj") 18 | (.delete migration-file)) 19 | (let [migration-file (create-migration-file "builder-test")] 20 | (test-file migration-file "003_builder_test.clj") 21 | (.delete migration-file)) 22 | (is (nil? (create-migration-file nil))) 23 | (is (nil? (create-migration-file (find-or-create-migrate-directory) nil))) 24 | (is (nil? (create-migration-file nil "create-test"))) 25 | (is (nil? (create-migration-file nil nil)))) -------------------------------------------------------------------------------- /test/drift/test_generator.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-generator 2 | (:use clojure.test 3 | drift.generator 4 | test-helper) 5 | (:require config.finished-config 6 | [drift.builder :as builder])) 7 | 8 | (deftest test-migration-usage 9 | (migration-usage)) 10 | 11 | (deftest test-create-file-content 12 | (is (create-file-content "migrations.001-create-tests" nil nil nil))) 13 | 14 | (deftest test-generate-migration-file-cmdline 15 | (with-redefs [drift.generator/generate-migration-file 16 | (fn [mn] 17 | (is (= drift.config/*config-fn-symbol* 'foo.bar/baz)) 18 | (is (= mn "blahblah")))] 19 | 20 | (generate-migration-file-cmdline 21 | ["-c" "foo.bar/baz" "blahblah"]))) 22 | 23 | (deftest test-finished-fn-called 24 | (with-redefs [builder/find-or-create-migrate-directory (fn []) 25 | builder/create-migration-file (fn [dir fname]) 26 | drift.generator/generate-file-content (fn [migration-file migration-name ns-content up-content down-content])] 27 | 28 | (generate-migration-file-cmdline ["-c" "config.finished-config/migrate-config" "blahblah"]) 29 | 30 | (is (= @config.finished-config/finished-run? true)))) 31 | -------------------------------------------------------------------------------- /src/drift/Drift.clj: -------------------------------------------------------------------------------- 1 | (ns drift.Drift 2 | (:require [drift.core :as core] 3 | [drift.execute :as execute] 4 | [drift.runner :as runner] 5 | [drift.version :as version]) 6 | (:import [drift.listener_protocol ListenerProtocol]) 7 | (:gen-class 8 | :methods [[init [java.util.List] Object] 9 | [migrate [Long java.util.List] Void] 10 | [migrationCount [Long java.util.List] Integer] 11 | [currentVersion [] Long] 12 | [maxMigrationNumber [] Long] 13 | [addListener [drift.listener_protocol.ListenerProtocol] java.util.Collection] 14 | [removeListener [drift.listener_protocol.ListenerProtocol] java.util.Collection]])) 15 | 16 | (defn -init [_ other-args] 17 | (core/run-init other-args)) 18 | 19 | (defn -migrate [_ version other-args] 20 | (execute/migrate version other-args)) 21 | 22 | (defn -migrationCount [_ version other-args] 23 | (execute/migration-count version other-args)) 24 | 25 | (defn -currentVersion [_] 26 | (long (version/current-db-version))) 27 | 28 | (defn -maxMigrationNumber [_] 29 | (long (core/max-migration-number))) 30 | 31 | (defn -addListener [_ listener] 32 | (runner/add-listener listener)) 33 | 34 | (defn -removeListener [_ listener] 35 | (runner/remove-listener listener)) -------------------------------------------------------------------------------- /test/drift/test_args.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-args 2 | (:use clojure.test drift.args)) 3 | 4 | (deftest test-split-args 5 | (is (= (split-args ["foo" "-v" "bar" "baz"] #{"-v"} ) 6 | [["foo"] ["-v" "bar" "baz"]])) 7 | 8 | (is (= (split-args ["foo" "bar" "baz"] #{"-v"} ) 9 | [["foo" "bar" "baz"] []]))) 10 | 11 | (deftest test-remove-opt 12 | (is (= (remove-opt ["-v" "123" "foo" "bar"] {:key :version :matcher #{"-v"}}) 13 | [{:version "123"} ["foo" "bar"]])) 14 | 15 | (is (= (remove-opt ["-v"] {:key :version :matcher #{"-v"}}) 16 | [{} ["-v"]])) 17 | 18 | (is (= (remove-opt [] {:key :version :matcher #{"-v"}}) 19 | [{} []]))) 20 | 21 | (deftest test-parse-args 22 | (is (= (parse-args ["foo" "-v" "10" "bar" "--config" "conf" "baz"] 23 | [{:key :version :matcher #{"-v" "--version"}} 24 | {:key :config :matcher #{"-v" "--config"}}]) 25 | [{:version "10" :config "conf"} ["foo" "bar" "baz"]]))) 26 | 27 | (deftest test-parse-migrate-args 28 | (is (= (parse-migrate-args ["foo" "--other" "blah" "-v" "10" "bar" "--config" "conf" "baz"]) 29 | [{:version "10" :config 'conf} ["foo" "--other" "blah" "bar" "baz"]]))) 30 | 31 | (deftest test-parse-create-migration-args 32 | (is (= (parse-create-migration-args ["foo" "--other" "blah" "-v" "10" "bar" "--config" "conf" "baz"]) 33 | [{:config 'conf} ["foo" "--other" "blah" "-v" "10" "bar" "baz"]]))) 34 | -------------------------------------------------------------------------------- /src/drift/execute.clj: -------------------------------------------------------------------------------- 1 | (ns drift.execute 2 | (:require [clojure.string :as string] 3 | [clojure.tools.logging :as logging] 4 | [drift.args :as args] 5 | [drift.config :as config] 6 | [drift.core :as core] 7 | [drift.runner :as runner])) 8 | 9 | (defn 10 | #^{:doc "Gets the version number from the passed in version parameter. If the given version string is nil, then this method returns Long/MAX_VALUE. If the version parameter is invalid, then this method prints an error and returns nil."} 11 | version-number [version] 12 | (if version 13 | (if (string? version) 14 | (Long/parseLong version) 15 | version) 16 | Long/MAX_VALUE)) 17 | 18 | (defn migration-count 19 | "Returns the total number of migrations to run to update the database to the given version number." 20 | [version remaining-args] 21 | (core/with-init-config remaining-args 22 | (fn [] 23 | (runner/migration-count (version-number version))))) 24 | 25 | (defn 26 | migrate [version remaining-args] 27 | (core/with-init-config remaining-args 28 | (fn [] 29 | (runner/update-to-version (version-number version))))) 30 | 31 | (defn 32 | run [args] 33 | (let [[opts remaining] (args/parse-migrate-args args)] 34 | (if (empty? remaining) 35 | (config/with-config-fn-symbol 36 | (:config opts) 37 | (fn [] 38 | (migrate (:version opts) remaining))) 39 | (do (logging/error "Invalid arguments:" (string/join " " remaining)) 40 | (args/print-usage "migrate" args/migrate-arg-specs))))) 41 | -------------------------------------------------------------------------------- /src/drift/builder.clj: -------------------------------------------------------------------------------- 1 | (ns drift.builder 2 | (:import [java.io File] 3 | [java.text SimpleDateFormat] 4 | [java.util Date]) 5 | (:require [clojure.tools.logging :as logging] 6 | [clojure.tools.loading-utils :as loading-utils] 7 | [clojure.tools.string-utils :as util-string-utils] 8 | [drift.core :as core] 9 | [drift.config :as config])) 10 | 11 | (defn 12 | #^{ :doc "Finds or creates if missing, the migrate directory in the given db directory." } 13 | find-or-create-migrate-directory 14 | ([] (find-or-create-migrate-directory (core/migrate-directory))) 15 | ([migrate-directory] 16 | (when migrate-directory 17 | (if (.exists migrate-directory) 18 | (logging/info "Migrate directory already exists.") 19 | (do 20 | (logging/info "Creating migrate directory...") 21 | (.mkdirs migrate-directory))) 22 | migrate-directory))) 23 | 24 | (defn incremental-migration-number-generator [] 25 | (util-string-utils/prefill (str (core/find-next-migrate-number)) 3 "0")) 26 | 27 | (defn timestamp-migration-number-generator [] 28 | (.format (SimpleDateFormat. "yyyyMMddHHmmss") (new Date))) 29 | 30 | (defn migration-number-generator-fn [] 31 | (or (config/migration-number-generator) timestamp-migration-number-generator)) 32 | 33 | (defn migration-number [] 34 | ((migration-number-generator-fn))) 35 | 36 | (defn 37 | #^{ :doc "Creates a new migration file from the given migration name." } 38 | create-migration-file 39 | ([migration-name] (create-migration-file (find-or-create-migrate-directory) migration-name)) 40 | ([migrate-directory migration-name] 41 | (if (and migrate-directory migration-name) 42 | (let [migration-file-name (str (migration-number) "_" (loading-utils/dashes-to-underscores migration-name) ".clj") 43 | migration-file (new File migrate-directory migration-file-name)] 44 | (logging/info (str "Creating migration file " migration-file-name "...")) 45 | (.createNewFile migration-file) 46 | migration-file)))) 47 | -------------------------------------------------------------------------------- /test/drift/test_execute.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-execute 2 | (:use clojure.test 3 | drift.execute) 4 | (:require [config.migrate-config :as config] 5 | config.finished-config 6 | [test-helper :as test-helper])) 7 | 8 | (deftest test-version-number 9 | (is (= 0 (version-number 0))) 10 | (is (= 1 (version-number "1"))) 11 | (is (= Long/MAX_VALUE (version-number nil)))) 12 | 13 | (defn 14 | test-migrated? [version] 15 | (is (compare-and-set! config/init-run? true false)) 16 | (is (= version (config/memory-current-version)))) 17 | 18 | (deftest test-migrate 19 | (compare-and-set! config/init-run? false false) 20 | (migrate nil []) 21 | (test-migrated? 2) 22 | (migrate 0 []) 23 | (test-migrated? 0) 24 | (migrate "1" []) 25 | (test-migrated? 1) 26 | (migrate 0 []) 27 | (test-migrated? 0)) 28 | 29 | (deftest test-run 30 | (compare-and-set! config/init-run? false false) 31 | (run []) 32 | (test-migrated? 2) 33 | (run ["-version" "0"]) 34 | (test-migrated? 0) 35 | (run ["-version" "1" "-other" "args"]) 36 | (test-migrated? 1) 37 | (run ["-other" "args" "-version" "0"]) 38 | (test-migrated? 0)) 39 | 40 | (deftest test-run-custom-config 41 | (with-redefs [drift.execute/migrate 42 | (fn [version remaining-args] 43 | (is (= version "1234")) 44 | (is (= drift.config/*config-fn-symbol* 'foo.bar/baz)) 45 | (is (= remaining-args ["bloop" "blargh"])))] 46 | (run ["-version" "1234" "bloop" "-c" "foo.bar/baz" "blargh"]))) 47 | 48 | (deftest test-run-dynamic-config 49 | (with-redefs [drift.runner/update-to-version 50 | (fn [version] 51 | (is (= version 1234)) 52 | (is (= 42 (:more-config drift.config/*config-map*))))] 53 | 54 | (run ["-version" "1234" "bloop" "-c" "config.dynamic-config/config" "blargh"]))) 55 | 56 | (deftest test-finished-fn-called 57 | [] 58 | (with-redefs [drift.runner/update-to-version (fn [version])] 59 | (run ["-version" "1234" "bloop" "-c" "config.finished-config/migrate-config" "blargh"]) 60 | 61 | (is (= @config.finished-config/finished-run? true)))) 62 | -------------------------------------------------------------------------------- /src/drift/args.clj: -------------------------------------------------------------------------------- 1 | (ns drift.args 2 | (:require [clojure.string :as string] 3 | [drift.config :as config])) 4 | 5 | (defn split-args 6 | "split an arglist using a matcher fn : returns 7 | [args-before-match, args-including-and-after-match]" 8 | [args matcher] 9 | (split-with #(not-any? (partial = %) matcher) args)) 10 | 11 | (defn remove-opt 12 | "given a matched option and whatever is after it in the arg list, remove the option 13 | and return [{opt-key opt-value} remaining-args]" 14 | [[switch val & rest :as args] spec] 15 | (let [parser (or (:parser spec) identity)] 16 | (if (and switch val) 17 | [{(:key spec) (parser val)} rest] 18 | [{} args]))) 19 | 20 | (defn parse-args 21 | "do a partial parse of args... removing only options we know about and leaving everything else to 22 | be passed on to the user-supplied init function. tools.cli is no use for this" 23 | [args specs] 24 | (reduce (fn [[opts args] spec] 25 | (let [[before match-rest] (split-args args (:matcher spec)) 26 | [new-opts after] (remove-opt match-rest spec)] 27 | 28 | [(merge opts new-opts) (vec (concat before after))])) 29 | [{} args] 30 | specs)) 31 | 32 | (def config-arg-spec 33 | {:key :config 34 | :matcher ["-c" "-config" "--config"] 35 | :parser symbol 36 | :default config/default-config-fn-symbol 37 | :desc "Fully qualified name of function returning a Drift configuration map."}) 38 | 39 | (def migrate-arg-specs 40 | [{:key :version 41 | :matcher ["-v" "-version" "--version"] 42 | :desc "The version number to migrate to. Can be lower than the current version to roll back migrations."} 43 | config-arg-spec]) 44 | 45 | (defn parse-migrate-args 46 | [args] 47 | (parse-args args migrate-arg-specs)) 48 | 49 | (def create-migration-arg-specs 50 | [config-arg-spec]) 51 | 52 | (defn parse-create-migration-args 53 | [args] 54 | (parse-args args create-migration-arg-specs)) 55 | 56 | (defn print-usage 57 | #^{:doc "Prints out how to use a command with a given name and specification."} 58 | [cmd spec & [required-arg]] 59 | (println "Usage: lein" cmd "[options]" (or required-arg "")) 60 | (println "Options:") 61 | (doseq [arg spec] 62 | (println (str " " (string/join " " (:matcher arg)) "\t" (:desc arg))) 63 | (when (:default arg) (println "\t\t\tDefault value:" (:default arg))))) 64 | -------------------------------------------------------------------------------- /src/drift/config.clj: -------------------------------------------------------------------------------- 1 | (ns drift.config 2 | (:require [clojure.string :as string])) 3 | 4 | (declare find-migrate-dir-name outer-dir-in-path find-config missing-param) 5 | 6 | (def default-config-fn-symbol 'config.migrate-config/migrate-config) 7 | (def ^:dynamic *config-fn-symbol* default-config-fn-symbol) 8 | (def ^:dynamic *config-map* nil) 9 | 10 | (defn with-config-fn-symbol 11 | [config-fn-symbol f] 12 | (with-bindings* {#'*config-fn-symbol* (or config-fn-symbol default-config-fn-symbol)} f)) 13 | 14 | (defn with-config-map 15 | [config-map f] 16 | (with-bindings* {#'*config-map* config-map} f)) 17 | 18 | (def accessors 19 | {'current-version-fn :current-version 20 | 'default-ns-content :ns-content 21 | 'find-init-fn :init 22 | 'find-finished-fn :finished 23 | 'find-migrate-dir-name :directory 24 | 'find-src-dir :src 25 | 'migration-namespaces :migration-namespaces 26 | 'migration-number-generator :migration-number-generator 27 | 'namespace-prefix :namespace-prefix 28 | 'update-version-fn :update-version}) 29 | 30 | (def defaults 31 | {:directory (constantly "/src/migrate") 32 | :src #(-> % find-migrate-dir-name outer-dir-in-path)}) 33 | 34 | (def required-params 35 | #{:current-version :update-version}) 36 | 37 | (defn- get-param 38 | ([name] (get-param name (or *config-map* (find-config)))) 39 | ([name config] 40 | (or (get config name) 41 | (when-let [default-fn (defaults name)] (default-fn config)) 42 | (if (contains? required-params name) 43 | (missing-param name))))) 44 | 45 | (doseq [[fn-name param-name] (seq accessors)] 46 | (intern *ns* fn-name #(apply get-param param-name %&))) 47 | 48 | (defn find-config-namespace [] 49 | (require (symbol (namespace *config-fn-symbol*))) 50 | (find-ns (symbol (namespace *config-fn-symbol*)))) 51 | 52 | (defn find-config [] 53 | (when-let [migrate-config-namespace (find-config-namespace)] 54 | (if-let [migrate-config-fn (ns-resolve migrate-config-namespace (symbol (name *config-fn-symbol*)))] 55 | (migrate-config-fn) 56 | (throw (RuntimeException. (str "can't find config function: " *config-fn-symbol*)))))) 57 | 58 | (defn- missing-param [param] 59 | (throw (java.lang.NullPointerException. 60 | (str "Missing configuration parameter in migrate-config: " param)))) 61 | 62 | (def ^:private separators #{"/" "\\"}) 63 | 64 | (defn outer-dir-in-path [path] 65 | (let 66 | [separator-positions 67 | (filter (complement neg?) (map #(.indexOf path % 1) separators))] 68 | (when-let 69 | [first-separator-position 70 | (if-not (empty? separator-positions) (apply min separator-positions))] 71 | (.substring path 0 (inc first-separator-position))))) 72 | -------------------------------------------------------------------------------- /test/drift/test_config.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-config 2 | (:import [java.io File]) 3 | (:use clojure.test drift.config) 4 | (:require [test-helper :as test-helper] 5 | [config.migrate-config :as migrate-config])) 6 | 7 | (deftest test-find-config-namespace 8 | (is (find-config-namespace))) 9 | 10 | (deftest test-find-config 11 | (let [config-map (find-config)] 12 | (is config-map) 13 | (is (map? config-map)))) 14 | 15 | (deftest test-find-config-borks 16 | (binding [drift.config/*config-fn-symbol* 'config.migrate-config/bar] 17 | (is (thrown? RuntimeException (find-config))))) 18 | 19 | (deftest test-current-version-fn 20 | (is (= println (current-version-fn {:current-version println }))) 21 | (is (= ((migrate-config/migrate-config) :current-version) (current-version-fn))) 22 | (is (thrown-with-msg? java.lang.NullPointerException 23 | #"Missing .* :current-version" 24 | (current-version-fn {})))) 25 | 26 | (deftest test-update-version-fn 27 | (is (= println (update-version-fn {:update-version println }))) 28 | (is (= ((migrate-config/migrate-config) :update-version) (update-version-fn))) 29 | (is (thrown-with-msg? java.lang.NullPointerException 30 | #"Missing .* :update-version" 31 | (update-version-fn {})))) 32 | 33 | (deftest test-find-init-fn 34 | (is (= migrate-config/init (find-init-fn))) 35 | (is (= migrate-config/init (find-init-fn (find-config)))) 36 | (is (nil? (find-init-fn {})))) 37 | 38 | (deftest test-default-ns-content 39 | (is (= "\n (:use clojure.contrib.sql)" (default-ns-content))) 40 | (is (= "\n (:use clojure.contrib.sql)" (default-ns-content (find-config)))) 41 | (is (nil? (default-ns-content {})))) 42 | 43 | (deftest test-find-migrate-dir-name 44 | (let [migrate-dir-name (find-migrate-dir-name)] 45 | (is migrate-dir-name) 46 | (is (string? migrate-dir-name)) 47 | (is (= "/test/migrations" migrate-dir-name))) 48 | (is (= "/test/migrations" (find-migrate-dir-name (find-config)))) 49 | (is (= "/src/migrate" (find-migrate-dir-name {})))) 50 | 51 | (deftest test-find-src-dir 52 | (is (= "/test/" (find-src-dir))) 53 | (is (= "/test/" (find-src-dir (find-config)))) 54 | (is (= "/something/" (find-src-dir { :directory "/something/somewhere" }))) 55 | (is (= "/src/" (find-src-dir { :src "/src/" } )))) 56 | 57 | (deftest test-outer-dir-in-path 58 | (is (= "/test/" (outer-dir-in-path "/test/migrations"))) 59 | (is (= "test/" (outer-dir-in-path "test/migrations"))) 60 | (is (= "\\test\\" (outer-dir-in-path "\\test\\migrations"))) 61 | (is (= "test\\" (outer-dir-in-path "test\\migrations"))) 62 | (is (nil? (outer-dir-in-path "migrations")))) 63 | 64 | -------------------------------------------------------------------------------- /src/drift/generator.clj: -------------------------------------------------------------------------------- 1 | (ns drift.generator 2 | (:require [clojure.string :as string] 3 | [clojure.tools.logging :as logging] 4 | [drift.args :as args] 5 | [drift.builder :as builder] 6 | [drift.config :as config] 7 | [drift.core :as core])) 8 | 9 | (defn 10 | #^{ :doc "Prints out how to use the generate migration command." } 11 | migration-usage [] 12 | (println "You must supply a migration name (Like migration-name).") 13 | (println "Usage: lein.bat migration ")) 14 | 15 | (defn 16 | create-file-content [migration-namespace ns-content up-content down-content] 17 | (let [migration-number (core/migration-number-from-namespace migration-namespace)] 18 | (str "(ns " migration-namespace (or ns-content (config/default-ns-content)) ") 19 | 20 | (defn up 21 | \"Migrates the database up to version " migration-number ".\" 22 | [] 23 | " (or up-content (str "(println \"" migration-namespace " up...\")"))") 24 | 25 | (defn down 26 | \"Migrates the database down from version " migration-number ".\" 27 | [] 28 | " (or down-content (str "(println \"" migration-namespace " down...\")"))")"))) 29 | 30 | (defn 31 | #^{ :doc "Generates the migration content and saves it into the given migration file." } 32 | generate-file-content [migration-file migration-name ns-content up-content down-content] 33 | (let [migration-namespace (core/migration-namespace migration-file) 34 | content (create-file-content migration-namespace ns-content up-content down-content)] 35 | (spit migration-file content))) 36 | 37 | (defn 38 | #^{ :doc "Creates the migration file from the given migration-name." } 39 | generate-migration-file 40 | ([migration-name] (generate-migration-file migration-name (config/default-ns-content) nil nil)) 41 | ([migration-name ns-content up-content down-content] 42 | (core/run-init []) 43 | (if migration-name 44 | (let [migrate-directory (builder/find-or-create-migrate-directory) 45 | migration-file (builder/create-migration-file migrate-directory migration-name)] 46 | (generate-file-content migration-file migration-name ns-content up-content down-content)) 47 | (migration-usage)) 48 | (core/run-finished))) 49 | 50 | (defn generate-migration-file-cmdline 51 | "parse command-line args from lein, set up any custom config, 52 | and invoke generate-migration-file" 53 | [args] 54 | (let [[opts [migration-name & remaining]] (args/parse-create-migration-args args)] 55 | (if (empty? remaining) 56 | (config/with-config-fn-symbol 57 | (:config opts) 58 | (fn [] 59 | (generate-migration-file migration-name))) 60 | (do (logging/error "Invalid arguments:" (string/join " " remaining)) 61 | (args/print-usage "create-migration" args/create-migration-arg-specs "migration-name"))))) 62 | -------------------------------------------------------------------------------- /sample_migrate_config.clj: -------------------------------------------------------------------------------- 1 | (ns sample.migrate-config 2 | (:use drift.core) 3 | (:require [drift.builder :as builder] 4 | [drift-db.migrate :as drift-db-migrate])) 5 | 6 | ;; This is a sample migrate-config file. All possible options are listed with comments. 7 | 8 | (defn init-flavor [args] 9 | (println "Initializing drift.")) 10 | 11 | (defn finished [] 12 | (println "Action has been run.")) 13 | 14 | (defn migrate-namespaces [migrate-dir-name migrate-namespace-prefix] 15 | (migration-namespaces)) 16 | 17 | (defn migrate-config [] 18 | { 19 | 20 | ; This is a function takes no arguments and returns the database's current version. In the example below, the 21 | ; drift-db current-version function is used. Required. 22 | :current-version drift-db-migrate/current-version 23 | 24 | ; This is a function which takes the version from Drift and set the database to that version. In the example below, 25 | ; the drift-db update-version function is used. Required. 26 | :update-version drift-db-migrate/update-version 27 | 28 | ; This is the path where your migrations can be found. It is :src path, in this 29 | ; case '/src', is not included in the namespace for the migrations. Defaults to "/src/migrations" Optional. 30 | :directory "/src/database/migrations" 31 | 32 | ; This is the source directory path. This path is subtracted from the :directory to determine how to generate the 33 | ; namespace-prefix. Defaults to "/src" 34 | :src "/src" 35 | 36 | ; This is the namespace prefix for each migration. The namespace prefix is the part of the migration namespace not 37 | ; including the file name. For example, for the migration file 001_test.clj, the full namespace calculated from the 38 | ; namespace-prefix would be database.migrations.001-test. If the namespace prefix is not set, then it is calculated 39 | ; from the :directory. Optional. 40 | :namespace-prefix "database.migrations" 41 | 42 | ; This is the initialization function which will be called before any migrations are run. If you have any 43 | ; initialization code which needs to be called before your migrations are called, add it to this function. The init 44 | ; function must accept a single argument which will be a list of arguments passed to drift excluding the version 45 | ; argument. Init may be called multiple times before the migrations are run. Optional. 46 | :init init-flavor 47 | 48 | ; This is a function which will be called after the action has been run. Optional. 49 | :finished finished 50 | 51 | ; This is a function which returns the next number to use when creating a new migration file. There are two 52 | ; migration number generators included with Drift: incremental-migration-number-generator and 53 | ; timestamp-migration-number-generator. The incremental-migration-number-generator simply starts with '001' and 54 | ; increments the migration number for each new migration file. The timestamp-migration-number-generator uses the 55 | ; current date and time to create the migration number. If the migration number generator is not given, the 56 | ; timestamp-migration-number-generator is used. 57 | :migration-number-generator builder/incremental-migration-number-generator 58 | 59 | ; This is extra namespace content added when Drift generates a new migration file. The text is simply added in the 60 | ; call to ns, after the namespace. Optional. 61 | :ns-content "\n (:use drift-db.core)" 62 | 63 | ; This is a function which takes the migration directory and the namespace prefix and returns all of the migration 64 | ; namespaces. If this option is not set, Drift uses the :directory and :namespace-prefix to find all of the 65 | ; migration namespaces. You should only have to use this option if your directory structure is very unusual. 66 | ; Optional. 67 | :migration-namespaces migrate-namespaces 68 | }) -------------------------------------------------------------------------------- /test/drift/test_runner.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-runner 2 | (:use clojure.test 3 | drift.runner) 4 | (:require [drift.core :as core] 5 | [drift.listener-protocol :as listener-protocol] 6 | [drift.version :as version] 7 | [test-helper :as test-helper]) 8 | (:import [drift.listener_protocol ListenerProtocol])) 9 | 10 | (def first-migration "create-tests") 11 | (def second-migration "tests-update") 12 | 13 | (defn reset-db [test-fn] 14 | (test-fn) 15 | (migrate-down-all)) 16 | 17 | (use-fixtures :once reset-db) 18 | 19 | (deftest test-run-migrate-up 20 | (is (= 0 (version/current-db-version))) 21 | (run-migrate-up (core/find-migration-namespace first-migration)) 22 | (is (= 1 (version/current-db-version))) 23 | (version/update-db-version 0) 24 | (is (= 0 (version/current-db-version))) 25 | (run-migrate-up nil) 26 | (is (= 0 (version/current-db-version)))) 27 | 28 | (deftest test-run-migrate-down 29 | (is (= 0 (version/current-db-version))) 30 | (run-migrate-up (core/find-migration-namespace first-migration)) 31 | (is (= 1 (version/current-db-version))) 32 | (run-migrate-down (core/find-migration-namespace first-migration)) 33 | (is (= 0 (version/current-db-version))) 34 | (run-migrate-down nil) 35 | (is (= 0 (version/current-db-version))) 36 | (run-migrate-down (core/find-migration-namespace first-migration)) 37 | (is (= 0 (version/current-db-version)))) 38 | 39 | (deftest test-migrate-up-all 40 | (is (= 0 (version/current-db-version))) 41 | (migrate-up-all (core/migration-namespaces)) 42 | (is (= 2 (version/current-db-version))) 43 | (version/update-db-version 0) 44 | (is (= 0 (version/current-db-version))) 45 | (migrate-up-all) 46 | (is (= 2 (version/current-db-version))) 47 | (version/update-db-version 0) 48 | (is (= 0 (version/current-db-version))) 49 | (migrate-up-all nil) 50 | (is (= 0 (version/current-db-version)))) 51 | 52 | (deftest test-migrate-down-all 53 | (is (= 0 (version/current-db-version))) 54 | (migrate-up-all (core/migration-namespaces)) 55 | (is (= 2 (version/current-db-version))) 56 | (migrate-down-all (reverse (core/migration-namespaces))) 57 | (is (= 0 (version/current-db-version))) 58 | (migrate-up-all (core/migration-namespaces)) 59 | (is (= 2 (version/current-db-version))) 60 | (migrate-down-all) 61 | (is (= 0 (version/current-db-version))) 62 | (migrate-down-all nil) 63 | (is (= 0 (version/current-db-version)))) 64 | 65 | (deftest test-migrate-up 66 | (is (= 0 (version/current-db-version))) 67 | (migrate-up 0 1) 68 | (is (= 1 (version/current-db-version))) 69 | (version/update-db-version 0) 70 | (is (= 0 (version/current-db-version))) 71 | (migrate-up nil 1) 72 | (is (= 0 (version/current-db-version))) 73 | (migrate-up 0 nil) 74 | (is (= 0 (version/current-db-version))) 75 | (migrate-up nil nil) 76 | (is (= 0 (version/current-db-version)))) 77 | 78 | (deftest test-migrate-down 79 | (is (= 0 (version/current-db-version))) 80 | (migrate-up 0 1) 81 | (is (= 1 (version/current-db-version))) 82 | (migrate-down 1 0) 83 | (is (= 0 (version/current-db-version))) 84 | (migrate-down nil 0) 85 | (is (= 0 (version/current-db-version))) 86 | (migrate-down 1 nil) 87 | (is (= 0 (version/current-db-version))) 88 | (migrate-down nil nil) 89 | (is (= 0 (version/current-db-version)))) 90 | 91 | (deftest test-update-to-version 92 | (is (= 0 (version/current-db-version))) 93 | (update-to-version 1) 94 | (is (= 1 (version/current-db-version))) 95 | (update-to-version 2) 96 | (is (= 2 (version/current-db-version))) 97 | (update-to-version 3) 98 | (is (= 2 (version/current-db-version))) 99 | (update-to-version 1) 100 | (is (= 1 (version/current-db-version))) 101 | (update-to-version 0) 102 | (is (= 0 (version/current-db-version))) 103 | (update-to-version nil) 104 | (is (= 0 (version/current-db-version)))) 105 | 106 | (def test-listener (reify ListenerProtocol 107 | (start [this namespaces up?] ) 108 | (running [this namespace up?] ) 109 | (end [this] ))) 110 | 111 | (deftest test-listeners 112 | (is (empty? (runner-listeners))) 113 | (add-listener test-listener) 114 | (is (= (runner-listeners) [test-listener])) 115 | (remove-listener test-listener) 116 | (is (empty? (runner-listeners)))) -------------------------------------------------------------------------------- /src/drift/runner.clj: -------------------------------------------------------------------------------- 1 | (ns drift.runner 2 | (:import [java.io File]) 3 | (:require [clojure.tools.logging :as logging] 4 | [drift.core :as core] 5 | [drift.listener-protocol :as listener-protocol] 6 | [drift.version :as version])) 7 | 8 | (def listeners (atom [])) 9 | 10 | (defn add-listener [listener] 11 | (swap! listeners conj listener)) 12 | 13 | (defn remove-listener [listener] 14 | (swap! listeners (fn [listeners listener] (remove #(= listener %) listeners)) listener)) 15 | 16 | (defn runner-listeners [] 17 | @listeners) 18 | 19 | (defn start-migration [namespaces ^Boolean up?] 20 | (doseq [listener (runner-listeners)] 21 | (listener-protocol/start listener (map core/namespace-name-str namespaces) up?))) 22 | 23 | (defn running-migration [^String namespace ^Boolean up?] 24 | (doseq [listener (runner-listeners)] 25 | (listener-protocol/running listener namespace up?))) 26 | 27 | (defn end-migration [] 28 | (doseq [listener (runner-listeners)] 29 | (listener-protocol/end listener))) 30 | 31 | (defn 32 | #^{ :doc "Runs the up function in the given migration file." } 33 | run-migrate-up [migration-namespace] 34 | (if migration-namespace 35 | (let [namespace-name (core/namespace-name-str migration-namespace) 36 | namespace-symbol (symbol namespace-name)] 37 | (logging/info (str "Running " namespace-name " up...")) 38 | (running-migration namespace-name (Boolean. true)) 39 | (require namespace-symbol) 40 | (when-let [up-fn (ns-resolve namespace-symbol 'up)] 41 | (up-fn) 42 | (version/update-db-version (core/migration-number-from-namespace migration-namespace)))) 43 | (logging/error (str "Invalid migration-namespace: " migration-namespace ". No changes were made to the database.")))) 44 | 45 | (defn 46 | #^{ :doc "Runs the down function in the given migration file." } 47 | run-migrate-down [migration-namespace] 48 | (if migration-namespace 49 | (let [namespace-name (core/namespace-name-str migration-namespace) 50 | namespace-symbol (symbol namespace-name)] 51 | (logging/info (str "Running " namespace-name " down...")) 52 | (running-migration namespace-name (Boolean. false)) 53 | (require namespace-symbol) 54 | (when-let [down-fn (ns-resolve namespace-symbol 'down)] 55 | (down-fn) 56 | (version/update-db-version 57 | (core/migration-number-before (core/migration-number-from-namespace migration-namespace))))) 58 | (logging/error (str "Invalid migration-file: " migration-namespace ". No changes were made to the database.")))) 59 | 60 | (defn 61 | #^{ :doc "Runs the up function on all of the given migration files." } 62 | migrate-up-all 63 | ([] (migrate-up-all (core/migration-namespaces))) 64 | ([migration-namespaces] 65 | (start-migration (seq migration-namespaces) (Boolean. true)) 66 | (let [output (when (and migration-namespaces (not-empty migration-namespaces)) 67 | (reduce max 0 (map run-migrate-up migration-namespaces)))] 68 | (end-migration) 69 | output))) 70 | 71 | (defn 72 | #^{ :doc "Runs the down function on all of the given migration files." } 73 | migrate-down-all 74 | ([] (migrate-down-all (reverse (core/migration-namespaces)))) 75 | ([migration-namespaces] 76 | (start-migration (seq migration-namespaces) (Boolean. false)) 77 | (let [output (when (and migration-namespaces (not-empty migration-namespaces)) 78 | (reduce min Long/MAX_VALUE (map run-migrate-down migration-namespaces)))] 79 | (end-migration) 80 | output))) 81 | 82 | (defn 83 | #^{ :doc "Migrates the database up from from-version to to-version." } 84 | migrate-up [from-version to-version] 85 | (if (and from-version to-version) 86 | (if-let [new-version (migrate-up-all (core/migration-namespaces-in-range from-version to-version))] 87 | (logging/info (str "Migrated to version: " new-version)) 88 | (logging/info "No changes were made to the database.")) 89 | (logging/error (str "Invalid version number: " from-version " or " to-version ". No changes were made to the database.")))) 90 | 91 | (defn 92 | #^{ :doc "Migrates the database down from from-version to to-version." } 93 | migrate-down [from-version to-version] 94 | (if (and from-version to-version) 95 | (if-let [new-version (migrate-down-all (reverse (core/migration-namespaces-in-range to-version from-version)))] 96 | (logging/info (str "Migrated to version: " new-version)) 97 | (logging/info "No changes were made to the database.")) 98 | (logging/error (str "Invalid version number: " from-version " or " to-version ". No changes were made to the database.")))) 99 | 100 | (defn migration-count 101 | "Returns the total number of migrations to run to update the database to the given version number." 102 | [version-number] 103 | (when version-number 104 | (let [db-version (version/current-db-version) 105 | version-number-min (min (max version-number 0) (core/max-migration-number))] 106 | (if (< db-version version-number-min) 107 | (count (core/migration-namespaces-in-range (inc db-version) version-number-min)) 108 | (count (core/migration-namespaces-in-range db-version (inc version-number-min))))))) 109 | 110 | (defn 111 | #^{ :doc "Updates the database to the given version number. If the version number is less than the current database 112 | version number, then this function causes a roll back." } 113 | update-to-version [version-number] 114 | (if version-number 115 | (let [db-version (version/current-db-version)] 116 | (logging/info (str "Current database version: " db-version)) 117 | (let [version-number-min (min (max version-number 0) (core/max-migration-number))] 118 | (logging/info (str "Updating to version: " version-number-min)) 119 | (if (< db-version version-number-min) 120 | (migrate-up (inc db-version) version-number-min) 121 | (migrate-down db-version (inc version-number-min))))) 122 | (logging/error (str "Invalid version-number: " version-number)))) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drift 2 | 3 | Drift is a migration library written in Clojure. Drift works much like 4 | Rails migrations where a directory in your project contains all of the 5 | migration files. Drift will detect which migration files need to be 6 | run and run them as appropriate. 7 | 8 | Migration files must contain an `up` and `down` function in which you can 9 | perform the migration using your prefered database library. 10 | 11 | ## Usage 12 | 13 | To use Drift you'll need to add drift to your Leiningen 14 | project. Simply add the following to your project.clj file: 15 | 16 | ```clojure 17 | [drift "x.x.x"] 18 | ``` 19 | 20 | Where "x.x.x" is the latest version of drift which you can find on 21 | clojars: http://clojars.org/drift 22 | 23 | If you want to use the lein commands, you'll need to add the above 24 | vector to your `:dev-dependencies` vector. If you want to access drift 25 | code directly in your project, you'll need to add it to your 26 | `:dependencies` vector. You can add drift to both vectors without any 27 | problems. 28 | 29 | If you're using drift with lein2, you'll need to add it to the :plugins list in the project.clj file. 30 | 31 | To set your Drift migration directory, simply add a clojure file 32 | called "migrate_config.clj" to the directory "src/config". 33 | 34 | Your migrate_config.clj file should look something like: 35 | 36 | ```clojure 37 | (ns config.migrate-config) 38 | 39 | (defn migrate-config [] 40 | { :directory "/src/migrations" 41 | :current-version current-db-version-fn 42 | :update-version update-db-version-fn }) 43 | ``` 44 | 45 | `directory` must be a directory included in the classpath. Most likely you want your migrations somewhere under src. 46 | 47 | `current-db-version-fn` and `update-db-version-fn` are both functions 48 | which you must implement to let Drift read and set the current db 49 | version. 50 | 51 | `current-db-version-fn` does not take any parameters, and returns the 52 | current version of the database. If no version has been set, then 53 | `current-db-version-fn` must return 0. 54 | 55 | `update-db-version-fn` takes only a new-version parameter. After 56 | `update-db-version-fn` is run with a given new-version, 57 | `current-db-version-fn` must return that version. 58 | 59 | For example, here is an in memory db version (note, you do not want to 60 | do this): 61 | 62 | ```clojure 63 | (ns config.migrate-config) 64 | 65 | (def version (atom nil)) 66 | 67 | (defn memory-current-db-version [] 68 | (or @version 0)) 69 | 70 | (defn memory-update-db-version [new-version] 71 | (reset! version new-version)) 72 | 73 | (defn migrate-config [] 74 | { :directory "/test/migrations" 75 | :current-version memory-current-db-version 76 | :update-version memory-update-db-version }) 77 | ``` 78 | 79 | Here is an example database version using a table named 80 | "schema_migrations" that has one version column with a single value 81 | holding the current database version: 82 | 83 | ```clojure 84 | (ns config.migrate-config 85 | (:require [clojure.contrib.sql :as sql]) 86 | (:use warehouse.core)) 87 | 88 | (defn db-version [] 89 | (sql/with-connection DB 90 | (sql/with-query-results res 91 | ["select version from schema_migrations"] 92 | (or (:version (last res)) 0)))) 93 | 94 | (defn update-db-version [version] 95 | (sql/with-connection DB 96 | (sql/insert-values :schema_migrations [:version] [version]))) 97 | ``` 98 | 99 | Your migration files must contain an `up` and `down` function in which you 100 | perform the migration and rollback using your prefered database library. 101 | 102 | For example: 103 | 104 | ```clojure 105 | (ns migrations.001-create-authors-table 106 | (:require [clojure.java.jdbc :as j]) 107 | 108 | (def mysql-db { :dbtype "mysql" :dbname "my_db" :user "root" }) 109 | 110 | (defn up [] 111 | (j/query mysql-db 112 | ["CREATE TABLE authors(id INT PRIMARY KEY)"])) 113 | 114 | (defn down [] 115 | (j/query mysql-db 116 | ["DROP TABLE authors"])) 117 | ``` 118 | 119 | ## Initialization 120 | 121 | If you need to run some initialization code, add `:init` to your 122 | migrate-config. For example: 123 | 124 | ```clojure 125 | (defn migrate-config [] 126 | { :directory "/test/migrations" 127 | :init init 128 | :current-version memory-current-db-version 129 | :update-version memory-update-db-version }) 130 | ``` 131 | 132 | The above `migrate-config` will call the function "init" before any 133 | migrations are run. If you pass parameters to `lein migrate`, they 134 | will be passed along to the init function. 135 | 136 | If you want to include a special use or require section in the 137 | namespace function of all migration files you can use the `:ns-content` 138 | key. 139 | 140 | For example: 141 | 142 | ```clojure 143 | (defn migrate-config [] 144 | { :directory "/test/migrations" 145 | :ns-content "\n (:use database.util)" 146 | :current-version memory-current-db-version 147 | :update-version memory-update-db-version }) 148 | ``` 149 | 150 | The above `migrate-config` will add "`\n (:use database.util)`" to the 151 | namespace function call at the top of every migration file. 152 | 153 | ## Using Leiningen 154 | 155 | To migrate to the most recent version: 156 | 157 | ```bash 158 | $ lein migrate 159 | ``` 160 | 161 | To migrate to a specific version, pass the version as a parameter to 162 | migrate. For example, to migrate to version 1: 163 | 164 | ```bash 165 | $ lein migrate -version 1 166 | ``` 167 | 168 | To undo all migrations and start with a clean database, simply pass 0 169 | as the migration number. For example: 170 | 171 | ```bash 172 | $ lein migrate -version 0 173 | ``` 174 | 175 | To create a new migration file which you can then edit: 176 | 177 | ```bash 178 | $ lein create-migration 179 | ``` 180 | 181 | ## Calling Drift From Java 182 | 183 | You can call Drift from any java application. Simply create an instance of the Drift object: 184 | 185 | ```java 186 | Drift myDrift = new Drift(); 187 | ``` 188 | 189 | After you create an instance of the Drift object, you must initialize it, or your Drift migrations may run in a bad state. 190 | 191 | ```java 192 | myDrift.init(Collections.EMPTY_LIST); 193 | ``` 194 | 195 | The one argument to init is a list which will be passed on to the init function set in your migrate_config. In the above example, the init function does not require any parameters, so an empty list is passed in. 196 | 197 | You can get the current database version with: 198 | 199 | ```java 200 | myDrift.currentVersion(); 201 | ``` 202 | 203 | You can get the maximum migration number with: 204 | 205 | ```java 206 | myDrift.maxMigrationNumber(); 207 | ``` 208 | 209 | Finally, to run the migrations, you can use the function `migrate`: 210 | 211 | ```java 212 | myDrift.migrate(myDrift.maxMigrationNumber(), Collections.EMPTY_LIST); 213 | ``` 214 | 215 | The `migrate` function takes the migration number to migrate to, and a parameter list which is passed onto the init function. In the above example, we tell Drift to migrate to the maximum version and pass no arguments since our init function does not need any. 216 | 217 | ## License 218 | 219 | Copyright (C) 2009 Matthew Courtney and released under the Apache 2.0 220 | license. 221 | -------------------------------------------------------------------------------- /src/drift/core.clj: -------------------------------------------------------------------------------- 1 | (ns drift.core 2 | (:import [java.io File] 3 | [java.util Comparator TreeSet]) 4 | (:require [clojure.string :as string] 5 | [clojure.tools.logging :as logging] 6 | [clojure.tools.loading-utils :as loading-utils] 7 | [drift.config :as config])) 8 | 9 | (defn 10 | #^{ :doc "Runs the init function with the given args." } 11 | run-init [args] 12 | (when-let [init-fn (config/find-init-fn)] 13 | (init-fn args))) 14 | 15 | (defn run-finished 16 | "runs the finished function" 17 | [] 18 | (when-let [finished-fn (config/find-finished-fn)] 19 | (finished-fn))) 20 | 21 | (defn with-init-config 22 | "run the init fn, merge results into config, then call the next function with that config 23 | bound to drift.config/*config-map*" 24 | [args f] 25 | (let [init-fn (config/find-init-fn) 26 | init-config (if init-fn (init-fn args))] 27 | (config/with-config-map 28 | (merge (config/find-config) (if (map? init-config) init-config)) 29 | (fn [] 30 | (let [result (f)] 31 | (run-finished) 32 | result))))) 33 | 34 | (defn 35 | #^{:doc "Returns the directory where Conjure is running from."} 36 | user-directory [] 37 | (new File (.getProperty (System/getProperties) "user.dir"))) 38 | 39 | (defn 40 | migrate-directory [] 41 | (File. (user-directory) (config/find-migrate-dir-name))) 42 | 43 | (defn 44 | #^{:doc "Returns the file object if the given file is in the given directory, nil otherwise."} 45 | find-directory [directory file-name] 46 | (when (and file-name directory (string? file-name) (instance? File directory)) 47 | (let [file (File. (.getPath directory) file-name)] 48 | (when (and file (.exists file)) 49 | file)))) 50 | 51 | (defn 52 | #^{ :doc "Finds the migrate directory." } 53 | find-migrate-directory [] 54 | (let [user-directory (user-directory) 55 | migrate-dir-name (config/find-migrate-dir-name)] 56 | (find-directory user-directory migrate-dir-name))) 57 | 58 | (defn 59 | migrate-namespace-dir 60 | ([] (migrate-namespace-dir (config/find-migrate-dir-name))) 61 | ([migrate-dir-name] 62 | (when migrate-dir-name 63 | (.substring migrate-dir-name (count (config/find-src-dir)))))) 64 | 65 | (defn 66 | #^{ :doc "Returns the namespace prefix for the migrate directory name." } 67 | migrate-namespace-prefix-from-directory 68 | ([] (migrate-namespace-prefix-from-directory (config/find-migrate-dir-name))) 69 | ([migrate-dir-name] 70 | (loading-utils/slashes-to-dots (loading-utils/underscores-to-dashes (migrate-namespace-dir migrate-dir-name))))) 71 | 72 | (defn 73 | migrate-namespace-prefix [] 74 | (or (config/namespace-prefix) (migrate-namespace-prefix-from-directory))) 75 | 76 | (defn 77 | #^{ :doc "Returns a string for the namespace of the given file in the given directory." } 78 | namespace-string-for-file [file-name] 79 | (when file-name 80 | (str (migrate-namespace-prefix) "." (loading-utils/clj-file-to-symbol-string file-name)))) 81 | 82 | (defn 83 | namespace-name-str [migration-namespace] 84 | (when migration-namespace 85 | (if (string? migration-namespace) 86 | migration-namespace 87 | (name (ns-name migration-namespace))))) 88 | 89 | (defn 90 | migration-namespace? [migration-namespace] 91 | (.startsWith (namespace-name-str migration-namespace) (str (migrate-namespace-prefix) "."))) 92 | 93 | (defn 94 | migration-number-from-namespace [migration-namespace] 95 | (when migration-namespace 96 | (when-let [migration-number-str (re-find #"^[0-9]+" (last (string/split (namespace-name-str migration-namespace) #"\.")))] 97 | (Long/parseLong migration-number-str)))) 98 | 99 | (defn migration-compartor [ascending?] 100 | (reify Comparator 101 | (compare [this namespace1 namespace2] 102 | (try 103 | (if ascending? 104 | (.compareTo (migration-number-from-namespace namespace1) (migration-number-from-namespace namespace2)) 105 | (.compareTo (migration-number-from-namespace namespace2) (migration-number-from-namespace namespace1))) 106 | (catch Throwable t 107 | (.printStackTrace t)))) 108 | (equals [this object] (= this object)))) 109 | 110 | (defn user-migration-namespaces [] 111 | (when-let [migration-namespaces (config/migration-namespaces)] 112 | (migration-namespaces (config/find-migrate-dir-name) (migrate-namespace-prefix)))) 113 | 114 | (defn default-migration-namespaces [] 115 | (->> (find-migrate-directory) 116 | .listFiles 117 | (map #(.getName ^File %)) 118 | (filter #(re-matches #".*\.clj$" %)) 119 | (map namespace-string-for-file))) 120 | 121 | (defn sort-migration-namespaces 122 | ([migration-namespaces] (sort-migration-namespaces migration-namespaces true)) 123 | ([migration-namespaces ascending?] 124 | (seq 125 | (doto (TreeSet. (migration-compartor ascending?)) 126 | (.addAll migration-namespaces))))) 127 | 128 | (defn unsorted-migration-namespaces [] 129 | (set (or (user-migration-namespaces) (default-migration-namespaces)))) 130 | 131 | (defn migration-namespaces 132 | ([] (migration-namespaces true)) 133 | ([ascending?] 134 | (sort-migration-namespaces (unsorted-migration-namespaces) ascending?))) 135 | 136 | (defn 137 | #^{ :doc "Returns all of the migration file names with numbers between low-number and high-number inclusive." } 138 | migration-namespaces-in-range [low-number high-number] 139 | (sort-migration-namespaces 140 | (filter 141 | (fn [migration-namespace] 142 | (let [migration-number (migration-number-from-namespace migration-namespace)] 143 | (and (>= migration-number low-number) (<= migration-number high-number)))) 144 | (unsorted-migration-namespaces)) 145 | (< low-number high-number))) 146 | 147 | (defn 148 | #^{ :doc "Returns all of the numbers prepended to the migration files." } 149 | migration-numbers 150 | ([] (migration-numbers (migration-namespaces))) 151 | ([migration-namespaces] 152 | (filter identity (map migration-number-from-namespace migration-namespaces)))) 153 | 154 | (defn max-migration-number 155 | "Returns the maximum number of all migration files." 156 | ([migration-namespaces] (reduce max 0 (migration-numbers migration-namespaces))) 157 | ([] (reduce max 0 (migration-numbers)))) 158 | 159 | (defn 160 | #^{ :doc "Returns the next number to use for a migration file." } 161 | find-next-migrate-number [] 162 | (inc (max-migration-number))) 163 | 164 | (defn 165 | #^{ :doc "Finds the number of the migration file before the given number" } 166 | migration-number-before 167 | ([migration-number] (migration-number-before migration-number (migration-namespaces))) 168 | ([migration-number migration-namespaces] 169 | (when migration-number 170 | (apply max 0 (filter #(< %1 migration-number) (migration-numbers migration-namespaces)))))) 171 | 172 | (defn 173 | find-migration-namespace [migration-name] 174 | (some 175 | #(when (re-find (re-pattern (str (migrate-namespace-prefix) "\\.[0-9]+-" migration-name)) %1) %1) 176 | (map namespace-name-str (migration-namespaces)))) 177 | 178 | (defn 179 | #^{ :doc "The migration file with the given migration name." } 180 | find-migration-file 181 | ([migration-name] (find-migration-file (find-migrate-directory) migration-name)) 182 | ([migrate-directory migration-name] 183 | (when-let [namespace-str (find-migration-namespace migration-name)] 184 | (File. migrate-directory (.getName (File. (loading-utils/symbol-string-to-clj-file namespace-str))))))) 185 | 186 | (defn 187 | #^{ :doc "Returns the migration namespace for the given migration file." } 188 | migration-namespace [migration-file] 189 | (when migration-file 190 | (namespace-string-for-file (.getName migration-file)))) 191 | -------------------------------------------------------------------------------- /test/drift/test_core.clj: -------------------------------------------------------------------------------- 1 | (ns drift.test-core 2 | (:import [java.io File]) 3 | (:use clojure.test 4 | drift.core) 5 | (:require [test-helper :as test-helper])) 6 | 7 | (def migration-name "create-tests") 8 | 9 | (deftest test-migrate-directory 10 | (let [migrate-dir (migrate-directory)] 11 | (is migrate-dir) 12 | (is (instance? File migrate-dir)))) 13 | 14 | (deftest test-find-migrate-directory 15 | (let [migrate-dir (find-migrate-directory)] 16 | (is migrate-dir) 17 | (is (instance? File migrate-dir)))) 18 | 19 | (deftest test-migrate-namespace-prefix-from-directory 20 | (is (= "migrations" (migrate-namespace-prefix-from-directory))) 21 | (is (= "migrations" (migrate-namespace-prefix-from-directory "/test/migrations"))) 22 | (is (= "db.migrate" (migrate-namespace-prefix-from-directory "/test/db/migrate"))) 23 | (is (nil? (migrate-namespace-prefix-from-directory nil)))) 24 | 25 | (deftest test-sort-migration-namespaces 26 | (is (= (sort-migration-namespaces ["migrations.001-create-tests" "migrations.002-test-update"]) 27 | ["migrations.001-create-tests" "migrations.002-test-update"])) 28 | (is (= (sort-migration-namespaces ["migrations.002-test-update" "migrations.001-create-tests"]) 29 | ["migrations.001-create-tests" "migrations.002-test-update"])) 30 | (is (= 31 | (sort-migration-namespaces 32 | ["imsma.database.migrations.20111113100406-init-for-clojure", 33 | "imsma.database.migrations.20120306100918-add-poly-prop-fields", 34 | "imsma.database.migrations.20120327161904-fix-gazetteer-fields", 35 | "imsma.database.migrations.20121203164415-update-uncategorised", 36 | "imsma.database.migrations.20121205100302-add-field-report-sub-object-fields", 37 | "imsma.database.migrations.20130409102159-create-reconciliation-update-type", 38 | "imsma.database.migrations.20110800000010-create-link-to-index", 39 | "imsma.database.migrations.20130102104441-add-linked-ids-fields", 40 | "imsma.database.migrations.20130510133842-add-assistance-tables", 41 | "imsma.database.migrations.20110600000070-change-custom-view-fonts", 42 | "imsma.database.migrations.20110800000020-expand-preference-key-column", 43 | "imsma.database.migrations.20120223153548-poly-property-cleanup", 44 | "imsma.database.migrations.20120312143040-add-poly-props-enums", 45 | "imsma.database.migrations.20120326165854-fix-geospatialinfo-fields", 46 | "imsma.database.migrations.20120327163802-set-version-5-8-3", 47 | "imsma.database.migrations.20130226142811-fix-missing-and-duplicate-categories", 48 | "imsma.database.migrations.20130423161211-update-organisation-category", 49 | "imsma.database.migrations.20130527111619-add-deleted-current-view-guid-to-assistance-version", 50 | "imsma.database.migrations.20110600000030-update-cdfvalue-types", 51 | "imsma.database.migrations.20110600000040-update-field-table-fieldtype-column-width", 52 | "imsma.database.migrations.20110600000080-update-link-table", 53 | "imsma.database.migrations.20120326134444-remove-poly-property", 54 | "imsma.database.migrations.20120420142920-update-geopoint-precision", 55 | "imsma.database.migrations.20130121093438-create-classification-base-data", 56 | "imsma.database.migrations.20110600000050-adds-values-to-category-table", 57 | "imsma.database.migrations.20120105111122-geopoint-precision", 58 | "imsma.database.migrations.20120410180758-fix-null-imsmaenum-dataenterers", 59 | "imsma.database.migrations.20120516171019-update-mimetype-length", 60 | "imsma.database.migrations.20121010133359-set-version-6-0-0", 61 | "imsma.database.migrations.20130117154649-add-object-ids-fields", 62 | "imsma.database.migrations.20130118095826-create-classification-level-tables", 63 | "imsma.database.migrations.20130419151756-add-missing-search-fields", 64 | "imsma.database.migrations.20130514121330-add-deleted-current-view-guid", 65 | "imsma.database.migrations.20130524144040-add-contains-assistance-version-to-field-report-desc", 66 | "imsma.database.migrations.20110600000060-add-values-to-field-table", 67 | "imsma.database.migrations.20111100000010-add-field-report-submit-permission", 68 | "imsma.database.migrations.20130419142709-update-travel-time-fields", 69 | "imsma.database.migrations.20130424130139-add-field-column-to-translation", 70 | "imsma.database.migrations.20130424142608-user-entered-coordinates", 71 | "imsma.database.migrations.20130506163750-remove-sector-and-sample", 72 | "imsma.database.migrations.20111123101028-set-version-5-9-0", 73 | "imsma.database.migrations.20121203143146-add-field-report-report-verified-date-field", 74 | "imsma.database.migrations.20130418105528-update-mre-type-field", 75 | "imsma.database.migrations.20110800000030-update-decimal-handling", 76 | "imsma.database.migrations.20121010133408-update-is-active-fields", 77 | "imsma.database.migrations.20130515143404-add-version-sub-object-fields", 78 | "imsma.database.migrations.20110600000090-update-float-fields", 79 | "imsma.database.migrations.20111123101344-add-poly-property", 80 | "imsma.database.migrations.20130329095946-add-object-filtering-to-fieldreportdesc"]) 81 | ["imsma.database.migrations.20110600000030-update-cdfvalue-types", 82 | "imsma.database.migrations.20110600000040-update-field-table-fieldtype-column-width", 83 | "imsma.database.migrations.20110600000050-adds-values-to-category-table", 84 | "imsma.database.migrations.20110600000060-add-values-to-field-table", 85 | "imsma.database.migrations.20110600000070-change-custom-view-fonts", 86 | "imsma.database.migrations.20110600000080-update-link-table", 87 | "imsma.database.migrations.20110600000090-update-float-fields", 88 | "imsma.database.migrations.20110800000010-create-link-to-index", 89 | "imsma.database.migrations.20110800000020-expand-preference-key-column", 90 | "imsma.database.migrations.20110800000030-update-decimal-handling", 91 | "imsma.database.migrations.20111100000010-add-field-report-submit-permission", 92 | "imsma.database.migrations.20111113100406-init-for-clojure", 93 | "imsma.database.migrations.20111123101028-set-version-5-9-0", 94 | "imsma.database.migrations.20111123101344-add-poly-property", 95 | "imsma.database.migrations.20120105111122-geopoint-precision", 96 | "imsma.database.migrations.20120223153548-poly-property-cleanup", 97 | "imsma.database.migrations.20120306100918-add-poly-prop-fields", 98 | "imsma.database.migrations.20120312143040-add-poly-props-enums", 99 | "imsma.database.migrations.20120326134444-remove-poly-property", 100 | "imsma.database.migrations.20120326165854-fix-geospatialinfo-fields", 101 | "imsma.database.migrations.20120327161904-fix-gazetteer-fields", 102 | "imsma.database.migrations.20120327163802-set-version-5-8-3", 103 | "imsma.database.migrations.20120410180758-fix-null-imsmaenum-dataenterers", 104 | "imsma.database.migrations.20120420142920-update-geopoint-precision", 105 | "imsma.database.migrations.20120516171019-update-mimetype-length", 106 | "imsma.database.migrations.20121010133359-set-version-6-0-0", 107 | "imsma.database.migrations.20121010133408-update-is-active-fields", 108 | "imsma.database.migrations.20121203143146-add-field-report-report-verified-date-field", 109 | "imsma.database.migrations.20121203164415-update-uncategorised", 110 | "imsma.database.migrations.20121205100302-add-field-report-sub-object-fields", 111 | "imsma.database.migrations.20130102104441-add-linked-ids-fields", 112 | "imsma.database.migrations.20130117154649-add-object-ids-fields", 113 | "imsma.database.migrations.20130118095826-create-classification-level-tables", 114 | "imsma.database.migrations.20130121093438-create-classification-base-data", 115 | "imsma.database.migrations.20130226142811-fix-missing-and-duplicate-categories", 116 | "imsma.database.migrations.20130329095946-add-object-filtering-to-fieldreportdesc" 117 | "imsma.database.migrations.20130409102159-create-reconciliation-update-type", 118 | "imsma.database.migrations.20130418105528-update-mre-type-field", 119 | "imsma.database.migrations.20130419142709-update-travel-time-fields", 120 | "imsma.database.migrations.20130419151756-add-missing-search-fields", 121 | "imsma.database.migrations.20130423161211-update-organisation-category", 122 | "imsma.database.migrations.20130424130139-add-field-column-to-translation", 123 | "imsma.database.migrations.20130424142608-user-entered-coordinates", 124 | "imsma.database.migrations.20130506163750-remove-sector-and-sample", 125 | "imsma.database.migrations.20130510133842-add-assistance-tables", 126 | "imsma.database.migrations.20130514121330-add-deleted-current-view-guid", 127 | "imsma.database.migrations.20130515143404-add-version-sub-object-fields", 128 | "imsma.database.migrations.20130524144040-add-contains-assistance-version-to-field-report-desc", 129 | "imsma.database.migrations.20130527111619-add-deleted-current-view-guid-to-assistance-version"]))) 130 | 131 | (deftest test-migration-namespaces 132 | (let [migration-namespaces (migration-namespaces)] 133 | (is (= 2 (count migration-namespaces))) 134 | (is (= ["migrations.001-create-tests" "migrations.002-test-update"] (map namespace-name-str migration-namespaces)))) 135 | (is (= ["migrations.002-test-update" "migrations.001-create-tests"] (map namespace-name-str (migration-namespaces false))))) 136 | 137 | (deftest test-migration-number-from-namespace 138 | (is (= 1 (migration-number-from-namespace "migrations.001-create-tests"))) 139 | (is (= 2 (migration-number-from-namespace "migrations.002-test-update"))) 140 | (is (= 20130527111619 (migration-number-from-namespace "database.migrations.20130527111619-test-long-name"))) 141 | (is (= 20111113100406 (migration-number-from-namespace "imsma.database.migrations.20111113100406-init-for-clojure"))) 142 | (is (= 20110600000030 (migration-number-from-namespace "imsma.database.migrations.20110600000030-update-cdfvalue-types")))) 143 | 144 | (deftest test-migration-compartor 145 | (let [ascending-migration-compartor (migration-compartor true)] 146 | (is (< (.compare ascending-migration-compartor "imsma.database.migrations.20110600000030-update-cdfvalue-types" "imsma.database.migrations.20111113100406-init-for-clojure"))) 147 | (is (> (.compare ascending-migration-compartor "imsma.database.migrations.20111113100406-init-for-clojure" "imsma.database.migrations.20110600000030-update-cdfvalue-types")))) 148 | (let [descending-migration-compartor (migration-compartor false)] 149 | (is (> (.compare descending-migration-compartor "imsma.database.migrations.20110600000030-update-cdfvalue-types" "imsma.database.migrations.20111113100406-init-for-clojure"))) 150 | (is (< (.compare descending-migration-compartor "imsma.database.migrations.20111113100406-init-for-clojure" "imsma.database.migrations.20110600000030-update-cdfvalue-types"))))) 151 | 152 | (deftest test-find-migrate-directory 153 | (let [migrate-directory (find-migrate-directory)] 154 | (is (not (nil? migrate-directory))) 155 | (when migrate-directory 156 | (is (= "migrations" (.getName migrate-directory)))))) 157 | 158 | (deftest test-migration-namespaces-in-range 159 | (let [migration-namespaces (migration-namespaces-in-range 0 1)] 160 | (is (not-empty migration-namespaces)) 161 | (is (= migration-namespaces ["migrations.001-create-tests"]))) 162 | (let [migration-namespaces (migration-namespaces-in-range 0 2)] 163 | (is (not-empty migration-namespaces)) 164 | (is (= migration-namespaces ["migrations.001-create-tests" "migrations.002-test-update"]))) 165 | (let [migration-namespaces (migration-namespaces-in-range 0 0)] 166 | (is (empty? migration-namespaces)))) 167 | 168 | (deftest test-all-migration-numbers 169 | (let [migration-numbers (migration-numbers)] 170 | (is (not-empty migration-numbers)) 171 | (is (number? (first migration-numbers))) 172 | (is (= [1 2] migration-numbers))) 173 | (let [migration-numbers (migration-numbers ["migrations.001-test" "migrations.002-test" "migrations.005-test"])] 174 | (is (not-empty migration-numbers)) 175 | (is (number? (first migration-numbers))) 176 | (is (= [1 2 5] migration-numbers)))) 177 | 178 | (deftest test-max-migration-number 179 | (let [max-number (max-migration-number)] 180 | (is (= max-number 2))) 181 | (let [max-number (max-migration-number (migration-namespaces))] 182 | (is (= max-number 2))) 183 | (let [max-number (max-migration-number ["migrations.001-test" "migrations.002-test" "migrations.005-test"])] 184 | (is (= max-number 5)))) 185 | 186 | (deftest test-find-next-migrate-number 187 | (is (= (find-next-migrate-number) (inc (max-migration-number))))) 188 | 189 | (deftest test-migration-number-before 190 | (let [migration-number (migration-number-before 1 (migration-namespaces))] 191 | (is (= 0 migration-number))) 192 | (let [migration-number (migration-number-before 1)] 193 | (is (= 0 migration-number))) 194 | (is (nil? (migration-number-before nil))) 195 | (is (nil? (migration-number-before nil (migration-namespaces))))) 196 | 197 | (deftest test-find-migration-file 198 | (let [migration-file (find-migration-file "create-tests")] 199 | (is migration-file) 200 | (when migration-file 201 | (is (instance? File migration-file)) 202 | (is (= "001_create_tests.clj" (.getName migration-file))) 203 | (is (= (find-migrate-directory) (.getParentFile migration-file)))))) --------------------------------------------------------------------------------