Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ pom.xml.asc
/.nrepl-port
.hgignore
.hg/
.clj-kondo/
.cpcache/
.lsp/
resources/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -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"}}}
14 changes: 7 additions & 7 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -21,7 +21,7 @@
:min-lein-version "2.6.1"

:target-path "target/%s"

:uberjar-name "migrana.jar"

:profiles {:uberjar {:aot :all}})
72 changes: 53 additions & 19 deletions src/leiningen/migrana.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
Expand All @@ -21,49 +25,79 @@
Syntax: lein migrana info <URI>

<URI> 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 <URI>. More details: $ lein help migrana info")) )
(main/abort "Must specify <URI>. More details: $ lein help migrana info")))

(defn create
"Creates new manual migration.

Syntax: lein migrana create <name>"
[project args]
(if-let [n (second args)]
(core/create n)
(main/abort "Must specify <name>. 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 <name>. More details: $ lein help migrana name")

:else
(core/create n options))))

(defn dry-run
"Simulates what `run` would do.

Syntax: lein migrana dry-run <URI> <options>

<URI> 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 <URI>. 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 <URI>. More details: $ lein help migrana dry-run")

:else
(core/dry-run uri options))))

(defn run
"Transacts pending migrations onto database.

Syntax: lein migrana run <URI> <options>

<URI> 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 <URI>. 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 <URI>. More details: $ lein help migrana run")

:else
(core/run uri options))))

(defn set-db
"Sets the database timestamp.

Syntax: lein migrana set-db <URI> <timestamp> <options>

<URI> defaults to environment variable $DATOMIC_URI if available."
[project args]
[_project args]
(letfn [(abort []
(main/abort
"Must specify <URI> and <timestamp>. More details: $ lein help migrana set-db"))]
Expand Down
67 changes: 31 additions & 36 deletions src/migrana/core.clj
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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]
Expand All @@ -160,18 +155,18 @@

(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)}))

(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)
Expand All @@ -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
Expand All @@ -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")))

Expand All @@ -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))
Expand All @@ -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 []})))
Expand Down