| pixel ( @ 2009-07-21 00:17:00 |
| Entry tags: | cl-perec, lisp, programming, tutorial |
Sensible Serializing with cl-perec
Don't forget to check out the other articles in this series:
- Getting Started with cl-perec
- Persisting Simple Types with cl-perec
- Sensible Serializing with cl-perec (you are here)
- Peering Down the Rabbit Hole with cl-perec
If a class slot contains an object, cl-perec will do its best to persist that object into the database using cl-serializer. Unfortunately, that persistence leaves something to be desired: namely, interoperability with non-lisp systems. I mean, binary strings and type codes? Ew!¹
Fortunately, because we know our data fairly well, we're going to serialize without the hassle of type codes. First, let's revisit our TPS-REPORT class, and add a pathname² at which that report can be found.
WARNING! Don't execute this yet, or you'll have to restart your lisp image.³
(cl-perec:defpclass* tps-report () ((report-title :type boring-string) (report-text :type even-string) (report-path :type pathname)))
Calling back upon cl-perec::defmapping, which we discovered earlier, we map the pathname type to an sql character type.
(cl-perec::defmapping pathname (cl-rdbms::sql-character-large-object-type) 'pathname-sql-reader 'pathname-sql-writer)
pathname-sql-reader and -writer don't exist, so we'll have to make them. They aren't terribly complicated functions: all they do is turn a pathname into a string and vice versa. Actually, that sounds kinda like some functions that already exist in Common Lisp, making our job pretty easy.
The reader function is supposed to take a sequence and an index, then get its value from that sequence starting at index. The writer function takes a value to convert, and a sequence plus index where it is supposed to store the value.⁴
(defun pathname-sql-reader (rdbms-values index)
(pathname (elt rdbms-values index)))
(defun pathname-sql-writer (slot-value rdbms-values index)
(setf (elt rdbms-values index)
(namestring slot-value)))
Exciting, no? But there's one more thing we need to do: ensure cl-perec will actually use our mapping.
(pushnew 'pathname cl-perec::*mapped-type-precedence-list*)
Note this is a different list from what we used when we were defining types. I don't know why.
Our mapping defined, it's safe to redefine the tps-report class as above. Redefining the class earlier would have caused the pathname type mapping to be set⁵, after which changing the mapping has no effect.³
That done, we can point our tps-report records to their corresponding file.
(cl-perec:with-transaction
(make-instance 'tps-report
:report-title "Testing P"
:report-text "This is a test"
:report-path #p"/share/tps-reports/Testing"))
test_db=> select * from tps_report;
_oid report_title report_text report_path 4752604 Testing P This is a test /share/tps-reports/Testing
And, best of all, non-lisp code can still make sense of report_path!
cl-perec offers an incredible amount of flexibility in how your data is mapped between your lisp image and the database, upon which we have barely scratched the surface. Just remember that with great power comes great responsibility, and probably a few bugs. Also remember that order matters, so if something isn't working you think should be, execution order may be to blame.
Footnotes
- As I've mentioned before, the entire point of using an ORM rather than an object database is so other systems in other languages can play along.
- Strangely, pathnames are not supported by cl-serializer, so we'd have to jump through these hoops anyway. But it applies just as well to other objects.
- cl-perec seems to cache computed values a little too aggressively and I haven't figured out how to force it to recompute things. Unfortunately, this means I end up restarting my lisp pretty frequently during development and that's a pretty painful way to mold a codebase.
- I'm told you can use the index to do funky things like splitting a value across multiple columns. I haven't found myself with the desire for that and so leave that particular exercise to the reader (for now, at least).
- Specifically, the pathname type would be mapped to the t class, causing pathnames to run through the cl-serializer machinery and eventually error out during a database write because cl-serializer doesn't support pathname serialization.
Continue to Peering Down the Rabbit Hole with cl-perec