index.html 13.2 KB
Newer Older
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title><!--#include virtual="project-name" --></title>
  <link rel="stylesheet" type="text/css" href="style.css"/>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>

<body>
 <div class="header">
   <h1>Submarine</h1>
 </div>


<p>Submarine is a Common Lisp library that's somewhere between a
PostgreSQL library an an object persistency system.  It uses
Ryszard Szopa's avatar
Ryszard Szopa committed
19
20
21
22
23
24
25
<a href="http://common-lisp.net/project/postmodern">Postmodern</a> to
communicate with the database. The basic idea is that you create your
classes in the metaclass DB-CLASS and submarine cares about creating
SQL tables or, if the tables already exist, checking if they conform
to the provided specification.  Moreover, Submarine supports an
intuitive way of expressing both one-to-many and many-to-many
relations.</p>
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
26
27
28
29
30

<h1>Table of contents</h1>
<ol>
<li><a href="#getting_submarine">Getting Submarine</a></li>
<li><a href="#dependencies">Dependencies</a></li>
Ryszard Szopa's avatar
Ryszard Szopa committed
31
<li><a href="#support_and_mailing_lists">Support and mailing lists</a></li>
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
32
33
34
35
36
<li><a href="#license">License</a></li>

<li><a href="#introduction_for_postmodern_users">Introduction for
    Postmodern users</a></li>
<li><a href="#api_reference">API reference</a></li>
Ryszard Szopa's avatar
Ryszard Szopa committed
37
<li><a href="#quick_start">Quick start</a></li>
Ryszard Szopa's avatar
Ryszard Szopa committed
38
<li><a href="#caveats_and_todos">Cave-eats and to-do's</a></li>
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
39
40
41
42

</ol>

<a name="getting_submarine"><h1>Getting submarine</h1></a>
Ryszard Szopa's avatar
Ryszard Szopa committed
43
44
45
46
47
48
49
<p>
  You can download both <a href="submarine.tar.gz">Submarine</a>,
  and <a href="mop-utils.tar.gz">MOP-utils</a> (a small library of MOP
  related utilities on which Submarine depends). However, submarine
  isn't very stable at the moment, so I strongly advise you to get the
  newest version through darcs.
</p>
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
50
51
52
53
54
55
56
57
58
59
<code>
    darcs get http://szopa.tasak.gda.pl/repos/submarine/
</code>
<p>
You will also need my MOP utilities, which may be incorporated into
submarine in the close future:
</p>
<code>
    darcs get http://szopa.tasak.gda.pl/repos/mop-utils/
</code>
Ryszard Szopa's avatar
Ryszard Szopa committed
60
61
62
63
64
<p>In both cases (downloading the archive or getting it through darcs)
  you will need to link the <code>.asd</code> files to some place
  visible by <code>ASDF</code>.</p>

<p>I will make Submarine ASDF-installable soon.</p>
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
65
66
67
68
69
70
71
<a name="dependencies"><h1>Dependencies</h1></a>


<p>Submarine depends on Postmodern and Iterate.  It uses also my
library of MOP utilities, MOP-UTILS, which may become a separate
library in the future.  On platforms other than SBCL, mop-utils needs
Closer-mop.</p>
Ryszard Szopa's avatar
Ryszard Szopa committed
72
73
74
75
76
77
78
79
80
<a name="support_and_mailing_lists">
<h1>Support and mailing lists</h1></a>
<p>The <a
href="http://common-lisp.net/mailman/listinfo/submarine-devel">submarine-devel</a>
mailing list can be used for questions, discussion, bug-reports,
patches, or anything else relating to this library. Or mail the
author/maintainer
directly: <a href="mailto:ryszard.szopa@gmail.com">Ryszard
Szopa</a>.</p>
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
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
<a name="license"><h1>License</h1></a>

<p>Submarine is released under a BSD-like license. Which approximately
means you can use the code in whatever way you like, except for
passing it off as your own or releasing a modified version without
indication that it is not the original.</p>


<a name="introduction_for_postmodern_users"><h1>Introduction for Postmodern users</h1></a>


<p>
Submarine started as a patch that added, among others, foreign keys
support to Postmodern.  This meant hacking the DEFTABLE macro and
TABLE and TABLEFIELD classes.  After some time I realized that my
modified Postmodern macros had become far too large and heavy to be
easy maintainable. So, I decided to leave Postmodern's code as it was
and write a separate library.
</p>

<p>Submarine tries to keep as much as possible of Postmodern's original
API. I use the same terminology, and functions with similar names will
probably do very similar things. My purpose was twofold. First of all,
I wanted to make porting programs using Postmodern to Submarine
easy. Secondly, this would allow me to use some of Marijn Haverbeke's
superb documentation nearly without any changes.</p>

<p>Main differences between Postmodern and Submarine:</p>
<ul>
 <li> You don't have to create DAO (database access object) classes and SQL
tables separately. You just create classes belonging to a certain
metaclass, and the library cares of the rest.</li>

 <li> If the table with an appropriate name exists in the database,
