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