Skip to content
Snippets Groups Projects
index.shtml 13.7 KiB
Newer Older
Ryszard Szopa's avatar
Ryszard Szopa committed
<?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
<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

<h1>Table of contents</h1>
<ol>
<li><a href="#getting_submarine">Getting Submarine</a></li>
<li><a href="#dependencies">Dependencies</a></li>
<li><a href="#support_and_mailing_lists">Support and mailing lists</a></li>
Ryszard Szopa's avatar
Ryszard Szopa committed
<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>
<li><a href="#quick_start">Quick start</a></li>
Ryszard Szopa's avatar
Ryszard Szopa committed
<li><a href="#caveats_and_todos">Caveats and to-do's</a></li>
Ryszard Szopa's avatar
Ryszard Szopa committed

</ol>

<a name="getting_submarine"><h1>Getting submarine</h1></a>
Ryszard Szopa's avatar
Ryszard Szopa committed
<p>Submarine is ASDF-installable.</p>

<p> You can also download directly 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> <code> darcs
  get http://common-lisp.net/project/submarine/darcs/submarine/ </code> <p> You
Ryszard Szopa's avatar
Ryszard Szopa committed
  will also need my MOP utilities, which may be incorporated into
  submarine in the close future: </p> <code> darcs get
  http://common-lisp.net/project/submarine/darcs/mop-utils/ </code> <p>In both
Ryszard Szopa's avatar
Ryszard Szopa committed
  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>

Ryszard Szopa's avatar
Ryszard Szopa committed

<p>You can also browse the source of <a href="http://common-lisp.net/cgi-bin/darcsweb/darcsweb.cgi?r=submarine-submarine;a=summary">submarine</a>
and <a
href="http://common-lisp.net/cgi-bin/darcsweb/darcsweb.cgi?r=submarine-mop-utils;a=summary">mop-utils</a>.</p>
Ryszard Szopa's avatar
Ryszard Szopa committed
<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>
<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
<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
<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>
<p>The arguments in the :connection-spec are the following: name of the
Ryszard Szopa's avatar
Ryszard Szopa committed
  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

Ryszard Szopa's avatar
Ryszard Szopa committed
(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 "~@{~2&All ~A: ~%~{ * ~A~&~}~}~&"
	  "persons"            (mapcar 'person-name (select-dao 'person))
	  "democrats"          (mapcar 'person-name (get-all 'person democ))
	  "masons"             (mapcar 'person-name (person lodge))
	  "the clubs of Henry" (mapcar 'club-name (club henry))))
Ryszard Szopa's avatar
Ryszard Szopa committed
</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

Ryszard Szopa's avatar
Ryszard Szopa committed
<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
<a name="caveats_and_todos"<h1>Caveats and to-do's</h1></a>
Ryszard Szopa's avatar
Ryszard Szopa committed
<ul>
Ryszard Szopa's avatar
Ryszard Szopa committed
  <li>Transactions don't seem to be completely reliable. When
Ryszard Szopa's avatar
Ryszard Szopa committed
  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
  <li>When a table with the same name as the one to be created exists,
  Submarine does check if the right columns exist, but does nothing
  about the constraints involving the table (specially, doesn't delete
  unnecessary ones). This is may be very annoying, and fixing it is very
Ryszard Szopa's avatar
Ryszard Szopa committed
  high on my priority list.</li>
Ryszard Szopa's avatar
Ryszard Szopa committed
  <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

<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-2621006-1";
urchinTracker();
</script>
Ryszard Szopa's avatar
Ryszard Szopa committed
</body>
</html>