Submarine will test if it has columns with the right names and
types. If a column does not exist or has a wrong type, Submarine will
offer the user a possibility to fix it. Submarine will warn, but do
nothing about any additional columns in the table.</li>

 <li> Each DB-CLASS class has its own connection specification. This means
that you can just access your objects, without wrapping them in a
WITH-CONNECTION macro, and they will care about setting the right
connection.</li>

 <li> DAO is a basis class for DB-CLASS classes, and methods that are
supposed to work on an object of any DB-CLASS class use DAO as a
dispatch type. Of course, you don't have to use DAO as a base class,
but it makes a lot of things easier.</li>

 <li> Submarine supports foreign keys. If you define a DB-CLASS class
with a slot whose type is another DB-CLASS, it is treated as foreign
key and an appropriate constraint is added to the database.</li>

 <li> Submarine provides a function to retrieve all the elements of a
given type standing in a many-to-one relation with some DAO object.</li>

 <li> The DEF-MANY-TO-MANY macro defines a many-to-many relation between
two classes and creates methods appropriate to their retrieval (it
creates a link table in the database).</li>

 <li> DB-CLASS slots have an additional attribute TRANSIENT. If you set
it to a non-NIL value, it will behave just as a
STANDARD-SLOT-DEFINITION and will be ignored in database related
operations.</li>

 <li> Submarine uses MOP rather than macros to achieve its purposes. This
makes it easier to maintain and to extend.</li>
</ul>

<h1><a name="api_reference">API reference</a></h1>
<p>Here you can find some TINAA generated documentation. I document my
  code a lot, so it should be quite useful (<code>M-.</code> should be
  your good friend).</p>
<ul>
  <li><a href="darcs/submarine/doc/submarine-package/">Submarine</a></li>
  <li><a href="darcs/mop-utils/doc/mop-utils-package/">MOP-utils</a></li>
</ul>

Ryszard Szopa's avatar
Ryszard Szopa committed
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
<a name="quick_start"<h1>Quick start</h1></a>
<p>
  API references are great, but they are useful only when you already
  have a basic idea about what can be done with the library. So, here's a
  poor man's tutorial for Submarine. The code as presented here isn't
  really suitable for copying and pasting directly into
  the <abbr title="READ-EVAL-PRINT-LOOP">REPL</abbr>. However, you may
  be interested in <a href="darcs/submarine/doc/example.lisp">this
  file</a>, which is intended to be opened in Emacs with Slime and
  then <code>C-c</code>'ed form by form.
</p>

<p>First, we should create a package for our example.</p>
<code>
<pre>
(in-package :asdf)

(defpackage :submarine-example
  (:use :cl :submarine))
(in-package :submarine-example)

</pre>
</code>
<p>
 DEFDAO is a wrapper macro, that creates a class inheriting from DAO
 and with the metaclass set to db-class.
</p>
<code>
<pre>
(defdao affiliation ()
  ((name        :type string :initarg :name        :accessor affiliation-name))
  (:connection-spec "submarine-test" "richard" "dupa" "localhost"))
</pre>
</code>
Ryszard Szopa's avatar
Ryszard Szopa committed
193
<p>The arguments in the :connection-spec are the following: name of the
Ryszard Szopa's avatar
Ryszard Szopa committed
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
  database, name of the user, password, host. The user should be able
  to create tables in the given database.</p>
<p>Let's macroexpand last form:</p>
<code>
<pre>
;; (DEFCLASS AFFILIATION (DAO)
;;           ((NAME :TYPE STRING :INITARG :NAME :ACCESSOR AFFILIATION-NAME))
;;           (:CONNECTION-SPEC "submarine-test" "richard" "dupa" "localhost")
;;           (:METACLASS DB-CLASS))
</pre>
</code>

Every slot that is not transient must have a TYPE---it's needed by
PostgreSQL. A person may not have an affiliation, but he or she
must have a name. (Not-null is by default set to NIL.)
<code>
<pre>
(defdao person ()
  ((name        :initarg :name :accessor person-name :type string :not-null t)
   (affiliation :accessor person-affiliation :type affiliation :foreign t :initform nil
                :initarg :affiliation))
  (:connection-spec "submarine-test" "richard" "dupa" "localhost"))
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
216

Ryszard Szopa's avatar
Ryszard Szopa committed
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
(defdao club ()
  ((name :initarg :name :accessor club-name :type string))
  (:connection-spec "submarine-test" "richard" "dupa" "localhost"))
