;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
;;;; *************************************************************************
;;;;
;;;; CLSQL broadcast streams which can be used to monitor the
;;;; flow of commands to, and results from, a database.
;;;;
;;;; This file is part of CLSQL.
;;;;
;;;; CLSQL users are granted the rights to distribute and use this software
;;;; as governed by the terms of the Lisp Lesser GNU Public License
;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
;;;; *************************************************************************

(in-package #:clsql-sys)

(defun start-sql-recording (&key (type :commands) (database *default-database*))
  "Starts recording of SQL commands sent to and/or results
returned from DATABASE which defaults to *DEFAULT-DATABASE*. The
SQL is output on one or more broadcast streams, initially just
*STANDARD-OUTPUT*, and the functions ADD-SQL-STREAM and
DELETE-SQL-STREAM may be used to add or delete command or result
recording streams. The default value of TYPE is :commands which
means that SQL commands sent to DATABASE are recorded. If TYPE
is :results then SQL results returned from DATABASE are
recorded. Both commands and results may be recorded by passing
TYPE value of :both."
  (when (or (eq type :both) (eq type :commands))
    (setf (command-recording-stream database)
          (make-broadcast-stream *standard-output*)))
  (when (or (eq type :both) (eq type :results))
    (setf (result-recording-stream database)
          (make-broadcast-stream *standard-output*)))
  (values))

(defun stop-sql-recording (&key (type :commands) (database *default-database*))
  "Stops recording of SQL commands sent to and/or results
returned from DATABASE which defaults to *DEFAULT-DATABASE*. The
default value of TYPE is :commands which means that SQL commands
sent to DATABASE will no longer be recorded. If TYPE is :results
then SQL results returned from DATABASE will no longer be
recorded. Recording may be stopped for both commands and results
by passing TYPE value of :both."
  (when (or (eq type :both) (eq type :commands))
    (setf (command-recording-stream database) nil))
  (when (or (eq type :both) (eq type :results))
    (setf (result-recording-stream database) nil))
  (values))

(defun sql-recording-p (&key (type :commands) (database *default-database*))
  "Predicate to test whether the SQL recording specified by TYPE
is currently enabled for DATABASE which defaults to *DEFAULT-DATABASE*.
TYPE may be one of :commands, :results, :both or :either, defaulting to
:commands, otherwise nil is returned."
  (when (or (and (eq type :commands)
                 (command-recording-stream database))
            (and (eq type :results)
                 (result-recording-stream database))
            (and (eq type :both)
                 (result-recording-stream database)
                 (command-recording-stream database))
            (and (eq type :either)
                 (or (result-recording-stream database)
                     (command-recording-stream database))))
    t))

(defun add-sql-stream (stream &key (type :commands)
                              (database *default-database*))
  "Adds the supplied stream STREAM (or T for *standard-output*)
as a component of the recording broadcast stream for the SQL
recording type specified by TYPE on DATABASE which defaults to
*DEFAULT-DATABASE*. TYPE must be one of :commands, :results,
or :both, defaulting to :commands, depending on whether the
stream is to be added for recording SQL commands, results or
both."
  (when (or (eq type :both) (eq type :commands))
    (unless (member stream
                    (list-sql-streams :type :commands :database database))
      (setf (command-recording-stream database)
            (apply #'make-broadcast-stream
                   (cons stream (list-sql-streams :type :commands
                                                  :database database))))))
  (when (or (eq type :both) (eq type :results))
    (unless (member stream (list-sql-streams :type :results :database database))
      (setf (result-recording-stream database)
            (apply #'make-broadcast-stream
                   (cons stream (list-sql-streams :type :results
                                                  :database database))))))
  stream)

(defun delete-sql-stream (stream &key (type :commands)
                                 (database *default-database*))
 "Removes the supplied stream STREAM from the recording broadcast
stream for the SQL recording type specified by TYPE on DATABASE
which defaults to *DEFAULT-DATABASE*. TYPE must be one
of :commands, :results, or :both, defaulting to :commands,
depending on whether the stream is to be added for recording SQL
commands, results or both."
  (when (or (eq type :both) (eq type :commands))
    (setf (command-recording-stream database)
          (apply #'make-broadcast-stream
                 (remove stream (list-sql-streams :type :commands
                                                  :database database)))))
  (when (or (eq type :both) (eq type :results))
    (setf (result-recording-stream database)
          (apply #'make-broadcast-stream
                 (remove stream (list-sql-streams :type :results
                                                  :database database)))))
  stream)

(defun list-sql-streams (&key (type :commands) (database *default-database*))
  "Returns the list of component streams for the broadcast stream
recording SQL commands sent to and/or results returned from
DATABASE which defaults to *DEFAULT-DATABASE*. TYPE must be one
of :commands, :results, or :both, defaulting to :commands, and
determines whether the listed streams contain those recording SQL
commands, results or both."
  (let ((crs (command-recording-stream database))
        (rrs (result-recording-stream database)))
    (cond
      ((eq type :commands)
       (when crs (broadcast-stream-streams crs)))
      ((eq type :results)
       (when rrs (broadcast-stream-streams rrs)))
      ((eq type :both)
       (append (when crs (broadcast-stream-streams crs))
               (when rrs (broadcast-stream-streams rrs))))
      (t
       (error "Unknown recording type. ~A" type)))))

(defun sql-stream (&key (type :commands) (database *default-database*))
  "Returns the broadcast stream used for recording SQL commands
sent to or results returned from DATABASE which defaults to
*DEFAULT-DATABASE*. TYPE must be one of :commands or :results,
defaulting to :commands, and determines whether the stream
returned is that used for recording SQL commands or results."
  (cond
    ((eq type :commands)
     (command-recording-stream database))
    ((eq type :results)
     (result-recording-stream database))
    (t
     (error "Unknown recording type. ~A" type))))

(defun record-sql-command (expr database)
  (when database
    (with-slots (command-recording-stream)
        database
      (when command-recording-stream
        (format command-recording-stream "~&;; ~A ~A => ~A~%"
                (iso-timestring (get-time))
                (database-name database)
                expr)))))

(defun record-sql-result (res database)
  (when database
    (with-slots (result-recording-stream)
        database
      (when result-recording-stream
        (format result-recording-stream "~&;; ~A ~A <= ~A~%"
                (iso-timestring (get-time))
                (database-name database)
                res)))))



