diff --git a/.gitignore b/.gitignore index c53038e..c198cc6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ pom.xml.asc /.nrepl-port .hgignore .hg/ +.clj-kondo/ +.cpcache/ +.lsp/ +resources/ diff --git a/README.md b/README.md index 24a3a89..b5ee1ae 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Let's assume you have a Datomic schema file on `resources/schema.edn`: {:db/ident :relationship-status/single} {:db/ident :relationship-status/married} {:db/ident :relationship-status/divorced} - {:db/ident :relationship-status/widowed}]] + {:db/ident :relationship-status/widowed}] ``` Then run: @@ -144,7 +144,7 @@ Let's change our `resources/schema.edn` to: {:db/ident :relationship-status/single} {:db/ident :relationship-status/married} {:db/ident :relationship-status/divorced} - {:db/ident :relationship-status/widowed}]] + {:db/ident :relationship-status/widowed}] ``` After the change we run `lein migrana run` again: diff --git a/deps.edn b/deps.edn index bab6447..a46981a 100644 --- a/deps.edn +++ b/deps.edn @@ -1,6 +1,6 @@ {:deps - {clj-time {:mvn/version "0.14.2"} + {clj-time {:mvn/version "0.15.2"} environ {:mvn/version "1.1.0"} - camel-snake-kebab {:mvn/version "0.4.0"} - org.clojure/tools.cli {:mvn/version "0.3.5"} - com.datomic/datomic-pro {:mvn/version "0.9.5561.62"}}} + camel-snake-kebab {:mvn/version "0.4.3"} + org.clojure/tools.cli {:mvn/version "1.1.230"} + com.datomic/peer {:mvn/version "1.0.7260"}}} diff --git a/project.clj b/project.clj index 34114f4..d5207cb 100644 --- a/project.clj +++ b/project.clj @@ -4,15 +4,15 @@ :license {:name "MIT" :url "https://opensource.org/licenses/MIT"} - :dependencies [[org.clojure/clojure "1.9.0"] - [clj-time "0.14.2"] - [com.datomic/datomic-pro "0.9.5561.62"] + :dependencies [[org.clojure/clojure "1.12.0"] + [clj-time "0.15.2"] + [com.datomic/peer "1.0.7260"] [environ "1.1.0"] - [camel-snake-kebab "0.4.0"] - [org.clojure/tools.cli "0.3.5"]] + [camel-snake-kebab "0.4.3"] + [org.clojure/tools.cli "1.1.230"]] :eval-in-leiningen true - + :plugins [[lein-environ "1.1.0"]] :repositories {"my.datomic.com" {:url "https://my.datomic.com/repo" @@ -21,7 +21,7 @@ :min-lein-version "2.6.1" :target-path "target/%s" - + :uberjar-name "migrana.jar" :profiles {:uberjar {:aot :all}}) diff --git a/src/leiningen/migrana.clj b/src/leiningen/migrana.clj index eb97bb8..95e633d 100644 --- a/src/leiningen/migrana.clj +++ b/src/leiningen/migrana.clj @@ -7,10 +7,14 @@ [migrana.core :as core])) (def ^:private cli-options - [["-s" "--schema SCHEMA_FILE" "Schema file (default resources/schema.edn)" - :id :schema] - ["-m" "--migrations MIGRATIONS_PATH" "Migrations path (default resources/migrations/)" - :id :migrations] + [["-s" "--schema SCHEMA_FILE" "Schema file" + :id :schema + :default "resources/schema.edn"] + + ["-m" "--migrations MIGRATIONS_PATH" "Migrations path" + :id :migrations + :default "resources/migrations/"] + [nil "--no-inference" "Runs with no schema change inference" :id :no-inference :default false]]) @@ -21,19 +25,29 @@ Syntax: lein migrana info defaults to environment variable $DATOMIC_URI if available." - [project args] + [_project args] (if-let [uri (or (second args) (environ/env :datomic-uri))] (core/info uri) - (main/abort "Must specify . More details: $ lein help migrana info")) ) + (main/abort "Must specify . More details: $ lein help migrana info"))) (defn create "Creates new manual migration. Syntax: lein migrana create " - [project args] - (if-let [n (second args)] - (core/create n) - (main/abort "Must specify . More details: $ lein help migrana name"))) + [_project args] + (let [{:keys [options arguments errors summary]} (parse-opts args cli-options) + n (second arguments)] + (cond + errors + (do + (println "Error parsing options:" (clojure.string/join "\n" errors)) + (main/abort summary)) + + (nil? n) + (main/abort "Must specify . More details: $ lein help migrana name") + + :else + (core/create n options)))) (defn dry-run "Simulates what `run` would do. @@ -41,10 +55,20 @@ Syntax: lein migrana dry-run defaults to environment variable $DATOMIC_URI if available." - [project args] - (if-let [uri (or (second args) (environ/env :datomic-uri))] - (core/dry-run uri) - (main/abort "Must specify . More details: $ lein help migrana dry-run"))) + [_project args] + (let [{:keys [options arguments errors summary]} (parse-opts args cli-options) + uri (or (second arguments) (environ/env :datomic-uri))] + (cond + errors + (do + (println "Error parsing options:" (clojure.string/join "\n" errors)) + (main/abort summary)) + + (nil? uri) + (main/abort "Must specify . More details: $ lein help migrana dry-run") + + :else + (core/dry-run uri options)))) (defn run "Transacts pending migrations onto database. @@ -52,10 +76,20 @@ Syntax: lein migrana run defaults to environment variable $DATOMIC_URI if available." - [project args] - (if-let [uri (or (second args) (environ/env :datomic-uri))] - (core/run uri) - (main/abort "Must specify . More details: $ lein help migrana run"))) + [_project args] + (let [{:keys [options arguments errors summary]} (parse-opts args cli-options) + uri (or (second arguments) (environ/env :datomic-uri))] + (cond + errors + (do + (println "Error parsing options:" (clojure.string/join "\n" errors)) + (main/abort summary)) + + (nil? uri) + (main/abort "Must specify . More details: $ lein help migrana run") + + :else + (core/run uri options)))) (defn set-db "Sets the database timestamp. @@ -63,7 +97,7 @@ Syntax: lein migrana set-db defaults to environment variable $DATOMIC_URI if available." - [project args] + [_project args] (letfn [(abort [] (main/abort "Must specify and . More details: $ lein help migrana set-db"))] diff --git a/src/migrana/core.clj b/src/migrana/core.clj index bebe9bc..f35d472 100644 --- a/src/migrana/core.clj +++ b/src/migrana/core.clj @@ -1,6 +1,5 @@ (ns migrana.core - (:require [camel-snake-kebab.core :refer [->snake_case_string]] - [clj-time.core :as time] + (:require [camel-snake-kebab.core :as csk] [clj-time.format :as format] [clj-time.local :as local] [clojure.data :as data] @@ -13,10 +12,6 @@ (def ^:private inference-suffix "_schema_inference.edn") -(def ^:private schema-path "resources/schema.edn") - -(def ^:private migrations-path "resources/migrations/") - (def ^:private migrana-schema [{:db/ident :migrana/migration :db/valueType :db.type/keyword @@ -76,14 +71,14 @@ (let [payload (concat (flatten-tx-data conn tx) [{:migrana/migration :current :migrana/timestamp (:timestamp tx) - :migrana/schema (prn-str (:schema tx))}])] + :migrana/schema (prn-str (:schema tx))}])] @(datomic/transact conn payload)))) (defn ^:private print-left-behind-changes [txs] (doseq [tx txs] (println "=> Would transact" (:timestamp tx)) - (if (:tx-fn tx) (println "... would evaluate" (:tx-fn tx) "for" )))) + (when (:tx-fn tx) (println "... would evaluate" (:tx-fn tx) "for")))) (defn ^:private new-time-stamp "Returns a new time stamp based on local time" @@ -101,16 +96,16 @@ (defn ^:private build-new-inference "Compares the schema in the DB and on disk and creates an inferred migration file if there are differences." - [conn] - (let [{:keys [migrana/timestamp migrana/schema]} (current-db-info conn) - schema-on-disk (-> schema-path slurp edn/read-string) + [conn options] + (let [{:keys [migrana/schema]} (current-db-info conn) + schema-on-disk (-> (:schema options) slurp edn/read-string) diff (data/diff (set schema-on-disk) (set (edn/read-string schema))) gap-on-disk (vec (first diff))] - (if (> (count gap-on-disk) 0) + (if (> (count gap-on-disk) 0) (let [new-ts (new-time-stamp) - migration-name (str migrations-path new-ts inference-suffix)] + migration-name (str (:migrations options) new-ts inference-suffix)] (println "=> Schema changes detected") - (.mkdir (io/file migrations-path)) + (.mkdir (io/file (:migrations options))) (spit migration-name (with-out-str (pprint/pprint {:tx-data gap-on-disk @@ -120,9 +115,9 @@ false))) (defn ^:private dryrun-new-inference - [last-tx] - (let [{:keys [timestamp schema]} last-tx - schema-on-disk (-> schema-path slurp edn/read-string) + [last-tx options] + (let [{:keys [schema]} last-tx + schema-on-disk (-> (:schema options) slurp edn/read-string) diff (data/diff (set schema-on-disk) (set schema)) gap-on-disk (vec (first diff))] (if (> (count gap-on-disk) 0) @@ -139,11 +134,11 @@ (defn ^:private migration-files "Returns seq with all the migration files in chronological order" - [] + [options] (sort #(compare (.getName %1) (.getName %2)) - (.listFiles (io/file migrations-path)))) + (.listFiles (io/file (:migrations options))))) (defn ^:private pre-process-files [files] @@ -160,9 +155,9 @@ (defn ^:private pre-process-migrations "Returns the migrations that still need to be applied to the DB" - [conn] + [conn options] (let [{:keys [migrana/timestamp]} (current-db-info conn) - pre-processed-migrations (pre-process-files (migration-files))] + pre-processed-migrations (pre-process-files (migration-files options))] {:filtered-migrations (filter #(> (compare (:timestamp %) timestamp) 0) pre-processed-migrations) :last-migration (last pre-processed-migrations)})) @@ -170,8 +165,8 @@ (defn ^:private transact-to-latest "Transacts the DB to the latest state" [conn & args] - (let [{:keys [dryrun]} (apply hash-map args) - pre-processed-migrations (pre-process-migrations conn) + (let [{:keys [dryrun options]} (apply hash-map args) + pre-processed-migrations (pre-process-migrations conn options) left-behind-txs (:filtered-migrations pre-processed-migrations)] (if dryrun (print-left-behind-changes left-behind-txs) @@ -182,7 +177,7 @@ "Connects to URI, makes sure the DB exists and ensures bsasic migrana schema is in place" [uri] - (if-not uri (throw (Throwable. "Must have URI to connect to"))) + (when-not uri (throw (Throwable. "Must have URI to connect to"))) (println "=> Connecting to" uri) (-> uri ensure-db-exists @@ -192,13 +187,13 @@ (defn run "Connect to the DB, fast forwards it to the latest state in disk, infers new schema changes, creates extra migration if needed, and then fast forward to this new state" - [uri] + [uri options] (let [conn (base-uri-connect uri) {:keys [migrana/timestamp]} (current-db-info conn)] (println "=> DB is currently at" (or timestamp "N/A")) - (transact-to-latest conn) - (if (build-new-inference conn) - (transact-to-latest conn)) + (transact-to-latest conn :options options) + (when (build-new-inference conn options) + (transact-to-latest conn :options options)) (datomic/release conn) (println "=> DB is up-to-date!\n"))) @@ -213,14 +208,14 @@ (defn dry-run "Similar to apply-run but instead of applying the outstanding migrations it prints out what the migrations would do." - [uri] + [uri options] (let [conn (base-uri-connect uri) {:keys [migrana/timestamp]} (current-db-info conn)] (println "=> DB is currently at" (or timestamp "N/A")) - (let [last-tx (:last-migration (transact-to-latest conn :dryrun true)) - would-infer? (dryrun-new-inference last-tx)] + (let [last-tx (:last-migration (transact-to-latest conn :options options :dryrun true)) + would-infer? (dryrun-new-inference last-tx options)] (println "=> Last known migration at" (or (:timestamp last-tx) "N/A")) - (if would-infer? + (when would-infer? (println "=> Would transact inferred schema changes")) (if (and (= timestamp (:timestamp last-tx)) @@ -233,11 +228,11 @@ (defn create "Creates a migration named n" - [n] + [n {:keys [migrations]}] (let [new-ts (new-time-stamp) - migration-name (str migrations-path new-ts "_" - (->snake_case_string n) ".edn")] - (.mkdir (io/file migrations-path)) + migration-name (str migrations new-ts "_" + (csk/->snake_case n) ".edn")] + (.mkdir (io/file migrations)) (spit migration-name (with-out-str (pprint/pprint {:tx-data []})))