Merge commit 'master' into development
[clsql.git] / sql / oodml.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;;
4 ;;;; The CLSQL Object Oriented Data Manipulation Language (OODML).
5 ;;;;
6 ;;;; This file is part of CLSQL.
7 ;;;;
8 ;;;; CLSQL users are granted the rights to distribute and use this software
9 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
10 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
11 ;;;; *************************************************************************
12
13 (in-package #:clsql-sys)
14
15
16 (defun key-qualifier-for-instance (obj &key (database *default-database*) this-class)
17   (let* ((obj-class (or this-class (class-of obj)))
18          (tb (view-table obj-class)))
19     (flet ((qfk (k)
20              (sql-operation '==
21                             (sql-expression :attribute
22                                             (view-class-slot-column k)
23                                             :table tb)
24                             (db-value-from-slot
25                              k
26                              (slot-value obj (slot-definition-name k))
27                              database))))
28       (let* ((keys (keyslots-for-class obj-class))
29              (keyxprs (mapcar #'qfk (reverse keys))))
30         (cond
31           ((= (length keyxprs) 0) nil)
32           ((= (length keyxprs) 1) (car keyxprs))
33           ((> (length keyxprs) 1) (apply #'sql-operation 'and keyxprs)))))))
34
35 ;;
36 ;; Function used by 'generate-selection-list'
37 ;;
38
39 (defun generate-attribute-reference (vclass slotdef)
40   (cond
41     ((eq (view-class-slot-db-kind slotdef) :base)
42      (sql-expression :attribute (view-class-slot-column slotdef)
43                      :table (view-table vclass)))
44     ((eq (view-class-slot-db-kind slotdef) :key)
45      (sql-expression :attribute (view-class-slot-column slotdef)
46                      :table (view-table vclass)))
47     (t nil)))
48
49 ;;
50 ;; Function used by 'find-all'
51 ;;
52
53 (defun generate-selection-list (vclass)
54   (let* ((sels nil)
55          (this-class vclass)
56          (slots (if (normalizedp vclass)
57                     (labels ((getdslots ()
58                                (let ((sl (ordered-class-direct-slots this-class)))
59                                  (cond (sl)
60                                        (t
61                                         (setf this-class
62                                               (car (class-direct-superclasses this-class)))
63                                         (getdslots))))))
64                       (getdslots))
65                     (ordered-class-slots this-class))))
66     (dolist (slotdef slots)
67       (let ((res (generate-attribute-reference this-class slotdef)))
68         (when res
69           (push (cons slotdef res) sels))))
70     (if sels
71         sels
72         (error "No slots of type :base in view-class ~A" (class-name vclass)))))
73
74
75
76 (defun generate-retrieval-joins-list (vclass retrieval-method)
77   "Returns list of immediate join slots for a class."
78   (let ((join-slotdefs nil))
79     (dolist (slotdef (ordered-class-slots vclass) join-slotdefs)
80       (when (and (eq :join (view-class-slot-db-kind slotdef))
81                  (eq retrieval-method (gethash :retrieval (view-class-slot-db-info slotdef))))
82         (push slotdef join-slotdefs)))))
83
84 (defun generate-immediate-joins-selection-list (vclass)
85   "Returns list of immediate join slots for a class."
86   (let (sels)
87     (dolist (joined-slot (generate-retrieval-joins-list vclass :immediate) sels)
88       (let* ((join-class-name (gethash :join-class (view-class-slot-db-info joined-slot)))
89              (join-class (when join-class-name (find-class join-class-name))))
90         (dolist (slotdef (ordered-class-slots join-class))
91           (let ((res (generate-attribute-reference join-class slotdef)))
92             (when res
93               (push (cons slotdef res) sels))))))
94     sels))
95
96
97 ;; Called by 'get-slot-values-from-view'
98 ;;
99
100 (defmethod update-slot-from-db ((instance standard-db-object) slotdef value)
101   (declare (optimize (speed 3) #+cmu (extensions:inhibit-warnings 3)))
102   (let* ((slot-reader (view-class-slot-db-reader slotdef))
103          (slot-name   (slot-definition-name slotdef))
104          (slot-type   (specified-type slotdef)))
105     (cond ((and value (null slot-reader))
106            (setf (slot-value instance slot-name)
107                  (read-sql-value value (delistify slot-type)
108                                  (view-database instance)
109                                  (database-underlying-type
110                                   (view-database instance)))))
111           ((null value)
112            (update-slot-with-null instance slot-name slotdef))
113           ((typep slot-reader 'string)
114            (setf (slot-value instance slot-name)
115                  (format nil slot-reader value)))
116           ((typep slot-reader '(or symbol function))
117            (setf (slot-value instance slot-name)
118                  (apply slot-reader (list value))))
119           (t
120            (error "Slot reader is of an unusual type.")))))
121
122 (defmethod key-value-from-db (slotdef value database)
123   (declare (optimize (speed 3) #+cmu (extensions:inhibit-warnings 3)))
124   (let ((slot-reader (view-class-slot-db-reader slotdef))
125         (slot-type (specified-type slotdef)))
126     (cond ((and value (null slot-reader))
127            (read-sql-value value (delistify slot-type) database
128                            (database-underlying-type database)))
129           ((null value)
130            nil)
131           ((typep slot-reader 'string)
132            (format nil slot-reader value))
133           ((typep slot-reader '(or symbol function))
134            (apply slot-reader (list value)))
135           (t
136            (error "Slot reader is of an unusual type.")))))
137
138 (defun db-value-from-slot (slotdef val database)
139   (let ((dbwriter (view-class-slot-db-writer slotdef))
140         (dbtype (specified-type slotdef)))
141     (typecase dbwriter
142       (string (format nil dbwriter val))
143       ((and (or symbol function) (not null)) (apply dbwriter (list val)))
144       (t
145        (database-output-sql-as-type
146         (typecase dbtype
147           (cons (car dbtype))
148           (t dbtype))
149         val database (database-underlying-type database))))))
150
151 (defun check-slot-type (slotdef val)
152   (let* ((slot-type (specified-type slotdef))
153          (basetype (if (listp slot-type) (car slot-type) slot-type)))
154     (when (and slot-type val)
155       (unless (typep val basetype)
156         (error 'sql-user-error
157                :message
158                (format nil "Invalid value ~A in slot ~A, not of type ~A."
159                        val (slot-definition-name slotdef) slot-type))))))
160
161 ;;
162 ;; Called by find-all
163 ;;
164
165 (defmethod get-slot-values-from-view (obj slotdeflist values)
166   (flet ((update-slot (slot-def values)
167            (update-slot-from-db obj slot-def values)))
168     (mapc #'update-slot slotdeflist values)
169     obj))
170
171 (defmethod update-record-from-slot ((obj standard-db-object) slot &key
172                                     (database *default-database*))
173   (let* ((database (or (view-database obj) database))
174          (view-class (class-of obj)))
175     (when (normalizedp view-class)
176       ;; If it's normalized, find the class that actually contains
177       ;; the slot that's tied to the db
178       (setf view-class
179             (do ((this-class view-class
180                              (car (class-direct-superclasses this-class))))
181                 ((member slot
182                          (mapcar #'(lambda (esd) (slot-definition-name esd))
183                                  (ordered-class-direct-slots this-class)))
184                  this-class))))
185     (let* ((vct (view-table view-class))
186            (sd (slotdef-for-slot-with-class slot view-class)))
187       (check-slot-type sd (slot-value obj slot))
188       (let* ((att (view-class-slot-column sd))
189              (val (db-value-from-slot sd (slot-value obj slot) database)))
190         (cond ((and vct sd (view-database obj))
191                (update-records (sql-expression :table vct)
192                                :attributes (list (sql-expression :attribute att))
193                                :values (list val)
194                                :where (key-qualifier-for-instance
195                                        obj :database database :this-class view-class)
196                                :database database))
197               ((and vct sd (not (view-database obj)))
198                (insert-records :into (sql-expression :table vct)
199                                :attributes (list (sql-expression :attribute att))
200                                :values (list val)
201                                :database database)
202                (setf (slot-value obj 'view-database) database))
203               (t
204                (error "Unable to update record.")))))
205     (values)))
206
207 (defmethod update-record-from-slots ((obj standard-db-object) slots &key
208                                      (database *default-database*))
209   (when (normalizedp (class-of obj))
210     ;; FIXME: Rewrite to bundle slots for same table to be written
211     ;; as avpairs (like how is done for non-normalized view-classes below)
212     (dolist (slot slots)
213       (update-record-from-slot obj slot :database database))
214     (return-from update-record-from-slots (values)))
215
216   (let* ((database (or (view-database obj) database))
217          (vct (view-table (class-of obj)))
218          (sds (slotdefs-for-slots-with-class slots (class-of obj)))
219          (avps (mapcar #'(lambda (s)
220                            (let ((val (slot-value
221                                        obj (slot-definition-name s))))
222                              (check-slot-type s val)
223                              (list (sql-expression
224                                     :attribute (view-class-slot-column s))
225                                    (db-value-from-slot s val database))))
226                        sds)))
227     (cond ((and avps (view-database obj))
228            (update-records (sql-expression :table vct)
229                            :av-pairs avps
230                            :where (key-qualifier-for-instance
231                                    obj :database database)
232                            :database database))
233           ((and avps (not (view-database obj)))
234            (insert-records :into (sql-expression :table vct)
235                            :av-pairs avps
236                            :database database)
237            (setf (slot-value obj 'view-database) database))
238           (t
239            (error "Unable to update records"))))
240   (values))
241
242 (defmethod update-records-from-instance ((obj standard-db-object)
243                                          &key database this-class)
244   (let ((database (or database (view-database obj) *default-database*))
245         (pk nil))
246     (labels ((slot-storedp (slot)
247                (and (member (view-class-slot-db-kind slot) '(:base :key))
248                     (slot-boundp obj (slot-definition-name slot))))
249              (slot-value-list (slot)
250                (let ((value (slot-value obj (slot-definition-name slot))))
251                  (check-slot-type slot value)
252                  (list (sql-expression :attribute (view-class-slot-column slot))
253                        (db-value-from-slot slot value database)))))
254       (let* ((view-class (or this-class (class-of obj)))
255              (pk-slot (car (keyslots-for-class view-class)))
256              (view-class-table (view-table view-class))
257              (pclass (car (class-direct-superclasses view-class))))
258         (when (normalizedp view-class)
259           (setf pk (update-records-from-instance obj :database database
260                                                  :this-class pclass))
261           (when pk-slot
262             (setf (slot-value obj (slot-definition-name pk-slot)) pk)))
263         (let* ((slots (remove-if-not #'slot-storedp
264                                      (if (normalizedp view-class)
265                                          (ordered-class-direct-slots view-class)
266                                          (ordered-class-slots view-class))))
267                (record-values (mapcar #'slot-value-list slots)))
268           (cond ((and (not (normalizedp view-class))
269                       (not record-values))
270                  (error "No settable slots."))
271                 ((and (normalizedp view-class)
272                       (not record-values))
273                  nil)
274                 ((view-database obj)
275                  (update-records (sql-expression :table view-class-table)
276                                  :av-pairs record-values
277                                  :where (key-qualifier-for-instance
278                                          obj :database database
279                                          :this-class view-class)
280                                  :database database)
281                  (when pk-slot
282                    (setf pk (or pk
283                                 (slot-value obj (slot-definition-name pk-slot))))))
284                 (t
285                  (insert-records :into (sql-expression :table view-class-table)
286                                  :av-pairs record-values
287                                  :database database)
288                  (when pk-slot
289                    (if (or (and (listp (view-class-slot-db-constraints pk-slot))
290                                 (member :auto-increment (view-class-slot-db-constraints pk-slot)))
291                            (eql (view-class-slot-db-constraints pk-slot) :auto-increment))
292                        (setf pk (or pk
293                                     (car (query "SELECT LAST_INSERT_ID();"
294                                                 :flatp t :field-names nil
295                                                 :database database))))
296                        (setf pk (or pk
297                                     (slot-value obj (slot-definition-name pk-slot))))))
298                  (when (eql this-class nil)
299                    (setf (slot-value obj 'view-database) database)))))))
300     pk))
301
302 (defmethod delete-instance-records ((instance standard-db-object))
303   (let ((vt (sql-expression :table (view-table (class-of instance))))
304         (vd (view-database instance)))
305     (if vd
306         (let ((qualifier (key-qualifier-for-instance instance :database vd)))
307           (delete-records :from vt :where qualifier :database vd)
308           (setf (record-caches vd) nil)
309           (setf (slot-value instance 'view-database) nil)
310           (values))
311         (signal-no-database-error vd))))
312
313 (defmethod update-instance-from-records ((instance standard-db-object)
314                                          &key (database *default-database*)
315                                          this-class)
316   (let* ((view-class (or this-class (class-of instance)))
317          (pclass (car (class-direct-superclasses view-class)))
318          (pres nil))
319     (when (normalizedp view-class)
320       (setf pres (update-instance-from-records instance :database database
321                                                :this-class pclass)))
322     (let* ((view-table (sql-expression :table (view-table view-class)))
323            (vd (or (view-database instance) database))
324            (view-qual (key-qualifier-for-instance instance :database vd
325                                                            :this-class view-class))
326            (sels (generate-selection-list view-class))
327            (res nil))
328       (cond (view-qual
329              (setf res (apply #'select (append (mapcar #'cdr sels)
330                                                (list :from  view-table
331                                                      :where view-qual
332                                                      :result-types nil
333                                                      :database vd))))
334              (when res
335                (get-slot-values-from-view instance (mapcar #'car sels) (car res))))
336             (pres)
337             (t nil)))))
338
339 (defmethod update-slot-from-record ((instance standard-db-object)
340                                     slot &key (database *default-database*))
341   (let* ((view-class (find-class (class-name (class-of instance))))
342          (slot-def (slotdef-for-slot-with-class slot view-class)))
343     (when (normalizedp view-class)
344       ;; If it's normalized, find the class that actually contains
345       ;; the slot that's tied to the db
346       (setf view-class
347             (do ((this-class view-class
348                              (car (class-direct-superclasses this-class))))
349                 ((member slot
350                          (mapcar #'(lambda (esd) (slot-definition-name esd))
351                                  (ordered-class-direct-slots this-class)))
352                  this-class))))
353     (let* ((view-table (sql-expression :table (view-table view-class)))
354            (vd (or (view-database instance) database))
355            (view-qual (key-qualifier-for-instance instance :database vd
356                                                            :this-class view-class))
357            (att-ref (generate-attribute-reference view-class slot-def))
358            (res (select att-ref :from  view-table :where view-qual
359                                                   :result-types nil)))
360       (when res
361         (get-slot-values-from-view instance (list slot-def) (car res))))))
362
363 (defmethod update-slot-with-null ((object standard-db-object)
364                                   slotname
365                                   slotdef)
366   (setf (slot-value object slotname) (slot-value slotdef 'void-value)))
367
368 (defvar +no-slot-value+ '+no-slot-value+)
369
370 (defsql sql-slot-value (:symbol "slot-value") (classname slot &optional (value +no-slot-value+) (database *default-database*))
371         (let* ((class (find-class classname))
372                (sld (slotdef-for-slot-with-class slot class)))
373           (if sld
374               (if (eq value +no-slot-value+)
375                   (sql-expression :attribute (view-class-slot-column sld)
376                                   :table (view-table class))
377                   (db-value-from-slot
378                    sld
379                    value
380                    database))
381               (error "Unknown slot ~A for class ~A" slot classname))))
382
383 (defsql sql-view-class (:symbol "view-class") (classname &optional (database *default-database*))
384         (declare (ignore database))
385         (let* ((class (find-class classname)))
386           (unless (view-table class)
387             (error "No view-table for class ~A"  classname))
388           (sql-expression :table (view-table class))))
389
390
391 (defmethod database-get-type-specifier (type args database db-type)
392   (declare (ignore type args database db-type))
393   (format nil "VARCHAR(~D)" *default-string-length*))
394
395 (defmethod database-get-type-specifier ((type (eql 'integer)) args database db-type)
396   (declare (ignore database db-type))
397   (if args
398       (format nil "INT(~A)" (car args))
399       "INT"))
400
401 (deftype tinyint ()
402   "An 8-bit integer, this width may vary by SQL implementation."
403   'integer)
404
405 (defmethod database-get-type-specifier ((type (eql 'tinyint)) args database db-type)
406   (declare (ignore args database db-type))
407   "INT")
408
409 (deftype smallint ()
410   "An integer smaller than a 32-bit integer. this width may vary by SQL implementation."
411   'integer)
412
413 (defmethod database-get-type-specifier ((type (eql 'smallint)) args database db-type)
414   (declare (ignore args database db-type))
415   "INT")
416
417 (deftype mediumint ()
418   "An integer smaller than a 32-bit integer, but may be larger than a smallint. This width may vary by SQL implementation."
419   'integer)
420
421 (defmethod database-get-type-specifier ((type (eql 'mediumint)) args database db-type)
422   (declare (ignore args database db-type))
423   "INT")
424
425 (deftype bigint ()
426   "An integer larger than a 32-bit integer, this width may vary by SQL implementation."
427   'integer)
428
429 (defmethod database-get-type-specifier ((type (eql 'bigint)) args database db-type)
430   (declare (ignore args database db-type))
431   "BIGINT")
432
433 (deftype varchar (&optional size)
434   "A variable length string for the SQL varchar type."
435   (declare (ignore size))
436   'string)
437
438 (defmethod database-get-type-specifier ((type (eql 'varchar)) args
439                                         database db-type)
440   (declare (ignore database db-type))
441   (if args
442       (format nil "VARCHAR(~A)" (car args))
443       (format nil "VARCHAR(~D)" *default-string-length*)))
444
445 (defmethod database-get-type-specifier ((type (eql 'string)) args database db-type)
446   (declare (ignore database db-type))
447   (if args
448       (format nil "CHAR(~A)" (car args))
449       (format nil "VARCHAR(~D)" *default-string-length*)))
450
451 (deftype universal-time ()
452   "A positive integer as returned by GET-UNIVERSAL-TIME."
453   '(integer 1 *))
454
455 (defmethod database-get-type-specifier ((type (eql 'universal-time)) args database db-type)
456   (declare (ignore args database db-type))
457   "BIGINT")
458
459 (defmethod database-get-type-specifier ((type (eql 'wall-time)) args database db-type)
460   (declare (ignore args database db-type))
461   "TIMESTAMP")
462
463 (defmethod database-get-type-specifier ((type (eql 'date)) args database db-type)
464   (declare (ignore args database db-type))
465   "DATE")
466
467 (defmethod database-get-type-specifier ((type (eql 'duration)) args database db-type)
468   (declare (ignore database args db-type))
469   "VARCHAR")
470
471 (defmethod database-get-type-specifier ((type (eql 'money)) args database db-type)
472   (declare (ignore database args db-type))
473   "INT8")
474
475 #+ignore
476 (deftype char (&optional len)
477   "A lisp type for the SQL CHAR type."
478   `(string ,len))
479
480 (defmethod database-get-type-specifier ((type (eql 'float)) args database db-type)
481   (declare (ignore database db-type))
482   (if args
483       (format nil "FLOAT(~A)" (car args))
484       "FLOAT"))
485
486 (defmethod database-get-type-specifier ((type (eql 'long-float)) args database db-type)
487   (declare (ignore database db-type))
488   (if args
489       (format nil "FLOAT(~A)" (car args))
490       "FLOAT"))
491
492 (deftype generalized-boolean ()
493   "A type which outputs a SQL boolean value, though any lisp type can be stored in the slot."
494   t)
495
496 (defmethod database-get-type-specifier ((type (eql 'boolean)) args database db-type)
497   (declare (ignore args database db-type))
498   "BOOL")
499
500 (defmethod database-get-type-specifier ((type (eql 'generalized-boolean)) args database db-type)
501   (declare (ignore args database db-type))
502   "BOOL")
503
504 (defmethod database-get-type-specifier ((type (eql 'number)) args database db-type)
505   (declare (ignore database db-type))
506   (cond
507     ((and (consp args) (= (length args) 2))
508      (format nil "NUMBER(~D,~D)" (first args) (second args)))
509     ((and (consp args) (= (length args) 1))
510      (format nil "NUMBER(~D)" (first args)))
511     (t
512      "NUMBER")))
513
514 (defmethod database-get-type-specifier ((type (eql 'char)) args database db-type)
515   (declare (ignore database db-type))
516   (if args
517       (format nil "CHAR(~D)" (first args))
518       "CHAR(1)"))
519
520
521 (defmethod database-output-sql-as-type (type val database db-type)
522   (declare (ignore type database db-type))
523   val)
524
525 (defmethod database-output-sql-as-type ((type (eql 'list)) val database db-type)
526   (declare (ignore database db-type))
527   (progv '(*print-circle* *print-array*) '(t t)
528     (let ((escaped (prin1-to-string val)))
529       (substitute-char-string
530        escaped #\Null " "))))
531
532 (defmethod database-output-sql-as-type ((type (eql 'symbol)) val database db-type)
533   (declare (ignore database db-type))
534   (if val
535       (concatenate 'string
536                    (package-name (symbol-package val))
537                    "::"
538                    (symbol-name val))
539       ""))
540
541 (defmethod database-output-sql-as-type ((type (eql 'keyword)) val database db-type)
542   (declare (ignore database db-type))
543   (if val
544       (symbol-name val)
545       ""))
546
547 (defmethod database-output-sql-as-type ((type (eql 'vector)) val database db-type)
548   (declare (ignore database db-type))
549   (progv '(*print-circle* *print-array*) '(t t)
550     (prin1-to-string val)))
551
552 (defmethod database-output-sql-as-type ((type (eql 'array)) val database db-type)
553   (declare (ignore database db-type))
554   (progv '(*print-circle* *print-array*) '(t t)
555     (prin1-to-string val)))
556
557 (defmethod database-output-sql-as-type ((type (eql 'boolean)) val database db-type)
558   (declare (ignore database db-type))
559   (if val "t" "f"))
560
561 (defmethod database-output-sql-as-type ((type (eql 'generalized-boolean)) val database db-type)
562   (declare (ignore database db-type))
563   (if val "t" "f"))
564
565 (defmethod database-output-sql-as-type ((type (eql 'string)) val database db-type)
566   (declare (ignore database db-type))
567   val)
568
569 (defmethod database-output-sql-as-type ((type (eql 'char)) val database db-type)
570   (declare (ignore database db-type))
571   (etypecase val
572     (character (write-to-string val))
573     (string val)))
574
575 (defmethod database-output-sql-as-type ((type (eql 'float)) val database db-type)
576   (declare (ignore database db-type))
577   (if (eq (type-of val) 'null)
578       nil
579       (let ((*read-default-float-format* (type-of val)))
580        (format nil "~F" val))))
581
582 (defmethod read-sql-value (val type database db-type)
583   (declare (ignore type database db-type))
584   (read-from-string val))
585
586 (defmethod read-sql-value (val (type (eql 'string)) database db-type)
587   (declare (ignore database db-type))
588   val)
589
590 (defmethod read-sql-value (val (type (eql 'varchar)) database db-type)
591   (declare (ignore database db-type))
592   val)
593
594 (defmethod read-sql-value (val (type (eql 'char)) database db-type)
595   (declare (ignore database db-type))
596   (schar val 0))
597
598 (defmethod read-sql-value (val (type (eql 'keyword)) database db-type)
599   (declare (ignore database db-type))
600   (when (< 0 (length val))
601     (intern (symbol-name-default-case val)
602             (find-package '#:keyword))))
603
604 (defmethod read-sql-value (val (type (eql 'symbol)) database db-type)
605   (declare (ignore database db-type))
606   (when (< 0 (length val))
607     (unless (string= val (symbol-name-default-case "NIL"))
608       (read-from-string val))))
609
610 (defmethod read-sql-value (val (type (eql 'integer)) database db-type)
611   (declare (ignore database db-type))
612   (etypecase val
613     (string
614      (unless (string-equal "NIL" val)
615        (parse-integer val)))
616     (number val)))
617
618 (defmethod read-sql-value (val (type (eql 'smallint)) database db-type)
619   (declare (ignore database db-type))
620   (etypecase val
621     (string
622      (unless (string-equal "NIL" val)
623        (parse-integer val)))
624     (number val)))
625
626 (defmethod read-sql-value (val (type (eql 'bigint)) database db-type)
627   (declare (ignore database db-type))
628   (etypecase val
629     (string
630      (unless (string-equal "NIL" val)
631        (parse-integer val)))
632     (number val)))
633
634 (defmethod read-sql-value (val (type (eql 'float)) database db-type)
635   (declare (ignore database db-type))
636   ;; writing 1.0 writes 1, so we we *really* want a float, must do (float ...)
637   (etypecase val
638     (string
639      (float (read-from-string val)))
640     (float
641      val)))
642
643 (defmethod read-sql-value (val (type (eql 'boolean)) database db-type)
644   (declare (ignore database db-type))
645   (equal "t" val))
646
647 (defmethod read-sql-value (val (type (eql 'generalized-boolean)) database db-type)
648   (declare (ignore database db-type))
649   (equal "t" val))
650
651 (defmethod read-sql-value (val (type (eql 'number)) database db-type)
652   (declare (ignore database db-type))
653   (etypecase val
654     (string
655      (unless (string-equal "NIL" val)
656        (read-from-string val)))
657     (number val)))
658
659 (defmethod read-sql-value (val (type (eql 'universal-time)) database db-type)
660   (declare (ignore database db-type))
661   (unless (eq 'NULL val)
662     (etypecase val
663       (string
664        (parse-integer val))
665       (number val))))
666
667 (defmethod read-sql-value (val (type (eql 'wall-time)) database db-type)
668   (declare (ignore database db-type))
669   (unless (eq 'NULL val)
670     (parse-timestring val)))
671
672 (defmethod read-sql-value (val (type (eql 'date)) database db-type)
673   (declare (ignore database db-type))
674   (unless (eq 'NULL val)
675     (parse-datestring val)))
676
677 (defmethod read-sql-value (val (type (eql 'duration)) database db-type)
678   (declare (ignore database db-type))
679   (unless (or (eq 'NULL val)
680               (equal "NIL" val))
681     (parse-timestring val)))
682
683 ;; ------------------------------------------------------------
684 ;; Logic for 'faulting in' :join slots
685
686 ;; this works, but is inefficient requiring (+ 1 n-rows)
687 ;; SQL queries
688 #+ignore
689 (defun fault-join-target-slot (class object slot-def)
690   (let* ((res (fault-join-slot-raw class object slot-def))
691          (dbi (view-class-slot-db-info slot-def))
692          (target-name (gethash :target-slot dbi))
693          (target-class (find-class target-name)))
694     (when res
695       (mapcar (lambda (obj)
696                 (list
697                  (car
698                   (fault-join-slot-raw
699                    target-class
700                    obj
701                    (find target-name (class-slots (class-of obj))
702                          :key #'slot-definition-name)))
703                  obj))
704               res)
705       #+ignore ;; this doesn't work when attempting to call slot-value
706       (mapcar (lambda (obj)
707                 (cons obj (slot-value obj ts))) res))))
708
709 (defun fault-join-target-slot (class object slot-def)
710   (let* ((dbi (view-class-slot-db-info slot-def))
711          (ts (gethash :target-slot dbi))
712          (jc  (gethash :join-class dbi))
713          (jc-view-table (view-table (find-class jc)))
714          (tdbi (view-class-slot-db-info
715                 (find ts (class-slots (find-class jc))
716                       :key #'slot-definition-name)))
717          (retrieval (gethash :retrieval tdbi))
718          (tsc (gethash :join-class tdbi))
719          (ts-view-table (view-table (find-class tsc)))
720          (jq (join-qualifier class object slot-def))
721          (key (slot-value object (gethash :home-key dbi))))
722
723     (when jq
724       (ecase retrieval
725         (:immediate
726          (let ((res
727                 (find-all (list tsc)
728                           :inner-join (sql-expression :table jc-view-table)
729                           :on (sql-operation
730                                '==
731                                (sql-expression
732                                 :attribute (gethash :foreign-key tdbi)
733                                 :table ts-view-table)
734                                (sql-expression
735                                 :attribute (gethash :home-key tdbi)
736                                 :table jc-view-table))
737                           :where jq
738                           :result-types :auto
739                           :database (view-database object))))
740            (mapcar #'(lambda (i)
741                        (let* ((instance (car i))
742                               (jcc (make-instance jc :view-database (view-database instance))))
743                          (setf (slot-value jcc (gethash :foreign-key dbi))
744                                key)
745                          (setf (slot-value jcc (gethash :home-key tdbi))
746                                (slot-value instance (gethash :foreign-key tdbi)))
747                          (list instance jcc)))
748                    res)))
749         (:deferred
750          ;; just fill in minimal slots
751          (mapcar
752           #'(lambda (k)
753               (let ((instance (make-instance tsc :view-database (view-database object)))
754                     (jcc (make-instance jc :view-database (view-database object)))
755                     (fk (car k)))
756                 (setf (slot-value instance (gethash :home-key tdbi)) fk)
757                 (setf (slot-value jcc (gethash :foreign-key dbi))
758                       key)
759                 (setf (slot-value jcc (gethash :home-key tdbi))
760                       fk)
761                 (list instance jcc)))
762           (select (sql-expression :attribute (gethash :foreign-key tdbi) :table jc-view-table)
763                   :from (sql-expression :table jc-view-table)
764                   :where jq
765                   :database (view-database object))))))))
766
767
768 ;;; Remote Joins
769
770 (defvar *default-update-objects-max-len* nil
771   "The default value to use for the MAX-LEN keyword argument to
772   UPDATE-OBJECT-JOINS.")
773
774 (defun update-objects-joins (objects &key (slots t) (force-p t)
775                              class-name (max-len
776                                          *default-update-objects-max-len*))
777   "Updates from the records of the appropriate database tables
778 the join slots specified by SLOTS in the supplied list of View
779 Class instances OBJECTS.  SLOTS is t by default which means that
780 all join slots with :retrieval :immediate are updated. CLASS-NAME
781 is used to specify the View Class of all instance in OBJECTS and
782 default to nil which means that the class of the first instance
783 in OBJECTS is used. FORCE-P is t by default which means that all
784 join slots are updated whereas a value of nil means that only
785 unbound join slots are updated. MAX-LEN defaults to
786 *DEFAULT-UPDATE-OBJECTS-MAX-LEN* and when non-nil specifies that
787 UPDATE-OBJECT-JOINS may issue multiple database queries with a
788 maximum of MAX-LEN instances updated in each query."
789   (assert (or (null max-len) (plusp max-len)))
790   (when objects
791     (unless class-name
792       (setq class-name (class-name (class-of (first objects)))))
793     (let* ((class (find-class class-name))
794            (class-slots (ordered-class-slots class))
795            (slotdefs
796             (if (eq t slots)
797                 (generate-retrieval-joins-list class :deferred)
798                 (remove-if #'null
799                            (mapcar #'(lambda (name)
800                                        (let ((slotdef (find name class-slots :key #'slot-definition-name)))
801                                          (unless slotdef
802                                            (warn "Unable to find slot named ~S in class ~S." name class))
803                                          slotdef))
804                                    slots)))))
805       (dolist (slotdef slotdefs)
806         (let* ((dbi (view-class-slot-db-info slotdef))
807                (slotdef-name (slot-definition-name slotdef))
808                (foreign-key (gethash :foreign-key dbi))
809                (home-key (gethash :home-key dbi))
810                (object-keys
811                 (remove-duplicates
812                  (if force-p
813                      (mapcar #'(lambda (o) (slot-value o home-key)) objects)
814                      (remove-if #'null
815                                 (mapcar
816                                  #'(lambda (o) (if (slot-boundp o slotdef-name)
817                                                    nil
818                                                    (slot-value o home-key)))
819                                  objects)))))
820                (n-object-keys (length object-keys))
821                (query-len (or max-len n-object-keys)))
822
823           (do ((i 0 (+ i query-len)))
824               ((>= i n-object-keys))
825             (let* ((keys (if max-len
826                              (subseq object-keys i (min (+ i query-len) n-object-keys))
827                              object-keys))
828                    (results (unless (gethash :target-slot dbi)
829                               (find-all (list (gethash :join-class dbi))
830                                         :where (make-instance 'sql-relational-exp
831                                                               :operator 'in
832                                                               :sub-expressions (list (sql-expression :attribute foreign-key)
833                                                                                      keys))
834                                         :result-types :auto
835                                         :flatp t)) ))
836
837               (dolist (object objects)
838                 (when (or force-p (not (slot-boundp object slotdef-name)))
839                   (let ((res (if results
840                                  (remove-if-not #'(lambda (obj)
841                                                     (equal obj (slot-value
842                                                                 object
843                                                                 home-key)))
844                                                 results
845                                                 :key #'(lambda (res)
846                                                          (slot-value res
847                                                                      foreign-key)))
848
849                                  (progn
850                                    (when (gethash :target-slot dbi)
851                                      (fault-join-target-slot class object slotdef))))))
852                     (when res
853                       (setf (slot-value object slotdef-name)
854                             (if (gethash :set dbi) res (car res)))))))))))))
855   (values))
856
857 (defun fault-join-slot-raw (class object slot-def)
858   (let* ((dbi (view-class-slot-db-info slot-def))
859          (jc (gethash :join-class dbi)))
860     (let ((jq (join-qualifier class object slot-def)))
861       (when jq
862         (select jc :where jq :flatp t :result-types nil
863                 :database (view-database object))))))
864
865 (defun fault-join-slot (class object slot-def)
866   (let* ((dbi (view-class-slot-db-info slot-def))
867          (ts (gethash :target-slot dbi)))
868     (if (and ts (gethash :set dbi))
869         (fault-join-target-slot class object slot-def)
870         (let ((res (fault-join-slot-raw class object slot-def)))
871           (when res
872             (cond
873               ((and ts (not (gethash :set dbi)))
874                (mapcar (lambda (obj) (slot-value obj ts)) res))
875               ((and (not ts) (not (gethash :set dbi)))
876                (car res))
877               ((and (not ts) (gethash :set dbi))
878                res)))))))
879
880 ;;;; Should we not return the whole result, instead of only
881 ;;;; the one slot-value? We get all the values from the db
882 ;;;; anyway, so?
883 (defun fault-join-normalized-slot (class object slot-def)
884   (labels ((getsc (this-class)
885              (let ((sc (car (class-direct-superclasses this-class))))
886                (if (key-slots sc)
887                    sc
888                    (getsc sc)))))
889     (let* ((sc (getsc class))
890            (hk (slot-definition-name (car (key-slots class))))
891            (fk (slot-definition-name (car (key-slots sc)))))
892       (let ((jq (sql-operation '==
893                                (typecase fk
894                                  (symbol
895                                   (sql-expression
896                                    :attribute
897                                    (view-class-slot-column
898                                     (slotdef-for-slot-with-class fk sc))
899                                    :table (view-table sc)))
900                                  (t fk))
901                                (typecase hk
902                                  (symbol
903                                   (slot-value object hk))
904                                  (t hk)))))
905
906         ;; Caching nil in next select, because in normalized mode
907         ;; records can be changed through other instances (children,
908         ;; parents) so changes possibly won't be noticed
909         (let ((res (car (select (class-name sc) :where jq
910                                                 :flatp t :result-types nil
911                                                 :caching nil
912                                                 :database (view-database object))))
913               (slot-name (slot-definition-name slot-def)))
914
915           ;; If current class is normalized and wanted slot is not
916           ;; a direct member, recurse up
917           (if (and (normalizedp class)
918                    (not (member slot-name
919                                 (mapcar #'(lambda (esd) (slot-definition-name esd))
920                                         (ordered-class-direct-slots class))))
921                    (not (slot-boundp res slot-name)))
922               (fault-join-normalized-slot sc res slot-def)
923               (slot-value res slot-name)))))) )
924
925 (defun join-qualifier (class object slot-def)
926   (declare (ignore class))
927   (let* ((dbi (view-class-slot-db-info slot-def))
928          (jc (find-class (gethash :join-class dbi)))
929          ;;(ts (gethash :target-slot dbi))
930          ;;(tsdef (if ts (slotdef-for-slot-with-class ts jc)))
931          (foreign-keys (gethash :foreign-key dbi))
932          (home-keys (gethash :home-key dbi)))
933     (when (every #'(lambda (slt)
934                      (and (slot-boundp object slt)
935                           (not (null (slot-value object slt)))))
936                  (if (listp home-keys) home-keys (list home-keys)))
937       (let ((jc
938              (mapcar #'(lambda (hk fk)
939                          (let ((fksd (slotdef-for-slot-with-class fk jc)))
940                            (sql-operation '==
941                                           (typecase fk
942                                             (symbol
943                                              (sql-expression
944                                               :attribute
945                                               (view-class-slot-column fksd)
946                                               :table (view-table jc)))
947                                             (t fk))
948                                           (typecase hk
949                                             (symbol
950                                              (slot-value object hk))
951                                             (t
952                                              hk)))))
953                      (if (listp home-keys)
954                          home-keys
955                          (list home-keys))
956                      (if (listp foreign-keys)
957                          foreign-keys
958                          (list foreign-keys)))))
959         (when jc
960           (if (> (length jc) 1)
961               (apply #'sql-and jc)
962               jc))))))
963
964 ;; FIXME: add retrieval immediate for efficiency
965 ;; For example, for (select 'employee-address) in test suite =>
966 ;; select addr.*,ea_join.* FROM addr,ea_join WHERE ea_join.aaddressid=addr.addressid\g
967
968 (defun build-objects (vals sclasses immediate-join-classes sels immediate-joins database refresh flatp instances)
969   "Used by find-all to build objects."
970   (labels ((build-object (vals vclass jclasses selects immediate-selects instance)
971              (let* ((db-vals (butlast vals (- (list-length vals)
972                                               (list-length selects))))
973                     (obj (if instance instance (make-instance (class-name vclass) :view-database database)))
974                     (join-vals (subseq vals (list-length selects)))
975                     (joins (mapcar #'(lambda (c) (when c (make-instance c :view-database database)))
976                                    jclasses)))
977
978                ;;(format t "joins: ~S~%db-vals: ~S~%join-values: ~S~%selects: ~S~%immediate-selects: ~S~%"
979                ;;joins db-vals join-vals selects immediate-selects)
980
981                ;; use refresh keyword here
982                (setf obj (get-slot-values-from-view obj (mapcar #'car selects) db-vals))
983                (mapc #'(lambda (jo)
984                          ;; find all immediate-select slots and join-vals for this object
985                          (let* ((jo-class (class-of jo))
986                                 (slots
987                                  (if (normalizedp jo-class)
988                                      (class-direct-slots jo-class)
989                                      (class-slots jo-class)))
990                                 (pos-list (remove-if #'null
991                                                      (mapcar
992                                                       #'(lambda (s)
993                                                           (position s immediate-selects
994                                                                     :key #'car
995                                                                     :test #'eq))
996                                                       slots))))
997                            (get-slot-values-from-view jo
998                                                       (mapcar #'car
999                                                               (mapcar #'(lambda (pos)
1000                                                                           (nth pos immediate-selects))
1001                                                                       pos-list))
1002                                                       (mapcar #'(lambda (pos) (nth pos join-vals))
1003                                                               pos-list))))
1004                      joins)
1005                (mapc
1006                 #'(lambda (jc)
1007                     (let* ((vslots
1008                             (class-slots vclass))
1009                            (slot (find (class-name (class-of jc)) vslots
1010                                        :key #'(lambda (slot)
1011                                                 (when (and (eq :join (view-class-slot-db-kind slot))
1012                                                            (eq (slot-definition-name slot)
1013                                                                (gethash :join-class (view-class-slot-db-info slot))))
1014                                                   (slot-definition-name slot))))))
1015                       (when slot
1016                         (setf (slot-value obj (slot-definition-name slot)) jc))))
1017                 joins)
1018                (when refresh (instance-refreshed obj))
1019                obj)))
1020     (let* ((objects
1021             (mapcar #'(lambda (sclass jclass sel immediate-join instance)
1022                         (prog1
1023                             (build-object vals sclass jclass sel immediate-join instance)
1024                           (setf vals (nthcdr (+ (list-length sel) (list-length immediate-join))
1025                                              vals))))
1026                     sclasses immediate-join-classes sels immediate-joins instances)))
1027       (if (and flatp (= (length sclasses) 1))
1028           (car objects)
1029           objects))))
1030
1031 (defun find-all (view-classes
1032                  &rest args
1033                  &key all set-operation distinct from where group-by having
1034                  order-by offset limit refresh flatp result-types
1035                  inner-join on
1036                  (database *default-database*)
1037                  instances)
1038   "Called by SELECT to generate object query results when the
1039   View Classes VIEW-CLASSES are passed as arguments to SELECT."
1040   (declare (ignore all set-operation group-by having offset limit inner-join on))
1041   (flet ((ref-equal (ref1 ref2)
1042            (string= (sql-output ref1 database)
1043                     (sql-output ref2 database)))
1044          (table-sql-expr (table)
1045            (sql-expression :table (view-table table)))
1046          (tables-equal (table-a table-b)
1047            (when (and table-a table-b)
1048              (string= (string (slot-value table-a 'name))
1049                       (string (slot-value table-b 'name))))))
1050     (remf args :from)
1051     (remf args :where)
1052     (remf args :flatp)
1053     (remf args :additional-fields)
1054     (remf args :result-types)
1055     (remf args :instances)
1056     (let* ((*db-deserializing* t)
1057            (sclasses (mapcar #'find-class view-classes))
1058            (immediate-join-slots
1059             (mapcar #'(lambda (c) (generate-retrieval-joins-list c :immediate)) sclasses))
1060            (immediate-join-classes
1061             (mapcar #'(lambda (jcs)
1062                         (mapcar #'(lambda (slotdef)
1063                                     (find-class (gethash :join-class (view-class-slot-db-info slotdef))))
1064                                 jcs))
1065                     immediate-join-slots))
1066            (immediate-join-sels (mapcar #'generate-immediate-joins-selection-list sclasses))
1067            (sels (mapcar #'generate-selection-list sclasses))
1068            (fullsels (apply #'append (mapcar #'append sels immediate-join-sels)))
1069            (sel-tables (collect-table-refs where))
1070            (tables (remove-if #'null
1071                               (remove-duplicates
1072                                (append (mapcar #'table-sql-expr sclasses)
1073                                        (mapcan #'(lambda (jc-list)
1074                                                    (mapcar
1075                                                     #'(lambda (jc) (when jc (table-sql-expr jc)))
1076                                                     jc-list))
1077                                                immediate-join-classes)
1078                                        sel-tables)
1079                                :test #'tables-equal)))
1080            (order-by-slots (mapcar #'(lambda (ob) (if (atom ob) ob (car ob)))
1081                                    (listify order-by)))
1082            (join-where nil))
1083
1084       ;;(format t "sclasses: ~W~%ijc: ~W~%tables: ~W~%" sclasses immediate-join-classes tables)
1085
1086       (dolist (ob order-by-slots)
1087         (when (and ob (not (member ob (mapcar #'cdr fullsels)
1088                                    :test #'ref-equal)))
1089           (setq fullsels
1090                 (append fullsels (mapcar #'(lambda (att) (cons nil att))
1091                                          order-by-slots)))))
1092       (dolist (ob (listify distinct))
1093         (when (and (typep ob 'sql-ident)
1094                    (not (member ob (mapcar #'cdr fullsels)
1095                                 :test #'ref-equal)))
1096           (setq fullsels
1097                 (append fullsels (mapcar #'(lambda (att) (cons nil att))
1098                                          (listify ob))))))
1099       (mapcar #'(lambda (vclass jclasses jslots)
1100                   (when jclasses
1101                     (mapcar
1102                      #'(lambda (jclass jslot)
1103                          (let ((dbi (view-class-slot-db-info jslot)))
1104                            (setq join-where
1105                                  (append
1106                                   (list (sql-operation '==
1107                                                        (sql-expression
1108                                                         :attribute (gethash :foreign-key dbi)
1109                                                         :table (view-table jclass))
1110                                                        (sql-expression
1111                                                         :attribute (gethash :home-key dbi)
1112                                                         :table (view-table vclass))))
1113                                   (when join-where (listify join-where))))))
1114                      jclasses jslots)))
1115               sclasses immediate-join-classes immediate-join-slots)
1116       ;; Reported buggy on clsql-devel
1117       ;; (when where (setq where (listify where)))
1118       (cond
1119         ((and where join-where)
1120          (setq where (list (apply #'sql-and where join-where))))
1121         ((and (null where) (> (length join-where) 1))
1122          (setq where (list (apply #'sql-and join-where)))))
1123
1124       (let* ((rows (apply #'select
1125                           (append (mapcar #'cdr fullsels)
1126                                   (cons :from
1127                                         (list (append (when from (listify from))
1128                                                       (listify tables))))
1129                                   (list :result-types result-types)
1130                                   (when where
1131                                     (list :where where))
1132                                   args)))
1133              (instances-to-add (- (length rows) (length instances)))
1134              (perhaps-extended-instances
1135               (if (plusp instances-to-add)
1136                   (append instances (do ((i 0 (1+ i))
1137                                          (res nil))
1138                                         ((= i instances-to-add) res)
1139                                       (push (make-list (length sclasses) :initial-element nil) res)))
1140                   instances))
1141              (objects (mapcar
1142                        #'(lambda (row instance)
1143                            (build-objects row sclasses immediate-join-classes sels
1144                                           immediate-join-sels database refresh flatp
1145                                           (if (and flatp (atom instance))
1146                                               (list instance)
1147                                               instance)))
1148                        rows perhaps-extended-instances)))
1149         objects))))
1150
1151 (defmethod instance-refreshed ((instance standard-db-object)))
1152
1153 (defvar *default-caching* t
1154   "Controls whether SELECT caches objects by default. The CommonSQL
1155 specification states caching is on by default.")
1156
1157 (defun select (&rest select-all-args)
1158   "Executes a query on DATABASE, which has a default value of
1159 *DEFAULT-DATABASE*, specified by the SQL expressions supplied
1160 using the remaining arguments in SELECT-ALL-ARGS. The SELECT
1161 argument can be used to generate queries in both functional and
1162 object oriented contexts.
1163
1164 In the functional case, the required arguments specify the
1165 columns selected by the query and may be symbolic SQL expressions
1166 or strings representing attribute identifiers. Type modified
1167 identifiers indicate that the values selected from the specified
1168 column are converted to the specified lisp type. The keyword
1169 arguments ALL, DISTINCT, FROM, GROUP-by, HAVING, ORDER-BY,
1170 SET-OPERATION and WHERE are used to specify, using the symbolic
1171 SQL syntax, the corresponding components of the SQL query
1172 generated by the call to SELECT. RESULT-TYPES is a list of
1173 symbols which specifies the lisp type for each field returned by
1174 the query. If RESULT-TYPES is nil all results are returned as
1175 strings whereas the default value of :auto means that the lisp
1176 types are automatically computed for each field. FIELD-NAMES is t
1177 by default which means that the second value returned is a list
1178 of strings representing the columns selected by the query. If
1179 FIELD-NAMES is nil, the list of column names is not returned as a
1180 second value.
1181
1182 In the object oriented case, the required arguments to SELECT are
1183 symbols denoting View Classes which specify the database tables
1184 to query. In this case, SELECT returns a list of View Class
1185 instances whose slots are set from the attribute values of the
1186 records in the specified table. Slot-value is a legal operator
1187 which can be employed as part of the symbolic SQL syntax used in
1188 the WHERE keyword argument to SELECT. REFRESH is nil by default
1189 which means that the View Class instances returned are retrieved
1190 from a cache if an equivalent call to SELECT has previously been
1191 issued. If REFRESH is true, the View Class instances returned are
1192 updated as necessary from the database and the generic function
1193 INSTANCE-REFRESHED is called to perform any necessary operations
1194 on the updated instances.
1195
1196 In both object oriented and functional contexts, FLATP has a
1197 default value of nil which means that the results are returned as
1198 a list of lists. If FLATP is t and only one result is returned
1199 for each record selected in the query, the results are returned
1200 as elements of a list."
1201
1202   (flet ((select-objects (target-args)
1203            (and target-args
1204                 (every #'(lambda (arg)
1205                            (and (symbolp arg)
1206                                 (find-class arg nil)))
1207                        target-args))))
1208     (multiple-value-bind (target-args qualifier-args)
1209         (query-get-selections select-all-args)
1210       (unless (or *default-database* (getf qualifier-args :database))
1211         (signal-no-database-error nil))
1212
1213       (cond
1214         ((select-objects target-args)
1215          (let ((caching (getf qualifier-args :caching *default-caching*))
1216                (result-types (getf qualifier-args :result-types :auto))
1217                (refresh (getf qualifier-args :refresh nil))
1218                (database (or (getf qualifier-args :database) *default-database*))
1219                (order-by (getf qualifier-args :order-by)))
1220            (remf qualifier-args :caching)
1221            (remf qualifier-args :refresh)
1222            (remf qualifier-args :result-types)
1223
1224            ;; Add explicity table name to order-by if not specified and only
1225            ;; one selected table. This is required so FIND-ALL won't duplicate
1226            ;; the field
1227            (when (and order-by (= 1 (length target-args)))
1228              (let ((table-name (view-table (find-class (car target-args))))
1229                    (order-by-list (copy-seq (listify order-by))))
1230
1231                (loop for i from 0 below (length order-by-list)
1232                   do (etypecase (nth i order-by-list)
1233                        (sql-ident-attribute
1234                         (unless (slot-value (nth i order-by-list) 'qualifier)
1235                           (setf (slot-value (nth i order-by-list) 'qualifier) table-name)))
1236                        (cons
1237                         (unless (slot-value (car (nth i order-by-list)) 'qualifier)
1238                           (setf (slot-value (car (nth i order-by-list)) 'qualifier) table-name)))))
1239                (setf (getf qualifier-args :order-by) order-by-list)))
1240
1241            (cond
1242              ((null caching)
1243               (apply #'find-all target-args
1244                      (append qualifier-args
1245                              (list :result-types result-types :refresh refresh))))
1246              (t
1247               (let ((cached (records-cache-results target-args qualifier-args database)))
1248                 (cond
1249                   ((and cached (not refresh))
1250                    cached)
1251                   ((and cached refresh)
1252                    (let ((results (apply #'find-all (append (list target-args) qualifier-args `(:instances ,cached :result-types :auto :refresh ,refresh)))))
1253                      (setf (records-cache-results target-args qualifier-args database) results)
1254                      results))
1255                   (t
1256                    (let ((results (apply #'find-all target-args (append qualifier-args
1257                                                                         `(:result-types :auto :refresh ,refresh)))))
1258                      (setf (records-cache-results target-args qualifier-args database) results)
1259                      results))))))))
1260         (t
1261          (let* ((expr (apply #'make-query select-all-args))
1262                 (specified-types
1263                  (mapcar #'(lambda (attrib)
1264                              (if (typep attrib 'sql-ident-attribute)
1265                                  (let ((type (slot-value attrib 'type)))
1266                                    (if type
1267                                        type
1268                                        t))
1269                                  t))
1270                          (slot-value expr 'selections))))
1271            (destructuring-bind (&key (flatp nil)
1272                                      (result-types :auto)
1273                                      (field-names t)
1274                                      (database *default-database*)
1275                                      &allow-other-keys)
1276                qualifier-args
1277              (query expr :flatp flatp
1278                     :result-types
1279                     ;; specifying a type for an attribute overrides result-types
1280                     (if (some #'(lambda (x) (not (eq t x))) specified-types)
1281                         specified-types
1282                         result-types)
1283                     :field-names field-names
1284                     :database database))))))))
1285
1286 (defun compute-records-cache-key (targets qualifiers)
1287   (list targets
1288         (do ((args *select-arguments* (cdr args))
1289              (results nil))
1290             ((null args) results)
1291           (let* ((arg (car args))
1292                  (value (getf qualifiers arg)))
1293             (when value
1294               (push (list arg
1295                           (typecase value
1296                             (cons (cons (sql (car value)) (cdr value)))
1297                             (%sql-expression (sql value))
1298                             (t value)))
1299                     results))))))
1300
1301 (defun records-cache-results (targets qualifiers database)
1302   (when (record-caches database)
1303     (gethash (compute-records-cache-key targets qualifiers) (record-caches database))))
1304
1305 (defun (setf records-cache-results) (results targets qualifiers database)
1306   (unless (record-caches database)
1307     (setf (record-caches database)
1308           (make-hash-table :test 'equal
1309                            #+allegro   :values    #+allegro :weak
1310                            #+clisp     :weak      #+clisp :value
1311                            #+lispworks :weak-kind #+lispworks :value)))
1312   (setf (gethash (compute-records-cache-key targets qualifiers)
1313                  (record-caches database)) results)
1314   results)
1315
1316
1317
1318 ;;; Serialization functions
1319
1320 (defun write-instance-to-stream (obj stream)
1321   "Writes an instance to a stream where it can be later be read.
1322 NOTE: an error will occur if a slot holds a value which can not be written readably."
1323   (let* ((class (class-of obj))
1324          (alist '()))
1325     (dolist (slot (ordered-class-slots (class-of obj)))
1326       (let ((name (slot-definition-name slot)))
1327         (when (and (not (eq 'view-database name))
1328                    (slot-boundp obj name))
1329           (push (cons name (slot-value obj name)) alist))))
1330     (setq alist (reverse alist))
1331     (write (cons (class-name class) alist) :stream stream :readably t))
1332   obj)
1333
1334 (defun read-instance-from-stream (stream)
1335   (let ((raw (read stream nil nil)))
1336     (when raw
1337       (let ((obj (make-instance (car raw))))
1338         (dolist (pair (cdr raw))
1339           (setf (slot-value obj (car pair)) (cdr pair)))
1340         obj))))