Changes to more broadly support auto-increment. new odbc-postgresql-database type
[clsql.git] / doc / ref-ooddl.xml
index 5c51f4b5f35ff3f3c636cea7c2e8e37485a047e8..4a2cffa140b4600703290805f4e6352301b5af1d 100644 (file)
@@ -5,9 +5,9 @@
 %myents;
 ]>
 
-<!-- Object Oriented Data Definition Language --> 
-<reference id="ref-ooddl"> 
-  <title>Object Oriented Data Definition Language (OODDL)</title> 
+<!-- Object Oriented Data Definition Language -->
+<reference id="ref-ooddl">
+  <title>Object Oriented Data Definition Language (OODDL)</title>
   <partintro>
     <para>
       The Object Oriented Data Definition Language (OODDL) provides
@@ -15,7 +15,7 @@
       (CLOS) objects.  SQL tables are mapped to CLOS objects with the
       SQL columns being mapped to slots of the CLOS object.
     </para>
-    <para> 
+    <para>
       The mapping between SQL tables and CLOS objects is defined with
       the macro <link
       linkend="def-view-class"><function>def-view-class</function></link>. SQL
     <refsect1>
       <title>Slots</title>
       <para>
-       <simplelist> 
+       <simplelist>
          <member>slot VIEW-DATABASE is of type (OR NULL DATABASE)
          which stores the associated database for the
          instance.</member>
-       </simplelist> 
+       </simplelist>
       </para>
     </refsect1>
   </refentry>
       <title>Value Type</title>
       <para>
        Fixnum
-      </para> 
+      </para>
     </refsect1>
     <refsect1>
       <title>Initial Value</title>
       <para><parameter>255</parameter></para>
     </refsect1>
     <refsect1>
-      <title>Description</title> 
+      <title>Description</title>
       <para>
        If a slot of a class defined by
        <function>def-view-class</function> is of the type
      (c :type varchar))))
 => #&lt;Standard-Db-Class S80 {480A431D}>
 
