/src/lobos/schema.clj

http://github.com/budu/lobos · Clojure · 760 lines · 570 code · 120 blank · 70 comment · 43 complexity · bae4503939b00068f742e03917ebffed 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.schema
  9. "This namespace include the abstract schema data-structures, an
  10. handful of helpers to create them and the protocol to build the into
  11. an abstract syntax tree of implementation agnostic SQL
  12. statements. Abstract schema data-structures can be divided in two
  13. categories.
  14. First, schema elements which include the `schema` and the `table`
  15. element definitions. Those can be created or dropped, also the `table`
  16. element can be altered.
  17. Then there's the table elements that serves to define tables. There's
  18. the completly abstract `column` and `constraint` elements, which are
  19. only meant to be used directly with the alter action. Each of them
  20. have more specialized function to help you define tables, like the
  21. `unique`, `primary-key`, `foreign-key` and `check` constraints
  22. definitons and the typed data definitions."
  23. (:refer-clojure :exclude [defonce replace
  24. bigint boolean char double float time])
  25. (:require (lobos [ast :as ast]))
  26. (:use (clojure [walk :only [postwalk]]
  27. [set :only [union]]
  28. [string :only [replace]])
  29. lobos.utils))
  30. (ast/import-all)
  31. ;; -----------------------------------------------------------------------------
  32. ;; ## Protocols
  33. (defprotocol Alterable
  34. "The Alterable protocol add the possibility of building alter
  35. statements from an object implementing it. *For internal use*."
  36. (build-alter-statement [this action db-spec]))
  37. (defprotocol Buildable
  38. "The Buildable protocol is currently used only by table elements.
  39. *For internal use*."
  40. (build-definition [this db-spec]))
  41. (defprotocol Creatable
  42. "The Creatable protocol add the possibility of building create
  43. statements from an object implementing it. *For internal use*."
  44. (build-create-statement [this db-spec]))
  45. (defprotocol Dropable
  46. "The Dropable protocol add the possibility of building drop
  47. statements from an object implementing it. *For internal use*."
  48. (build-drop-statement [this behavior db-spec]))
  49. ;; -----------------------------------------------------------------------------
  50. ;; ## Common Exception
  51. (defn name-required
  52. "Throws an IllegalArgumentException when the given name is nil with a
  53. default message using the given type of elements."
  54. [name etype]
  55. (when-not name
  56. (throw (IllegalArgumentException.
  57. (format "A %s definition needs at least a name."
  58. etype)))))
  59. ;; -----------------------------------------------------------------------------
  60. ;; ## Definition Predicate
  61. (defn definition?
  62. "Returns true if the given object is an abstract schema element
  63. definition. *For internal use*."
  64. [o]
  65. (isa? (type o) ::definition))
  66. ;; -----------------------------------------------------------------------------
  67. ;; ## Expression Definitions
  68. (def ^{:doc "A set of symbol representing SQL infix operators."}
  69. sql-infix-operators
  70. '#{;; math operators
  71. + - * /
  72. ;; boolean operators
  73. < > <= >= = != or and in like})
  74. (def ^{:doc "A set of symbol representing SQL prefix operators."}
  75. sql-prefix-operators
  76. '#{not})
  77. (def ^{:doc "A set of symbol representing SQL functions."}
  78. sql-functions
  79. '#{;; string functions
  80. length lower position replace str subs trim upper
  81. ;; numeric functions
  82. abs ceil floor mod
  83. ;; datetime functions
  84. extract now current_date current_time current_timestamp})
  85. (def sql-symbols
  86. (union sql-infix-operators
  87. sql-prefix-operators
  88. sql-functions))
  89. (defrecord Expression [value]
  90. Buildable
  91. (build-definition [this db-spec]
  92. (postwalk
  93. #(do
  94. (cond (vector? %)
  95. (let [[f & n] %]
  96. (if (keyword? f)
  97. (condp contains? (-> f name symbol)
  98. sql-infix-operators
  99. (OperatorExpression. db-spec f (first n) (next n))
  100. sql-prefix-operators
  101. (OperatorExpression. db-spec f nil n)
  102. sql-functions
  103. (FunctionExpression. db-spec f n))
  104. %))
  105. (and (keyword? %)
  106. (not (contains? sql-symbols (-> % name symbol))))
  107. (IdentifierExpression. db-spec % nil)
  108. (not (keyword? %))
  109. (ScalarExpression. db-spec %)
  110. :else %))
  111. value)))
  112. (defn expression?
  113. "Returns true if the given object is an Expression."
  114. [o]
  115. (instance? Expression o))
  116. (defmacro expression [form]
  117. `(Expression.
  118. ~(postwalk
  119. #(if (and (seq? %)
  120. (sql-symbols (first %)))
  121. (apply vector
  122. (keyword (first %))
  123. (rest %))
  124. %)
  125. form)))
  126. ;; -----------------------------------------------------------------------------
  127. ;; ## Index Definitions
  128. (defrecord Index [iname tname columns options]
  129. Creatable Dropable
  130. (build-create-statement [this db-spec]
  131. (CreateIndexStatement. db-spec iname tname columns options))
  132. (build-drop-statement [this behavior db-spec]
  133. (DropStatement. db-spec :index iname nil {:tname tname})))
  134. (defn index?
  135. "Returns true if the given object is an Index."
  136. [o]
  137. (instance? Index o))
  138. (defn index
  139. "Constructs an index on the specified table and columns. Can take an
  140. optional index name and a set of options of which only :unique is
  141. available for now. This can be used inside or outside a table
  142. definition, in the later case you just have to provide the name of the
  143. table as a keyword."
  144. {:arglists '([table name? columns & options])}
  145. [table & args]
  146. (let [tname (if (keyword? table) table (:name table))
  147. [name args] (optional keyword? args)
  148. [columns & options] args
  149. name (or name (make-index-name tname
  150. (or ((set options) :unique)
  151. :index)
  152. columns))]
  153. (if (keyword? table)
  154. (Index. name tname columns options)
  155. (update-in table [:indexes] conj
  156. [name (Index. name tname columns options)]))))
  157. ;; -----------------------------------------------------------------------------
  158. ;; ## Constraint Definitions
  159. ;; `Constraint` records are only used to define unspecified constraint.
  160. ;; These type of constraints are useful with the alter drop action. They
  161. ;; can be be construct using the `constraint` function.
  162. ;; *For internal use*.
  163. (defrecord Constraint [cname]
  164. Buildable
  165. (build-definition [this db-spec]
  166. (ConstraintDefinition. db-spec cname)))
  167. (defn constraint?
  168. "Returns true if the given object is a Constraint."
  169. [o]
  170. (instance? Constraint o))
  171. (defn constraint
  172. "Constructs an unspecified abstract constraint definition and add it
  173. to the given table. To be used with alter action while dropping a
  174. constraint."
  175. [table name]
  176. (update-in table [:constraints] conj
  177. [name (Constraint. name)]))
  178. ;; `UniqueConstraint` records can be constructed using the `primary-key` or
  179. ;; `unique` functions. It can represent either a unique or primary key
  180. ;; constraint. *For internal use*.
  181. (defrecord UniqueConstraint [cname ctype columns]
  182. Buildable
  183. (build-definition [this db-spec]
  184. (UniqueConstraintDefinition.
  185. db-spec
  186. cname
  187. ctype
  188. columns)))
  189. (defn unique-constraint
  190. "Constructs an abstract unique (or primary-key depending on the given
  191. type) constraint definition and add it to the given table."
  192. [table name type column-names]
  193. (let [name (or name (make-index-name table type column-names))]
  194. (update-in table [:constraints] conj
  195. [name (UniqueConstraint. name type (vec column-names))])))
  196. (defn primary-key
  197. "Constructs an abstract primary key constraint definition and add it
  198. to the given table. If the name isn't specified, this constraint will
  199. be named using its specification."
  200. ([table column-names] (primary-key table nil column-names))
  201. ([table name column-names]
  202. (unique-constraint table name :primary-key column-names)))
  203. (defn unique
  204. "Constructs an abstract unique constraint definition and add it to the
  205. given table. If the name isn't specified, this constraint will
  206. be named using its specification."
  207. ([table column-names] (unique table nil column-names))
  208. ([table name column-names]
  209. (unique-constraint table name :unique column-names)))
  210. ;; `ForeignKeyConstraint` record can be constructed using the
  211. ;; `foreign-key` function. *For internal use*.
  212. (defrecord ForeignKeyConstraint
  213. [cname columns parent-table parent-columns match triggered-actions]
  214. Buildable
  215. (build-definition [this db-spec]
  216. (ForeignKeyConstraintDefinition.
  217. db-spec
  218. cname
  219. columns
  220. parent-table
  221. parent-columns
  222. match
  223. triggered-actions)))
  224. (defn foreign-key
  225. "Constructs an abstract foreign key constraint definition and add it
  226. to the given table. The `columns` and `parent-table` arguments must be
  227. specified. If no `parent-columns` are specified, the `columns` will be
  228. used in its place.
  229. The `match` optional argument can be one of `:full`, `:partial` or
  230. `:simple`, but note that this isn't supported by most databases.
  231. You can specify `triggered-actions` with pairs of keyword, the first
  232. of the pairs must be one of `:on-delete` or `:on-update`, while the
  233. second one can be one of `:cascade`, `:set-null`, `:restrict`,
  234. `:set-default` or `:no-action`. The actions keywords are directly
  235. translated to SQL keywords, so you can specify custom ones if the
  236. database you're using provide more.
  237. If the name isn't specified, this constraint will be named
  238. using its specification."
  239. {:arglists '([table name? column-names parent-table
  240. parent-column-names? match? & triggered-actions])}
  241. [table & args]
  242. (let [[constraint-name args] (optional keyword? args)
  243. columns (first args)
  244. parent-table (second args)
  245. args (nnext args)
  246. [parent-columns args] (optional vector? args)
  247. parent-columns (or parent-columns columns)
  248. [match args] (optional #{:full :partial :simple} args)
  249. triggered-actions (apply hash-map args)
  250. constraint-name (or constraint-name
  251. (make-index-name table "fkey" columns))]
  252. (update-in table [:constraints] conj
  253. [constraint-name
  254. (ForeignKeyConstraint. constraint-name
  255. columns
  256. parent-table
  257. parent-columns
  258. match
  259. triggered-actions)])))
  260. ;; `CheckConstraint` record can be constructed using the
  261. ;; `check` macro or the `chech*` function. *For internal use*.
  262. (defrecord CheckConstraint
  263. [cname condition]
  264. Buildable
  265. (build-definition [this db-spec]
  266. (CheckConstraintDefinition.
  267. db-spec
  268. cname
  269. (build-definition condition db-spec))))
  270. (defn check*
  271. "Constructs an abstract check constraint definition and add it to the
  272. given table. The `constraint-name` argument is mandatory."
  273. [table constraint-name condition]
  274. (name-required constraint-name "check constraint")
  275. (update-in table [:constraints] conj
  276. [constraint-name
  277. (CheckConstraint. constraint-name
  278. condition)]))
  279. (defmacro check
  280. "Constructs an abstract check constraint definition and add it to the
  281. given table."
  282. [table constraint-name condition]
  283. `(check*
  284. ~table
  285. ~constraint-name
  286. (expression ~condition)))
  287. ;; -----------------------------------------------------------------------------
  288. ;; ## Data-type Definition
  289. ;; `DataType` records can be constructed using the `data-type` function.
  290. ;; *For internal use*.
  291. (defrecord DataType [name args options])
  292. (defn data-type?
  293. "Returns true if the given object is a DataType."
  294. [o]
  295. (instance? DataType o))
  296. (defn data-type
  297. "Constructs an abstract data-type definition using the given keyword
  298. `name`. Can also take an options list of arguments (`args`) and
  299. `options`."
  300. [name & [args options]]
  301. (check-valid-options options :encoding :collate :time-zone)
  302. (update-options
  303. (DataType. name (vec args) {})
  304. options))
  305. ;; -----------------------------------------------------------------------------
  306. ;; ## Column Definition
  307. (defn datetime-now-alias
  308. "If the given default value, it will be replaced by the standard
  309. function returning the current time, date or timestamp depending on
  310. the specified data-type. *For internal use*."
  311. [name default]
  312. (let [value (:value default)]
  313. (if (= value [:now])
  314. (Expression.
  315. (or ({:date [:current_date]
  316. :time [:current_time]
  317. :timestamp [:current_timestamp]} name) value))
  318. default)))
  319. ;; `Column` records can be constructed using the `column` function or
  320. ;; the more specific typed column functions. The `build-definition`
  321. ;; method will create the appropriate `DataTypeClause` for data-type
  322. ;; definitions and `*Expression` AST for default values.
  323. ;; *For internal use*.
  324. (defrecord Column [cname data-type default auto-inc not-null others]
  325. Buildable
  326. (build-definition [this db-spec]
  327. (let [{:keys [name args options]} data-type]
  328. (ColumnDefinition.
  329. db-spec
  330. cname
  331. (when data-type
  332. (DataTypeClause. db-spec name args options))
  333. (if (= default :drop)
  334. :drop
  335. (when default
  336. (build-definition
  337. (datetime-now-alias name default)
  338. db-spec)))
  339. (when auto-inc (AutoIncClause. db-spec))
  340. not-null
  341. others))))
  342. (defn column?
  343. "Returns true if the given object is a Column."
  344. [o]
  345. (instance? Column o))
  346. (defmacro default [form]
  347. `[:default (expression ~form)])
  348. (defn column*
  349. "Constructs an abstract column definition. It'll parse the column
  350. specific options. See the `column` function for more details.
  351. *For internal use*."
  352. [column-name data-type options]
  353. (let [[map-entries others] (separate vector? (filter identity options))
  354. [kw-options others] (separate keyword? others)
  355. {:keys [default encoding collate] :as option-map}
  356. ;; HACK: trying to get refer option, but it's not a map entry and
  357. ;; it's actually consumed by `column`, will clean up later.
  358. (into {} (map (fn [[f & r]] [f (first r)]) map-entries))
  359. option-set (set kw-options)
  360. data-type (update-options data-type
  361. (assoc (select-keys option-map
  362. [:encoding
  363. :collate])
  364. :time-zone (option-set :time-zone)))
  365. not-null (when (:null option-set) false)
  366. not-null (if (and (nil? not-null) (:not-null option-set)) true not-null)
  367. auto-inc (when (:auto-inc option-set) true)]
  368. (name-required column-name "column")
  369. (check-valid-options (into option-set (keys option-map))
  370. :null :not-null :auto-inc :default :primary-key :unique
  371. :encoding :collate :time-zone :refer)
  372. (Column. column-name
  373. data-type
  374. default
  375. auto-inc
  376. not-null
  377. others)))
  378. (defn column
  379. "Constructs an abstract column definition and add it to the given
  380. table. Also creates and add the appropriate column constraints when
  381. these are specified as options. Here's a list of available options:
  382. * `:unique` which construct an unique constraint on that column
  383. * `:primary-key` which make the current column the primary key
  384. * `[:refer tname & options]` which add a foreign key constraint to
  385. the specified table. The options are the same as the `foreign-key`
  386. function with the exception that you can specify only one parent
  387. column.
  388. * `:null` allow null values
  389. * `:not-null` prevents this column from being null
  390. * `:auto-inc` (for integers types) which makes it auto-populated with
  391. incremented integers
  392. * `[:encoding enc]` (for character types) determines which encoding to
  393. use if supported by the database. Also see the natianal character types.
  394. * `[:collate type]` (for character types) determines how equality is
  395. handled
  396. * `:time-zone` (for time types) determines if the type includes a time-zone
  397. It also can be used in alter modify and rename actions. In that
  398. case, if data-type is :to, it acts as a column rename clause and if
  399. data-type is :drop-default, it acts as a column drop default clause."
  400. {:arglists '([table column-name data-type? & options])}
  401. [table column-name & options]
  402. (let [[data-type options] (optional #(instance? DataType %) options)
  403. reference? #(and (vector? %) (= (first %) :refer))
  404. [ptable pcol & others] (->> options (filter reference?) first next)
  405. options (filter (comp not reference?) options)
  406. option-set (when (seq? options) (set options))
  407. add-constraint #(cond (:primary-key option-set)
  408. (primary-key % [column-name])
  409. (:unique option-set)
  410. (unique % [column-name])
  411. ptable
  412. (apply foreign-key % [column-name] ptable
  413. (when pcol [pcol]) others)
  414. :else %)]
  415. (add-constraint
  416. (update-in table [:columns] conj
  417. [column-name
  418. (case (first options)
  419. :to (Column. column-name nil nil nil nil (second options))
  420. :drop-default (Column. column-name nil :drop nil nil [])
  421. (column* column-name data-type options))]))))
  422. ;; -----------------------------------------------------------------------------
  423. ;; ## Typed Column Definitions
  424. ;; Instead of calling the `column` option directly and including the
  425. ;; data-type argument, you can use typed column definition in which case
  426. ;; each types have their own functions.
  427. ;; ### Typed Column Helpers
  428. (defn def-typed-columns*
  429. "Helper for macros that create typed columns definitions. It takes a
  430. sequence of names and define a function for each of them, a vector of
  431. arguments for those functions, `dargs` must specify how to handle
  432. these arguement and `options` must specify the generic column options.
  433. The optional `docs` arguement is appended to the generic docstring.
  434. *For internal use*."
  435. [names args dargs options & [docs]]
  436. `(do
  437. ~@(for [n names]
  438. `(defn ~n
  439. ~(format (str "Constructs an abstract %s column definition and"
  440. " add it to the given table." docs)
  441. (name n))
  442. ~args
  443. (let [dargs# ~dargs
  444. options# ~options]
  445. (apply column
  446. ~'table
  447. ~'column-name
  448. (data-type ~(keyword n) dargs#)
  449. options#))))))
  450. (defmacro def-simple-typed-columns
  451. "Defines typed columns for simple data-types taking no arguments.
  452. *For internal use*."
  453. [& names]
  454. (def-typed-columns*
  455. names
  456. '[table column-name & options]
  457. '[]
  458. 'options))
  459. (defmacro def-numeric-like-typed-columns
  460. "Defines numeric-like typed columns. These typed column funcitons can
  461. take an optional `precision` and `scale` argument. *For internal use*."
  462. [& names]
  463. (def-typed-columns*
  464. names
  465. '[table column-name & [precision scale & options]]
  466. '(-> []
  467. (conj-when (integer? precision) precision)
  468. (conj-when (integer? scale) scale))
  469. '(-> options
  470. (conj-when (not (integer? precision)) precision)
  471. (conj-when (not (integer? scale)) scale))
  472. " Takes an optional `precision` and `scale` arguments."))
  473. (defmacro def-optional-precision-typed-columns
  474. "Defines typed columns with optional precision. Used by `float` and
  475. time data-types. *For internal use*."
  476. [& names]
  477. (def-typed-columns*
  478. names
  479. '[table column-name & [precision & options]]
  480. '(conj-when [] (integer? precision) precision)
  481. '(conj-when options (not (integer? precision)) precision)
  482. " Takes an optional `precision` argument."))
  483. (defmacro def-optional-length-typed-columns
  484. "Defines optionally length-bounded typed columns. Used by binary and
  485. character types. *For internal use*."
  486. [& names]
  487. (def-typed-columns*
  488. names
  489. '[table column-name & [length & options]]
  490. '(conj-when [] (integer? length) length)
  491. '(conj-when options (not (integer? length)) length)
  492. " Takes an optional `length` argument."))
  493. (defmacro def-length-bounded-typed-columns
  494. "Defines length-bounded typed columns. Used by variable binary and
  495. character types. *For internal use*."
  496. [& names]
  497. (def-typed-columns*
  498. names
  499. '[table column-name length & options]
  500. '(conj-when [] (integer? length) length)
  501. '(conj-when options (not (integer? length)) length)
  502. " The `length` arguemnt is mandatory."))
  503. ;; ### Numeric Types
  504. (def-simple-typed-columns
  505. smallint
  506. integer
  507. bigint)
  508. (def-numeric-like-typed-columns
  509. numeric
  510. decimal)
  511. (def-simple-typed-columns
  512. real
  513. double-precision)
  514. (def double double-precision)
  515. (def-optional-precision-typed-columns
  516. float)
  517. ;; ### Character Types
  518. (def-optional-length-typed-columns
  519. char
  520. nchar
  521. clob
  522. nclob)
  523. (def text clob)
  524. (def ntext nclob)
  525. (def-length-bounded-typed-columns
  526. varchar
  527. nvarchar)
  528. ;; ### Binary Types
  529. (def-optional-length-typed-columns
  530. binary
  531. blob)
  532. (def-length-bounded-typed-columns
  533. varbinary)
  534. ;; ### Boolean Type
  535. (def-simple-typed-columns
  536. boolean)
  537. ;; ### Data/time Types
  538. (def-simple-typed-columns
  539. date)
  540. (def-optional-precision-typed-columns
  541. time
  542. timestamp)
  543. ;; -----------------------------------------------------------------------------
  544. ;; ## Table Definition
  545. (defn- build-table-elements [db-spec method & elements]
  546. (->> (apply concat elements)
  547. (map #(when (second %)
  548. (method (second %) db-spec)))
  549. (filter identity)))
  550. ;; `Table` records can be constructed using the `table*` function or
  551. ;; the `table` macro. *For internal use*.
  552. (defrecord Table [name columns constraints indexes]
  553. Alterable Creatable Dropable
  554. (build-alter-statement [this action db-spec]
  555. (let [elements (build-table-elements db-spec
  556. build-definition
  557. columns
  558. constraints)]
  559. (for [element elements]
  560. (AlterTableStatement.
  561. db-spec
  562. name
  563. action
  564. element))))
  565. (build-create-statement [this db-spec]
  566. (conj
  567. (build-table-elements db-spec build-create-statement indexes)
  568. (CreateTableStatement.
  569. db-spec
  570. name
  571. (build-table-elements db-spec build-definition columns constraints))))
  572. (build-drop-statement [this behavior db-spec]
  573. (DropStatement. db-spec :table name behavior nil)))
  574. (defn table?
  575. "Returns true if the given object is a Table."
  576. [o]
  577. (instance? Table o))
  578. (defn table*
  579. "Constructs an abstract table definition. The `table-name` is
  580. mandatory."
  581. [name & [columns constraints indexes]]
  582. (name-required name "table")
  583. (Table. name
  584. (or columns {})
  585. (or constraints {})
  586. (or indexes {})))
  587. (defmacro table
  588. "Constructs an abstract table definition containing the given
  589. elements."
  590. [name & elements]
  591. `(-> (table* ~name) ~@(reverse elements)))
  592. ;; -----------------------------------------------------------------------------
  593. ;; ## Schema Definition
  594. ;; `Schema` records can be constructed using the `schema` function.
  595. ;; *For internal use*.
  596. (defrecord Schema [sname tables indexes options]
  597. Creatable Dropable
  598. (build-create-statement [this db-spec]
  599. (CreateSchemaStatement.
  600. db-spec
  601. sname
  602. (flatten
  603. (map #(build-create-statement (second %) db-spec)
  604. (concat tables indexes)))))
  605. (build-drop-statement [this behavior db-spec]
  606. (DropStatement. db-spec :schema sname behavior nil)))
  607. (defn schema?
  608. "Returns true if the given object is a Schema."
  609. [o]
  610. (instance? Schema o))
  611. (defn- filtered-elements->map
  612. [pred elements]
  613. (into (sorted-map)
  614. (map #(vector (:name %) %)
  615. (filter pred elements))))
  616. (defn schema
  617. "Constructs an abstract schema definition."
  618. {:arglists '([schema-name options? & elements])}
  619. [schema-name & args]
  620. (name-required schema-name "schema")
  621. (let [[options elements] (optional (comp not definition?) args)]
  622. (Schema.
  623. schema-name
  624. (filtered-elements->map table? elements)
  625. (filtered-elements->map index? elements)
  626. (or options {}))))
  627. ;; -----------------------------------------------------------------------------
  628. ;; ## Definitions Hierarchy
  629. ;; The definition hierarchy makes it easy to test if an object represent
  630. ;; an abstract schema element definition. See the `definition?`
  631. ;; predicate.
  632. (derive Index ::definition)
  633. (derive Constraint ::definition)
  634. (derive UniqueConstraint ::definition)
  635. (derive ForeignKeyConstraint ::definition)
  636. (derive CheckConstraint ::definition)
  637. (derive DataType ::definition)
  638. (derive Column ::definition)
  639. (derive Table ::definition)
  640. (derive Schema ::definition)