[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5. New Backends


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1 About

You can define your own backends in cl-store to do custom object I/O. Theoretically one can add a backend that can do socket based communication with any language provided you know the correct format to output objects in. If the framework is not sufficient to add your own backend just drop me a line and we will see what we can do about it.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2 The Process


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.1 Add the backend

Use defbackend to define the new backend choosing the output format, an optional magic number, extra fields for the backend and a backend to extend which defaults to the base backend. eg. (from the cl-store-backend)

 
(defbackend cl-store :magic-number 1347643724
                :stream-type '(unsigned-byte 8)
                :old-magic-numbers (1912923 1886611788 1347635532)
                :extends resolving-backend
                :fields ((restorers :accessor restorers :initform (make-hash-table))))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.2 Recognizing Objects.

Decide how to recognize objects on restoration. When restoring objects the backend has a responsibility to return a symbol identifying the defrestore method to call by overriding the get-next-reader method. In the cl-store backend this is done by keeping a mapping of type codes to symbols. When storing an object the type code is written down the stream first and then the restoring details for that particular object. The get-next-reader method is then specialized to read the type code and look up the symbol in a hash-table kept on the backend.

eg. (from the cl-store-backend)

 
(defvar *cl-store-backend* (find-backend 'cl-store))
;; This is a util method to register the code with a symbol
(defun register-code (code name &optional (errorp t))
  (aif (and (gethash code (restorers *cl-store-backend*)) errorp)
       (error "Code ~A is already defined for ~A." code name)
       (setf (gethash code (restorers *cl-store-backend*))
             name))
  code)
;; An example of registering the code 7 with ratio
(defconstant +ratio-code+ (register-code 7 'ratio))

;; Extending the get-next-reader method
(defmethod get-next-reader ((backend cl-store) (stream stream))
  (let ((type-code (read-type-code stream)))
    (or (gethash type-code (restorers backend))
        (values nil (format nil "Type ~A" type-code)))))

(defstore-cl-store (obj ratio stream)
  (output-type-code +ratio-code+ stream) ;; output the type code
  (store-object (numerator obj) stream)
  (store-object (denominator obj) stream))


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2.3 Extending the Resolving backend

If you are extending the resolving-backend you have a couple of extra responsibilities to ensure that circular references are resolved correctly. Store-referrer must be extended for your backend to output the referrer code. This must be done as if it were a defstore for a referrer. A defrestore-<backend-name> must also be defined for the referrer which must return a referrer created with make-referrer. Once that is done you can use resolving-object and setting to resolve circularities in objects.

eg (from the cl-store backend)

 
(defconstant +referrer-code+ (register-code 1 'referrer nil))
(defmethod store-referrer (ref stream (backend cl-store))
  (output-type-code +referrer-code+ stream)
  (store-32-bit ref stream))

(defrestore-cl-store (referrer stream)
  (make-referrer :val (read-32-bit stream nil)))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.3 Example: Simple Pickle Format

As a short example we will define a backend that can handle simple objects using the python pickle format.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.3.1 Define the backend

 
(in-package :cl-user)
(use-package :cl-store)

(defbackend pickle :stream-type 'character)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.3.2 Recognize Objects

This is just a simple example to be able to handle single strings stored with Python's pickle module.

 
(defvar *pickle-mapping*
  '((#\S . string)))

(defmethod get-next-reader ((backend pickle) (stream stream))
  (let ((type-code (read-char stream)))
    (or (cdr (assoc type-code *pickle-mapping*))
        (values nil (format nil "Type ~A" type-code)))))

(defrestore-pickle (noop stream))

(defstore-pickle (obj string stream)
  (format stream "S'~A'~%p0~%." obj))

(defrestore-pickle (string stream)
  (let ((val (read-line stream)))
    (read-line stream) ;; remove the PUSH op
    (read-line stream) ;; remove the END op
    (subseq val 1 (1- (length val)))))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.3.3 Test the new Backend.

This can be tested with the code

 
Python
>>> import pickle
>>> pickle.dump('Foobar', open('/tmp/foo.p', 'w'))

Lisp
* (cl-store:restore "/tmp/foo.p" 'pickle)
=> "Foobar"
And 

Lisp 
* (cl-store:store "BarFoo" "/tmp/foo.p" 'pickle)

Python
>>> pickle.load(open('/tmp/foo.p'))
'BarFoo'

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4 API


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4.1 Functions

Generic: backend-restore backend place

Restore the object found in stream place using backend backend. Checks the magic-number and invokes backend-restore-object. Called by restore, override for custom restoring.

Generic: backend-restore backend place

Find the next function to call to restore the next object with backend and invoke it with place. Called by restore-object, override this method to do custom restoring (see `circularities.lisp' for an example).

Generic: backend-store backend place obj

Stores the backend code and calls store-object. This is called by store. Override for custom storing.

Generic: backend-store-object backend obj place

Called by store-object, override this to do custom storing (see `circularities.lisp' for an example).

Generic: get-next-reader backend place

Method which must be specialized for backend to return the next symbol designating a defrestore instance to restore an object from place. If no reader is found return a second value which will be included in the error.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4.2 Macros

Macro: defbackend name &key (stream-type (required-arg "stream-type")) magic-number fields (extends 'backend) old-magic-numbers

eg. (defbackend pickle :stream-type 'character) This creates a new backend called name, stream-type describes the type of stream that the backend will serialize to which must be suitable as an argument to open. Magic-number, when present, must be of type (unsigned-byte 32) which will be written as a verifier for the backend. Fields are extra fields to be added to the new class which will be created. By default the extends keyword is backend,the root backend, but this can be any legal backend. Old-magic-numbers holds previous magic-numbers that have been used by the backend to identify incompatible versions of objects stored.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Sean on September, 1 2005 using texi2html 1.76.