/src/lobos/core.clj

http://github.com/budu/lobos · Clojure · 232 lines · 174 code · 38 blank · 20 comment · 15 complexity · 6d68ddaa447ea468dbaccd6ec4b0eb70 MD5 · raw file

  1. ; Copyright (c) Nicolas Buduroi. All rights reserved.
  2. ; The use and distribution terms for this software are covered by the
  3. ; Eclipse Public License 1.0 which can be found in the file
  4. ; epl-v10.html at the root of this distribution. By using this software
  5. ; in any fashion, you are agreeing to be bound by the terms of this
  6. ; license.
  7. ; You must not remove this notice, or any other, from this software.
  8. (ns lobos.core
  9. "The `core` namespace provide the basic interface to interact with a
  10. database. It contains a set of functions and *actions* (a special kind
  11. of functions acting on abstract schemas or their elements) used to
  12. manipulate database schemas in an implementation agnostic way.
  13. To find out more about **Lobos**, check out:
  14. * [Lobos website](http://budu.github.com/lobos/)
  15. * [Lobos repo](https://github.com/budu/lobos)
  16. * [Lobos wiki](https://github.com/budu/lobos/wiki)"
  17. {:author "Nicolas Buduroi"}
  18. (:refer-clojure :exclude [alter defonce drop])
  19. (:require (lobos [compiler :as compiler]
  20. [connectivity :as conn]
  21. [migration :as mig]
  22. [schema :as schema]))
  23. (:use (clojure.tools [macro :only [name-with-attributes]])
  24. (clojure [pprint :only [pprint]])
  25. lobos.internal
  26. lobos.utils))
  27. ;; -----------------------------------------------------------------------------
  28. ;; ## Helpers
  29. (defmacro without-migration [& body]
  30. `(binding [mig/*record* nil]
  31. ~@body))
  32. ;; -----------------------------------------------------------------------------
  33. ;; ## Debugging Interface
  34. (defn set-debug-level
  35. "Set the current debugging level. The level argument can be one of
  36. `:schema`, `:ast` or `:sql`. Currently only `:sql` is supported for
  37. all actions. e.g.:
  38. user> (set-debug-level :sql)"
  39. [level]
  40. (swap! debug-level (constantly level)))
  41. (defn debug
  42. "Prints useful information on the given combination protocol method
  43. and schema (or elements). For the available methods, see the
  44. `lobos.schema` namespace. For methods taking extra argument use the
  45. optional `args` argument, which takes a sequence. You can also supply
  46. a `level` and `connection-info` argument. Use the default connection
  47. and the `:sql` level when not specified. *For debugging purpose*. e.g.:
  48. user> (debug build-create-statement
  49. (sample-schema))"
  50. [method object-or-fn & [args level connection-info]]
  51. (let [level (or level @debug-level :sql)
  52. object (if (fn? object-or-fn)
  53. (object-or-fn)
  54. object-or-fn)
  55. db-spec (conn/get-db-spec connection-info)
  56. ast (when-not (= :schema level)
  57. (apply method object (conj args db-spec)))]
  58. (case level
  59. :sql (doseq [ast-element ast]
  60. (pprint (compiler/compile ast-element)))
  61. :ast (do (println (type ast))
  62. ;; TODO: walk to remove db-spec
  63. (pprint ast))
  64. :schema (do (println (type object))
  65. (pprint object)))))
  66. ;; -----------------------------------------------------------------------------
  67. ;; ## Lobos Action
  68. ;; ### Action Macro
  69. (defmacro defaction
  70. "Defines an action applicable to an optional abstract schema or
  71. database connection. *Actions* are simply a special kind of
  72. functions. They will have an augmented argument list, which is the
  73. given one prepended by the optional `cnx-or-schema` argument.
  74. All actions must return a built statement (or list of statements)
  75. using one of the protocol method available.
  76. The defined actions will have access to two extra local variables. The
  77. `schema` variable will contain the given schema if `cnx-or-schema` is
  78. one else it be nil. The `db-spec` argument will contain the db-spec
  79. map found with the given connection or in the given schema. *For
  80. internal use*."
  81. {:arglists '([name doc-string? attr-map? [params*] & body])}
  82. [name & args]
  83. (let [params (seq (first (filter vector? args)))
  84. name* (symbol (str name \*))
  85. [name args] (name-with-attributes name args)
  86. [params* & body] args]
  87. `(do
  88. (defn ~name* [self# & params#]
  89. (let [[~'db-spec ~'schema ~params*]
  90. (optional-cnx-or-schema params#)]
  91. (execute
  92. (do ~@body)
  93. ~'db-spec)
  94. (mig/record self#)))
  95. (defmacro ~name [~'& args#]
  96. `(~~name* (quote ~~'&form) ~@args#))
  97. (.setMeta #'~name
  98. (merge (.meta #'~name)
  99. {:arglists '(~(vec (conj params 'cnx-or-schema?)))})))))
  100. ;; ### Actions
  101. (defaction create
  102. "Builds a create statement with the given schema element and execute
  103. it. See the `lobos.schema` namespace for more details on schema
  104. elements definition. e.g.:
  105. user> (create (table :foo (integer :a)))"
  106. [element]
  107. (schema/build-create-statement (or element schema) db-spec))
  108. (defaction alter
  109. "Builds an alter statement with the given schema element and execute
  110. it. There's four types of alter actions: `:add`, `:drop`, `:modify`
  111. and `:rename`. See the `lobos.schema` namespace for more details on
  112. schema elements definition. e.g.:
  113. user> (alter :add (table :foo (integer :a)))
  114. user> (alter :modify (table :foo (column :a [:default 0])))
  115. user> (alter :rename (table :foo (column :a :to :b)))"
  116. [action element]
  117. (check-valid-options action :add :drop :modify :rename)
  118. (schema/build-alter-statement element action db-spec))
  119. (defaction drop
  120. "Builds a drop statement with the given schema element and execute
  121. it. It can take an optional `behavior` argument, when `:cascade` is
  122. specified drops all elements relying on the one being dropped. e.g.:
  123. user> (drop (table :foo) :cascade)"
  124. [element & [behavior]]
  125. (schema/build-drop-statement element behavior db-spec))
  126. (defaction exec
  127. "Execute the given statements as an action."
  128. [& statements]
  129. statements)
  130. ;; -----------------------------------------------------------------------------
  131. ;; ## Lobos Migration
  132. ;; ### Migration Command Macro
  133. (defmacro defcommand
  134. [name & args]
  135. (let [params (seq (first (filter vector? args)))
  136. [name args] (name-with-attributes name args)
  137. [params* & body] args]
  138. `(do
  139. (defn ~name [& params#]
  140. (require mig/*migrations-namespace*)
  141. (let [[~'db-spec ~'sname ~params*]
  142. (optional-cnx-and-sname params#)]
  143. (mig/create-migrations-table ~'db-spec ~'sname)
  144. ~@body))
  145. (.setMeta #'~name
  146. (merge (.meta #'~name)
  147. {:arglists
  148. '(~(vec (conj params
  149. 'sname?
  150. 'cnx-or-schema?)))})))))
  151. ;; ### Migration Commands
  152. (defn print-stash []
  153. (when (.exists mig/*stash-file*)
  154. (print (slurp mig/*stash-file*))))
  155. (defcommand print-done []
  156. (doseq [name (mig/query-migrations-table db-spec sname)]
  157. (println name)))
  158. (defcommand print-pending []
  159. (doseq [name (mig/pending-migrations db-spec sname)]
  160. (println name)))
  161. (defcommand migrate [& names]
  162. (let [names (if (empty? names)
  163. (mig/pending-migrations db-spec sname)
  164. names)]
  165. (mig/do-migrations db-spec sname :up names)))
  166. (defcommand rollback [& args]
  167. (let [names (cond
  168. (empty? args)
  169. [(last (mig/query-migrations-table db-spec sname))]
  170. (= 1 (count args))
  171. (let [arg (first args)
  172. migs (mig/query-migrations-table db-spec sname)]
  173. (cond
  174. (integer? arg) (take arg (reverse migs))
  175. (= arg :all) migs
  176. :else args))
  177. :else args)]
  178. (mig/do-migrations db-spec sname :down names)))
  179. (defcommand reset [& args]
  180. (apply rollback args)
  181. (migrate))
  182. (defcommand generate-migration [name & [msg]]
  183. (let [name (symbol (if (string? name)
  184. name
  185. (clojure.core/name name)))]
  186. (when-not name
  187. (throw (IllegalArgumentException.
  188. "Migration must be named.")))
  189. (when ((set (mig/list-migrations-names)) (str name))
  190. (throw (IllegalArgumentException.
  191. "Migration name is already taken.")))
  192. (mig/generate-migration* db-spec sname name msg
  193. (mig/read-stash-file))
  194. (mig/clear-stash-file)))