Improved documentation of normalized classes and changelog entry
[clsql.git] / sql / ooddl.lisp
index eae4f0efdd4bc58af0b1c6ed22f7eb24016862ad..50c37a691a7639ad1c48fdfe71d6f4c1848e4a8b 100644 (file)
@@ -1,8 +1,6 @@
 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
 ;;;; *************************************************************************
 ;;;;
-;;;; $Id$
-;;;;
 ;;;; The CLSQL Object Oriented Data Definitional Language (OODDL)
 ;;;;
 ;;;; This file is part of CLSQL.
@@ -17,7 +15,7 @@
 
 (defclass standard-db-object ()
   ((view-database :initform nil :initarg :view-database :reader view-database
-    :db-kind :virtual))
+                  :db-kind :virtual))
   (:metaclass standard-db-class)
   (:documentation "Superclass for all CLSQL View Classes."))
 
 (defvar *db-initializing* nil)
 
 (defmethod slot-value-using-class ((class standard-db-class) instance slot-def)
+  "When a slot is unbound but should contain a join object or a value from a
+   normalized view-class, then retrieve and set those slots, so the value can
+   be returned"
   (declare (optimize (speed 3)))
   (unless *db-deserializing*
     (let* ((slot-name (%svuc-slot-name slot-def))
-           (slot-object (%svuc-slot-object slot-def class))
-           (slot-kind (view-class-slot-db-kind slot-object)))
-      (when (and (eql slot-kind :join)
-                 (not (slot-boundp instance slot-name)))
+           (slot-object (%svuc-slot-object slot-def class)))
+      (unless (slot-boundp instance slot-name)
         (let ((*db-deserializing* t))
-          (if (view-database instance)
-              (setf (slot-value instance slot-name)
-                    (fault-join-slot class instance slot-object))
-              (setf (slot-value instance slot-name) nil))))))
+          (cond
+            ((join-slot-p slot-def)
+             (setf (slot-value instance slot-name)
+                   (if (view-database instance)
+                       (fault-join-slot class instance slot-object)
+                       ;; TODO: you could in theory get a join object even if
+                       ;; its joined-to object was not in the database
+                       nil
+                       )))
+            ((not-direct-normalized-slot-p class slot-def)
+             (if (view-database instance)
+                 (update-fault-join-normalized-slot class instance slot-def)
+                 (setf (slot-value instance slot-name) nil))))))))
   (call-next-method))
 
 (defmethod (setf slot-value-using-class) (new-value (class standard-db-class)
                                           instance slot-def)
+  "Handle auto syncing values to the database if *db-auto-sync* is t"
   (declare (ignore new-value))
   (let* ((slot-name (%svuc-slot-name slot-def))
          (slot-object (%svuc-slot-object slot-def class))
          (slot-kind (view-class-slot-db-kind slot-object)))
     (prog1
-      (call-next-method)
+        (call-next-method)
       (when (and *db-auto-sync*
                  (not *db-initializing*)
                  (not *db-deserializing*)
@@ -62,7 +71,7 @@
         (update-record-from-slot instance slot-name)))))
 
 (defmethod initialize-instance ((object standard-db-object)
-                                        &rest all-keys &key &allow-other-keys)
+                                &rest all-keys &key &allow-other-keys)
   (declare (ignore all-keys))
   (let ((*db-initializing* t))
     (call-next-method)
 in DATABASE which defaults to *DEFAULT-DATABASE*."
   (let ((tclass (find-class view-class-name)))
     (if tclass
-        (let ((*default-database* database))
+        (let ((*default-database* database)
+              (pclass (car (class-direct-superclasses tclass))))
+          (when (and (normalizedp tclass) (not (table-exists-p pclass)))
+            (create-view-from-class (class-name pclass)
+                                    :database database :transactions transactions))
           (%install-class tclass database :transactions transactions))
         (error "Class ~s not found." view-class-name)))
   (values))
 
+(defmethod auto-increment-column-p (slotdef &optional (database clsql-sys:*default-database*))
+  (declare (ignore database))
+  (or (member :auto-increment (listify (view-class-slot-db-constraints slotdef)))
+      (slot-value slotdef 'autoincrement-sequence)))
+
 (defmethod %install-class ((self standard-db-class) database
                            &key (transactions t))
-  (let ((schemadef '()))
-    (dolist (slotdef (ordered-class-slots self))
-      (let ((res (database-generate-column-definition (class-name self)
-                                                      slotdef database)))
+  (let ((schemadef '())
+        (ordered-slots (slots-for-possibly-normalized-class self)))
+    (dolist (slotdef ordered-slots)
+      (let ((res (database-generate-column-definition self slotdef database)))
         (when res
           (push res schemadef))))
-    (unless schemadef
-      (error "Class ~s has no :base slots" self))
-    (create-table (sql-expression :table (view-table self)) (nreverse schemadef)
-                  :database database
-                  :transactions transactions
-                  :constraints (database-pkey-constraint self database))
-    (push self (database-view-classes database)))
+    (if (not schemadef)
+        (unless (normalizedp self)
+          (error "Class ~s has no :base slots" self))
+        (progn
+          (database-add-autoincrement-sequence self database)
+          (create-table (sql-expression :table (database-identifier self database))
+                        (nreverse schemadef)
+                        :database database
+                        :transactions transactions
+                        :constraints (database-pkey-constraint self database))
+          (push self (database-view-classes database)))))
   t)
 
 (defmethod database-pkey-constraint ((class standard-db-class) database)
-  (let ((keylist (mapcar #'view-class-slot-column (keyslots-for-class class)))
-        (table (view-table class)))
+  ;; Keylist will always be a list of escaped-indentifier
+  (let ((keylist (mapcar #'(lambda (x) (escaped-database-identifier x database))
+                         (keyslots-for-class class)))
+        (table (escaped (combine-database-identifiers
+                         (list class 'PK)
+                         database))))
     (when keylist
-      (etypecase table
-        (string
-         (format nil "CONSTRAINT \"~APK\" PRIMARY KEY~A" table
-                 (sql-output keylist database)))
-        ((or symbol sql-ident)
-         (format nil "CONSTRAINT ~APK PRIMARY KEY~A" table
-                 (sql-output keylist database)))))))
+      (format nil "CONSTRAINT ~A PRIMARY KEY (~{~A~^,~})" table
+              keylist))))
 
 (defmethod database-generate-column-definition (class slotdef database)
-  (declare (ignore database class))
-  (when (member (view-class-slot-db-kind slotdef) '(:base :key))
+  (declare (ignore class))
+  (when (key-or-base-slot-p slotdef)
     (let ((cdef
-           (list (sql-expression :attribute (view-class-slot-column slotdef))
+           (list (sql-expression :attribute (database-identifier slotdef database))
                  (specified-type slotdef))))
       (setf cdef (append cdef (list (view-class-slot-db-type slotdef))))
       (let ((const (view-class-slot-db-constraints slotdef)))
@@ -133,7 +154,7 @@ in DATABASE which defaults to *DEFAULT-DATABASE*."
 ;;
 
 (defun drop-view-from-class (view-class-name &key (database *default-database*)
-                                             (owner nil))
+                             (owner nil))
   "Removes a table defined by the View Class VIEW-CLASS-NAME from
 DATABASE which defaults to *DEFAULT-DATABASE*."
   (let ((tclass (find-class view-class-name)))
@@ -144,12 +165,13 @@ DATABASE which defaults to *DEFAULT-DATABASE*."
   (values))
 
 (defun %uninstall-class (self &key
-                              (database *default-database*)
-                              (owner nil))
-  (drop-table (sql-expression :table (view-table self))
+                         (database *default-database*)
+                         (owner nil))
+  (drop-table (sql-expression :table (database-identifier self database))
               :if-does-not-exist :ignore
               :database database
               :owner owner)
+  (database-remove-autoincrement-sequence self database)
   (setf (database-view-classes database)
         (remove self (database-view-classes database))))
 
@@ -213,12 +235,12 @@ defaults to NIL. The :db-constraints slot option is a string
 representing an SQL table constraint expression or a list of such
 strings."
   `(progn
-    (defclass ,class ,supers ,slots
-      ,@(if (find :metaclass `,cl-options :key #'car)
-            `,cl-options
-            (cons '(:metaclass clsql-sys::standard-db-class) `,cl-options)))
-    (finalize-inheritance (find-class ',class))
-    (find-class ',class)))
+     (defclass ,class ,supers ,slots
+       ,@(if (find :metaclass `,cl-options :key #'car)
+             `,cl-options
+             (cons '(:metaclass clsql-sys::standard-db-class) `,cl-options)))
+     (finalize-inheritance (find-class ',class))
+     (find-class ',class)))
 
 (defun keyslots-for-class (class)
   (slot-value class 'key-slots))