;; ------------------------------------------------------------
;; metaclass: view-class
-(defclass view-metaclass (standard-class)
+(defclass standard-db-class (standard-class)
((view-table
:accessor view-table
:initarg :view-table)
:accessor object-definition
:initarg :definition
:initform nil)
- (version
- :accessor object-version
- :initarg :version
- :initform 0)
(key-slots
:accessor key-slots
:initform nil)
:accessor view-class-qualifier
:initarg :qualifier
:initform nil))
- (:documentation "VIEW-CLASS metaclass."))
+ (:documentation "Metaclass for all CLSQL View Classes."))
-#+lispworks
-(defmacro push-on-end (value location)
- `(setf ,location (nconc ,location (list ,value))))
+;;; Lispworks 4.2 and before requires special processing of extra slot and class options
-;; As Heiko Kirscke (author of PLOB!) would say: !@##^@%! Lispworks!
-#+lispworks
-(defconstant +extra-slot-options+ '(:column :db-kind :db-reader :nulls-ok
- :db-writer :db-type :db-info))
-
-#+lispworks
-(define-setf-expander assoc (key alist &environment env)
- (multiple-value-bind (temps vals stores store-form access-form)
- (get-setf-expansion alist env)
- (let ((new-value (gensym "NEW-VALUE-"))
- (keyed (gensym "KEYED-"))
- (accessed (gensym "ACCESSED-"))
- (store-new-value (car stores)))
- (values (cons keyed temps)
- (cons key vals)
- `(,new-value)
- `(let* ((,accessed ,access-form)
- (,store-new-value (assoc ,keyed ,accessed)))
- (if ,store-new-value
- (rplacd ,store-new-value ,new-value)
- (progn
- (setq ,store-new-value
- (acons ,keyed ,new-value ,accessed))
- ,store-form))
- ,new-value)
- `(assoc ,new-value ,access-form)))))
-
-#+lispworks
-(defmethod clos::canonicalize-defclass-slot :around
- ((prototype view-metaclass) slot)
- "\\lw\\ signals an error on unknown slot options; so this method
-removes any extra allowed options before calling the default method
-and returns the canonicalized extra options concatenated to the result
-of the default method. The extra allowed options are the value of the
-\\fcite{+extra-slot-options+}."
- (let ((extra-slot-options ())
- (rest-options ())
- (result ()))
- (do ((olist (cdr slot) (cddr olist)))
- ((null olist))
- (let ((option (car olist)))
- (cond
- ((find option +extra-slot-options+)
- ;;(push (cons option (cadr olist)) extra-slot-options))
- (setf (assoc option extra-slot-options) (cadr olist)))
- (t
- (push (cadr olist) rest-options)
- (push (car olist) rest-options)))))
- (setf result (call-next-method prototype (cons (car slot) rest-options)))
- (dolist (option extra-slot-options)
- (push-on-end (car option) result)
- (push-on-end `(quote ,(cdr option)) result))
- result))
+(defvar +extra-slot-options+ '(:column :db-kind :db-type :db-reader :void-value :db-constraints
+ :db-writer :db-info))
+(defvar +extra-class-options+ '(:base-table))
#+lispworks
-(defconstant +extra-class-options+ '(:base-table :version :schemas))
-
-#+lispworks
-(defmethod clos::canonicalize-class-options :around
- ((prototype view-metaclass) class-options)
- "\\lw\\ signals an error on unknown class options; so this method
-removes any extra allowed options before calling the default method
-and returns the canonicalized extra options concatenated to the result
-of the default method. The extra allowed options are the value of the
-\\fcite{+extra-class-options+}."
- (let ((extra-class-options nil)
- (rest-options ())
- (result ()))
- (dolist (o class-options)
- (let ((option (car o)))
- (cond
- ((find option +extra-class-options+)
- ;;(push (cons option (cadr o)) extra-class-options))
- (setf (assoc option extra-class-options) (cadr o)))
- (t
- (push o rest-options)))))
- (setf result (call-next-method prototype rest-options))
- (dolist (option extra-class-options)
- (push-on-end (car option) result)
- (push-on-end `(quote ,(cdr option)) result))
- result))
+(dolist (slot-option +extra-slot-options+)
+ (eval `(process-slot-option standard-db-class ,slot-option)))
+#+lispworks
+(dolist (class-option +extra-class-options+)
+ (eval `(process-class-option standard-db-class ,class-option)))
-(defmethod validate-superclass ((class view-metaclass)
+(defmethod validate-superclass ((class standard-db-class)
(superclass standard-class))
t)
((typep arg 'sql-ident)
(slot-value arg 'name))
((stringp arg)
- (intern (string-upcase arg)))))
+ (intern (symbol-name-default-case arg)))))
(defun column-name-from-arg (arg)
(cond ((symbolp arg)
((typep arg 'sql-ident)
(slot-value arg 'name))
((stringp arg)
- (intern (string-upcase arg)))))
+ (intern (symbol-name-default-case arg)))))
(defun remove-keyword-arg (arglist akey)
(pop-arg mylist))
newlist))
-(defmethod initialize-instance :around ((class view-metaclass)
+(defmethod initialize-instance :around ((class standard-db-class)
&rest all-keys
&key direct-superclasses base-table
- schemas version qualifier
+ qualifier
&allow-other-keys)
(let ((root-class (find-class 'standard-db-object nil))
- (vmc (find-class 'view-metaclass)))
+ (vmc (find-class 'standard-db-class)))
(setf (view-class-qualifier class)
(car qualifier))
(if root-class
(car base-table)
base-table))
(class-name class)))))
- (setf (object-version class) version)
- (mapc (lambda (schema)
- (pushnew (class-name class) (gethash schema *object-schemas*)))
- (if (listp schemas) schemas (list schemas)))
(register-metaclass class (nth (1+ (position :direct-slots all-keys))
all-keys))))
-(defmethod reinitialize-instance :around ((class view-metaclass)
+(defmethod reinitialize-instance :around ((class standard-db-class)
&rest all-keys
- &key base-table schemas version
+ &key base-table
direct-superclasses qualifier
&allow-other-keys)
(let ((root-class (find-class 'standard-db-object nil))
- (vmc (find-class 'view-metaclass)))
+ (vmc (find-class 'standard-db-class)))
(setf (view-table class)
(table-name-from-arg (sql-escape (or (and base-table
(if (listp base-table)
direct-superclasses)
(remove-keyword-arg all-keys :direct-superclasses)))
(call-next-method)))
- (setf (object-version class) version)
- (mapc (lambda (schema)
- (pushnew (class-name class) (gethash schema *object-schemas*)))
- (if (listp schemas) schemas (list schemas)))
(register-metaclass class (nth (1+ (position :direct-slots all-keys))
all-keys)))
(setq all-slots (remove-if #'not-db-col all-slots))
(setq all-slots (stable-sort all-slots #'string< :key #'car))
(setf (object-definition class) all-slots))
- #-(or allegro openmcl)
+ #-allegro
(setf (key-slots class) (remove-if-not (lambda (slot)
(eql (slot-value slot 'db-kind)
:key))
(ordered-class-slots class)))))
-#+(or allegro openmcl)
-(defmethod finalize-inheritance :after ((class view-metaclass))
- ;; KMRL for slots without a type set, openmcl sets type-predicate to ccl:false
- ;; for view-metaclass
- #+openmcl
- (mapcar
- #'(lambda (s)
- (if (eq 'ccl:false (slot-value s 'ccl::type-predicate))
- (setf (slot-value s 'ccl::type-predicate) 'ccl:true)))
- (class-slots class))
-
+#+allegro
+(defmethod finalize-inheritance :after ((class standard-db-class))
(setf (key-slots class) (remove-if-not (lambda (slot)
(eql (slot-value slot 'db-kind)
:key))
:accessor view-class-slot-db-kind
:initarg :db-kind
:initform :base
- :type keyword
+ ;; openmcl 0.14.2 stores the value as list in the DSD
+ ;; :type (or list keyword)
+ #-openmcl :type #-openmcl keyword
:documentation
"The kind of DB mapping which is performed for this slot. :base
indicates the slot maps to an ordinary column of the DB view. :key
:initarg :db-constraints
:initform nil
:documentation
- "A single constraint or list of constraints for this column")
- (nulls-ok
- :accessor view-class-slot-nulls-ok
- :initarg :nulls-ok
+ "A keyword symbol representing a single SQL column constraint or list of such symbols.")
+ (void-value
+ :accessor view-class-slot-void-value
+ :initarg :void-value
:initform nil
:documentation
- "If t, all sql NULL values retrieved from the database become nil; if nil,
-all NULL values retrieved are converted by DATABASE-NULL-VALUE")
+ "Value to store if the SQL value is NULL. Default is NIL.")
(db-info
:accessor view-class-slot-db-info
:initarg :db-info
standard-effective-slot-definition)
())
-(defmethod direct-slot-definition-class ((class view-metaclass)
+(defmethod direct-slot-definition-class ((class standard-db-class)
#+kmr-normal-dsdc &rest
initargs)
(declare (ignore initargs))
(find-class 'view-class-direct-slot-definition))
-(defmethod effective-slot-definition-class ((class view-metaclass)
+(defmethod effective-slot-definition-class ((class standard-db-class)
#+kmr-normal-esdc &rest
initargs)
(declare (ignore initargs))
(find-class 'view-class-effective-slot-definition))
#+openmcl
-(defun compute-class-precedence-list (class)
- ;; safe to call this in openmcl
- (class-precedence-list class))
+(when (not (symbol-function 'compute-class-precedence-list))
+ (eval
+ (defun compute-class-precedence-list (class)
+ (class-precedence-list class))))
-#-(or sbcl cmu)
-(defmethod compute-slots ((class view-metaclass))
+#-mop-slot-order-reversed
+(defmethod compute-slots ((class standard-db-class))
"Need to sort order of class slots so they are the same across
implementations."
(let ((slots (call-next-method))
(defun compute-lisp-type-from-slot-specification (slotd specified-type)
"Computes the Lisp type for a user-specified type. Needed for OpenMCL
which does type checking before storing a value in a slot."
- #-openmcl (declare (ignore slotd))
;; This function is called after the base compute-effective-slots is called.
;; OpenMCL sets the type-predicate based on the initial value of the slots type.
;; so we have to override the type-predicates here
(cond
((and (symbolp (car specified-type))
(string-equal (symbol-name (car specified-type)) "string"))
- #+openmcl (setf (slot-value slotd 'ccl::type-predicate) 'stringp)
+ 'string)
+ ((and (symbolp (car specified-type))
+ (string-equal (symbol-name (car specified-type)) "varchar"))
+ 'string)
+ ((and (symbolp (car specified-type))
+ (string-equal (symbol-name (car specified-type)) "char"))
'string)
(t
- #+openmcl (setf (slot-value slotd 'ccl::type-predicate) 'ccl:true)
specified-type)))
- #+openmcl
- ((null specified-type)
- ;; setting this here is not enough since openmcl later sets the
- ;; type-predicate to ccl:false. So, have to check slots again
- ;; in finalize-inheritance
- #+openmcl (setf (slot-value slotd 'ccl::type-predicate) 'ccl:true)
- t)
+ ((eq (ensure-keyword specified-type) :bigint)
+ 'integer)
+ ((eq (ensure-keyword specified-type) :char)
+ 'character)
+ ((eq (ensure-keyword specified-type) :varchar)
+ 'string)
+ ((and specified-type
+ (not (eql :not-null (slot-value slotd 'db-constraints))))
+ `(or null ,specified-type))
(t
- ;; This can be improved for OpenMCL to set a more specific type
- ;; predicate based on the value specified-type
- #+openmcl (setf (slot-value slotd 'ccl::type-predicate) 'ccl:true)
specified-type)))
;; Compute the slot definition for slots in a view-class. Figures out
;; what kind of database value (if any) is stored there, generates and
;; verifies the column name.
-(defmethod compute-effective-slot-definition ((class view-metaclass)
+(declaim (inline delistify))
+(defun delistify (list)
+ "Some MOPs, like openmcl 0.14.2, cons attribute values in a list."
+ (if (listp list)
+ (car list)
+ list))
+
+(declaim (inline delistify))
+(defun delistify-dsd (list)
+ "Some MOPs, like openmcl 0.14.2, cons attribute values in a list."
+ (if (and (listp list) (null (cdr list)))
+ (car list)
+ list))
+
+(defmethod compute-effective-slot-definition ((class standard-db-class)
#+kmr-normal-cesd slot-name
direct-slots)
#+kmr-normal-cesd (declare (ignore slot-name))
-
- (let ((slotd (call-next-method))
- (sd (car direct-slots)))
-
- (typecase sd
- (view-class-slot-definition-mixin
- ;; Use the specified :column argument if it is supplied, otherwise
- ;; the column slot is filled in with the slot-name, but transformed
- ;; to be sql safe, - to _ and such.
- (setf (slot-value slotd 'column)
- (column-name-from-arg
- (if (slot-boundp sd 'column)
- (view-class-slot-column sd)
- (column-name-from-arg
- (sql-escape (slot-definition-name sd))))))
-
- (setf (slot-value slotd 'db-type)
- (when (slot-boundp sd 'db-type)
- (view-class-slot-db-type sd)))
-
- (setf (slot-value slotd 'nulls-ok)
- (view-class-slot-nulls-ok sd))
-
- ;; :db-kind slot value defaults to :base (store slot value in
- ;; database)
-
- (setf (slot-value slotd 'db-kind)
- (if (slot-boundp sd 'db-kind)
- (view-class-slot-db-kind sd)
- :base))
-
- (setf (slot-value slotd 'db-writer)
- (when (slot-boundp sd 'db-writer)
- (view-class-slot-db-writer sd)))
- (setf (slot-value slotd 'db-constraints)
- (when (slot-boundp sd 'db-constraints)
- (view-class-slot-db-constraints sd)))
-
- ;; I wonder if this slot option and the previous could be merged,
- ;; so that :base and :key remain keyword options, but :db-kind
- ;; :join becomes :db-kind (:join <db info .... >)?
-
- (setf (slot-value slotd 'db-info)
- (when (slot-boundp sd 'db-info)
- (if (listp (view-class-slot-db-info sd))
- (parse-db-info (view-class-slot-db-info sd))
- (view-class-slot-db-info sd))))
-
- ;; KMR: store the user-specified type and then compute
- ;; real Lisp type and store it
- (setf (specified-type slotd)
- (slot-definition-type slotd))
- (setf (slot-value slotd 'type)
- (compute-lisp-type-from-slot-specification
- slotd (slot-definition-type slotd)))
- )
- ;; all other slots
- (t
- (change-class slotd 'view-class-effective-slot-definition
- #+allegro :name
- #+allegro (slot-definition-name sd))
- (setf (slot-value slotd 'column)
- (column-name-from-arg
- (sql-escape (slot-definition-name sd))))
-
- (setf (slot-value slotd 'db-info) nil)
- (setf (slot-value slotd 'db-kind)
- :virtual)))
- slotd))
-
+
+ ;; KMR: store the user-specified type and then compute
+ ;; real Lisp type and store it
+ (let ((dsd (car direct-slots)))
+ (when (and (typep dsd 'view-class-slot-definition-mixin)
+ (null (specified-type dsd)))
+ (setf (specified-type dsd)
+ (slot-definition-type dsd))
+ (setf (slot-value dsd 'type)
+ (compute-lisp-type-from-slot-specification
+ dsd (slot-definition-type dsd))))
+
+ (let ((esd (call-next-method)))
+ (typecase dsd
+ (view-class-slot-definition-mixin
+ ;; Use the specified :column argument if it is supplied, otherwise
+ ;; the column slot is filled in with the slot-name, but transformed
+ ;; to be sql safe, - to _ and such.
+ (setf (slot-value esd 'column)
+ (column-name-from-arg
+ (if (slot-boundp dsd 'column)
+ (delistify-dsd (view-class-slot-column dsd))
+ (column-name-from-arg
+ (sql-escape (slot-definition-name dsd))))))
+
+ (setf (slot-value esd 'db-type)
+ (when (slot-boundp dsd 'db-type)
+ (delistify-dsd
+ (view-class-slot-db-type dsd))))
+
+ (setf (slot-value esd 'void-value)
+ (delistify-dsd
+ (view-class-slot-void-value dsd)))
+
+ ;; :db-kind slot value defaults to :base (store slot value in
+ ;; database)
+
+ (setf (slot-value esd 'db-kind)
+ (if (slot-boundp dsd 'db-kind)
+ (delistify-dsd (view-class-slot-db-kind dsd))
+ :base))
+
+ (setf (slot-value esd 'db-writer)
+ (when (slot-boundp dsd 'db-writer)
+ (delistify-dsd (view-class-slot-db-writer dsd))))
+ (setf (slot-value esd 'db-constraints)
+ (when (slot-boundp dsd 'db-constraints)
+ (delistify-dsd (view-class-slot-db-constraints dsd))))
+
+ ;; I wonder if this slot option and the previous could be merged,
+ ;; so that :base and :key remain keyword options, but :db-kind
+ ;; :join becomes :db-kind (:join <db info .... >)?
+
+ (setf (slot-value esd 'db-info)
+ (when (slot-boundp dsd 'db-info)
+ (let ((dsd-info (view-class-slot-db-info dsd)))
+ (cond
+ ((atom dsd-info)
+ dsd-info)
+ ((and (listp dsd-info) (> (length dsd-info) 1)
+ (atom (car dsd-info)))
+ (parse-db-info dsd-info))
+ ((and (listp dsd-info) (= 1 (length dsd-info))
+ (listp (car dsd-info)))
+ (parse-db-info (car dsd-info)))))))
+
+ (setf (specified-type esd)
+ (delistify-dsd (specified-type dsd)))
+
+ )
+ ;; all other slots
+ (t
+ (let ((type-predicate #+openmcl (slot-value esd 'ccl::type-predicate)))
+ #-openmcl (declare (ignore type-predicate))
+ (change-class esd 'view-class-effective-slot-definition
+ #+allegro :name
+ #+allegro (slot-definition-name dsd))
+ #+openmcl (setf (slot-value esd 'ccl::type-predicate)
+ type-predicate))
+
+ (setf (slot-value esd 'column)
+ (column-name-from-arg
+ (sql-escape (slot-definition-name dsd))))
+
+ (setf (slot-value esd 'db-info) nil)
+ (setf (slot-value esd 'db-kind) :virtual)
+ (setf (specified-type esd) (slot-definition-type dsd)))
+ )
+ esd)))
+
(defun slotdefs-for-slots-with-class (slots class)
(let ((result nil))
(dolist (s slots)