penpot/backend/src/app/util/migrations.clj
2021-02-01 09:48:28 +01:00

86 lines
2.6 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.util.migrations
(:require
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[clojure.tools.logging :as log]
[cuerdas.core :as str]
[next.jdbc :as jdbc]))
(s/def ::name string?)
(s/def ::step (s/keys :req-un [::name ::fn]))
(s/def ::steps (s/every ::step :kind vector?))
(s/def ::migrations
(s/keys :req-un [::name ::steps]))
;; --- Implementation
(defn- registered?
"Check if concrete migration is already registred."
[pool modname stepname]
(let [sql "select * from migrations where module=? and step=?"
rows (jdbc/execute! pool [sql modname stepname])]
(pos? (count rows))))
(defn- register!
"Register a concrete migration into local migrations database."
[pool modname stepname]
(let [sql "insert into migrations (module, step) values (?, ?)"]
(jdbc/execute! pool [sql modname stepname])
nil))
(defn- impl-migrate-single
[pool modname {:keys [name] :as migration}]
(when-not (registered? pool modname (:name migration))
(log/info (str/format "applying migration %s/%s" modname name))
(register! pool modname name)
((:fn migration) pool)))
(defn- impl-migrate
[conn migrations _opts]
(s/assert ::migrations migrations)
(let [mname (:name migrations)
steps (:steps migrations)]
(jdbc/with-transaction [conn conn]
(run! #(impl-migrate-single conn mname %) steps))))
(defprotocol IMigrationContext
(-migrate [_ migration options]))
;; --- Public Api
(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)"
");")]
(jdbc/execute! conn [sql])
nil))
(defn migrate!
"Main entry point for apply a migration."
([conn migrations]
(impl-migrate conn migrations nil))
([conn migrations options]
(impl-migrate conn migrations options)))
(defn resource
"Helper for setup migration functions
just using a simple path to sql file
located in the class path."
[path]
(fn [pool]
(let [sql (slurp (io/resource path))]
(jdbc/execute! pool [sql])
true)))