-(create-view-from-class 's80)   
-=> 
-(table-exists-p [s80]) 
+(create-view-from-class 's80)
+=>
+(table-exists-p [s80])
 => T
       </screen>
       <para>
 (def-view-class foo () ((a :type (string 80))))
 => #&lt;Standard-Db-Class FOO {4807F7CD}>
 (create-view-from-class 'foo)
-=> 
+=>
 (list-tables)
 => ("FOO")
       </screen>
                wide. [not supported by all database backends]
              </member>
              <member>
-               <parameter>bigint</parameter> - An integer column 
+               <parameter>bigint</parameter> - An integer column
                64-bits wide. [not supported by all database backends]
              </member>
              <member>
                provides a number of time manipulation functions to
                support objects of type <type>wall-time</type>.
              </member>
+             <member>
+               <parameter>date</parameter> - a slot which stores the
+               date (without any time of day resolution) in a
+                column. &clsql; provides a number of time
+               manipulation functions that operate on date values.
+             </member>
              <member>
                <parameter>duration</parameter> - stores a
                <type>duration</type> structure.  &clsql; provides
              </member>
              <member>
                <parameter>keyword</parameter> - stores a keyword
-             </member> 
+             </member>
              <member><parameter>symbol</parameter> - stores a symbol</member>
              <member>
                <parameter>list</parameter> - stores a list by writing
              similarly to <parameter>list</parameter></member>
            </simplelist>
          </para>
-         
+
        </listitem>
        <listitem>
          <para>
            slot value.
          </para>
        </listitem>
+       <listitem>
+         <para>
+           <parameter>:db-reader</parameter> - If a string, then when
+           reading values from the database, the string will be used
+           for a format string, with the only value being the value
+           from the database.  The resulting string will be used as
+           the slot value.  If a function then it will take one
+           argument, the value from the database, and return the
+           value that should be put into the slot. If a symbol, then
+           the symbol-function of the symbol will be used.
+          </para>
+        </listitem>
+       <listitem>
+         <para>
+           <parameter>:db-writer</parameter> - If a string, then when
+           reading values from the slot for the database, the string
+           will be used for a format string, with the only value
+           being the value of the slot.  The resulting string will be
+           used as the column value in the database.  If a function
+           then it will take one argument, the value of the slot, and
+           return the value that should be put into the database. If
+           a symbol, then the symbol-function of the symbol will be
+           used.
+          </para>
+        </listitem>
       </itemizedlist>
     </refsect1>
     <refsect1>
              are converted to underscore characters.
            </para>
          </listitem>
+         <listitem>
+           <para>
+             <parameter>:normalizedp</parameter> - specifies whether
+          this class uses normalized inheritance from parent classes.
+          Defaults to nil, i.e. non-normalized schemas. When true,
+          SQL database tables that map to this class and parent
+          classes are joined on their primary keys to get the full
+          set of database columns for this class.  This means that 
+          the primary key of the base class will be copied to all 
+          subclasses as we insert so that all parent classes of an 
+          instance will have the same value in their primary key slots
+          (see tests/ds-nodes.lisp and oodml.lisp)
+           </para>
+         </listitem>
        </itemizedlist>
       </para>
     </refsect1>
        this class.
       </para>
 
+      <refsect2>
+      <title>Normalized inheritance schemas</title>
+      <para>
+    Specifying that <symbol>:normalizedp</symbol> is <symbol>T</symbol>
+    tells &clsql; to normalize the database schema for inheritance.
+    What this means is shown in the examples below.
+      </para>
+
+      <para>
+    With <symbol>:normalizedp</symbol> equal to <symbol>NIL</symbol>
+    (the default) the class inheritance would result in the following:
+      </para>
+      <screen>
+(def-view-class node ()
+  ((title :accessor title :initarg :title :type (varchar 240))))
+
+SQL table NODE:
++-------+--------------+------+-----+---------+-------+
+| Field | Type         | Null | Key | Default | Extra |
++-------+--------------+------+-----+---------+-------+
+| TITLE | varchar(240) | YES  |     | NULL    |       |
++-------+--------------+------+-----+---------+-------+
+
+(def-view-class user (node)
+  ((user-id :accessor user-id :initarg :user-id
+            :type integer :db-kind :key :db-constraints (:not-null))
+   (nick :accessor nick :initarg :nick :type (varchar 64))))
+
+SQL table USER:
++---------+--------------+------+-----+---------+-------+
+| Field   | Type         | Null | Key | Default | Extra |
++---------+--------------+------+-----+---------+-------+
+| USER_ID | int(11)      | NO   | PRI |         |       |
+| NICK    | varchar(64)  | YES  |     | NULL    |       |
+| TITLE   | varchar(240) | YES  |     | NULL    |       |
++---------+--------------+------+-----+---------+-------+
+      </screen>
+
+      <para>
+    Using <symbol>:normalizedp</symbol> <symbol>T</symbol>, both
+    view-classes need a primary key to join them on:
+      </para>
+      <screen>
+(def-view-class node ()
+  ((node-id :accessor node-id :initarg :node-id
+            :type integer :db-kind :key
+            :db-constraints (:not-null))
+   (title :accessor title :initarg :title :type (varchar 240))))
+
+SQL table NODE:
++---------+--------------+------+-----+---------+-------+
+| Field   | Type         | Null | Key | Default | Extra |
++---------+--------------+------+-----+---------+-------+
+| NODE_ID | int(11)      | NO   | PRI |         |       |
+| TITLE   | varchar(240) | YES  |     | NULL    |       |
++---------+--------------+------+-----+---------+-------+
+
+(def-view-class user (node)
+  ((user-id :accessor user-id :initarg :user-id
+            :type integer :db-kind :key :db-constraints (:not-null))
+   (nick :accessor nick :initarg :nick :type (varchar 64)))
+  (:normalizedp t))
+
+SQL table USER:
++---------+-------------+------+-----+---------+-------+
+| Field   | Type        | Null | Key | Default | Extra |
++---------+-------------+------+-----+---------+-------+
+| USER_ID | int(11)     | NO   | PRI |         |       |
+| NICK    | varchar(64) | YES  |     | NULL    |       |
++---------+-------------+------+-----+---------+-------+
+      </screen>
+
+      <para>
+        In this second case, all slots of the view-class 'node
+        are also available in view-class 'user, and can be used
+        as one would expect. For example, with the above normalized
+        view-classes 'node and 'user, and SQL tracing turned on:
+      </para>
+      <screen>
+CLSQL> (setq test-user (make-instance 'user :node-id 1 :nick "test-user"
+                                            :title "This is a test user"))
+<![CDATA[#<USER {1003B392E1}>]]>
+
+CLSQL> (update-records-from-instance test-user :database db)
+<![CDATA[
+;; .. => INSERT INTO NODE (NODE_ID,TITLE) VALUES (1,'This is a test user')
+;; .. <= T
+;; .. => INSERT INTO USER (USER_ID,NICK) VALUES (1,'test-user')
+;; .. <= T
+1
+]]>
+
+CLSQL> (node-id test-user)
+1
+
+CLSQL> (title test-user)
+"This is a test user"
+
+CLSQL> (nick test-user)
+"test-user"
+      </screen>
+      <para>
+        Notes from a refactor of this code.
+
+        There are many assumptions that need to be met for normalized classes to work
+
+        * The each of the classes should have its own single key column (of a different name)
+          that will contain an identical value.  EG: node has a node-id, setting which
+          is a node has a node-id and a setting-id which must be equal.  You cannot use
+          node-id as the primary key on both tables (as I would have expected).  The exception
+          to this seems to be if your class has no slots at all, then you dont need to have a
+          single key column, because your class is fully represented in the db by its parent(s)
+
+        * more than one parent class per normalized class should be considered experimental
+          and untested (vaya con Dios)
+
+        * There are a few code paths that just dont pay any attention to normalized classes
+          eg: delete-records-for-instance
+
+      </para>
+      </refsect2>
     </refsect1>
     <refsect1>
       <title>Examples</title>
    (birthday :type clsql:wall-time :initarg :birthday)
    (bd-utime :type clsql:universal-time :initarg :bd-utime)
    (hobby :db-kind :virtual :initarg :hobby :initform nil)))
-  
+
 (def-view-class employee (person)
   ((emplid
     :db-kind :key
       <title>Examples</title>
       <screen>
 (list-tables)
-=> ("FOO" "BAR")       
+=> ("FOO" "BAR")
 (drop-view-from-class 'foo)
-=> 
+=>
 (list-tables)
-=> ("BAR")     
+=> ("BAR")
       </screen>
     </refsect1>
     <refsect1>
     <refsect1>
       <title>Notes</title>
       <para>
-        None. 
+        None.
       </para>
     </refsect1>
   </refentry>
       <screen>
 (list-classes)
 => (#&lt;clsql-sys::standard-db-class big> #&lt;clsql-sys::standard-db-class employee-address>
-    #&lt;clsql-sys::standard-db-class address> #&lt;clsql-sys::standard-db-class company> 
+    #&lt;clsql-sys::standard-db-class address> #&lt;clsql-sys::standard-db-class company>
     #&lt;clsql-sys::standard-db-class employee>)
 
 (list-classes :test #'(lambda (c) (> (length (symbol-name (class-name c))) 3)))
     <refsect1>
       <title>Notes</title>
       <para>
-        None. 
+        None.
       </para>
     </refsect1>
   </refentry>
 
 
-</reference> 
+</reference>