/src/lobos/analyzer.clj

http://github.com/budu/lobos · Clojure · 200 lines · 160 code · 20 blank · 20 comment · 18 complexity · 335e9f115cb0d4f746f5de05ebbafd71 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.analyzer
  9. "Analyze a database's meta-data to contruct an abstract schema."
  10. (:refer-clojure :exclude [defonce replace])
  11. (:require (lobos [connectivity :as conn]
  12. [schema :as schema]))
  13. (:use (clojure [string :only [replace]])
  14. lobos.internal
  15. lobos.metadata
  16. lobos.utils)
  17. (:import (java.sql DatabaseMetaData)
  18. (lobos.schema Column
  19. DataType
  20. Expression
  21. ForeignKeyConstraint
  22. Index
  23. Schema
  24. Table
  25. UniqueConstraint)))
  26. ;; -----------------------------------------------------------------------------
  27. ;; ## Analyzer
  28. (def db-hierarchy
  29. (atom (-> (make-hierarchy)
  30. (derive :h2 ::standard)
  31. (derive :mysql ::standard)
  32. (derive :postgresql ::standard)
  33. (derive :sqlite ::standard)
  34. (derive :microsoft-sql-server ::standard))))
  35. (defmulti analyze
  36. "Analyzes the specified part of a schema and returns its abstract
  37. equivalent."
  38. (fn [dispatch-val & args]
  39. (if (vector? dispatch-val)
  40. dispatch-val
  41. [(as-keyword (.getDatabaseProductName (db-meta)))
  42. dispatch-val]))
  43. :hierarchy db-hierarchy)
  44. ;; -----------------------------------------------------------------------------
  45. ;; ## Default Analyzer
  46. (defmethod analyze [::standard Expression]
  47. [_ expr]
  48. (when expr
  49. (Expression.
  50. (cond (re-find #"^(.*)::(.*)$" expr)
  51. (let [[_ & [value name]] (first (re-seq #"(.*)::(.*)" expr))]
  52. (read-string (replace (str value) \' \"))) ;; HACK: to replace!
  53. (re-find #"^\d+$" expr) (Integer/parseInt expr)
  54. (re-find #"^'.*'$" expr) (second (re-matches #"^'(.*)'$" expr))
  55. ;; HACK: consider only uppercase unquoted strings to be functions
  56. ;; because of http://bugs.mysql.com/bug.php?id=64614
  57. ;; will need to do something more clever if that bug isn't
  58. ;; fixed by the time work start on the Lobos 1.1 parser
  59. (re-find #"^(\[A-Z]+)(\(\))?$" expr)
  60. (let [[[_ func]] (re-seq #"([A-Z]+)(\(\))?" expr)]
  61. (keyword func))
  62. :else (str expr)))))
  63. (defmethod analyze [::standard UniqueConstraint]
  64. [_ sname tname cname index-meta]
  65. (let [pkeys (primary-keys sname tname)]
  66. (UniqueConstraint.
  67. (keyword cname)
  68. (if (pkeys (keyword cname))
  69. :primary-key
  70. :unique)
  71. (vec (map #(-> % :column_name keyword)
  72. index-meta)))))
  73. (def action-rules
  74. ;; HACK: keys coerce to short has Clojure 1.2 doesn't seems to handle
  75. ;; keys of different type, even weirder 1.3 says those keys are Longs
  76. ;; while 1.2 says Integers!
  77. {(short DatabaseMetaData/importedKeyCascade) :cascade
  78. (short DatabaseMetaData/importedKeySetNull) :set-null
  79. (short DatabaseMetaData/importedKeyRestrict) :restrict
  80. (short DatabaseMetaData/importedKeySetDefault) :set-default})
  81. (defmethod analyze [::standard ForeignKeyConstraint]
  82. [_ cname ref-meta]
  83. (let [pcolumns (vec (map #(-> % :pkcolumn_name keyword)
  84. ref-meta))
  85. fcolumns (vec (map #(-> % :fkcolumn_name keyword)
  86. ref-meta))
  87. ptable (-> ref-meta first :pktable_name keyword)
  88. on-delete (-> ref-meta first :delete_rule action-rules)
  89. on-delete (when on-delete [:on-delete on-delete])
  90. on-update (-> ref-meta first :update_rule action-rules)
  91. on-update (when on-delete [:on-update on-update])]
  92. (ForeignKeyConstraint.
  93. (keyword cname)
  94. fcolumns
  95. ptable
  96. pcolumns
  97. nil
  98. (into {} [on-delete on-update]))))
  99. (defmethod analyze [::standard :constraints]
  100. [_ sname tname]
  101. (concat
  102. (map (fn [[cname meta]] (analyze UniqueConstraint sname tname cname meta))
  103. (indexes-meta sname tname #(let [nu (:non_unique %)]
  104. (or (false? nu) (= nu 0)))))
  105. (map (fn [[cname meta]] (analyze ForeignKeyConstraint cname meta))
  106. (references-meta sname tname))))
  107. (defmethod analyze [::standard Index]
  108. [_ sname tname iname index-meta]
  109. (let [pkeys (primary-keys sname tname)]
  110. (Index.
  111. (keyword iname)
  112. tname
  113. (vec (map #(-> % :column_name keyword)
  114. index-meta))
  115. (when (-> index-meta first :non_unique not)
  116. (list :unique)))))
  117. (defmethod analyze [::standard :indexes]
  118. [_ sname tname]
  119. (map (fn [[iname meta]] (analyze Index sname tname iname meta))
  120. (indexes-meta sname tname)))
  121. (defn analyze-data-type-args
  122. "Returns a vector containing the data type arguments for the given
  123. column meta data."
  124. [dtype column-meta]
  125. (condp contains? dtype
  126. #{:nvarchar :varbinary :varchar} [(:column_size column-meta)]
  127. #{:binary :blob :char :clob :nchar :nclob
  128. :float :time :timestamp} [(:column_size column-meta)]
  129. #{:decimal :numeric} (let [scale (:decimal_digits column-meta)]
  130. (if (= scale 0)
  131. [(:column_size column-meta)]
  132. [(:column_size column-meta) scale]))
  133. []))
  134. (defmethod analyze [::standard DataType]
  135. [_ column-meta]
  136. (let [dtype (-> column-meta :type_name as-keyword)]
  137. (schema/data-type
  138. dtype
  139. (analyze-data-type-args dtype column-meta))))
  140. (defmethod analyze [::standard Column]
  141. [_ column-meta]
  142. (let [auto-inc (= (:is_autoincrement column-meta) "YES")]
  143. (Column. (-> column-meta :column_name keyword)
  144. (analyze DataType column-meta)
  145. (when-not auto-inc
  146. (analyze Expression (:column_def column-meta)))
  147. ;; HACK: to revise, need to have nil instead of false for
  148. ;; easier testing, could this cause problems?
  149. (or auto-inc nil)
  150. (or (= (:is_nullable column-meta) "NO") nil)
  151. [])))
  152. (defmethod analyze [::standard Table]
  153. [_ sname tname]
  154. (schema/table* tname
  155. (into {} (map #(let [c (analyze Column %)]
  156. [(:cname c) c])
  157. (columns-meta sname tname)))
  158. (into {} (map #(vector (:cname %) %)
  159. (analyze :constraints sname tname)))
  160. (into {} (map #(vector (:iname %) %)
  161. (analyze :indexes sname tname)))))
  162. (defmethod analyze [::standard Schema]
  163. [_ sname]
  164. (apply schema/schema sname {:db-spec (db-meta-spec)}
  165. (map #(analyze Table sname %)
  166. (tables sname))))
  167. (defn analyze-schema
  168. [& args]
  169. {:arglists '([connection-info? sname?])}
  170. (let [[db-spec sname _] (optional-cnx-and-sname args)]
  171. (with-db-meta db-spec
  172. (autorequire-backend db-spec)
  173. (let [sname (or (keyword sname)
  174. (default-schema)
  175. (first _))]
  176. (if-let [schemas (schemas)]
  177. (when (or (nil? sname)
  178. ((set schemas) sname))
  179. (analyze Schema sname))
  180. (analyze Schema sname))))))