r9252: Implement new SQL-QUERY-OBJECT class and change behavior of [select 'class]
authorKevin M. Rosenberg <kevin@rosenberg.net>
Wed, 5 May 2004 23:03:02 +0000 (23:03 +0000)
committerKevin M. Rosenberg <kevin@rosenberg.net>
Wed, 5 May 2004 23:03:02 +0000 (23:03 +0000)
ChangeLog
base/basic-sql.lisp
base/loop-extension.lisp [deleted file]
base/package.lisp
classic/package.lisp
clsql-base.asd
clsql.asd
sql/classes.lisp
sql/loop-extension.lisp [new file with mode: 0644]
sql/package.lisp
sql/sql.lisp

index 982bb0d7065656a7552b1f6def0f34f92a222470..a9cf64265eebf961d1966f0de6099309bf8d1238 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+4 May 2004 Kevin Rosenberg (kevin@rosenberg.net)
+       * sql/classes.lisp: Add SQL-OBJECT-QUERY type. Have [select 'class]
+       now return a sql-object-query type rather than directly performing a query.
+       This improves CommonSQL conformance.
+       * sql/sql.lisp: Add new QUERY method for SQL-OBJECT-QUERY. Move
+       from basic/basic-sql.lisp the DO-QUERY and MAP-QUERY since they now
+       depend on sql-object-query-type.
+       * sql/loop-extensions.lisp: Move from base package
+       * classic/package.lisp: remove references to map-query and do-query
+
 4 May 2004 Kevin Rosenberg (kevin@rosenberg.net)
        * TODO: New section on optimizations, especially optimizing JOINs.
        * sql/objects.lisp: Have :target-slot return of list of lists rather
@@ -6,8 +16,8 @@
        statement and just requiring one SQL query. Add :retrieval :deferrred
        to target-slot joins. Add placeholder for update-objects-join.
        * sql/classes.lisp: Add :inner-join and :on slots to sql-query class
-       and process them for query output-sql.
-       
+       and process them for query output-sql. 
+
 4 May 2004 Kevin Rosenberg (kevin@rosenberg.net)
        * Version 2.10.11
        * base/basic-sql.lisp: Avoid multiple evaluation
index 2f25fd42bed99e1a58e0ba21e255b16db794af73..258832d573b738d7bb749f164e2f09b948758612 100644 (file)
@@ -60,133 +60,6 @@ pair."))
     (record-sql-action res :result database))
   (values))
 
