[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
sql-reader.
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.
Footnotes:
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