lobos1.0.0-SNAPSHOTA library to create and manipulate SQL database schemas. dependencies
dev dependencies
| (this space intentionally left almost blank) | ||||||||||||||||||||||||
Migrations support. | (ns lobos.migration (:refer-clojure :exclude [complement defonce replace]) (:require (clojure.java [jdbc :as sql]) (lobos [analyzer :as analyzer] [compiler :as compiler] [connectivity :as conn] [schema :as schema])) (:use (clojure [walk :only [postwalk]]) (clojure.java [io :only [file writer]]) (clojure.tools [macro :only [name-with-attributes]]) (clojure pprint) lobos.internal lobos.utils) (:import (java.sql Timestamp) (java.util Date))) | ||||||||||||||||||||||||
Globals | |||||||||||||||||||||||||
(def ^{:dynamic true} *record* :stash) | |||||||||||||||||||||||||
(def ^{:dynamic true} *stash-file* (file ".lobos_stash.clj")) | |||||||||||||||||||||||||
(def ^{:dynamic true} *src-directory* "src/") | |||||||||||||||||||||||||
(def ^{:dynamic true} *migrations-namespace* 'lobos.migrations) | |||||||||||||||||||||||||
(def ^{:dynamic true} *config-namespace* 'lobos.config) | |||||||||||||||||||||||||
(def ^{:dynamic true} *migrations-table* :lobos_migrations) | |||||||||||||||||||||||||
Action Complement | |||||||||||||||||||||||||
(defn reverse-rename [form] (postwalk #(if (and (seq? %) (= 'column (first %))) (let [[elem from _ to] %] `(~elem ~to :to ~from)) %) form)) | |||||||||||||||||||||||||
Returns the complementary action to be use in the down part of a migration or nil. | (defmulti complement first) | ||||||||||||||||||||||||
(defmethod complement 'create [action] (let [not-cons-or-seq? #(not (or (seq? %) (isa? (type %) clojure.lang.Cons))) args (->> (rest action) (map #(if (seq? %) (filter not-cons-or-seq? %) %)))] (if (= 'schema (-> action reverse first first)) (concat ['drop] args [:cascade]) (apply list 'drop args)))) | |||||||||||||||||||||||||
(defmethod complement 'alter [action] (let [[element subaction cnx-or-schema] (reverse (rest action))] (filter identity (case subaction :add (list 'alter cnx-or-schema :drop element) :rename (list 'alter cnx-or-schema :rename (reverse-rename element)) nil)))) | |||||||||||||||||||||||||
(defmethod complement :default [_] nil) | |||||||||||||||||||||||||
Migration Protocol | |||||||||||||||||||||||||
Used to agregate migrations in order of definition, For internal use. | (def migrations (atom [])) | ||||||||||||||||||||||||
The migration protocol is meant to be reified into a single migration unit. See the defmigration macro, For internal use. | (defprotocol Migration (up [_]) (down [_])) | ||||||||||||||||||||||||
Defines a migration to be used by the migration commands. The code contained inside the up section is used to modify a database using Lobos' actions, while the down section revert those changes.
| (defmacro defmigration {:arglists '([name doc-string? attr-map? & bodies])} [name & args] (let [prepare-body #(conj (rest %) 'do) [name args] (name-with-attributes name args) [migrate-up migrate-down] args] `(do (def ~name (with-meta (reify Migration (up [_] ~(prepare-body migrate-up)) (down [_] ~(prepare-body migrate-down))) (.meta #'~name))) (swap! migrations conj ~name)))) | ||||||||||||||||||||||||
File Helpers | |||||||||||||||||||||||||
(defn- append [file content] (make-parents file) (with-open [wtr (writer file :append true)] (.write wtr "\n") (pprint content wtr))) | |||||||||||||||||||||||||
(defn ns-file [ns] (file *src-directory* (-> (str ns) (.replace "." "/") (str ".clj")))) | |||||||||||||||||||||||||
Stash File Helpers | |||||||||||||||||||||||||
(defn append-to-stash-file [action] (append *stash-file* action)) | |||||||||||||||||||||||||
(defn clear-stash-file [] (when (.exists *stash-file*) (spit *stash-file* ))) | |||||||||||||||||||||||||
(defn read-stash-file [] (when (.exists *stash-file*) (read-string (str \[ (slurp *stash-file*) \])))) | |||||||||||||||||||||||||
Migrations File Helpers | |||||||||||||||||||||||||
(defn migrations-file [] (ns-file *migrations-namespace*)) | |||||||||||||||||||||||||
(defn create-mfile [] (append (migrations-file) `(~'ns ~*migrations-namespace* (:refer-clojure :exclude [~'alter ~'defonce ~'drop ~'bigint ~'boolean ~'char ~'double ~'float ~'time]) (:use (~'lobos [~'migration :only [~'defmigration]] ~'core ~'schema) ~*config-namespace*)))) | |||||||||||||||||||||||||
(defn append-to-mfile [name msg up & [down]] (when-not (.exists (migrations-file)) (create-mfile)) (append (migrations-file) `(~'defmigration ~name ~@(when msg [msg]) (~'up [] ~@up) ~@(when down [`(~'down [] ~@down)])))) | |||||||||||||||||||||||||
(defn list-migrations [] (when (.exists (migrations-file)) (swap! migrations (constantly [])) (use :reload *migrations-namespace*) @migrations)) | |||||||||||||||||||||||||
Migrations Table Helpers | |||||||||||||||||||||||||
(defn create-migrations-table [db-spec sname] (autorequire-backend db-spec) (when-not (-> (analyzer/analyze-schema db-spec sname) :elements *migrations-table*) (let [action (schema/table *migrations-table* (schema/varchar :name 255)) db-spec (assoc db-spec :schema sname) create-stmt (schema/build-create-statement action db-spec)] (execute create-stmt db-spec)))) | |||||||||||||||||||||||||
(defn insert-migrations [db-spec sname & names] (when-not (empty? names) (sql/with-connection db-spec (apply sql/insert-rows (compiler/as-identifier db-spec *migrations-table* sname) (map (comp vector str) names))))) | |||||||||||||||||||||||||
(defn delete-migrations [db-spec sname & names] (when-not (empty? names) (conn/with-connection db-spec (delete db-spec sname *migrations-table* (in :name (vec (map str names))))))) | |||||||||||||||||||||||||
Commands Helpers | |||||||||||||||||||||||||
(defn record [action] (when *record* (append-to-stash-file action))) | |||||||||||||||||||||||||
(defn list-migrations-names [] (map #(-> % meta :name str) (list-migrations))) | |||||||||||||||||||||||||
(defn query-migrations-table [db-spec sname] (conn/with-connection db-spec (map :name (query db-spec sname *migrations-table*)))) | |||||||||||||||||||||||||
(defn pending-migrations [db-spec sname] (exclude (query-migrations-table db-spec sname) (list-migrations-names))) | |||||||||||||||||||||||||
(defn do-migrations [db-spec sname with names & [silent]] (let [filter-migs #(only % (list-migrations-names)) migrations (->> names (map str) filter-migs (when->> (= with :down) reverse) (map symbol) (map (partial ns-resolve *migrations-namespace*)) (map var-get))] (binding [*record* nil] (doseq [migration migrations] (let [name (-> migration meta :name)] (when-not silent (println name)) (if (= with :up) (do (up migration) (insert-migrations db-spec sname name)) (do (down migration) (delete-migrations db-spec sname name)))))))) | |||||||||||||||||||||||||
(defn generate-migration* [db-spec sname name msg actions] (append-to-mfile name msg actions (->> actions (map complement) (filter identity) seq)) (when-not (empty? actions) (insert-migrations db-spec sname name))) | |||||||||||||||||||||||||