index.html 13.2 KB
Newer Older
Ryszard Szopa's avatar
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
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
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>
31
<li><a href="#support_and_mailing_lists">Support and mailing lists</a></li>
Ryszard Szopa's avatar
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>
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
Ryszard Szopa committed
39 40 41 42

</ol>

<a name="getting_submarine"><h1>Getting submarine</h1></a>
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
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>
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
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>
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
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>
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
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
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
Ryszard Szopa committed
368 369
</body>
</html>