[Cl-perec-devel] Lots of questions :-)

Pixel // pinterface pinterface at gmail.com
Wed Sep 16 02:05:05 UTC 2009

Disclaimer: It's been nearly two months since I've done anything with
cl-perec (too many other projects, bleh).  I've done some minimal testing
of what I say below, so hopefully I'm not committing any major errors,
but there's no guarantee. :)

"Mihai Bazon" <mihai at bazon.net> wrote in
message news:f33c75c11638fc18c81f7cb445e6924c at bazon.net...
> Hi Attila,
> Thanks for your fast reply!
> Indeed, I knew about pinterface series, it helped a lot to get started.

I'm glad to hear that.[0]  Feel free to let me know if anything was unclear,
poorly explained, or seemed to be missing.  I generally know what I mean,
but that doesn't mean anybody else does! :)

> I tried defining my own type today and after some embarrassing amount of
> trial and error I came up with this:
> (cl-perec:defptype password ()
>   '(text 128))

(Aside: I somehow missed 'cl-perec:text entirely.  Good to know about that.)

> (cl-perec::defmapping password (cl-rdbms::sql-character-varying-type ...)
>   'cl-perec:identity-reader             ; unexported
>   'password-writer)
> (defun password-writer (val rdbms-values index)
>   (setf (elt rdbms-values index)
>         (ironclad:byte-array-to-hex-string
>          (ironclad:digest-sequence :sha1 (babel:string-to-octets val)))))
> (pushnew 'password cl-perec::*canonical-types*)
> (pushnew 'password cl-perec::*mapped-type-precedence-list*)
> It works, it creates SHA1 automatically for password fields, but there's
> one weird side effect: now *all* columns of a TEXT type are
> VARCHAR(128), and they are all mangled to SHA1. :-) Could you point out
> what did I do wrong?

The way Common Lisp types work, (subtypep 'password 'text) and (subtypep
'text 'password) are both true.  Not coincidentally, one of the tests
cl-perec uses to determine which reader/writer mapping to use is
#'subtypep[1].  Since--according to the CL type hierarchy--your 'password
type and perec's 'text types are equivalent, whichever appears in
*mapped-type-precedence-list* first will determine behavior for both (and
any other equivalent types).

You can generally trip up #'subtypep using a satisfies test.  For instance:
  (cl-perec:defptype password ()
    '(and (text 128) (satisfies constantly)))
Will result in[2]
  (subtypep 'password 'text) => t, t
  (subtypep 'text 'password) => nil, nil
which means so long as 'password appears in *mapped-type-precedence-list*
/before/ 'text, the 'password reader/writer mapping will be used for
passwords, but not text.  If 'text appears before 'password, 'password will
be run through 'text's reader/writer mapping.

HOWEVER!  As Attila has already mentioned, the SQL writer is the wrong place
to encrypt passwords anyway.  I /think/[3] you could do something like:
  (defmethod (setf password-of) (new-value object)
    (setf (slot-value object 'password) (encrypt-password new-value)))
or even
  (defmethod (setf password-of) (new-value object)
    (call-next-method (encrypt-password new-value) object))
with the caveat that you would always have to update the password using the
accessor function, and never using slot-value or with-slots (unless you
wanted to bypass the encryption for some reason).

Attila already covered this somewhat, but it bears repeating: the SQL-reader
and SQL-writer for a type must be the inverse of each other.  In essence,
  (equivalent-p thing (sql-reader (sql-writer thing)))
should be true (for whatever value of #'equivalent-p is relevant to your
type).  Any transformation you make in the sql-writer must be undone by the

In general, the only time you should be defining your own SQL readers and
writers is when you want to serialize an object in a funky way.  E.g., if
you want to convert objects of an 'e-mail class into strings of the form
<local-part>@<domain.tld> for the SQL database.

0. Hi!  I'm the guy who is writing that particular series on cl-perec. :)
1. see #'cl-perec::mapped-type-for in persistence/type.lisp
2. If you redefine the password type within the same lisp image, be sure to
   remove 'password from the cl-perec::*mapped-types* hash, otherwise
   cl-perec won't register the change in type hierarchy.[4]
3. "think" here meaning "Warning!  I haven't bothered to check!"
4. If it still doesn't register the change, restart your lisp image.  perec
   does a lot of aggressive internal caching and gets tripped up by changes
   a little too easily.

Hopefully that wasn't too confusing.

-pix (aka pinterface)

More information about the cl-perec-devel mailing list