mirror of
https://github.com/penpot/penpot.git
synced 2025-01-21 22:22:43 -05:00
219 lines
6.8 KiB
Clojure
219 lines
6.8 KiB
Clojure
|
(ns migrante.core
|
||
|
(:require [suricatta.core :as sc]
|
||
|
[suricatta.proto :as sp]
|
||
|
[cuerdas.core :as str]
|
||
|
[clojure.java.io :as io]))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;; Private Api: Helpers
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(def ^:dynamic *verbose* false)
|
||
|
(def ^:dynamic *ctx* nil)
|
||
|
|
||
|
(defmacro ^:private log
|
||
|
"A simple sugar syntax helper for log information
|
||
|
into the standard output."
|
||
|
[& args]
|
||
|
`(when *verbose*
|
||
|
(println ~@args)))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;; Private Api: Implementation
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(defn- migration-registered?
|
||
|
"Check if concrete migration is already registred."
|
||
|
([conn module step]
|
||
|
{:pre [(keyword? module) (keyword? step)]}
|
||
|
(let [sql (str "select * from migrations"
|
||
|
" where module=? and step=?")
|
||
|
res (sc/fetch conn [sql (name module) (name step)])]
|
||
|
(pos? (count res)))))
|
||
|
|
||
|
(defn- register-migration!
|
||
|
"Register a concrete migration into local migrations database."
|
||
|
([conn module step]
|
||
|
{:pre [(keyword? module) (keyword? step)]}
|
||
|
(let [sql "insert into migrations (module, step) values (?, ?)"]
|
||
|
(sc/execute conn [sql (name module) (name step)]))))
|
||
|
|
||
|
(defn- unregister-migration!
|
||
|
"Unregister a concrete migration from local migrations database."
|
||
|
([conn module step]
|
||
|
{:pre [(keyword? module) (keyword? step)]}
|
||
|
(let [sql "delete from migrations where module=? and step=?;"]
|
||
|
(sc/execute conn [sql (name module) (name step)]))))
|
||
|
|
||
|
(defn- setup!
|
||
|
"Initialize the database if it is not initialized."
|
||
|
[conn]
|
||
|
(let [sql (str "create table if not exists migrations ("
|
||
|
" module text,"
|
||
|
" step text,"
|
||
|
" created_at timestamp DEFAULT current_timestamp,"
|
||
|
" unique(module, step)"
|
||
|
");")]
|
||
|
(sc/execute conn sql)))
|
||
|
|
||
|
(defprotocol IMigration
|
||
|
"Define a migration step behavior on up and down
|
||
|
migration actons."
|
||
|
(-name [_] "Return the migration name")
|
||
|
(-desc [_] "Return the migration desc")
|
||
|
(-run-up [_ _] "Run function in migrate process.")
|
||
|
(-run-down [_ _] "Run function in rollback process."))
|
||
|
|
||
|
(deftype Migration [name desc up down])
|
||
|
|
||
|
(extend-protocol IMigration
|
||
|
Migration
|
||
|
(-run-up [step conn]
|
||
|
(let [upfn (.-up step)]
|
||
|
(binding [*ctx* conn]
|
||
|
(upfn))))
|
||
|
(-run-down [step conn]
|
||
|
(if-let [downfn (.-down step)]
|
||
|
(binding [*ctx* conn]
|
||
|
(downfn))))
|
||
|
(-name [step]
|
||
|
(str/collapse-whitespace
|
||
|
(.-name step)))
|
||
|
(-desc [step]
|
||
|
(str/collapse-whitespace
|
||
|
(.-desc step))))
|
||
|
|
||
|
(defn- do-migrate
|
||
|
[conn migration {:keys [until fake] :or {fake false}}]
|
||
|
(let [mid (:name migration)
|
||
|
steps (:steps migration)]
|
||
|
(log (str/format "Applying migrations for `%s`:" mid))
|
||
|
(sc/atomic conn
|
||
|
(run! (fn [[sid sdata]]
|
||
|
(when-not (migration-registered? conn mid sid)
|
||
|
(log (format "- %s - %s - %s" sid (-name sdata)
|
||
|
(str/prune (-desc sdata) 70)))
|
||
|
(sc/atomic conn
|
||
|
(when (not fake)
|
||
|
(-run-up sdata conn))
|
||
|
(register-migration! conn mid sid)))
|
||
|
(when (= until sid)
|
||
|
(reduced nil)))
|
||
|
steps))
|
||
|
(log "\n")))
|
||
|
|
||
|
(defn- do-rollback
|
||
|
[conn migration {:keys [until fake] :or {fake false}}]
|
||
|
(let [mid (:name migration)
|
||
|
steps (reverse (:steps migration))]
|
||
|
(sc/atomic conn
|
||
|
(run! (fn [[sid sdata]]
|
||
|
(when (migration-registered? conn mid sid)
|
||
|
(log (format "- Rollback migration %s/%s (%s - %s)"
|
||
|
mid sid (-name sdata) (-desc sdata)))
|
||
|
(sc/atomic conn
|
||
|
(when (not fake)
|
||
|
(-run-down sdata conn))
|
||
|
(unregister-migration! conn mid sid)))
|
||
|
(when (= until sid)
|
||
|
(reduced nil)))
|
||
|
steps))))
|
||
|
|
||
|
(defn- normalize-to-connection
|
||
|
[dbspec]
|
||
|
(if (satisfies? sp/IContextHolder dbspec)
|
||
|
dbspec
|
||
|
(sc/context dbspec)))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;; Public Api
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(defn execute
|
||
|
"Execute a query and return a number of rows affected."
|
||
|
[q]
|
||
|
(sc/execute *ctx* q))
|
||
|
|
||
|
(defn fetch
|
||
|
"Fetch eagerly results executing a query.
|
||
|
|
||
|
This function returns a vector of records (default) or
|
||
|
rows (depending on specified opts). Resources are relased
|
||
|
inmediatelly without specific explicit action for it."
|
||
|
([q]
|
||
|
(sc/fetch *ctx* q))
|
||
|
([q opts]
|
||
|
(sc/fetch *ctx* q opts)))
|
||
|
|
||
|
(defprotocol IMigrationContext
|
||
|
(-migrate [_ migration options])
|
||
|
(-rollback [_ migration options])
|
||
|
(-registered? [_ module step]))
|
||
|
|
||
|
(defn context
|
||
|
"Create new instance of migration context."
|
||
|
([conn] (context conn nil))
|
||
|
([conn {:keys [verbose] :or {verbose true}}]
|
||
|
(let [conn (normalize-to-connection conn)]
|
||
|
(setup! conn)
|
||
|
(reify
|
||
|
java.lang.AutoCloseable
|
||
|
(close [_] (.close conn))
|
||
|
|
||
|
IMigrationContext
|
||
|
(-migrate [_ migration options]
|
||
|
(sc/atomic conn
|
||
|
(binding [*verbose* verbose]
|
||
|
(do-migrate conn migration options))))
|
||
|
|
||
|
(-rollback [_ migration options]
|
||
|
(sc/atomic conn
|
||
|
(binding [*verbose* verbose]
|
||
|
(do-rollback conn migration options))))
|
||
|
(-registered? [_ module step]
|
||
|
(migration-registered? conn module step))))))
|
||
|
|
||
|
(defn migrate
|
||
|
"Main entry point for apply a migration."
|
||
|
([mctx migration]
|
||
|
(migrate mctx migration nil))
|
||
|
([mctx migration options]
|
||
|
(-migrate mctx migration options)))
|
||
|
|
||
|
(defn rollback
|
||
|
"Main entry point for apply a migration."
|
||
|
([mctx migration]
|
||
|
(rollback mctx migration nil))
|
||
|
([mctx migration options]
|
||
|
(-rollback mctx migration options)))
|
||
|
|
||
|
(defn registered?
|
||
|
[mctx module step]
|
||
|
(-registered? mctx module step))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;; Sugar Syntax
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(defmacro defmigration
|
||
|
[sym & [docs & args]]
|
||
|
(let [{:keys [up down]} (if (string? docs)
|
||
|
(apply hash-map args)
|
||
|
(apply hash-map docs args))
|
||
|
docs (if (string? docs) docs "")
|
||
|
mname (name sym)]
|
||
|
`(def ~sym
|
||
|
~docs
|
||
|
(->Migration ~mname ~docs
|
||
|
(or ~up identity)
|
||
|
(or ~down identity)))))
|
||
|
|
||
|
(defmacro resource
|
||
|
"Helper for setup migration functions
|
||
|
just using a simple path to sql file
|
||
|
located in the class path."
|
||
|
[path]
|
||
|
`(fn []
|
||
|
(let [sql# (slurp (io/resource ~path))]
|
||
|
(execute sql#))))
|