X-Git-Url: http://git.kpe.io/?a=blobdiff_plain;f=sql%2Fobjects.lisp;h=e6a9457a9dacf8fa2d00b814b37ba9fed7aedc96;hb=b2ff4969e20cce173d403de7542d5bf0e46938d7;hp=523ff6164973b7d1e778993927eaf9a0dcfeda67;hpb=8373c592f525a394a1ff66986ad4acf50e8e11b7;p=clsql.git diff --git a/sql/objects.lisp b/sql/objects.lisp index 523ff61..e6a9457 100644 --- a/sql/objects.lisp +++ b/sql/objects.lisp @@ -317,10 +317,10 @@ superclass of the newly-defined View Class." (basetype (if (listp slot-type) (car slot-type) slot-type))) (when (and slot-type val) (unless (typep val basetype) - (error 'clsql-type-error - :slotname (slot-definition-name slotdef) - :typespec slot-type - :value val))))) + (error 'sql-user-error + :message + (format nil "Invalid value ~A in slot ~A, not of type ~A." + val (slot-definition-name slotdef) slot-type)))))) ;; ;; Called by find-all @@ -423,7 +423,7 @@ superclass of the newly-defined View Class." (let ((qualifier (key-qualifier-for-instance instance :database vd))) (delete-records :from vt :where qualifier :database vd) (setf (slot-value instance 'view-database) nil)) - (error 'clsql-no-database-error :database nil)))) + (signal-no-database-error vd)))) (defmethod update-instance-from-records ((instance standard-db-object) &key (database *default-database*)) @@ -690,7 +690,11 @@ superclass of the newly-defined View Class." (defmethod read-sql-value (val (type (eql 'float)) database) (declare (ignore database)) ;; writing 1.0 writes 1, so we we *really* want a float, must do (float ...) - (float (read-from-string val))) + (etypecase val + (string + (float (read-from-string val))) + (float + val))) (defmethod read-sql-value (val (type (eql 'boolean)) database) (case (database-underlying-type database) @@ -804,31 +808,71 @@ superclass of the newly-defined View Class." :from (sql-expression :table jc-view-table) :where jq))))))) + +;;; Remote Joins + +(defvar *default-update-objects-max-len* nil + "The default maximum number of objects supplying data for a + query when updating remote joins.") + (defun update-object-joins (objects &key (slots t) (force-p t) class-name (max-len *default-update-objects-max-len*)) "Updates the remote join slots, that is those slots defined without :retrieval :immediate." + (assert (or (null max-len) (plusp max-len))) (when objects (unless class-name (setq class-name (class-name (class-of (first objects))))) (let* ((class (find-class class-name)) - (deferred-joins (generate-retrieval-joins-list class :deferred))) - (cond - (deferred-joins - (mapcar - #'(lambda (slotdef) - ;; FIXME: Rather than simply reading the values for each - ;; object, to meet CommonSQL spec need to generate a single - ;; query to read values for all objects, up to max-len count - (mapcar - #'(lambda (object) - (slot-value object (slot-definition-name slotdef))) - objects)) - deferred-joins)) - (t - (warn "Class ~A does not have any deferred join slots." class-name))) - ))) - + (class-slots (ordered-class-slots class)) + (slotdefs + (if (eq t slots) + (generate-retrieval-joins-list class :deferred) + (remove-if #'null + (mapcar #'(lambda (name) + (let ((slotdef (find name class-slots :key #'slot-definition-name))) + (unless slotdef + (warn "Unable to find slot named ~S in class ~S." name class)) + slotdef)) + slots))))) + (dolist (slotdef slotdefs) + (let* ((dbi (view-class-slot-db-info slotdef)) + (slotdef-name (slot-definition-name slotdef)) + (foreign-key (gethash :foreign-key dbi)) + (home-key (gethash :home-key dbi)) + (object-keys + (remove-duplicates + (if force-p + (mapcar #'(lambda (o) (slot-value o home-key)) objects) + (remove-if #'null + (mapcar + #'(lambda (o) (if (slot-boundp o slotdef-name) + nil + (slot-value o home-key))) + objects))))) + (n-object-keys (length object-keys)) + (query-len (or max-len n-object-keys))) + + (do ((i 0 (+ i query-len))) + ((>= i n-object-keys)) + (let* ((keys (if max-len + (subseq object-keys i (min (+ i query-len) n-object-keys)) + object-keys)) + (results (find-all (list (gethash :join-class dbi)) + :where (make-instance 'sql-relational-exp + :operator 'in + :sub-expressions (list (sql-expression :attribute foreign-key) + keys)) + :result-types :auto + :flatp t))) + (dolist (object objects) + (when (or force-p (not (slot-boundp object slotdef-name))) + (let ((res (find (slot-value object home-key) results + :key #'(lambda (res) (slot-value res foreign-key)) + :test #'equal))) + (when res + (setf (slot-value object slotdef-name) res))))))))))) + (values)) (defun fault-join-slot-raw (class object slot-def) (let* ((dbi (view-class-slot-db-info slot-def)) @@ -895,49 +939,51 @@ superclass of the newly-defined View Class." ;; For example, for (select 'employee-address) in test suite => ;; select addr.*,ea_join.* FROM addr,ea_join WHERE ea_join.aaddressid=addr.addressid\g -(defun build-objects (vals sclasses immediate-join-classes sels immediate-joins database refresh flatp) +(defun build-objects (vals sclasses immediate-join-classes sels immediate-joins database refresh flatp instances) "Used by find-all to build objects." - (labels ((build-object (vals vclass jclasses selects immediate-selects) - (let* ((class-name (class-name vclass)) - (db-vals (butlast vals (- (list-length vals) + (labels ((build-object (vals vclass jclasses selects immediate-selects instance) + (let* ((db-vals (butlast vals (- (list-length vals) (list-length selects)))) + (obj (if instance instance (make-instance (class-name vclass) :view-database database))) (join-vals (subseq vals (list-length selects))) - (obj (make-instance class-name :view-database database)) (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) ;; use refresh keyword here - (setf obj (get-slot-values-from-view obj (mapcar #'car selects) - db-vals)) + (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)) joins) (mapc - #'(lambda (jc) (let ((slot (find (class-name (class-of jc)) (class-slots vclass) - :key #'(lambda (slot) (when (and (eq :join (view-class-slot-db-kind slot)) - (eq (slot-definition-name slot) - (gethash :join-class (view-class-slot-db-info slot)))) - (slot-definition-name slot)))))) - (when slot - (setf (slot-value obj (slot-definition-name slot)) jc)))) - + #'(lambda (jc) + (let ((slot (find (class-name (class-of jc)) (class-slots vclass) + :key #'(lambda (slot) + (when (and (eq :join (view-class-slot-db-kind slot)) + (eq (slot-definition-name slot) + (gethash :join-class (view-class-slot-db-info slot)))) + (slot-definition-name slot)))))) + (when slot + (setf (slot-value obj (slot-definition-name slot)) jc)))) joins) (when refresh (instance-refreshed obj)) obj))) - (let ((objects (mapcar #'(lambda (sclass jclass sel immediate-join) - (prog1 (build-object vals sclass jclass sel immediate-join) - (setf vals (nthcdr (+ (list-length sel) (list-length immediate-join)) - vals)))) - sclasses immediate-join-classes sels immediate-joins))) + (let* ((objects + (mapcar #'(lambda (sclass jclass sel immediate-join instance) + (prog1 + (build-object vals sclass jclass sel immediate-join instance) + (setf vals (nthcdr (+ (list-length sel) (list-length immediate-join)) + vals)))) + sclasses immediate-join-classes sels immediate-joins instances))) (if (and flatp (= (length sclasses) 1)) (car objects) - objects)))) + objects)))) (defun find-all (view-classes &rest args &key all set-operation distinct from where group-by having - order-by order-by-descending offset limit refresh - flatp result-types inner-join on - (database *default-database*)) + order-by offset limit refresh flatp result-types + inner-join on + (database *default-database*) + instances) "Called by SELECT to generate object query results when the View Classes VIEW-CLASSES are passed as arguments to SELECT." (declare (ignore all set-operation group-by having offset limit inner-join on) @@ -956,14 +1002,17 @@ superclass of the newly-defined View Class." (remf args :flatp) (remf args :additional-fields) (remf args :result-types) + (remf args :instances) (let* ((*db-deserializing* t) (sclasses (mapcar #'find-class view-classes)) - (immediate-join-slots (mapcar #'(lambda (c) (generate-retrieval-joins-list c :immediate)) sclasses)) - (immediate-join-classes (mapcar #'(lambda (jcs) - (mapcar #'(lambda (slotdef) - (find-class (gethash :join-class (view-class-slot-db-info slotdef)))) - jcs)) - immediate-join-slots)) + (immediate-join-slots + (mapcar #'(lambda (c) (generate-retrieval-joins-list c :immediate)) sclasses)) + (immediate-join-classes + (mapcar #'(lambda (jcs) + (mapcar #'(lambda (slotdef) + (find-class (gethash :join-class (view-class-slot-db-info slotdef)))) + jcs)) + immediate-join-slots)) (immediate-join-sels (mapcar #'generate-immediate-joins-selection-list sclasses)) (sels (mapcar #'generate-selection-list sclasses)) (fullsels (apply #'append (mapcar #'append sels immediate-join-sels))) @@ -976,19 +1025,16 @@ superclass of the newly-defined View Class." jcs)) immediate-join-classes) sel-tables) - :test #'tables-equal)))) - (dolist (ob (listify order-by)) - (when (and ob (not (member ob (mapcar #'cdr fullsels) - :test #'ref-equal))) - (setq fullsels - (append fullsels (mapcar #'(lambda (att) (cons nil att)) - (listify ob)))))) - (dolist (ob (listify order-by-descending)) + :test #'tables-equal))) + (order-by-slots (mapcar #'(lambda (ob) (if (atom ob) ob (car ob))) + (listify order-by)))) + + (dolist (ob order-by-slots) (when (and ob (not (member ob (mapcar #'cdr fullsels) :test #'ref-equal))) (setq fullsels - (append fullsels (mapcar #'(lambda (att) (cons nil att)) - (listify ob)))))) + (append fullsels (mapcar #'(lambda (att) (cons nil att)) + order-by-slots))))) (dolist (ob (listify distinct)) (when (and (typep ob 'sql-ident) (not (member ob (mapcar #'cdr fullsels) @@ -1021,10 +1067,22 @@ superclass of the newly-defined View Class." (list :result-types result-types) (when where (list :where where)) args))) + (instances-to-add (- (length rows) (length instances))) + (perhaps-extended-instances + (if (plusp instances-to-add) + (append instances (do ((i 0 (1+ i)) + (res nil)) + ((= i instances-to-add) res) + (push (make-list (length sclasses) :initial-element nil) res))) + instances)) (objects (mapcar - #'(lambda (r) - (build-objects r sclasses immediate-join-classes sels immediate-join-sels database refresh flatp)) - rows))) + #'(lambda (row instance) + (build-objects row sclasses immediate-join-classes sels + immediate-join-sels database refresh flatp + (if (and flatp (atom instance)) + (list instance) + instance))) + rows perhaps-extended-instances))) objects)))) (defmethod instance-refreshed ((instance standard-db-object))) @@ -1068,25 +1126,54 @@ ENABLE-SQL-READER-SYNTAX." 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)) - (refresh (getf qualifier-args :refresh)) - (database (or (getf qualifier-args :database) *default-database*))) + (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 qualifier-args)) + (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) - (update-cached-results target-args qualifier-args database)) + (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 qualifier-args))) + (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 @@ -1125,6 +1212,7 @@ ENABLE-SQL-READER-SYNTAX." (when value (push (list arg (typecase value + (cons (cons (sql (car value)) (cdr value))) (%sql-expression (sql value)) (t value))) results))))))