r10039: * BUGS: New file. Document suspected SIGPIPE
[clsql.git] / sql / oodml.lisp
index d701f0906e3a2352cbf01262c8b4179f6b02990a..77617e7932a23f0cf0fc9daf9d3247e2d0168c31 100644 (file)
     (if vd
        (let ((qualifier (key-qualifier-for-instance instance :database vd)))
          (delete-records :from vt :where qualifier :database vd)
-         (setf (slot-value instance 'view-database) nil))
+         (setf (slot-value instance 'view-database) nil)
+          (values))
        (signal-no-database-error vd))))
 
 (defmethod update-instance-from-records ((instance standard-db-object)
   (declare (ignore database db-type))
   (if args
       (format nil "INT(~A)" (car args))
-      "INT"))
+    "INT"))
+
+(deftype tinyint () 
+  "An 8-bit integer, this width may vary by SQL implementation."
+  'integer)
+
+(defmethod database-get-type-specifier ((type (eql 'tinyint)) args database db-type)
+  (declare (ignore args database db-type))
+  "INT")
+
+(deftype smallint () 
+  "An integer smaller than a 32-bit integer, this width may vary by SQL implementation."
+  'integer)
+
+(defmethod database-get-type-specifier ((type (eql 'smallint)) args database db-type)
+  (declare (ignore args database db-type))
+  "INT")
 
 (deftype bigint () 
   "An integer larger than a 32-bit integer, this width may vary by SQL implementation."
   (declare (ignore args database db-type))
   "BIGINT")
 
-(deftype varchar () 
+(deftype varchar (&optional size
   "A variable length string for the SQL varchar type."
+  (declare (ignore size))
   'string)
 
 (defmethod database-get-type-specifier ((type (eql 'varchar)) args
   (declare (ignore database db-type))
   val)
 
-(defmethod database-output-sql-as-type ((type (eql 'char))
-                                       val database db-type)
+(defmethod database-output-sql-as-type ((type (eql 'char)) val database db-type)
   (declare (ignore database db-type))
   (etypecase val
     (character (write-to-string val))
     (string val)))
 
+(defmethod database-output-sql-as-type ((type (eql 'float)) val database db-type)
+  (declare (ignore database db-type))
+  (let ((*read-default-float-format* (type-of val)))
+    (format nil "~F" val)))
+
 (defmethod read-sql-value (val type database db-type)
   (declare (ignore type database db-type))
   (read-from-string val))
        (parse-integer val)))
     (number val)))
 
+(defmethod read-sql-value (val (type (eql 'smallint)) database db-type)
+  (declare (ignore database db-type))
+  (etypecase val
+    (string
+     (unless (string-equal "NIL" val)
+       (parse-integer val)))
+    (number val)))
+
 (defmethod read-sql-value (val (type (eql 'bigint)) database db-type)
   (declare (ignore database db-type))
   (etypecase val
@@ -782,10 +812,29 @@ maximum of MAX-LEN instances updated in each query."
                    (join-vals (subseq vals (list-length selects)))
                    (joins (mapcar #'(lambda (c) (when c (make-instance c :view-database database)))
                                   jclasses)))
-              ;;(format t "db-vals: ~S, join-values: ~S~%" db-vals join-vals)
+              
+              ;;(format t "joins: ~S~%db-vals: ~S~%join-values: ~S~%selects: ~S~%immediate-selects: ~S~%" 
+              ;;joins db-vals join-vals selects immediate-selects)
+              
               ;; use refresh keyword here 
               (setf obj (get-slot-values-from-view obj (mapcar #'car selects) db-vals))
-              (mapc #'(lambda (jc) (get-slot-values-from-view jc (mapcar #'car immediate-selects) join-vals))
+              (mapc #'(lambda (jo)
+                        ;; find all immediate-select slots and join-vals for this object
+                        (let* ((slots (class-slots (class-of jo)))
+                               (pos-list (remove-if #'null
+                                                    (mapcar
+                                                     #'(lambda (s)
+                                                         (position s immediate-selects
+                                                                   :key #'car
+                                                                   :test #'eq))
+                                                     slots))))
+                          (get-slot-values-from-view jo
+                                                     (mapcar #'car 
+                                                             (mapcar #'(lambda (pos)
+                                                                         (nth pos immediate-selects))
+                                                                     pos-list))
+                                                     (mapcar #'(lambda (pos) (nth pos join-vals))
+                                                             pos-list))))
                     joins)
               (mapc
                #'(lambda (jc) 
@@ -822,15 +871,15 @@ maximum of MAX-LEN instances updated in each query."
   View Classes VIEW-CLASSES are passed as arguments to SELECT."
   (declare (ignore all set-operation group-by having offset limit inner-join on)
            (optimize (debug 3) (speed 1)))
-  (labels ((ref-equal (ref1 ref2)
-            (equal (sql ref1)
-                   (sql ref2)))
-          (table-sql-expr (table)
-            (sql-expression :table (view-table table)))
-          (tables-equal (table-a table-b)
-            (when (and table-a table-b)
-              (string= (string (slot-value table-a 'name))
-                       (string (slot-value table-b 'name))))))
+  (flet ((ref-equal (ref1 ref2)
+           (string= (sql-output ref1 database)
+                    (sql-output ref2 database)))
+         (table-sql-expr (table)
+           (sql-expression :table (view-table table)))
+         (tables-equal (table-a table-b)
+           (when (and table-a table-b)
+             (string= (string (slot-value table-a 'name))
+                      (string (slot-value table-b 'name))))))
     (remf args :from)
     (remf args :where)
     (remf args :flatp)
@@ -852,17 +901,22 @@ maximum of MAX-LEN instances updated in each query."
           (fullsels (apply #'append (mapcar #'append sels immediate-join-sels)))
           (sel-tables (collect-table-refs where))
           (tables (remove-if #'null
-                             (remove-duplicates (append (mapcar #'table-sql-expr sclasses)
-                                                        (mapcar #'(lambda (jcs)
-                                                                    (mapcan #'(lambda (jc)
-                                                                                (when jc (table-sql-expr jc)))
-                                                                            jcs))
-                                                                immediate-join-classes)
-                                                        sel-tables)
-                                                :test #'tables-equal)))
+                             (remove-duplicates
+                              (append (mapcar #'table-sql-expr sclasses)
+                                      (mapcan #'(lambda (jc-list)
+                                                  (mapcar
+                                                   #'(lambda (jc) (when jc (table-sql-expr jc)))
+                                                   jc-list))
+                                              immediate-join-classes)
+                                      sel-tables)
+                              :test #'tables-equal)))
           (order-by-slots (mapcar #'(lambda (ob) (if (atom ob) ob (car ob)))
-                                  (listify order-by))))
-                                
+                                  (listify order-by)))
+          (join-where nil))
+          
+
+      ;;(format t "sclasses: ~W~%ijc: ~W~%tables: ~W~%" sclasses immediate-join-classes tables)
+      
       (dolist (ob order-by-slots)
        (when (and ob (not (member ob (mapcar #'cdr fullsels)
                                   :test #'ref-equal)))
@@ -881,25 +935,34 @@ maximum of MAX-LEN instances updated in each query."
                    (mapcar
                     #'(lambda (jclass jslot)
                         (let ((dbi (view-class-slot-db-info jslot)))
-                          (setq where
-                                (append
-                                 (list (sql-operation '==
-                                                     (sql-expression
-                                                      :attribute (gethash :foreign-key dbi)
-                                                      :table (view-table jclass))
-                                                     (sql-expression
-                                                      :attribute (gethash :home-key dbi)
-                                                      :table (view-table vclass))))
-                                 (when where (listify where))))))
+                          (setq join-where
+                            (append
+                             (list (sql-operation '==
+                                                  (sql-expression
+                                                   :attribute (gethash :foreign-key dbi)
+                                                   :table (view-table jclass))
+                                                  (sql-expression
+                                                   :attribute (gethash :home-key dbi)
+                                                   :table (view-table vclass))))
+                             (when join-where (listify join-where))))))
                     jclasses jslots)))
              sclasses immediate-join-classes immediate-join-slots)
+      (when where 
+       (setq where (listify where)))
+      (cond
+       ((and where join-where)
+       (setq where (list (apply #'sql-and where join-where))))
+       ((and (null where) (> (length join-where) 1))
+       (setq where (list (apply #'sql-and join-where)))))
+      
       (let* ((rows (apply #'select 
                          (append (mapcar #'cdr fullsels)
                                  (cons :from 
                                        (list (append (when from (listify from)) 
                                                      (listify tables)))) 
                                  (list :result-types result-types)
-                                 (when where (list :where where))
+                                 (when where
+                                   (list :where where))
                                  args)))
             (instances-to-add (- (length rows) (length instances)))
             (perhaps-extended-instances
@@ -921,6 +984,10 @@ maximum of MAX-LEN instances updated in each query."
 
 (defmethod instance-refreshed ((instance standard-db-object)))
 
+(defvar *default-caching* t
+  "Controls whether SELECT caches objects by default. The CommonSQL
+specification states caching is on by default.")
+
 (defun select (&rest select-all-args) 
    "Executes a query on DATABASE, which has a default value of
 *DEFAULT-DATABASE*, specified by the SQL expressions supplied
@@ -966,89 +1033,89 @@ a list of lists. If FLATP is t and only one result is returned
 for each record selected in the query, the results are returned
 as elements of a list."
 
-  (flet ((select-objects (target-args)
-           (and target-args
-                (every #'(lambda (arg)
-                           (and (symbolp arg)
-                                (find-class arg nil)))
-                       target-args))))
-    (multiple-value-bind (target-args qualifier-args)
-        (query-get-selections select-all-args)
-      (unless (or *default-database* (getf qualifier-args :database))
-       (signal-no-database-error nil))
-   
-       (cond
-         ((select-objects target-args)
-          (let ((caching (getf qualifier-args :caching t))
-                (result-types (getf qualifier-args :result-types :auto))
-                (refresh (getf qualifier-args :refresh nil))
-                (database (or (getf qualifier-args :database) *default-database*))
-                (order-by (getf qualifier-args :order-by)))
-            (remf qualifier-args :caching)
-            (remf qualifier-args :refresh)
-            (remf qualifier-args :result-types)
-            
-            
-            ;; Add explicity table name to order-by if not specified and only
-            ;; one selected table. This is required so FIND-ALL won't duplicate
-            ;; the field
-            (when (and order-by (= 1 (length target-args)))
-              (let ((table-name  (view-table (find-class (car target-args))))
-                    (order-by-list (copy-seq (listify order-by))))
-                
-                (loop for i from 0 below (length order-by-list)
-                    do (etypecase (nth i order-by-list)
-                         (sql-ident-attribute
-                          (unless (slot-value (nth i order-by-list) 'qualifier)
-                            (setf (slot-value (nth i order-by-list) 'qualifier) table-name)))
-                         (cons
-                          (unless (slot-value (car (nth i order-by-list)) 'qualifier)
-                            (setf (slot-value (car (nth i order-by-list)) 'qualifier) table-name)))))
-                (setf (getf qualifier-args :order-by) order-by-list)))
-       
-            (cond
-              ((null caching)
-               (apply #'find-all target-args
-                      (append qualifier-args (list :result-types result-types))))
-              (t
-               (let ((cached (records-cache-results target-args qualifier-args database)))
-                 (cond
-                   ((and cached (not refresh))
-                    cached)
-                   ((and cached refresh)
-                    (let ((results (apply #'find-all (append (list target-args) qualifier-args `(:instances ,cached :result-types :auto)))))
-                      (setf (records-cache-results target-args qualifier-args database) results)
-                      results))
-                   (t
-                    (let ((results (apply #'find-all target-args (append qualifier-args
-                                                                         '(:result-types :auto)))))
-                      (setf (records-cache-results target-args qualifier-args database) results)
-                      results))))))))
-         (t
-          (let* ((expr (apply #'make-query select-all-args))
-                 (specified-types
-                  (mapcar #'(lambda (attrib)
-                              (if (typep attrib 'sql-ident-attribute)
-                                  (let ((type (slot-value attrib 'type)))
-                                    (if type
-                                        type
-                                        t))
-                                  t))
-                          (slot-value expr 'selections))))
-            (destructuring-bind (&key (flatp nil)
-                                      (result-types :auto)
-                                      (field-names t) 
-                                      (database *default-database*)
-                                      &allow-other-keys)
-                qualifier-args
-              (query expr :flatp flatp 
-                     :result-types 
-                     ;; specifying a type for an attribute overrides result-types
-                     (if (some #'(lambda (x) (not (eq t x))) specified-types) 
-                         specified-types
-                         result-types)
-                     :field-names field-names
-                     :database database))))))))
+   (flet ((select-objects (target-args)
+            (and target-args
+                 (every #'(lambda (arg)
+                            (and (symbolp arg)
+                                 (find-class arg nil)))
+                        target-args))))
+     (multiple-value-bind (target-args qualifier-args)
+         (query-get-selections select-all-args)
+       (unless (or *default-database* (getf qualifier-args :database))
+         (signal-no-database-error nil))
+       
+       (cond
+         ((select-objects target-args)
+          (let ((caching (getf qualifier-args :caching *default-caching*))
+                (result-types (getf qualifier-args :result-types :auto))
+                (refresh (getf qualifier-args :refresh nil))
+                (database (or (getf qualifier-args :database) *default-database*))
+                (order-by (getf qualifier-args :order-by)))
+            (remf qualifier-args :caching)
+            (remf qualifier-args :refresh)
+            (remf qualifier-args :result-types)
+            
+            ;; Add explicity table name to order-by if not specified and only
+            ;; one selected table. This is required so FIND-ALL won't duplicate
+            ;; the field
+            (when (and order-by (= 1 (length target-args)))
+              (let ((table-name  (view-table (find-class (car target-args))))
+                    (order-by-list (copy-seq (listify order-by))))
+                
+                (loop for i from 0 below (length order-by-list)
+                      do (etypecase (nth i order-by-list)
+                           (sql-ident-attribute
+                            (unless (slot-value (nth i order-by-list) 'qualifier)
+                              (setf (slot-value (nth i order-by-list) 'qualifier) table-name)))
+                           (cons
+                            (unless (slot-value (car (nth i order-by-list)) 'qualifier)
+                              (setf (slot-value (car (nth i order-by-list)) 'qualifier) table-name)))))
+                (setf (getf qualifier-args :order-by) order-by-list)))
+            
+            (cond
+              ((null caching)
+               (apply #'find-all target-args
+                      (append qualifier-args 
+                              (list :result-types result-types :refresh refresh))))
+              (t
+               (let ((cached (records-cache-results target-args qualifier-args database)))
+                 (cond
+                   ((and cached (not refresh))
+                    cached)
+                   ((and cached refresh)
+                    (let ((results (apply #'find-all (append (list target-args) qualifier-args `(:instances ,cached :result-types :auto :refresh ,refresh)))))
+                      (setf (records-cache-results target-args qualifier-args database) results)
+                      results))
+                   (t
+                    (let ((results (apply #'find-all target-args (append qualifier-args
+                                                                         `(:result-types :auto :refresh ,refresh)))))
+                      (setf (records-cache-results target-args qualifier-args database) results)
+                      results))))))))
+         (t
+          (let* ((expr (apply #'make-query select-all-args))
+                 (specified-types
+                  (mapcar #'(lambda (attrib)
+                              (if (typep attrib 'sql-ident-attribute)
+                                  (let ((type (slot-value attrib 'type)))
+                                    (if type
+                                        type
+                                        t))
+                                  t))
+                          (slot-value expr 'selections))))
+            (destructuring-bind (&key (flatp nil)
+                                      (result-types :auto)
+                                      (field-names t) 
+                                      (database *default-database*)
+                                      &allow-other-keys)
+                qualifier-args
+              (query expr :flatp flatp 
+                     :result-types 
+                     ;; specifying a type for an attribute overrides result-types
+                     (if (some #'(lambda (x) (not (eq t x))) specified-types) 
+                         specified-types
+                         result-types)
+                     :field-names field-names
+                     :database database))))))))
 
 (defun compute-records-cache-key (targets qualifiers)
   (list targets
@@ -1073,7 +1140,8 @@ as elements of a list."
   (unless (record-caches database)
     (setf (record-caches database)
          (make-hash-table :test 'equal
-                          #+allegro :values #+allegro :weak)))
+                          #+allegro :values #+allegro :weak
+                           #+lispworks :weak-kind #+lispworks :value)))
   (setf (gethash (compute-records-cache-key targets qualifiers)
                 (record-caches database)) results)
   results)