-(defmacro do-query (((&rest args) query-expression
-                    &key (database '*default-database*) (result-types :auto))
-                   &body body)
-  "Repeatedly executes BODY within a binding of ARGS on the
-attributes of each record resulting from QUERY-EXPRESSION. The
-return value is determined by the result of executing BODY. The
-default value of DATABASE is *DEFAULT-DATABASE*."
-  (let ((result-set (gensym "RESULT-SET-"))
-       (qe (gensym "QUERY-EXPRESSION-"))
-       (columns (gensym "COLUMNS-"))
-       (row (gensym "ROW-"))
-       (db (gensym "DB-")))
-    `(let ((,qe ,query-expression))
-      (typecase ,qe
-       (list
-        ;; Object query 
-         (dolist (,row ,qe)
-           (destructuring-bind ,args 
-               ,row
-             ,@body)))
-       (t
-        ;; Functional query 
-        (let ((,db ,database))
-          (multiple-value-bind (,result-set ,columns)
-              (database-query-result-set ,qe ,db
-                                         :full-set nil 
-                                         :result-types ,result-types)
-            (when ,result-set
-              (unwind-protect
-                   (do ((,row (make-list ,columns)))
-                       ((not (database-store-next-row ,result-set ,db ,row))
-                        nil)
-                     (destructuring-bind ,args ,row
-                       ,@body))
-                (database-dump-result-set ,result-set ,db))))))))))
-
-(defun map-query (output-type-spec function query-expression
-                 &key (database *default-database*)
-                 (result-types :auto))
-  "Map the function over all tuples that are returned by the
-query in QUERY-EXPRESSION. The results of the function are
-collected as specified in OUTPUT-TYPE-SPEC and returned like in
-MAP."
-  (if (listp query-expression)
-      ;; Object query 
-      (map output-type-spec #'(lambda (x) (apply function x)) query-expression)
-      ;; Functional query 
-      (macrolet ((type-specifier-atom (type)
-                  `(if (atom ,type) ,type (car ,type))))
-       (case (type-specifier-atom output-type-spec)
-         ((nil) 
-          (map-query-for-effect function query-expression database 
-                                result-types))
-         (list 
-          (map-query-to-list function query-expression database result-types))
-         ((simple-vector simple-string vector string array simple-array
-                         bit-vector simple-bit-vector base-string
-                         simple-base-string)
-          (map-query-to-simple output-type-spec function query-expression 
-                               database result-types))
-         (t
-          (funcall #'map-query 
-                   (cmucl-compat:result-type-or-lose output-type-spec t)
-                   function query-expression :database database 
-                   :result-types result-types))))))
-
-(defun map-query-for-effect (function query-expression database result-types)
-  (multiple-value-bind (result-set columns)
-      (database-query-result-set query-expression database :full-set nil
-                                :result-types result-types)
-    (when result-set
-      (unwind-protect
-          (do ((row (make-list columns)))
-              ((not (database-store-next-row result-set database row))
-               nil)
-            (apply function row))
-       (database-dump-result-set result-set database)))))
-                    
-(defun map-query-to-list (function query-expression database result-types)
-  (multiple-value-bind (result-set columns)
-      (database-query-result-set query-expression database :full-set nil
-                                :result-types result-types)
-    (when result-set
-      (unwind-protect
-          (let ((result (list nil)))
-            (do ((row (make-list columns))
-                 (current-cons result (cdr current-cons)))
-                ((not (database-store-next-row result-set database row))
-                 (cdr result))
-              (rplacd current-cons (list (apply function row)))))
-       (database-dump-result-set result-set database)))))
-
-
-(defun map-query-to-simple (output-type-spec function query-expression database result-types)
-  (multiple-value-bind (result-set columns rows)
-      (database-query-result-set query-expression database :full-set t
-                                :result-types result-types)
-    (when result-set
-      (unwind-protect
-          (if rows
-              ;; We know the row count in advance, so we allocate once
-              (do ((result
-                    (cmucl-compat:make-sequence-of-type output-type-spec rows))
-                   (row (make-list columns))
-                   (index 0 (1+ index)))
-                  ((not (database-store-next-row result-set database row))
-                   result)
-                (declare (fixnum index))
-                (setf (aref result index)
-                      (apply function row)))
-              ;; Database can't report row count in advance, so we have
-              ;; to grow and shrink our vector dynamically
-              (do ((result
-                    (cmucl-compat:make-sequence-of-type output-type-spec 100))
-                   (allocated-length 100)
-                   (row (make-list columns))
-                   (index 0 (1+ index)))
-                  ((not (database-store-next-row result-set database row))
-                   (cmucl-compat:shrink-vector result index))
-                (declare (fixnum allocated-length index))
-                (when (>= index allocated-length)
-                  (setq allocated-length (* allocated-length 2)
-                        result (adjust-array result allocated-length)))
-                (setf (aref result index)
-                      (apply function row))))
-       (database-dump-result-set result-set database)))))
-
 ;;; Large objects support
 
 (defun create-large-object (&key (database *default-database*))
diff --git a/base/loop-extension.lisp b/base/loop-extension.lisp
deleted file mode 100644 (file)
index 4fc421b..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
-;;;; *************************************************************************
-;;;; FILE IDENTIFICATION
-;;;;
-;;;; Name: loop-extension.lisp
-;;;; Purpose: Extensions to the Loop macro for CLSQL
-;;;;
-;;;; Copyright (c) 2001-2004 Kevin Rosenberg and (c) 1999-2001 Pierre R. Mai
-;;;;
-;;;; $Id$
-;;;; *************************************************************************
-
-(in-package #:cl-user)
-
-#+(or allegro sbcl)
-(eval-when (:compile-toplevel :load-toplevel :execute)
-  (defpackage #:ansi-loop 
-    (:import-from #+sbcl #:sb-loop #+allegro #:excl
-                 #:loop-error
-                 #:*loop-epilogue*
-                 #:*loop-ansi-universe* 
-                 #:add-loop-path)))
-
-#+(or allegro sbcl)
-(defun ansi-loop::loop-gentemp (&optional (pref 'loopva-))
-  (gensym (string pref)))
-
-#+(or cmu scl sbcl openmcl allegro)
-(defun loop-record-iteration-path (variable data-type prep-phrases)
-  (let ((in-phrase nil)
-       (from-phrase nil))
-    (loop for (prep . rest) in prep-phrases
-         do
-         (case prep
-           ((:in :of)
-            (when in-phrase
-              (ansi-loop::loop-error
-               "Duplicate OF or IN iteration path: ~S." (cons prep rest)))
-            (setq in-phrase rest))
-           ((:from)
-            (when from-phrase
-              (ansi-loop::loop-error
-               "Duplicate FROM iteration path: ~S." (cons prep rest)))
-            (setq from-phrase rest))
-           (t
-            (ansi-loop::loop-error
-             "Unknown preposition: ~S." prep))))
-    (unless in-phrase
-      (ansi-loop::loop-error "Missing OF or IN iteration path."))
-    (unless from-phrase
-      (setq from-phrase '(clsql-base:*default-database*)))
-
-    (unless (consp variable)
-      (setq variable (list variable)))
-
-    (cond
-      ;; Object query resulting in a list of returned object instances
-     ((and (consp (first in-phrase))
-          (consp (second (first in-phrase)))
-          (eq 'quote (first (second (first in-phrase))))
-          (symbolp (second (second (first in-phrase)))))
-
-       (let ((result-var (ansi-loop::loop-gentemp
-                             'loop-record-result-))
-            (step-var (ansi-loop::loop-gentemp 'loop-record-step-)))
-        `(((,variable nil ,@(and data-type (list data-type)))
-           (,result-var ,(first in-phrase))
-           (,step-var nil))
-          ()
-          ()
-          ()
-          (if (null ,result-var)
-              t
-              (progn
-                (setq ,step-var (first ,result-var))
-                (setq ,result-var (rest ,result-var))
-                nil))
-          (,variable ,step-var)
-          (null ,result-var)
-          ()
-          (if (null ,result-var)
-              t
-              (progn
-                (setq ,step-var (first ,result-var))
-                (setq ,result-var (rest ,result-var))
-                nil))
-          (,variable ,step-var))))
-      
-      ((consp variable)
-       (let ((query-var (ansi-loop::loop-gentemp 'loop-record-))
-            (db-var (ansi-loop::loop-gentemp 'loop-record-database-))
-            (result-set-var (ansi-loop::loop-gentemp
-                             'loop-record-result-set-))
-            (step-var (ansi-loop::loop-gentemp 'loop-record-step-)))
-        (push `(when ,result-set-var
-                 (clsql-base:database-dump-result-set ,result-set-var ,db-var))
-              ansi-loop::*loop-epilogue*)
-        `(((,variable nil ,@(and data-type (list data-type)))
-           (,query-var ,(first in-phrase))
-           (,db-var ,(first from-phrase))
-           (,result-set-var nil)
-           (,step-var nil))
-          ((multiple-value-bind (%rs %cols)
-               (clsql-base:database-query-result-set ,query-var ,db-var :result-types :auto)
-             (setq ,result-set-var %rs ,step-var (make-list %cols))))
-          ()
-          ()
-          (not (clsql-base:database-store-next-row ,result-set-var ,db-var ,step-var))
-          (,variable ,step-var)
-          (not ,result-set-var)
-          ()
-          (not (clsql-base:database-store-next-row ,result-set-var ,db-var ,step-var))
-          (,variable ,step-var)))))))
-
-#+(or cmu scl sbcl openmcl allegro)
-(ansi-loop::add-loop-path '(record records tuple tuples)
-                         'loop-record-iteration-path
-                         ansi-loop::*loop-ansi-universe*
-                         :preposition-groups '((:of :in) (:from))
-                         :inclusive-permitted nil)
-
-#+lispworks 
-(eval-when (:compile-toplevel :load-toplevel :execute)
-  (in-package loop))
-
-#+lispworks
-(cl-user::define-loop-method (record records tuple tuples) clsql-loop-method 
-  (in of from))
-
-#+lispworks
-(defun clsql-loop-method (method-name iter-var iter-var-data-type 
-                         prep-phrases inclusive? allowed-preps 
-                         method-specific-data)
-  (declare (ignore method-name inclusive? allowed-preps method-specific-data))
-  (let ((in-phrase nil)
-       (from-phrase nil))
-    (loop for (prep . rest) in prep-phrases
-         do
-         (cond
-           ((or (eq prep 'in) (eq prep 'of))
-            (when in-phrase
-              (error
-               "Duplicate OF or IN iteration path: ~S." (cons prep rest)))
-            (setq in-phrase rest))
-           ((eq prep 'from)
-            (when from-phrase
-              (error
-               "Duplicate FROM iteration path: ~S." (cons prep rest)))
-            (setq from-phrase rest))
-           (t
-            (error
-             "Unknown preposition: ~S." prep))))
-    (unless in-phrase
-      (error "Missing OF or IN iteration path."))
-    (unless from-phrase
-      (setq from-phrase '(clsql-base:*default-database*)))
-
-    (unless (consp iter-var)
-      (setq iter-var (list iter-var)))
-
-    (cond
-      ;; Object query resulting in a list of returned object instances
-      ((and (string-equal "select" (symbol-name (car in-phrase)))
-           (eq 'quote (first (second in-phrase)))
-           (symbolp (second (second in-phrase))))
-
-       (let ((result-var (gensym "LOOP-RECORD-RESULT-"))
-            (step-var (gensym "LOOP-RECORD-STEP-")))
-        (values
-         t
-         nil
-         `(,@(mapcar (lambda (v) `(,v nil)) iter-var)
-           (,result-var ,in-phrase)
-           (,step-var nil))
-         ()
-         ()
-         ()
-         `((if (null ,result-var)
-               t
-               (progn
-                 (setq ,step-var (first ,result-var))
-                 (setq ,result-var (rest ,result-var))
-                 nil)))
-         `(,iter-var ,step-var)
-         `((if (null ,result-var)
-               t
-               (progn
-                 (setq ,step-var (first ,result-var))
-                 (setq ,result-var (rest ,result-var))
-                 nil)))
-          `(,iter-var ,step-var)
-          ()
-          ()
-          )))
-      
-      ((consp iter-var)
-       (let ((query-var (gensym "LOOP-RECORD-"))
-            (db-var (gensym "LOOP-RECORD-DATABASE-"))
-            (result-set-var (gensym "LOOP-RECORD-RESULT-SET-"))
-            (step-var (gensym "LOOP-RECORD-STEP-")))
-        (values
-         t
-         nil
-         `(,@(mapcar (lambda (v) `(,v nil)) iter-var)
-           (,query-var ,in-phrase)
-           (,db-var ,(first from-phrase))
-           (,result-set-var nil)
-           (,step-var nil))
-         `((multiple-value-bind (%rs %cols)
-               (clsql-base:database-query-result-set ,query-var ,db-var :result-types :auto)
-             (setq ,result-set-var %rs ,step-var (make-list %cols))))
-         ()
-         ()
-         `((unless (clsql-base:database-store-next-row ,result-set-var ,db-var ,step-var)
-             (when ,result-set-var
-               (clsql-base:database-dump-result-set ,result-set-var ,db-var))
-             t))
-         `(,iter-var ,step-var)
-         `((unless (clsql-base:database-store-next-row ,result-set-var ,db-var ,step-var)
-             (when ,result-set-var
-               (clsql-base:database-dump-result-set ,result-set-var ,db-var))
-             t))
-         `(,iter-var ,step-var)
-         ()
-         ()))))))
-
index 241d0348c778cd940fadf1547725ed11828e4424..1650d3e0f3adcdec11b21a2d5572118448852743 100644 (file)
      #:write-large-object
      #:read-large-object
      #:delete-large-object
-     #:do-query
-     #:map-query
      #:describe-table
      
      ;; Transactions
index adf267281e40cd5ee6fce6caf4c70d90a9237186..004dd47144af1d6d5c0002c8235d287a22ca7c9c 100644 (file)
@@ -99,8 +99,6 @@
         #:write-large-object
         #:read-large-object
         #:delete-large-object
-        #:do-query
-        #:map-query
 
         ;; Transactions
         #:with-transaction
index 7484c4c6ca6aa6f1696ad0749c396ba7c9a7a3c6..7e94f6e387f20861db9e6f0aca0252d76e436675 100644 (file)
@@ -41,7 +41,6 @@
             (:file "conditions" :depends-on ("classes"))
             (:file "db-interface" :depends-on ("conditions"))
             (:file "initialize" :depends-on ("db-interface" "utils"))
-            (:file "loop-extension" :depends-on ("db-interface"))
             (:file "time" :depends-on ("package"))
             (:file "database" :depends-on ("initialize"))
             (:file "recording" :depends-on ("time" "database"))
index 2842c944c411cdc94384ccd8563d280a95b8f6d6..3ee350c7595e385a5493315c21379bd3242eb94e 100644 (file)
--- a/clsql.asd
+++ b/clsql.asd
@@ -41,6 +41,7 @@ a functional and an object oriented interface."
                        :pathname ""
                        :components ((:file "generics")
                                     (:file "classes" :depends-on ("generics"))
+                                    (:file "loop-extension" :depends-on ("classes"))
                                     (:file "operations" :depends-on ("classes"))
                                     (:file "syntax" :depends-on ("operations")))
                        :depends-on (:package))
index 1be0e0b3d9f0728b53192ecd79b1eb506b30673a..9e2338cb9731bfd292713afcb4bb38f8d1727299 100644 (file)
     :initform nil))
   (:documentation "An SQL SELECT query."))
 
+(defclass sql-object-query (%sql-expression)
+  ((objects
+    :initarg :objects
+    :initform nil)
+   (flatp
+    :initarg :flatp
+    :initform nil)
+   (exp
+    :initarg :exp
+    :initform nil)
+   (refresh
+    :initarg :refresh
+    :initform nil)))
+
 (defmethod collect-table-refs ((sql sql-query))
   (remove-duplicates (collect-table-refs (slot-value sql 'where))
                      :test (lambda (tab1 tab2)
@@ -459,7 +473,10 @@ uninclusive, and the args from that keyword to the end."
     (multiple-value-bind (selections arglist)
        (query-get-selections args)
       (if (select-objects selections) 
-         (apply #'select args)
+         (destructuring-bind (&key flatp refresh &allow-other-keys) arglist
+           (make-instance 'sql-object-query :objects selections
+                          :flatp flatp :refresh refresh
+                          :exp arglist))
          (destructuring-bind (&key all flatp set-operation distinct from where
                                    group-by having order-by order-by-descending
                                    offset limit inner-join on &allow-other-keys)
@@ -542,6 +559,13 @@ uninclusive, and the args from that keyword to the end."
       (write-string ")" *sql-stream*)))
   t)
 
+(defmethod output-sql ((query sql-object-query) database)
+  (with-slots (objects)
+      query
+    (when objects
+      (format *sql-stream* "(~{~A~^ ~})" objects))))
+
+
 ;; INSERT
 
 (defclass sql-insert (%sql-expression)
diff --git a/sql/loop-extension.lisp b/sql/loop-extension.lisp
new file mode 100644 (file)
index 0000000..d0f816f
--- /dev/null
@@ -0,0 +1,229 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; *************************************************************************
+;;;; FILE IDENTIFICATION
+;;;;
+;;;; Name:    loop-extension.lisp
+;;;; Purpose: Extensions to the Loop macro for CLSQL
+;;;;
+;;;; Copyright (c) 2001-2004 Kevin Rosenberg and (c) 1999-2001 Pierre R. Mai
+;;;;
+;;;; $Id$
+;;;; *************************************************************************
+
+(in-package #:cl-user)
+
+#+(or allegro sbcl)
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (defpackage #:ansi-loop 
+    (:import-from #+sbcl #:sb-loop #+allegro #:excl
+                 #:loop-error
+                 #:*loop-epilogue*
+                 #:*loop-ansi-universe* 
+                 #:add-loop-path)))
+
+#+(or allegro sbcl)
+(defun ansi-loop::loop-gentemp (&optional (pref 'loopva-))
+  (gensym (string pref)))
+
+#+(or cmu scl sbcl openmcl allegro)
+(defun loop-record-iteration-path (variable data-type prep-phrases)
+  (let ((in-phrase nil)
+       (from-phrase nil))
+    (loop for (prep . rest) in prep-phrases
+         do
+         (case prep
+           ((:in :of)
+            (when in-phrase
+              (ansi-loop::loop-error
+               "Duplicate OF or IN iteration path: ~S." (cons prep rest)))
+            (setq in-phrase rest))
+           ((:from)
+            (when from-phrase
+              (ansi-loop::loop-error
+               "Duplicate FROM iteration path: ~S." (cons prep rest)))
+            (setq from-phrase rest))
+           (t
+            (ansi-loop::loop-error
+             "Unknown preposition: ~S." prep))))
+    (unless in-phrase
+      (ansi-loop::loop-error "Missing OF or IN iteration path."))
+    (unless from-phrase
+      (setq from-phrase '(clsql:*default-database*)))
+
+    (unless (consp variable)
+      (setq variable (list variable)))
+
+    (cond
+     ;; object query
+     ((and (consp (first in-phrase))
+          (string-equal "sql-query" (symbol-name (caar in-phrase)))
+          (consp (second (first in-phrase)))
+          (eq 'quote (first (second (first in-phrase))))
+          (symbolp (second (second (first in-phrase)))))
+
+       (let ((result-var (ansi-loop::loop-gentemp
+                             'loop-record-result-))
+            (step-var (ansi-loop::loop-gentemp 'loop-record-step-)))
+        `(((,variable nil ,@(and data-type (list data-type)))
+           (,result-var (clsql:query ,(first in-phrase)))
+           (,step-var nil))
+          ()
+          ()
+          ()
+          (if (null ,result-var)
+              t
+              (progn
+                (setq ,step-var (first ,result-var))
+                (setq ,result-var (rest ,result-var))
+                nil))
+          (,variable ,step-var)
+          (null ,result-var)
+          ()
+          (if (null ,result-var)
+              t
+              (progn
+                (setq ,step-var (first ,result-var))
+                (setq ,result-var (rest ,result-var))
+                nil))
+          (,variable ,step-var))))
+      
+      ((consp variable)
+       (let ((query-var (ansi-loop::loop-gentemp 'loop-record-))
+            (db-var (ansi-loop::loop-gentemp 'loop-record-database-))
+            (result-set-var (ansi-loop::loop-gentemp
+                             'loop-record-result-set-))
+            (step-var (ansi-loop::loop-gentemp 'loop-record-step-)))
+        (push `(when ,result-set-var
+                 (clsql:database-dump-result-set ,result-set-var ,db-var))
+              ansi-loop::*loop-epilogue*)
+        `(((,variable nil ,@(and data-type (list data-type)))
+           (,query-var ,(first in-phrase))
+           (,db-var ,(first from-phrase))
+           (,result-set-var nil)
+           (,step-var nil))
+          ((multiple-value-bind (%rs %cols)
+               (clsql:database-query-result-set ,query-var ,db-var :result-types :auto)
+             (setq ,result-set-var %rs ,step-var (make-list %cols))))
+          ()
+          ()
+          (not (clsql:database-store-next-row ,result-set-var ,db-var ,step-var))
+          (,variable ,step-var)
+          (not ,result-set-var)
+          ()
+          (not (clsql:database-store-next-row ,result-set-var ,db-var ,step-var))
+          (,variable ,step-var)))))))
+
+#+(or cmu scl sbcl openmcl allegro)
+(ansi-loop::add-loop-path '(record records tuple tuples)
+                         'loop-record-iteration-path
+                         ansi-loop::*loop-ansi-universe*
+                         :preposition-groups '((:of :in) (:from))
+                         :inclusive-permitted nil)
+
+#+lispworks 
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (in-package loop))
+
+#+lispworks
+(cl-user::define-loop-method (record records tuple tuples) clsql-loop-method 
+  (in of from))
+
+#+lispworks
+(defun clsql-loop-method (method-name iter-var iter-var-data-type 
+                         prep-phrases inclusive? allowed-preps 
+                         method-specific-data)
+  (declare (ignore method-name inclusive? allowed-preps method-specific-data))
+  (let ((in-phrase nil)
+       (from-phrase nil))
+    (loop for (prep . rest) in prep-phrases
+         do
+         (cond
+           ((or (eq prep 'in) (eq prep 'of))
+            (when in-phrase
+              (error
+               "Duplicate OF or IN iteration path: ~S." (cons prep rest)))
+            (setq in-phrase rest))
+           ((eq prep 'from)
+            (when from-phrase
+              (error
+               "Duplicate FROM iteration path: ~S." (cons prep rest)))
+            (setq from-phrase rest))
+           (t
+            (error
+             "Unknown preposition: ~S." prep))))
+    (unless in-phrase
+      (error "Missing OF or IN iteration path."))
+    (unless from-phrase
+      (setq from-phrase '(clsql:*default-database*)))
+
+    (unless (consp iter-var)
+      (setq iter-var (list iter-var)))
+
+    (cond
+     ;; object query
+     ((and (consp in-phrase)
+          (string-equal "sql-query" (symbol-name (car in-phrase)))
+          (consp (second in-phrase))
+          (eq 'quote (first (second in-phrase)))
+          (symbolp (second (second in-phrase))))
+
+       (let ((result-var (gensym "LOOP-RECORD-RESULT-"))
+            (step-var (gensym "LOOP-RECORD-STEP-")))
+        (values
+         t
+         nil
+         `(,@(mapcar (lambda (v) `(,v nil)) iter-var)
+             (,result-var (clsql:query ,in-phrase))
+             (,step-var nil))
+         ()
+         ()
+         ()
+         `((if (null ,result-var)
+               t
+               (progn
+                 (setq ,step-var (first ,result-var))
+                 (setq ,result-var (rest ,result-var))
+                 nil)))
+         `(,iter-var ,step-var)
+         `((if (null ,result-var)
+               t
+               (progn
+                 (setq ,step-var (first ,result-var))
+                 (setq ,result-var (rest ,result-var))
+                 nil)))
+          `(,iter-var ,step-var)
+          ()
+          ()
+          )))
+      
+      ((consp iter-var)
+       (let ((query-var (gensym "LOOP-RECORD-"))
+            (db-var (gensym "LOOP-RECORD-DATABASE-"))
+            (result-set-var (gensym "LOOP-RECORD-RESULT-SET-"))
+            (step-var (gensym "LOOP-RECORD-STEP-")))
+        (values
+         t
+         nil
+         `(,@(mapcar (lambda (v) `(,v nil)) iter-var)
+           (,query-var ,in-phrase)
+           (,db-var ,(first from-phrase))
+           (,result-set-var nil)
+           (,step-var nil))
+         `((multiple-value-bind (%rs %cols)
+               (clsql:database-query-result-set ,query-var ,db-var :result-types :auto)
+             (setq ,result-set-var %rs ,step-var (make-list %cols))))
+         ()
+         ()
+         `((unless (clsql:database-store-next-row ,result-set-var ,db-var ,step-var)
+             (when ,result-set-var
+               (clsql:database-dump-result-set ,result-set-var ,db-var))
+             t))
+         `(,iter-var ,step-var)
+         `((unless (clsql:database-store-next-row ,result-set-var ,db-var ,step-var)
+             (when ,result-set-var
+               (clsql:database-dump-result-set ,result-set-var ,db-var))
+             t))
+         `(,iter-var ,step-var)
+         ()
+         ()))))))
+
index 98bfd2d404267d5ac61916e394624a12c577a26e..ba3f9726795eecf06498a78a5dcbcaf1f792da15 100644 (file)
        #:write-large-object
        #:read-large-object
        #:delete-large-object
-       #:do-query
-       #:map-query
        #:describe-table
        #:create-large-object
        #:write-large-object
     #:database-underlying-type
    
    .
-   ;; Shared exports for re-export by CLSQL. 
+   ;; Shared exports for re-export by CLSQL-USER
    ;; I = Implemented, D = Documented
    ;;  name                                 file       ID
    ;;====================================================
        #:sql-and-qualifier
        #:sql-escape
        #:sql-query
+       #:sql-object-query
        #:sql-any
        #:sql-all
        #:sql-not
        #:sql-view-class
        #:sql_slot-value
 
+       #:do-query
+       #:map-query
+
        . 
        #1#
        ))
index 273b8498d55eff1982ef69dd46aa397125b42640..0397bd031ca01984702a667bd031bba537fd824c 100644 (file)
   (query (sql-output expr database) :database database :flatp flatp
          :result-types result-types :field-names field-names))
 
+(defmethod query ((expr sql-object-query) &key (database *default-database*)
+                                              (result-types :auto) (flatp nil))
+  (declare (ignore result-types))
+  (apply #'select (append (slot-value expr 'objects)
+                         (slot-value expr 'exp) 
+                         (when (slot-value expr 'refresh) 
+                           (list :refresh (sql-output expr database)))
+                         (when (or flatp (slot-value expr 'flatp) )
+                           (list :flatp t))
+                         (list :database database))))
+
 (defun truncate-database (&key (database *default-database*))
   (unless (typep database 'database)
     (clsql-base::signal-no-database-error database))
@@ -272,3 +283,133 @@ condition is true."
       (when sequence
         (create-sequence-from-class class)))))
  
+;;; Iteration
+
+
+(defmacro do-query (((&rest args) query-expression
+                    &key (database '*default-database*) (result-types :auto))
+                   &body body)
+  "Repeatedly executes BODY within a binding of ARGS on the
+attributes of each record resulting from QUERY-EXPRESSION. The
+return value is determined by the result of executing BODY. The
+default value of DATABASE is *DEFAULT-DATABASE*."
+  (let ((result-set (gensym "RESULT-SET-"))
+       (qe (gensym "QUERY-EXPRESSION-"))
+       (columns (gensym "COLUMNS-"))
+       (row (gensym "ROW-"))
+       (db (gensym "DB-")))
+    `(let ((,qe ,query-expression))
+      (typecase ,qe
+       (sql-object-query
+         (dolist (,row (query ,qe))
+           (destructuring-bind ,args 
+               ,row
+             ,@body)))
+       (t
+        ;; Functional query 
+        (let ((,db ,database))
+          (multiple-value-bind (,result-set ,columns)
+              (database-query-result-set ,qe ,db
+                                         :full-set nil 
+                                         :result-types ,result-types)
+            (when ,result-set
+              (unwind-protect
+                   (do ((,row (make-list ,columns)))
+                       ((not (database-store-next-row ,result-set ,db ,row))
+                        nil)
+                     (destructuring-bind ,args ,row
+                       ,@body))
+                (database-dump-result-set ,result-set ,db))))))))))
+
+(defun map-query (output-type-spec function query-expression
+                 &key (database *default-database*)
+                 (result-types :auto))
+  "Map the function over all tuples that are returned by the
+query in QUERY-EXPRESSION. The results of the function are
+collected as specified in OUTPUT-TYPE-SPEC and returned like in
+MAP."
+  (typecase query-expression
+    (sql-object-query
+     (map output-type-spec #'(lambda (x) (apply function x))
+         (query query-expression)))
+    (t
+     ;; Functional query 
+     (macrolet ((type-specifier-atom (type)
+                 `(if (atom ,type) ,type (car ,type))))
+       (case (type-specifier-atom output-type-spec)
+        ((nil) 
+         (map-query-for-effect function query-expression database 
+                               result-types))
+        (list 
+         (map-query-to-list function query-expression database result-types))
+        ((simple-vector simple-string vector string array simple-array
+                        bit-vector simple-bit-vector base-string
+                        simple-base-string)
+         (map-query-to-simple output-type-spec function query-expression 
+                              database result-types))
+        (t
+         (funcall #'map-query 
+                  (cmucl-compat:result-type-or-lose output-type-spec t)
+                  function query-expression :database database 
+                  :result-types result-types)))))))
+  
+(defun map-query-for-effect (function query-expression database result-types)
+  (multiple-value-bind (result-set columns)
+      (database-query-result-set query-expression database :full-set nil
+                                :result-types result-types)
+    (when result-set
+      (unwind-protect
+          (do ((row (make-list columns)))
+              ((not (database-store-next-row result-set database row))
+               nil)
+            (apply function row))
+       (database-dump-result-set result-set database)))))
+                    
+(defun map-query-to-list (function query-expression database result-types)
+  (multiple-value-bind (result-set columns)
+      (database-query-result-set query-expression database :full-set nil
+                                :result-types result-types)
+    (when result-set
+      (unwind-protect
+          (let ((result (list nil)))
+            (do ((row (make-list columns))
+                 (current-cons result (cdr current-cons)))
+                ((not (database-store-next-row result-set database row))
+                 (cdr result))
+              (rplacd current-cons (list (apply function row)))))
+       (database-dump-result-set result-set database)))))
+
+
+(defun map-query-to-simple (output-type-spec function query-expression database result-types)
+  (multiple-value-bind (result-set columns rows)
+      (database-query-result-set query-expression database :full-set t
+                                :result-types result-types)
+    (when result-set
+      (unwind-protect
+          (if rows
+              ;; We know the row count in advance, so we allocate once
+              (do ((result
+                    (cmucl-compat:make-sequence-of-type output-type-spec rows))
+                   (row (make-list columns))
+                   (index 0 (1+ index)))
+                  ((not (database-store-next-row result-set database row))
+                   result)
+                (declare (fixnum index))
+                (setf (aref result index)
+                      (apply function row)))
+              ;; Database can't report row count in advance, so we have
+              ;; to grow and shrink our vector dynamically
+              (do ((result
+                    (cmucl-compat:make-sequence-of-type output-type-spec 100))
+                   (allocated-length 100)
+                   (row (make-list columns))
+                   (index 0 (1+ index)))
+                  ((not (database-store-next-row result-set database row))
+                   (cmucl-compat:shrink-vector result index))
+                (declare (fixnum allocated-length index))
+                (when (>= index allocated-length)
+                  (setq allocated-length (* allocated-length 2)
+                        result (adjust-array result allocated-length)))
+                (setf (aref result index)
+                      (apply function row))))
+       (database-dump-result-set result-set database)))))