+(defun select-reference-equal (r1 r2)
+ "determines if two sql select references are equal
+ using database identifier equal"
+ (flet ((id-of (r)
+ (etypecase r
+ (cons (cdr r))
+ (sql-ident-attribute r))))
+ (database-identifier-equal (id-of r1) (id-of r2))))
+
+(defun join-slot-qualifier (class join-slot)
+ "Creates a sql-expression expressing the join between the home-key on the table
+ and its respective key on the joined-to-table"
+ (sql-operation
+ '==
+ (sql-expression
+ :attribute (join-slot-info-value join-slot :foreign-key)
+ :table (view-table (join-slot-class join-slot)))
+ (sql-expression
+ :attribute (join-slot-info-value join-slot :home-key)
+ :table (view-table class))))
+
+(defun all-immediate-join-classes-for (classes)
+ "returns a list of all join-classes needed for a list of classes"
+ (loop for class in (listify classes)
+ appending (loop for slot in (immediate-join-slots class)
+ collect (join-slot-class slot))))
+
+(defun %tables-for-query (classes from where inner-joins)
+ "Given lists of classes froms wheres and inner-join compile a list
+ of tables that should appear in the FROM section of the query.
+
+ This includes any immediate join classes from each of the classes"
+ (let ((inner-join-tables (collect-table-refs (listify inner-joins))))
+ (loop for tbl in (append
+ (mapcar #'select-table-sql-expr classes)
+ (mapcar #'select-table-sql-expr
+ (all-immediate-join-classes-for classes))
+ (collect-table-refs (listify where))
+ (collect-table-refs (listify from)))
+ when (and tbl
+ (not (find tbl rtn :test #'database-identifier-equal))
+ ;; TODO: inner-join is currently hacky as can be
+ (not (find tbl inner-join-tables :test #'database-identifier-equal)))
+ collect tbl into rtn
+ finally (return rtn))))
+
+
+(defclass select-list ()
+ ((view-class :accessor view-class :initarg :view-class :initform nil)
+ (select-list :accessor select-list :initarg :select-list :initform nil)
+ (slot-list :accessor slot-list :initarg :slot-list :initform nil)
+ (joins :accessor joins :initarg :joins :initform nil)
+ (join-slots :accessor join-slots :initarg :join-slots :initform nil))
+ (:documentation
+ "Collects the classes, slots and their respective sql representations
+ so that update-instance-from-recors, find-all, build-objects can share this
+ info and calculate it once. Joins are select-lists for each immediate join-slot
+ but only if make-select-list is called with do-joins-p"))
+
+(defmethod view-table ((o select-list))
+ (view-table (view-class o)))
+
+(defmethod sql-table ((o select-list))
+ (sql-expression :table (view-table o)))
+
+(defun make-select-list (class-and-slots &key (do-joins-p nil))
+ "Make a select-list for the current class (or class-and-slots) object."
+ (let* ((class-and-slots
+ (etypecase class-and-slots
+ (class-and-slots class-and-slots)
+ ((or symbol standard-db-class)
+ ;; find the first class with slots for us to select (this should be)
+ ;; the first of its classes / parent-classes with slots
+ (first (reverse (view-classes-and-storable-slots
+ (to-class class-and-slots)))))))
+ (class (view-class class-and-slots))
+ (join-slots (when do-joins-p (immediate-join-slots class))))
+ (multiple-value-bind (slots sqls)
+ (loop for slot in (slot-defs class-and-slots)
+ for sql = (generate-attribute-reference class slot)
+ collect slot into slots
+ collect sql into sqls
+ finally (return (values slots sqls)))
+ (unless slots
+ (error "No slots of type :base in view-class ~A" (class-name class)))
+ (make-instance
+ 'select-list
+ :view-class class
+ :select-list sqls
+ :slot-list slots
+ :join-slots join-slots
+ ;; only do a single layer of join objects
+ :joins (when do-joins-p
+ (loop for js in join-slots
+ collect (make-select-list
+ (join-slot-class js)
+ :do-joins-p nil)))))))
+
+(defun full-select-list ( select-lists )
+ "Returns a list of sql-ref of things to select for the given classes
+
+ THIS NEEDS TO MATCH THE ORDER OF build-objects
+ "
+ (loop for s in (listify select-lists)
+ appending (select-list s)
+ appending (loop for join in (joins s)
+ appending (select-list join))))
+
+(defun build-objects (select-lists row database &optional existing-instances)
+ "Used by find-all to build objects.
+
+ THIS NEEDS TO MATCH THE ORDER OF FULL-SELECT-LIST
+
+ TODO: this caching scheme seems bad for a number of reasons
+ * order is not guaranteed so references being held by one object
+ might change to represent a different database row (seems HIGHLY
+ suspect)
+ * also join objects are overwritten rather than refreshed
+
+ TODO: the way we handle immediate joins seems only valid if it is a single
+ object. I suspect that making a :set :immediate join column would result
+ in an invalid number of objects returned from the database, because there
+ would be multiple rows per object, but we would return an object per row
+ "
+ (setf existing-instances (listify existing-instances))
+ (loop
+ for select-list in select-lists
+ for class = (view-class select-list)
+ for existing = (pop existing-instances)
+ for object = (or existing
+ (make-instance class :view-database database))
+ do (loop for slot in (slot-list select-list)
+ do (update-slot-from-db-value object slot (pop row)))
+ do (loop for join-slot in (join-slots select-list)
+ for join in (joins select-list)
+ for join-class = (view-class join)
+ for join-object =
+ (setf (easy-slot-value object join-slot)
+ (make-instance join-class))
+ do (loop for slot in (slot-list join)
+ do (update-slot-from-db-value join-object slot (pop row))))
+ do (when existing (instance-refreshed object))
+ collect object))