Create/update function for CLSQL

Django has a very handy model method in its database library called get_or_create, which either creates a new record using the keyword arguments passed as field values or gets a record using the arguments passed.

from models import Person
person = Person.objects.get_or_create(first_name="John", last_name="Johnson")

It adds syntactic sugar to the common case of checking if an entry exists, creating it if it does not, then updating (or creating with new) values. The problem is that its slow.

I offload our most expensive database work on our server to a Lisp-based XML RPC server using CLSQL. I wrote an analog of get_or_create for CLSQL. It does not have the advantage of Django’s keyword searches; all searches use = and pull the first row that matches the value.

The first function, get-instance is a helper to get a row in a view-class using a field/value pair.
create/update takes the first three arguments and passes them to get-instance to get any existing match.

(defun get-instance (cls pk val)
  #.(clsql:locally-enable-sql-reader-syntax)
  (let ((inst (clsql:select cls :flatp t :refresh t :limit 1
                            :where [= (clsql:sql-expression :attribute pk) val])))
    #.(clsql:restore-sql-reader-syntax-state)
    (if inst (car inst) nil)))
 
(defun create/update (cls pk val &rest args)
  (let ((inst (get-instance cls pk val)))
        (if inst
            (apply #'reinitialize-instance inst args)
            (apply #'make-instance cls pk val args))))

Usage:

(clsql:def-view-class person ()
    ((id
        :reader person-id
        :db-kind :key
        :db-constraints :not-null
        :type integer
        :initarg :id)
    (last-name
        :accessor person-last-name
        :db-constraints :not-null
        :type (clsql:varchar 25)
        :initarg :last-name)
    (first-name
        :accessor person-first-name
        :db-constraints :not-null
        :type (clsql:varchar 25)
        :initarg :first-name)))
 
(defvar john (create/update 'person :id 3))

If there is row with id 3 in the database, it will grab that (or, if the key is not unique, it will grab the first matching row.) If there is no entry with that id, it will create a new one, that can then be updated the normal way:

(setf (person-last-name john) "John")
(setf (person-first-name john) "Johnson")

The other alternative is to get and set all in one go:

(defvar john (create/update 'person
                            :id 3
                            :last-name "Johnson"
                            :first-name "John"))

It’s pretty simple but it is very useful.

Leave a comment | Trackback
May 9th, 2008 | Posted in Programming
Tags: , , ,
No comments yet.