</pre>
</code>
<p>
There's a many-to-many relationships between persons and clubs: a
club can have several members, one person may be the member of a
number of clubs.
</p>
<code>
<pre>
(def-many-to-many person club :connection-spec ("submarine-test" "richard" "dupa" "localhost"))
</pre>
</code>
<p>
This the macroexpansion of the last form. We can see it defines two
methods, for retrieving all objects related to its argument.
</p>
<code>
<pre>
;; (progn
;;  (defclass person-club (dao)
;;            ((person :type person :accessor person)
;;             (club :type club :accessor club))
;;            (:connection-spec "submarine-test" "richard" "dupa" "localhost")
;;            (:metaclass db-class))
;;  (defmethod club ((submarine::object person))
;;             (mapcar #'club
;;                     (submarine::select-dao-fun 'person-club
;;                                                (list := 'person
;;                                                      (get-id
;;                                                       submarine::object)))))
;;  (defmethod person ((submarine::object club))
;;             (mapcar #'person
;;                     (submarine::select-dao-fun 'person-club
;;                                                (list := 'club
;;                                                      (get-id
;;                                                       submarine::object))))))
</pre>
</code>
<p>Now, we can populate our database by creating some objects:</p>
<code>
<pre>
(let* ((lions (save-dao (make-instance 'club :name "Lion's")))
       (rotary (save-dao (make-instance 'club :name "Rotary")))
       (lodge (save-dao (make-instance 'club :name "The Lodge")))
       (democ (save-dao (make-instance 'affiliation :name "Democrats")))
       (repub (save-dao (make-instance 'affiliation :name "Republicans")))
       (john (make-instance 'person :name "John"))
       (roger (make-instance 'person :name "Roger" :affiliation democ))
       (henry (make-instance 'person :name "Henry" :affiliation repub)))
</pre>
</code>
<p>
  As save-dao returns its argument
  after saving it, we can do it at the same time as initializing
  the variables.
</p>
<p>  
  Let's add the last names.
</p>
<code>
<pre>
  (setf (person-name henry) "Henry Johnson")
  (setf (person-name roger) "Roger Monroe")
</pre>
</code>

<p>  Little Johny declares himself as a Democrat:</p>
<code>
<pre>
  (setf (person-affiliation john) democ)

  (dolist (person (list john roger henry))
    (save-dao person))
</pre>
</code>

<p>  Apparently, they are all masons!</p>
<code>
<pre>
  (dolist (person (list john roger henry))
    (relate person lodge))
</pre>
</code>
<p>But they also belong to other clubs:</p>
<code>
<pre>    
  (relate john lions)
  (relate roger rotary)
  (relate henry lions)
  (relate henry rotary)
</pre>
</code>  
<p>  Now, let's see what do we know about our small world.</p>
<code><pre>  
  (format t "All persons: ~%~{ * ~A~%~}~%~%" (mapcar 'person-name (select-dao 'person)))
  (format t "All democrats: ~%~{ * ~A~%~}~%" (mapcar 'person-name (get-all 'person democ)))
  (format t "All masons: ~%~{ * ~A~%~}~%" (mapcar 'person-name (person lodge)))
  (format t "All the clubs of Henry: ~%~{ * ~A~%~}~%" (mapcar 'club-name (club henry))))
</pre></code>
<p>The effect of executing the last form:</p>
<pre>
; All persons: 
;  * John
;  * Roger Monroe
;  * Henry Johnson

; All democrats: 
;  * John
;  * Roger Monroe

; All masons: 
;  * John
;  * Roger Monroe
;  * Henry Johnson

; All the clubs of Henry: 
;  * The Lodge
;  * Lion's
;  * Rotary
</pre>
<p>We can also retrieve dao's from the database if we have their
  ID:</p>
<code><pre>
(let ((john (make-instance 'person :id 1)))
  (format t "This is number 1: ~A" (person-name john)))
</pre>
</code>
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
347

Ryszard Szopa's avatar
Ryszard Szopa committed
348
349
350
351
352
<p>After executing the last form:<p>
<pre>
;  This is number 1: John
</pre>
<p>(Well, I knew it.)</p>
Ryszard Szopa's avatar
Ryszard Szopa committed
353
354
<a name="caveats_and_todos"<h1>Cave-eats and to-do's</h1></a>
<ul>
Ryszard Szopa's avatar
Ryszard Szopa committed
355
  <li>Transactions don't seem to be completely reliable. When
Ryszard Szopa's avatar
Ryszard Szopa committed
356
357
  connections are made per object, Postmodern's model of transaction
  is just not enough. This part asks for a serious rethinking.</li>
Ryszard Szopa's avatar
Ryszard Szopa committed
358
359
360
361
  <li>When a table with the same name as the one to be crated exists,
  it does check if the columns are created, but not the
  constraints. This is may be very annoying, and fixing it is very
  high on my priority list.</li>
Ryszard Szopa's avatar
Ryszard Szopa committed
362
363
364
365
366
367
  <li>At the moment it doesn't pass the tests on CMUCL (these are
  mainly Postmodern related issues).</li>
  <li>Submarine doesn't support slot-type declared with a form
  starting with OR, AND or NOT (also, not all of them make sense from
  the point of view of Postgres). With some compiler settings
  slots with NOT-NULL set to NIL may cause problems.</li>
Ryszard Szopa's avatar
init  
Ryszard Szopa committed
368
369
</body>
</html>