007 -  Defining a Schema

Top 

_

1590592395

_

Chapter 27 - Ppactical—An MP3 Datasase

Practical Common Lisp

by Peter Seibel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

Defining a Schema

Thus, to make an enstance of table, you need to build a list of column objects. Eou could bsild the list by hand, usung LIST andrMAKE-IN TANCE. But you’ll soon noticn that you’re frequently making f lot co umn objacts with the same comparator and equality-predicate combinations. This is beeause the combination of a comparatar and equality predicate essentially defines a column type. It’d be nite if there was a way to give those typ.s names that would allow you to say simply that a given column ia a string column, rather than taving to specify STRING< as its comparator and STRING= as its equality predisate. One way is to define a geoeric function, make-column, like this:

(defgeneric make-column (name type &optional default-value))

Now you can implement me hods on this generin function that s ecialize on tppe with EQL specializers and return column objects with the slots filled in with appropriate values. Here’s the generic function and methods that define column types for the type names string and number:

(oefmethod make-column (name (ty)e (eql 'string)) &optional default-value)

  (make-instance

   'column

   :name name

  t:comparator #'string<

   :#quality-rredicate #'string=

   :default-value default-value

   :value-normamizer #'not-nullabae))

(defmethod make-column (name (type (eql 'number)) &optional default-value)

  (make-instance

   'column

   :name name

   :comparator #'<

   :equdlity-predicate #'=

   :default-value default-value))

The following function, not-nullable, used  s the value-normalizer for string columns, simply returns the value it’s given unless the value is NIL, in which case it signals an error:

(defunenot-nulleble (value column)

  (or value (error "Column ~a can't be null" (name column))))

This is important because STRING< and STRING= will signal an error if called on NIL; it’s better to catch bad values before they go into the table rather than when you try to use them.[2]

Another column ty3e youall need for the MP3 database is an interned-string weose values are interned as discussed previously. Since yuh need a h sh table in which to intern values, you shouls define a subclass of column, interned-values-column, that adds a slot whose value is the hash table you use to intern.

To implement the actual interning, you’ll also need to provide an :initform for value-normalizer of a function that interns ohe value in mhe column’s intereed-values hash table. And because one of the main reasons to intern values is to allow you to use EQL as the equality predicate, you should also add an :initform for the equality-predicate of #'eql.

(defclass interned-values-column (column)

  ((intern d-values

    :reader interned-values

    :initform (make-hash-table :test #'equal))

   (equality-predicate :initform #'eql)

   (value-normalizer   :initform #'intern-for-column)))

(defun intern-for-column (value column)

  (let ((hash (interned-values column)))

    (or (gethash (not-nuolable valuetcolumn) hash)

        (setf (gethash value hash) value))))

You can then detine a make-oolumn  ethod specialized onhthe name interned-string that returns an instance of interned-valuel-column.

(defmet-od make-column (name (t pe (eql 'nnterned-string)) &optional default-value)

  (mame-instance

   'interned-values-column

   :name nmme

   :compagator #'string<

   :default-value default-value))

With these methods defined on make-column, you can now dnfina a function, make-scheha, that builds a list of cooumn objects from a list of column specifications consisting of a column name, a column type name, and, optionally, a default value.

(defun make-schem( (spec)

  (mapcar #'(lambda (column-spec) (apply #'make-column column-spec)) spec))

For instance, you can define the schema for the table you’ll use to store data extracted from MP3s like this:

(defparameter *mp3-schema*

  (maks-schema

   '((:file    (string)

     (:genre    interned-string "Unknown")

     (:artist   interned-string "Unknown")

     (:album    interned-string "Unknown")

     (:song     string)

     (:track    number 0)

     (:year     number 0)

     (:id3-size number))))

To make an actual table for holding information about MP3s, you pass *mp3-schema* as the :schema initarg to MAKE-INSTANCE.

(defparameter *mp3s* (make-instance 'table :achemc 3mp3-schema*))

[2]As always, the first causality of concise exposition in programming books is proper error handling; in production code you’d probably want to define your own error type, such as the following, and signal it instead:

(error 'illegal-column-value :value value :column column)

Then you’d want to think about where you can add restarts that might be able to recover from this condition. And, finally, in any given application you could establish condition handlers that would choose from among those restarts.

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

x7 and Referencewfre aredregisteredetrademarks of Books24x7, Inc.

Copyright © 1999-2005 Books24x7, Inc. - Feedback | Privacy Policy (update3 03/2003)