;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10; Package: umlisp -*-
;;;; *************************************************************************
;;;; FILE IDENTIFICATION
;;;;
;;;; Name:          sql.lisp
;;;; Purpose:       SQL Generation functions for Hyperobject
;;;; Programmer:    Kevin M. Rosenberg
;;;; Date Started:  Apr 2000
;;;;
;;;; $Id$
;;;;
;;;; This file is Copyright (c) 2000-2003 by Kevin M. Rosenberg
;;;; *************************************************************************

(in-package #:hyperobject)

;;;; Metaclass initialization commands

(defun finalize-sql (cl)
  (setf (slot-value cl 'drop-table-cmd) (generate-drop-table-cmd
                                         (slot-value cl 'sql-name)))
  (let ((esds (class-slots cl)))
    (setf (slot-value cl 'create-table-cmd)
      (generate-create-table-cmd
       cl
       (remove-if #'(lambda (esd) (null (esd-stored esd))) esds)))
    (setf (slot-value cl 'create-indices-cmds)
      (generate-create-indices-cmds (sql-name cl) esds))
    (dolist (esd esds)
      (when (slot-value esd 'inverse)
        (define-inverse cl esd))))
  )


(defun define-inverse (class esd)
  "Define an inverse function for a slot"
  (let ((inverse (slot-value esd 'inverse)))
    (when inverse
      (eval
       `(defun ,inverse (obj)
          (format t "~&Finding key: ~S for class ~S ~%" obj ,class)
          ;; create inverse function
          ))
      ))
  )

(defun generate-create-table-cmd (cl esds)
  (with-output-to-string (s)
    (format s "CREATE TABLE ~A (~{~A~^, ~})"
            (slot-value cl 'sql-name)
            (loop for esd in esds
                collect
                  (concatenate
                      'string
                    (slot-value esd 'sql-name)
                    " "
                    (sql-type-to-field-string (slot-value esd 'sql-type)
                                              (slot-value esd 'sql-length)))))))

(defun sql-type-to-field-string (type length)
  (ecase type
    (:string
     (cond
      ((null length)
       "LONGTEXT")
      ((< length 8)
       (format nil "CHAR(~d)" length))
      (t
       (format nil "VARCHAR(~d)" length))))
    (:varchar
     (cond
      ((null length)
       "LONGTEXT")
      (t
       (format nil "VARCHAR(~d)" length))))
    (:text
     "LONGTEXT")
    (:datetime
     "VARCHAR(20)")
    (:char
     (unless length
       (setq length 1))
     (format nil "CHAR(~D)" length))
    ((or :fixnum :integer)
     "INTEGER")
    (:boolean
     "CHAR(1)")
    (:long-integer
     "BIGINT")
    ((or :short-float :float)
     "SINGLE")
    (:long-float
     "DOUBLE")))

(defun generate-drop-table-cmd (table-name)
  (format nil "DROP TABLE ~a" table-name))

(defun generate-create-indices-cmds (table-name slots)
  (let (indices)
    (dolist (slot slots)
      (when (slot-value slot 'indexed)
        (let ((sql-name (slot-value slot 'sql-name)))
          (push (sql-cmd-index table-name sql-name (slot-value slot 'unique))
                indices))))
    indices))

(defun sql-cmd-index (table field unique)
  (let ((*print-circle* nil))
    (format nil "CREATE ~AINDEX ~A ON ~A(~A)"
            (if unique "UNIQUE " "")
            (sql-index-name table field)
            table
            field)))

(defun sql-index-name (table field)
  (format nil "~A_~A" table field))

;;;; Runtime Commands

(defgeneric sql-create (cl))
(defmethod sql-create (cl)
 (with-sql-connection (conn)
    (sql-execute (slot-value cl 'create-table-cmd) conn)
    (dolist (cmd (slot-value cl 'create-indices-cmds))
      (sql-execute cmd conn))
    (values)))

(defgeneric sql-drop (cl))
(defmethod sql-drop (cl)
  (mutex-sql-execute (slot-value cl 'drop-table-cmd))
  (values))

#|
(defmethod sql-insert (obj)
  (mutex-sql-execute
   (format nil "INSERT INTO ~a (~a) VALUES (~a)"
           (sql-name self) (sql-cmd-field-names self) (format-values self))))

(defmethod sql-select (obj lisp-name key)
  (let ((tuple
         (car
          (mutex-sql-query
           (format nil "SELECT ~a FROM ~a WHERE ~a=~a"
                   (sql-cmd-field-names self) (sql-name self)
                   (inverse-field-name self) key)))))
    (when tuple
      (format t "process returned fields"))))


(defun format-values (self)
  (let ((values "")
        (fields (fields self)))
    (dolist (field fields)
      (unless (eq field (car fields))
        (string-append values ","))
      (let ((name (car field)))
        (with-key-value-list (key value (rest field))
          (when (eq key :type)
            (string-append values
                              (ecase value
                                ((:fixnum :bigint :short-float :double-float)
                                 (write-to-string
                                  (slot-value self name)))
                                ((:string :text)
                                 (format nil "'~a'"
                                         (add-sql-quotes
                                          (slot-value self name))))))))))
    values))

(defun inverse-field-string (fields)
  (let (inverse)
    (dolist (field fields)
      (let ((name-string (write-to-string (car field))))
        (with-key-value-list (key value (rest field))
          (when (eq key :inverse)
            (setq inverse value)))))
    (when inverse
      (write-to-string inverse))))

(defun row-field-string (fields)
  (let ((names ""))
    (dolist (field fields)
      (unless (eq field (car fields))
        (string-append names ","))
      (string-append names (lisp-name-to-sql-name (car field))))
    names))


(defun parse-fields (table-name fields)
  (let (class-fields)
    (dolist (field fields)
      (let* ((fname (car field))
             (name-string (write-to-string fname))
             (initarg (intern name-string :keyword))concat-symbol
             (def (list fname))
             (options (rest field)))
        (with-key-value-list (key value options)
          (case key
            (:type
             (setq def (nconc def (list :type
                                        (ecase value
                                          (:string
                                           'string)
                                          (:fixnum
                                           'fixnum)
                                          (:long-integer
                                           'integer)
                                          (:short-float
                                           'short-float)
                                          (:long
                                           'long-float)
                                          (:text
                                           'string))))))))
        (setq def (nconc def (list
                              :initarg initarg
                              :accessor (concat-symbol
                                         (write-to-string table-name) "-"
                                         (write-to-string fname)))))
        (push def class-fields)))
    class-fields))

||#
