Add support for :default in db constraints. make-constraint-description: use next...
[clsql.git] / sql / oodml.lisp
index 9910ab484948088a569268f49b87fea8e9374a0d..0ddaabad5d5444eda23851945f018cf7d9a3bd67 100644 (file)
@@ -1,8 +1,6 @@
 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
 ;;;; *************************************************************************
 ;;;;
-;;;; $Id$
-;;;;
 ;;;; The CLSQL Object Oriented Data Manipulation Language (OODML).
 ;;;;
 ;;;; This file is part of CLSQL.
@@ -55,7 +53,7 @@
 (defun generate-selection-list (vclass)
   (let* ((sels nil)
          (this-class vclass)
-         (slots (if (normalisedp vclass)
+         (slots (if (normalizedp vclass)
                     (labels ((getdslots ()
                                (let ((sl (ordered-class-direct-slots this-class)))
                                  (cond (sl)
     obj))
 
 (defmethod update-record-from-slot ((obj standard-db-object) slot &key
-                                    (database *default-database*))
-  (let* ((database (or (view-database obj) database))
-         (view-class (class-of obj)))
-    (when (normalisedp view-class)
-      ;; If it's normalised, find the class that actually contains
-      ;; the slot that's tied to the db
-      (setf view-class
-            (do ((this-class view-class
-                             (car (class-direct-superclasses this-class))))
-                ((member slot
-                         (mapcar #'(lambda (esd) (slot-definition-name esd))
-                                 (ordered-class-direct-slots this-class)))
-                 this-class))))
-    (let* ((vct (view-table view-class))
-           (sd (slotdef-for-slot-with-class slot view-class)))
-      (check-slot-type sd (slot-value obj slot))
-      (let* ((att (view-class-slot-column sd))
-             (val (db-value-from-slot sd (slot-value obj slot) database)))
-        (cond ((and vct sd (view-database obj))
-               (update-records (sql-expression :table vct)
-                               :attributes (list (sql-expression :attribute att))
-                               :values (list val)
-                               :where (key-qualifier-for-instance
-                                       obj :database database :this-class view-class)
-                               :database database))
-              ((and vct sd (not (view-database obj)))
-               (insert-records :into (sql-expression :table vct)
-                               :attributes (list (sql-expression :attribute att))
-                               :values (list val)
-                               :database database)
-               (setf (slot-value obj 'view-database) database))
-              (t
-               (error "Unable to update record.")))))
-    (values)))
+                                    database)
+  (update-record-from-slots obj (list slot) :database database))
 
 (defmethod update-record-from-slots ((obj standard-db-object) slots &key
-                                     (database *default-database*))
-  (let* ((database (or (view-database obj) database))
-         (vct (view-table (class-of obj)))
-         (sds (slotdefs-for-slots-with-class slots (class-of obj)))
-         (avps (mapcar #'(lambda (s)
-                           (let ((val (slot-value
-                                       obj (slot-definition-name s))))
-                             (check-slot-type s val)
-                             (list (sql-expression
-                                    :attribute (view-class-slot-column s))
-                                   (db-value-from-slot s val database))))
-                       sds)))
-    (cond ((and avps (view-database obj))
-           (update-records (sql-expression :table vct)
-                           :av-pairs avps
-                           :where (key-qualifier-for-instance
-                                   obj :database database)
-                           :database database))
-          ((and avps (not (view-database obj)))
-           (insert-records :into (sql-expression :table vct)
-                           :av-pairs avps
-                           :database database)
-           (setf (slot-value obj 'view-database) database))
-          (t
-           (error "Unable to update records"))))
+                                    (database *default-database*))
+  (let ((database (or database (view-database obj) *default-database*))
+       (pk nil))
+    (labels
+       ((sstoredp (slot) (member (view-class-slot-db-kind slot) '(:base :key)))
+        (sboundp (slot) (slot-boundp obj (slot-definition-name slot)))
+        (slot-value-list (slot)
+          (let ((value (slot-value obj (slot-definition-name slot))))
+            (check-slot-type slot value)
+            (list (sql-expression :attribute (view-class-slot-column slot))
+                  (db-value-from-slot slot value database))))
+
+        (save-slots-for-class (view-class stored-slot-defs)
+          (let ((pk-slot (car (keyslots-for-class view-class)))
+                (table (sql-expression :table (view-table view-class)))
+                (pclass (car (class-direct-superclasses view-class)))
+
+                direct-slots    ; the slots to save on this iteration
+                parent-slots    ; slots to handle recursively
+                )
+            (if (normalizedp view-class)
+                (let ((cdsn (mapcar #'slot-definition-name
+                                    (class-direct-slots view-class))))
+                (dolist (s stored-slot-defs)
+                  (if (member (slot-definition-name s) cdsn)
+                      (push s direct-slots)
+                      (push s parent-slots))))
+                ;;not normalized, do everything now.
+                (setf direct-slots stored-slot-defs))
+            '(break "Class:~a ~%direct-slots:~a ~%parent-slots:~a ~%~a"
+                   view-class direct-slots parent-slots
+                   (class-direct-slots view-class))
+            (when parent-slots
+              ;;call recursively, collect primary key if we have it
+              (save-slots-for-class pclass parent-slots)
+              (when (and pk pk-slot)
+                (setf (slot-value obj (slot-definition-name pk-slot)) pk)))
+
+            ;;we've delayed doing the unbound test till here because
+            ;;the keys are sometimes only bound while updating the pclass
+            (let ((av-pairs (mapcar #'slot-value-list
+                                    (remove-if-not #'sboundp direct-slots))))
+              (cond
+                ((null av-pairs) nil)
+                ((view-database obj)
+                 (update-records table
+                                 :av-pairs av-pairs
+                                 :where (key-qualifier-for-instance
+                                         obj :database database
+                                         :this-class view-class)
+                                 :database database)
+                 (when (and pk-slot (not pk))
+                   (setf pk (slot-value obj (slot-definition-name pk-slot))))
+                 pk)
+                (t
+                 (insert-records :into table
+                                 :av-pairs av-pairs
+                                 :database database)
+                 (when (and pk-slot (not pk))
+                   (setf pk (if (member :auto-increment (listify (view-class-slot-db-constraints pk-slot)))
+                                (setf (slot-value obj (slot-definition-name pk-slot))
+                                      (database-last-auto-increment-id database
+                                                                      table
+                                                                      pk-slot))))
+
+                   )
+                 pk))))))
+
+
+      (save-slots-for-class
+       (class-of obj)
+       ;;convert to slot-defs, remove any non-stored.
+       (loop for s in slots
+            for sd = (etypecase s
+                       (symbol (slotdef-for-slot-with-class s (class-of obj)))
+                       (slot-definition s))
+            when (sstoredp sd)
+              collect sd))
+      ;; handle slots with defaults
+      (dolist (slot slots)
+       (when (and (slot-exists-p slot 'db-constraints)
+                  (listp (view-class-slot-db-constraints slot))
+                  (member :default (view-class-slot-db-constraints slot)))
+         (update-slot-from-record obj (slot-definition-name slot))))
+      ;;this may just be a NOP.
+      (setf (slot-value obj 'view-database) database)))
+
   (values))
 
 (defmethod update-records-from-instance ((obj standard-db-object)
-                                         &key database this-class)
-  (let ((database (or database (view-database obj) *default-database*))
-        (pk nil))
-    (labels ((slot-storedp (slot)
-               (and (member (view-class-slot-db-kind slot) '(:base :key))
-                    (slot-boundp obj (slot-definition-name slot))))
-             (slot-value-list (slot)
-               (let ((value (slot-value obj (slot-definition-name slot))))
-                 (check-slot-type slot value)
-                 (list (sql-expression :attribute (view-class-slot-column slot))
-                       (db-value-from-slot slot value database)))))
-      (let* ((view-class (or this-class (class-of obj)))
-             (pk-slot (car (keyslots-for-class view-class)))
-             (view-class-table (view-table view-class))
-             (pclass (car (class-direct-superclasses view-class))))
-        (when (normalisedp view-class)
-          (setf pk (update-records-from-instance obj :database database
-                                                 :this-class pclass))
-          (when pk-slot
-            (setf (slot-value obj (slot-definition-name pk-slot)) pk)))
-        (let* ((slots (remove-if-not #'slot-storedp
-                                     (if (normalisedp view-class)
-                                         (ordered-class-direct-slots view-class)
-                                         (ordered-class-slots view-class))))
-               (record-values (mapcar #'slot-value-list slots)))
-          (cond ((and (not (normalisedp view-class))
-                      (not record-values))
-                 (error "No settable slots."))
-                ((and (normalisedp view-class)
-                      (not record-values))
-                 nil)
-                ((view-database obj)
-                 (update-records (sql-expression :table view-class-table)
-                                 :av-pairs record-values
-                                 :where (key-qualifier-for-instance
-                                         obj :database database
-                                         :this-class view-class)
-                                 :database database)
-                 (when pk-slot
-                   (setf pk (or pk
-                                (slot-value obj (slot-definition-name pk-slot))))))
-                (t
-                 (insert-records :into (sql-expression :table view-class-table)
-                                 :av-pairs record-values
-                                 :database database)
-                 (when pk-slot
-                   (if (or (and (listp (view-class-slot-db-constraints pk-slot))
-                                (member :auto-increment (view-class-slot-db-constraints pk-slot)))
-                           (eql (view-class-slot-db-constraints pk-slot) :auto-increment))
-                       (setf pk (or pk
-                                    (car (query "SELECT LAST_INSERT_ID();"
-                                                :flatp t :field-names nil
-                                                :database database))))
-                       (setf pk (or pk
-                                    (slot-value obj (slot-definition-name pk-slot))))))
-                 (when (eql this-class nil)
-                   (setf (slot-value obj 'view-database) database)))))))
-    pk))
+                                        &key database)
+  (update-record-from-slots obj (class-slots (class-of obj)) :database database))
 
 (defmethod delete-instance-records ((instance standard-db-object))
   (let ((vt (sql-expression :table (view-table (class-of instance))))
   (let* ((view-class (or this-class (class-of instance)))
          (pclass (car (class-direct-superclasses view-class)))
          (pres nil))
-    (when (normalisedp view-class)
+    (when (normalizedp view-class)
       (setf pres (update-instance-from-records instance :database database
                                                :this-class pclass)))
     (let* ((view-table (sql-expression :table (view-table view-class)))
                                                      :result-types nil
                                                      :database vd))))
              (when res
+              (setf (slot-value instance 'view-database) vd)
                (get-slot-values-from-view instance (mapcar #'car sels) (car res))))
             (pres)
             (t nil)))))
                                     slot &key (database *default-database*))
   (let* ((view-class (find-class (class-name (class-of instance))))
          (slot-def (slotdef-for-slot-with-class slot view-class)))
-    (when (normalisedp view-class)
-      ;; If it's normalised, find the class that actually contains
+    (when (normalizedp view-class)
+      ;; If it's normalized, find the class that actually contains
       ;; the slot that's tied to the db
       (setf view-class
             (do ((this-class view-class
            (res (select att-ref :from  view-table :where view-qual
                                                   :result-types nil)))
       (when res
+       (setf (slot-value instance 'view-database) vd)
         (get-slot-values-from-view instance (list slot-def) (car res))))))
 
 (defmethod update-slot-with-null ((object standard-db-object)
        (format nil "~F" val))))
 
 (defmethod read-sql-value (val type database db-type)
-  (declare (ignore type database db-type))
-  (read-from-string val))
+  (declare (ignore database db-type))
+  (cond
+    ((null type) val) ;;we have no desired type, just give the value
+    ((typep val type) val) ;;check that it hasn't already been converted.
+    ((typep val 'string) (read-from-string val)) ;;maybe read will just take care of it?
+    (T (error "Unable to read-sql-value ~a as type ~a" val type))))
 
 (defmethod read-sql-value (val (type (eql 'string)) database db-type)
   (declare (ignore database db-type))
   (declare (ignore database db-type))
   ;; writing 1.0 writes 1, so we we *really* want a float, must do (float ...)
   (etypecase val
-    (string
-     (float (read-from-string val)))
-    (float
-     val)))
+    (string (float (read-from-string val)))
+    (float val)))
+
+(defmethod read-sql-value (val (type (eql 'double-float)) database db-type)
+  (declare (ignore database db-type))
+  ;; writing 1.0 writes 1, so if we *really* want a float, must do (float ...)
+  (etypecase val
+    (string (float
+            (let ((*read-default-float-format* 'double-float))
+              (read-from-string val))
+            1.0d0))
+    (double-float val)
+    (float (coerce val 'double-float))))
 
 (defmethod read-sql-value (val (type (eql 'boolean)) database db-type)
   (declare (ignore database db-type))
@@ -875,7 +862,7 @@ maximum of MAX-LEN instances updated in each query."
 ;;;; Should we not return the whole result, instead of only
 ;;;; the one slot-value? We get all the values from the db
 ;;;; anyway, so?
-(defun fault-join-normalised-slot (class object slot-def)
+(defun fault-join-normalized-slot (class object slot-def)
   (labels ((getsc (this-class)
              (let ((sc (car (class-direct-superclasses this-class))))
                (if (key-slots sc)
@@ -898,7 +885,7 @@ maximum of MAX-LEN instances updated in each query."
                                   (slot-value object hk))
                                  (t hk)))))
 
-        ;; Caching nil in next select, because in normalised mode
+        ;; Caching nil in next select, because in normalized mode
         ;; records can be changed through other instances (children,
         ;; parents) so changes possibly won't be noticed
         (let ((res (car (select (class-name sc) :where jq
@@ -907,14 +894,14 @@ maximum of MAX-LEN instances updated in each query."
                                                 :database (view-database object))))
               (slot-name (slot-definition-name slot-def)))
 
-          ;; If current class is normalised and wanted slot is not
+          ;; If current class is normalized and wanted slot is not
           ;; a direct member, recurse up
-          (if (and (normalisedp class)
+          (if (and (normalizedp class)
                    (not (member slot-name
                                 (mapcar #'(lambda (esd) (slot-definition-name esd))
                                         (ordered-class-direct-slots class))))
                    (not (slot-boundp res slot-name)))
-              (fault-join-normalised-slot sc res slot-def)
+              (fault-join-normalized-slot sc res slot-def)
               (slot-value res slot-name)))))) )
 
 (defun join-qualifier (class object slot-def)
@@ -979,7 +966,7 @@ maximum of MAX-LEN instances updated in each query."
                          ;; find all immediate-select slots and join-vals for this object
                          (let* ((jo-class (class-of jo))
                                 (slots
-                                 (if (normalisedp jo-class)
+                                 (if (normalizedp jo-class)
                                      (class-direct-slots jo-class)
                                      (class-slots jo-class)))
                                 (pos-list (remove-if #'null