Merge pull request #339 from sabracrolleton/master
[postmodern.git] / postmodern / table.lisp
blob881e157eaa516cc47b584e859e5b5de263d05414
1 ;;;; -*- Mode: LISP; Syntax: Ansi-Common-Lisp; Base: 10; Package: POSTMODERN; -*-
2 (in-package :postmodern)
4 (defparameter *direct-column-slot* nil
5 "This is used to communicate the fact that a slot is a column to
6 effective-slot-definition-class.")
8 (defclass effective-column-slot (standard-effective-slot-definition)
9 ((direct-slot :initform *direct-column-slot* :reader slot-column)))
11 (defclass direct-column-slot (standard-direct-slot-definition)
12 ((col-type :initarg :col-type :reader column-type)
13 (col-default :initarg :col-default :reader column-default)
14 (col-identity :initarg :col-identity :reader column-identity)
15 (col-unique :initarg :col-unique :reader column-unique)
16 (col-collate :initarg :col-collate :reader column-collate)
17 (col-primary-key :initarg :col-primary-key :reader column-primary-key)
18 (col-interval :initarg :col-interval :reader column-interval)
19 (col-check :initarg :col-check :reader column-check)
20 (col-references :initarg :col-references :reader column-references)
21 (col-export :initarg :col-export :initform nil :reader column-export)
22 (col-import :initarg :col-import :initform nil :reader column-import)
23 (ghost :initform nil :initarg :ghost :reader ghost)
24 (sql-name :reader slot-sql-name))
25 (:documentation "Type of slots that refer to database columns."))
27 (defclass dao-class (standard-class)
28 ((direct-keys :initarg :keys :initform nil :reader direct-keys)
29 (effective-keys :reader dao-keys)
30 (table-name)
31 (column-map :reader dao-column-map))
32 (:documentation "At the heart of Postmodern's DAO system is the dao-class
33 metaclass. It allows you to define classes for your database-access objects as
34 regular CLOS classes. Some of the slots in these classes will refer to columns
35 in the database. To specify that a slot refers to a column, give it a :col-type
36 option containing an S-SQL type expression (useful if you want to be able to
37 derive a table definition from the class definition), or simply a :column
38 option with value T. Such slots can also take a :col-default option, used to
39 provide a database-side default value as an S-SQL expression. You can use the
40 :col-name initarg (whose unevaluated value will be passed to to-sql-name) to
41 specify the slot's column's name.
43 DAO class definitions support two extra class options: :table-name to give the
44 name of the table that the class refers to (defaults to the class name), and
45 :keys to provide a set of primary keys for the table. If more than one key is
46 provided, this creates a multi-column primary key and all keys must be
47 specified when using operations such as update-dao and get-dao. When no primary
48 keys are defined, operations such as update-dao and get-dao will not work.
50 IMPORTANT: Class finalization for a dao class instance are wrapped with a
51 thread lock. However, any time you are using threads and a class that
52 inherits from other classes, you should ensure that classes are finalized
53 before you start generating threads that create new instances of that class.
55 The (or db-null integer) form is used to indicate a column can have NULL values
56 otherwise the column will be treated as NOT NULL.
58 Simple example:
60 (defclass users ()
61 ((name :col-type string :initarg :name :accessor user-name)
62 (creditcard :col-type (or db-null integer) :initarg :card :col-default :null)
63 (score :col-type bigint :col-default 0 :accessor user-score))
64 (:metaclass dao-class)
65 (:keys name))
67 In this case the name of the users will be treated as the primary key and the
68 database table is assume to be users.
70 Now look at a slightly more complex example.
72 (defclass country ()
73 ((id :col-type integer :col-identity t :accessor id)
74 (name :col-type text :col-unique t :initarg :country :accessor country)
75 (region-id :col-type integer :col-references ((regions id)) :initarg :region-id
76 :accessor region-id))
77 (:metaclass dao-class)
78 (:table-name countries))
80 In this example we have an id column which is specified to be an identity column.
81 Postgresql will automatically generate a sequence of of integers and this will
82 be the primary key.
84 We have a name column which is specified as unique and is not null.
86 We have a region-id column which references the id column in the regions table.
87 This is a foreign key constraint and Postgresql will not accept inserting a country
88 into the database unless there is an existing region with an id that matches this
89 number. Postgresql will also not allow deleting a region if there are countries
90 that reference that region's id. If we wanted Postgresql to delete countries when
91 regions are deleted, that column would be specified as:
93 (region-id :col-type integer :col-references ((regions id) :cascade)
94 :initarg :region-id :accessor region-id)
96 Now you can see why the double parens.
98 We also specify that the table name is not 'country' but 'countries'. (Some style guides
99 recommend that table names be plural and references to rows be singular.)
101 When inheriting from DAO classes, a subclass' set of columns also contains
102 all the columns of its superclasses. The primary key for such a class is the
103 union of its own keys and all the keys from its superclasses. Classes
104 inheriting from DAO classes should probably always use the dao-class metaclass
105 themselves.
107 When a DAO is created with make-instance, the :fetch-defaults keyword argument
108 can be passed, which, when T, will cause a query to fetch the default values
109 for all slots that refers to columns with defaults and were not bound through
110 initargs. In some cases, such as serial columns, which have an implicit default,
111 this will not work. You can work around this by creating your own sequence,
112 e.g. 'my_sequence', and defining a (:nextval \"my_sequence\") default.
114 Finally, DAO class slots can have an option :ghost t to specify them as ghost
115 slots. These are selected when retrieving instances, but not written when
116 updating or inserting, or even included in the table definition. The only known
117 use for this to date is for creating the table with (oids=true), and specify a
118 slot like this:
120 (oid :col-type integer :ghost t :accessor get-oid)"))
122 (defgeneric dao-keys (class)
123 (:documentation "Returns list of slot names that are the primary key of DAO
124 class. Explicit keys takes priority over col-identity which takes priority
125 over col-primary-key.
127 This is likely interesting if you have primary keys which are composed of
128 more than one slot. Pay careful attention to situations where the primary key
129 not only has more than one column, but they are actually in a different order
130 than they are in the database table itself. Obviously the table needs to have
131 been defined. You can provide a quoted class-name or an instance of a dao."))
133 (defmethod dao-keys :before ((class dao-class))
134 (unless (class-finalized-p class)
135 #+postmodern-thread-safe
136 (unless (class-finalized-p class)
137 (bordeaux-threads:with-lock-held (*class-finalize-lock*)
138 (unless (class-finalized-p class)
139 (finalize-inheritance class))))
140 #-postmodern-thread-safe
141 (finalize-inheritance class)))
143 (defgeneric find-primary-key-column (class)
144 (:documentation "Loops through a class's column definitions and returns
145 the first column name that has bound either col-identity or col-primary-key.
146 Returns a symbol."))
148 (defmethod find-primary-key-column ((class dao-class))
149 (loop for x in (dao-column-slots class) do
150 (if (or (slot-boundp x 'col-identity)
151 (slot-boundp x 'col-primary-key))
152 (return (slot-definition-name x)))))
154 (defmethod find-primary-key-column ((class symbol))
155 (loop for x in (dao-column-slots (find-class class)) do
156 (if (or (slot-boundp x 'col-identity)
157 (slot-boundp x 'col-primary-key))
158 (return (slot-definition-name x)))))
160 (defmethod find-primary-key-column (dao)
161 (loop for x in (dao-column-slots (class-of dao)) do
162 (if (or (slot-boundp x 'col-identity)
163 (slot-boundp x 'col-primary-key))
164 (return (slot-definition-name x)))))
166 (defmethod validate-superclass ((class dao-class) (super-class standard-class))
169 (defmethod dao-keys ((class-name symbol))
170 (dao-keys (find-class class-name)))
172 (defmethod dao-keys (dao)
173 (mapcar #'(lambda (slot)
174 (slot-value dao slot))
175 (dao-keys (the dao-class (class-of dao)))))
177 (defun dao-column-slots (class)
178 "Enumerate the slots in a class that refer to table rows."
179 (cond ((closer-mop:classp class)
180 (mapcar 'slot-column
181 (remove-if-not (lambda (x) (typep x 'effective-column-slot))
182 (closer-mop:class-slots class))))
183 ((symbolp class)
184 (mapcar 'slot-column
185 (remove-if-not (lambda (x) (typep x 'effective-column-slot))
186 (closer-mop:class-slots (find-class class)))))
187 ((typep class 'dao-class)
188 (mapcar 'slot-column
189 (remove-if-not (lambda (x) (typep x 'effective-column-slot))
190 (closer-mop:class-slots (class-of class)))))
191 (t nil)))
193 (defun dao-column-fields (class)
194 "Returns a list of symbols of the names of the slots in a class."
195 (mapcar 'slot-definition-name (dao-column-slots class)))
197 (defun dao-table-name (class)
198 "Get the name of the table associated with the given DAO class (or symbol naming
199 such a class)."
200 (when (symbolp class)
201 (setf class (find-class class)))
202 (if (slot-boundp class 'table-name)
203 (slot-value class 'table-name)
204 (class-name class)))
206 (defmethod shared-initialize :before ((class dao-class) slot-names
207 &key table-name &allow-other-keys)
208 (declare (ignore slot-names))
209 (setf (slot-value class 'direct-keys) nil)
210 (if table-name
211 (setf (slot-value class 'table-name)
212 (if (symbolp (car table-name))
213 (car table-name)
214 (intern (car table-name))))
215 (slot-makunbound class 'table-name)))
217 (defun dao-superclasses (class)
218 "Build a list of superclasses of a given class that are DAO
219 classes."
220 (let ((found ()))
221 (labels ((explore (class)
222 (when (typep class 'dao-class)
223 (pushnew class found))
224 (mapc #'explore (class-direct-superclasses class))))
225 (explore class)
226 found)))
228 (defmethod finalize-inheritance :after ((class dao-class))
229 "Building a row reader and a set of methods can only be done after
230 inheritance has been finalised."
231 ;; The effective set of keys of a class is the union of its keys and
232 ;; the keys of all its superclasses.
233 (setf (slot-value class 'effective-keys)
234 (reduce 'union (mapcar 'direct-keys (dao-superclasses class))))
235 (unless (every (lambda (x) (member x (dao-column-fields class))) (dao-keys class))
236 (error "Class ~A has a key that is not also a slot." (class-name class)))
237 (when (not (dao-keys class))
238 (setf (slot-value class 'effective-keys)
239 (list (find-primary-key-column class))))
240 (build-dao-methods class))
243 (defmethod shared-initialize :after ((slot direct-column-slot) slot-names
244 &key col-type col-default
245 (col-name nil col-name-p) &allow-other-keys)
246 (declare (ignore slot-names))
247 (setf (slot-value slot 'sql-name) (to-sql-name
248 (if col-name-p
249 col-name
250 (slot-definition-name slot))
251 s-sql:*escape-sql-names-p* t))
252 ;; The default for nullable columns defaults to :null.
253 (when (and (null col-default) (consp col-type) (eq (car col-type) 'or)
254 (member 'db-null col-type) (= (length col-type) 3))
255 (setf (slot-value slot 'col-default) :null)))
257 (defmethod direct-slot-definition-class ((class dao-class) &key column col-type
258 &allow-other-keys)
259 "Slots that have a :col-type option are column-slots."
260 (if (or column col-type)
261 (find-class 'direct-column-slot)
262 (call-next-method)))
265 (defmethod compute-effective-slot-definition ((class dao-class)
266 name
267 direct-slot-definitions)
268 (declare (ignore name))
269 (flet ((is-column (slot) (typep slot 'direct-column-slot)))
270 (let ((*direct-column-slot* (find-if #'is-column direct-slot-definitions)))
271 #+(or) ;; Things seem to work without this check. Removed for now.
272 (when (and *direct-column-slot*
273 (not (every #'is-column direct-slot-definitions)))
274 (error "Slot ~a in class ~a is both a column slot and a regular slot." name class))
275 (call-next-method))))
277 (defmethod effective-slot-definition-class ((class dao-class) &rest initargs)
278 (declare (ignore initargs))
279 (if *direct-column-slot*
280 (find-class 'effective-column-slot)
281 (call-next-method)))
283 (defgeneric dao-exists-p (dao)
284 (:documentation "Test whether a row with the same primary key as the given
285 dao exists in the database. Will also return NIL when any of the key slots in
286 the object are unbound."))
288 (defgeneric insert-dao (dao)
289 (:documentation "Insert the given dao into the database. Column slots of the
290 object which are unbound implies the database defaults. Hence, if these
291 columns has no defaults defined in the database, the the insertion of the dao
292 will be failed. (This feature only works on PostgreSQL 8.2 and up.)"))
294 (defgeneric update-dao (dao)
295 (:documentation "Update the representation of the given dao in the database
296 to the values in the object. This is not defined for tables that do not have
297 any non-primary-key columns. Raises an error when no row matching the dao
298 exists."))
300 (defgeneric delete-dao (dao)
301 (:documentation "Delete the given dao from the database."))
303 (defgeneric upsert-dao (dao)
304 (:documentation "Like save-dao or save-dao/transaction but using a different
305 method that doesn't involve a database exception. This is safe to use both in
306 and outside a transaction, though it's advisable to always do it in a
307 transaction to prevent a race condition. The way it works is:
309 If the object contains unbound slots, we call insert-dao directly, thus the
310 behavior is like save-dao.
312 Otherwise we try to update a record with the same primary key. If the
313 PostgreSQL returns a non-zero number of rows updated it treated as the
314 record is already exists in the database, and we stop here.
316 If the PostgreSQL returns a zero number of rows updated, it treated as the
317 record does not exist and we call insert-dao.
319 The race condition might occur at step 3 if there's no transaction: if UPDATE
320 returns zero number of rows updated and another thread inserts the record at
321 that moment, the insertion implied by step 3 will fail.
323 Note, that triggers and rules may affect the number of inserted or updated
324 rows returned by PostgreSQL, so zero or non-zero number of affected rows may
325 not actually indicate the existence of record in the database.
327 This method returns two values: the DAO object and a boolean (T if the object
328 was inserted, NIL if it was updated).
330 IMPORTANT: This is not the same as insert on conflict (sometimes called an upsert)
331 in Postgresq. An upsert in Postgresql terms is an insert with a fallback of updating
332 the row if the insert key conflicts with an already existing row. An upsert-dao
333 in Postmodern terms is the reverse. First you try updating an existing object. If
334 there is no existing object to oupdate, then you insert a new object."))
336 (defgeneric get-dao (type &rest args)
337 (:method ((class-name symbol) &rest args)
338 (let ((class (find-class class-name)))
339 (unless (class-finalized-p class)
340 #+postmodern-thread-safe
341 (unless (class-finalized-p class)
342 (bordeaux-threads:with-lock-held (*class-finalize-lock*)
343 (unless (class-finalized-p class)
344 (finalize-inheritance class))))
345 #-postmodern-thread-safe
346 (finalize-inheritance class-name))
347 (when (not (dao-keys class)) (error "Class ~a has no key slots."
348 (class-name class)))
349 (apply 'get-dao class-name args)))
350 (:documentation "Select the DAO object from the row that has the given primary
351 key values, or NIL if no such row exists. Objects created by this function will have
352 initialize-instance called on them (after loading in the values from the
353 database) without any arguments ― even :default-initargs are skipped.
354 The same goes for select-dao and query-dao."))
356 (defgeneric make-dao (type &rest args &key &allow-other-keys)
357 (:method ((class-name symbol) &rest args &key &allow-other-keys)
358 (let ((class (find-class class-name)))
359 (apply 'make-dao class args)))
360 (:method ((class dao-class) &rest args &key &allow-other-keys)
361 (unless (class-finalized-p class)
362 #+postmodern-thread-safe
363 (unless (class-finalized-p class)
364 (bordeaux-threads:with-lock-held (*class-finalize-lock*)
365 (unless (class-finalized-p class)
366 (finalize-inheritance class))))
367 #-postmodern-thread-safe
368 (finalize-inheritance class))
369 (let ((instance (apply #'make-instance class args)))
370 (insert-dao instance)))
371 (:documentation "Combines make-instance with insert-dao. Make the instance of
372 the given class and insert it into the database, returning the created instance."))
374 (defmacro define-dao-finalization (((dao-name class) &rest keyword-args) &body body)
375 "Create an :around-method for make-dao. The body is executed in a lexical
376 environment where dao-name is bound to a freshly created and inserted DAO. The
377 representation of the DAO in the database is then updated to reflect changes
378 that body might have introduced. Useful for processing values of slots with the
379 type serial, which are unknown before insert-dao."
380 (let ((args-name (gensym)))
381 `(defmethod make-dao :around ((class (eql ',class))
382 &rest ,args-name
383 &key ,@keyword-args &allow-other-keys)
384 (declare (ignorable ,args-name))
385 (let ((,dao-name (call-next-method)))
386 ,@body
387 (update-dao ,dao-name)))))
389 (defgeneric fetch-defaults (object)
390 (:documentation "Used to fetch the default values of an object on
391 creation. An example would be creating a dao object with unbounded slots.
392 Fetch-defaults could then be used to fetch the default values from the database
393 and bind the unbound slots which have default values. E.g.
395 (let ((dao (make-instance 'test-data :a 23)))
396 (pomo:fetch-defaults dao))
398 Returns dao if there were unbound slots with default values, nil otherwise."))
400 (defun %eval (code)
401 (funcall (compile nil `(lambda () ,code))))
403 (defun set-to-class (class)
404 "Take an imput that may be a symbol, actual class or dao object and returns the actual class."
405 (cond ((symbolp class)
406 (find-class class))
407 ((closer-mop:classp class)
408 class)
409 ((typep (class-of class) 'dao-class)
410 (class-of class))
411 (t nil)))
413 (defun col-type-text-p (column-slot)
414 "Returns t if a column-slot has text as a type. Could be text or (or text db-null)"
415 (when (and column-slot (typep column-slot 'direct-column-slot))
416 (let ((col-type (column-type column-slot)))
417 (cond ((and (listp col-type) (member 'text col-type))
419 ((eq col-type 'text)
421 (t nil)))))
423 (defun find-col-type (class column-name)
424 "Returns the col-type for a class and a column-name.
425 The column name must be a symbol"
426 (setf class (set-to-class class))
427 (when class
428 (let ((column-slot (find-dao-column-slot class column-name)))
429 (if column-slot
430 (column-type column-slot)
431 nil))))
433 ;; Note: Export functions need only take a value parameter and return
434 ;; a value parameter
436 (defun find-export-function (class column-name)
437 "Returns the export function, if any, for a class and a column-name.
438 Column name must be a symbol"
439 (setf class (set-to-class class))
440 (when (stringp column-name) (setf column-name (from-sql-name column-name)))
441 (when class
442 (let ((column-slot (find-dao-column-slot class column-name)))
443 (if column-slot
444 (column-export column-slot)
445 nil))))
447 (defun collect-export-functions (class)
448 "Collects the export functions for a class (if any) and returns a list of lists of form
449 (sql_name_of_field . export-function)"
450 (setf class (set-to-class class))
451 (loop for x in (dao-column-slots class)
452 when (column-export x)
453 collect
454 (cons (slot-sql-name x) (fdefinition (column-export x)))))
456 ;; Note: Import functions need to take a dao, a slot-name
457 ;; and the imported value
459 (defun find-import-function (class column-name)
460 "Returns the import function, if any, for a class and a column-name.
461 Column name must be a symbol"
462 (setf class (set-to-class class))
463 (when class
464 (when (stringp column-name)
465 (setf column-name (field-name-to-slot-name class column-name)))
466 (when column-name
467 (let ((col-slot (find-dao-column-slot class column-name)))
468 (if col-slot
469 (column-import col-slot)
470 nil)))))
472 (defun collect-import-functions (class)
473 "Collects the import functions for a class (if any) and returns a list of lists of form
474 (sql_name_of_field . export-function)"
475 (setf class (set-to-class class))
476 (loop for x in (dao-column-slots class)
477 when (column-import x)
478 collect
479 (cons (slot-sql-name x) (fdefinition (column-import x)))))
481 (defun find-dao-column-slot (class column-name)
482 "Given a class and a symbol returns the dao-column-slot class for the column
483 named by that symbol (not the sql_column_name). Column name can be a symbol or
484 a string."
485 (cond ((symbolp column-name)
486 (find (string-downcase (symbol-name column-name))
487 (dao-column-slots class)
488 :key #'slot-definition-name-as-string
489 :test 'equal))
490 ((stringp column-name)
491 (find (string-downcase column-name)
492 (dao-column-slots class)
493 :key #'slot-definition-name-as-string
494 :test 'equal))
495 (t nil)))
497 (defun slot-definition-name-as-string (slot)
498 "Given a dao slot, this returns a downcased string of the name of a slot
499 without the package name for use in find functions."
500 (string-downcase (symbol-name (closer-mop:slot-definition-name slot))))
502 (defun field-name-to-slot-name (class field-name)
503 "Takes a Postgresql column name and tries to match it to a dao slot name symbol.
504 This is trying to deal with the hyphens and underscores problem where
505 Postgresql table field names cannot use hyphens but that is normal CL
506 practice. The slot name symbol will be the full package::slot-name. field-name
507 must be a string."
508 (let ((slot (find field-name
509 (dao-column-slots class)
510 :key #'slot-definition-name-as-string
511 :test 'equal)))
512 (if slot
513 (closer-mop:slot-definition-name slot)
514 (progn
515 (setf field-name (cl-ppcre:regex-replace-all "_" field-name "-"))
516 (setf slot (find field-name
517 (dao-column-slots class)
518 :key #'slot-definition-name-as-string
519 :test 'equal))
520 (if slot
521 (closer-mop:slot-definition-name slot)
522 nil)))))
524 (defun build-dao-methods (class)
525 "Synthesise a number of methods for a newly defined DAO class.
526 \(Done this way because some of them are not defined in every
527 situation, and each of them needs to close over some pre-computed
528 values. Notes for future maintenance: Fields are the slot names
529 in a dao class. Field-sql-name returns the col-name for the
530 postgresql table, which may or may not be the same as the slot
531 names in the class and also may have no relation to the initarg
532 or accessor or reader.)"
533 (setf (slot-value class 'column-map)
534 (mapcar (lambda (s) (cons (slot-sql-name s) (slot-definition-name s)))
535 (dao-column-slots class)))
537 (%eval
538 `(let* ((fields (dao-column-fields ,class))
539 (key-fields (dao-keys ,class))
540 (ghost-slots (remove-if-not 'ghost (dao-column-slots ,class)))
541 (ghost-fields (mapcar 'slot-definition-name ghost-slots))
542 (value-fields (remove-if (lambda (x) (or (member x key-fields)
543 (member x ghost-fields)))
544 fields))
545 (table-name (dao-table-name ,class)))
546 (labels ((field-sql-name (field)
547 (make-symbol (car (find field (slot-value ,class 'column-map)
548 :key #'cdr :test #'eql))))
549 (test-fields (fields)
550 `(:and ,@(loop :for field :in fields
551 :collect (list := (field-sql-name field) '$$))))
552 (set-fields (fields)
553 (loop :for field :in fields
554 :append (list (field-sql-name field) '$$)))
555 (slot-values (object &rest slots)
556 (loop :for slot :in (apply 'append slots)
557 :collect
558 (if (and (slot-boundp object slot)
559 (find-export-function object slot))
560 (funcall (find-export-function object slot)
561 (slot-value object slot))
562 (slot-value object slot)))))
563 ;; When there is no primary key, a lot of methods make no sense.
564 (when key-fields
565 (let ((tmpl (sql-template `(:select (:exists (:select t :from ,table-name
566 :where ,(test-fields
567 key-fields)))))))
568 (defmethod dao-exists-p ((object ,class))
569 (and (every (lambda (s) (slot-boundp object s)) key-fields)
570 (query (apply tmpl (slot-values object key-fields)) :single))))
572 ;; When all values are primary keys, updating makes no sense.
573 (when value-fields
574 (let ((update-tmpl (sql-template `(:update ,table-name
575 :set ,@(set-fields value-fields)
576 :where ,(test-fields key-fields)))))
577 (defmethod update-dao ((object ,class))
578 (when (zerop (execute (apply update-tmpl
579 (slot-values object value-fields
580 key-fields))))
581 (error "Updated row does not exist."))
582 object)
583 ;; upsert in Postgresql terms is an insert with a fallback of updating
584 ;; the row if the insert key conflicts with an already existing row
585 ;; Historically an upsert-dao in Postmodern terms is the reverse
586 ;; updating an existing object and inserting a new object if there
587 ;; is no existing object to update.
588 (defmethod upsert-dao ((object ,class))
589 (handler-case
590 (if (zerop (execute (apply update-tmpl
591 (slot-values object value-fields
592 key-fields))))
593 (values (insert-dao object) t)
594 (values object nil))
595 (unbound-slot ()
596 (values (insert-dao object) t))))))
598 (let ((del-tmpl (sql-template `(:delete-from ,table-name
599 :where ,(test-fields key-fields)))))
600 (defmethod delete-dao ((object ,class))
601 (execute (apply del-tmpl (slot-values object key-fields)))))
603 (let ((get-tmpl (sql-template `(:select * :from ,table-name
604 :where ,(test-fields key-fields)))))
605 (defmethod get-dao ((type (eql (class-name ,class))) &rest keys)
606 (car (exec-query *database* (apply get-tmpl keys)
607 (dao-row-reader ,class))))))
609 (defmethod insert-dao ((object ,class))
610 (let (bound unbound)
611 (loop :for field :in fields
613 (if (slot-boundp object field)
614 (push field bound)
615 (push field unbound)))
616 (let* ((fields (remove-if (lambda (x) (member x ghost-fields))
617 bound))
618 (query (sql-compile
619 `(:insert-into ,table-name
620 :set ,@(loop for field in fields
621 collect (field-sql-name field)
622 collect
623 (if (and (slot-boundp object field)
624 (find-export-function object field))
625 (funcall (find-export-function object field)
626 (slot-value object field))
627 (slot-value object field)))
628 ,@(when unbound (cons :returning
629 (mapcar #'field-sql-name
630 unbound))))))
631 (returned
632 (query query :row)))
633 (when unbound
634 (loop :for value :in returned
635 :for field :in unbound
636 :do (setf (slot-value object field) value)))))
637 object)
639 (let* ((defaulted-slots (remove-if-not
640 (lambda (x)
641 (slot-boundp x 'col-default))
642 (dao-column-slots ,class)))
643 (defaulted-names (mapcar 'slot-definition-name defaulted-slots))
644 (default-values (mapcar 'column-default defaulted-slots)))
645 (if defaulted-slots
646 (defmethod fetch-defaults ((object ,class))
647 (let (names defaults)
648 ;; Gather unbound slots and their default expressions.
649 (loop :for slot-name :in defaulted-names
650 :for default :in default-values
651 :do (unless (slot-boundp object slot-name)
652 (push slot-name names)
653 (push default defaults)))
654 ;; If there are any unbound, defaulted slots, fetch their content.
655 (when names
656 (loop :for value :in (query
657 (sql-compile (cons :select defaults))
658 :list)
659 :for slot-name :in names
660 :do (setf (slot-value object slot-name) value))))
661 object)
662 (defmethod fetch-defaults ((object ,class))
663 nil)))
665 (defmethod shared-initialize :after ((object ,class) slot-names
666 &key (fetch-defaults nil)
667 &allow-other-keys)
668 (declare (ignore slot-names))
669 (when fetch-defaults
670 (fetch-defaults object)))))))
672 (defmacro with-column-writers ((&rest defs) &body body)
673 "Provides control over the way get-dao, select-dao, and query-dao read values
674 from the database. This is not commonly needed, but can be used to reduce the
675 amount of queries a system makes. writers should be a list of alternating
676 column names (strings or symbols) and writers, where writers are either
677 symbols referring to a slot in the objects, or functions taking two arguments ―
678 an instance and a value ― which can be used to somehow store the value in the
679 new instance. When any DAO-fetching function is called in the body, and
680 columns matching the given names are encountered in the result, the writers
681 are used instead of the default behaviour (try and store the value in the slot
682 that matches the column name).
684 An example of using this is to add some non-column slots to a DAO class, and
685 use query-dao within a with-column-writers form to pull in extra information
686 about the objects, and immediately store it in the new instances."
687 `(let ((*custom-column-writers*
688 (append (list ,@(loop :for (field writer) :on defs :by #'cddr
689 :collect `(cons (to-sql-name ,field nil) ,writer)))
690 *custom-column-writers*)))
691 ,@body))
693 (defun dao-from-fields (class column-map query-fields
694 result-next-field-generator-fn)
695 (let ((instance (allocate-instance class)))
696 (loop :for field :across query-fields
697 :for writer := (cdr (assoc (field-name field)
698 column-map
699 :test #'string=))
701 (etypecase writer
702 (null (if *ignore-unknown-columns*
703 (funcall result-next-field-generator-fn field)
704 (error "No slot named ~a in class ~a. DAO out of sync with table, or incorrect query used."
705 (field-name field) (class-name class))))
706 (symbol (setf (slot-value instance writer)
707 (funcall result-next-field-generator-fn field)))
708 (function (let ((import-function-symbol
709 (find-import-function instance (field-name field))))
710 (cond ((and import-function-symbol
711 (eq writer
712 (fdefinition import-function-symbol)))
713 (setf (slot-value instance (field-name-to-slot-name
714 class (field-name field)))
715 (funcall import-function-symbol
716 (funcall result-next-field-generator-fn field))))
717 ((and import-function-symbol
718 (not (functionp import-function-symbol)))
719 (setf (slot-value instance (field-name-to-slot-name
720 class (field-name field)))
721 (funcall (fdefinition import-function-symbol)
722 (funcall result-next-field-generator-fn field))))
723 ((and import-function-symbol
724 (functionp import-function-symbol))
725 (setf (slot-value instance (field-name-to-slot-name
726 class (field-name field)))
727 (funcall import-function-symbol
728 (funcall result-next-field-generator-fn field))))
730 (funcall writer instance
731 (funcall result-next-field-generator-fn field))))))))
732 (initialize-instance instance)
733 instance))
735 (defun dao-row-reader (class)
736 "Defines a row-reader for objects of a given class. Note that query fields are the
737 Postgresql column names, not the dao slot names."
738 (row-reader (query-fields)
739 (let ((column-map (append *custom-column-writers*
740 (collect-import-functions class)
741 (dao-column-map class))))
742 (loop :while (next-row)
743 :collect (dao-from-fields class column-map query-fields #'next-field)))))
746 (defun save-dao (dao)
747 "Tries to insert the given dao using insert-dao. If the dao has unbound slots,
748 those slots will be updated and bound by default data triggered by the
749 database. If this raises a unique key violation error, it tries to update it by
750 using update-dao instead. In this case, if the dao has unbound slots, updating
751 will fail with an unbound slots error.
753 Be aware that there is a possible race condition here ― if some other process
754 deletes the row at just the right moment, the update fails as well. Returns a
755 boolean telling you whether a new row was inserted.
757 This function is unsafe to use inside of a transaction ― when a row with the
758 given keys already exists, the transaction will be aborted. Use
759 save-dao/transaction instead in such a situation.
761 See also: upsert-dao."
762 (handler-case
763 (progn (insert-dao dao) t)
764 (cl-postgres-error:unique-violation ()
765 (update-dao dao)
766 nil)
767 (cl-postgres-error:columns-error ()
768 (update-dao dao)
769 nil)))
771 (defun save-dao/transaction (dao)
772 "The transaction safe version of save-dao. Tries to insert the given dao using
773 insert-dao. If this raises a unique key violation error, it tries to update it
774 by using update-dao instead. If the dao has unbound slots, updating will fail
775 with an unbound slots error. If the dao has unbound slots, those slots will be
776 updated and bound by default data triggered by the database.
778 Acts exactly like save-dao, except that it protects its attempt to insert the
779 object with a rollback point, so that a failure will not abort the transaction.
781 See also: upsert-dao."
782 (handler-case
783 (with-savepoint save-dao/transaction (insert-dao dao) t)
784 (cl-postgres-error:unique-violation ()
785 (update-dao dao)
786 nil)
787 (cl-postgres-error:columns-error ()
788 (update-dao dao)
789 nil)))
791 (defun query-dao% (type query row-reader &rest args)
792 (let ((class (find-class type)))
793 (unless (class-finalized-p class)
794 #+postmodern-thread-safe
795 (unless (class-finalized-p class)
796 (bordeaux-threads:with-lock-held (*class-finalize-lock*)
797 (unless (class-finalized-p class)
798 (finalize-inheritance class))))
799 #-postmodern-thread-safe
800 (finalize-inheritance class))
801 (if args
802 (progn
803 (prepare-query *database* "" query)
804 (exec-prepared *database* "" args row-reader))
805 (exec-query *database* query row-reader))))
807 (defmacro query-dao (type query &rest args)
808 "Execute the given query (which can be either a string or an S-SQL expression)
809 and return the result as DAOs of the given type. If the query contains
810 placeholders ($1, $2, etc) their values can be given as extra arguments. The
811 names of the fields returned by the query must either match slots in the DAO
812 class, or be bound through with-column-writers."
813 `(query-dao% ,type ,(real-query query) (dao-row-reader (find-class ,type)) ,@args))
815 (defmacro dao-row-reader-with-body ((type type-var) &body body)
816 (let ((fields (gensym))
817 (column-map (gensym)))
818 `(row-reader (,fields)
819 (let ((,column-map (append *custom-column-writers*
820 (collect-import-functions (find-class ,type))
821 (dao-column-map (find-class ,type)))))
822 (loop :while (next-row)
823 :do (let ((,type-var
824 (dao-from-fields (find-class ,type)
825 ,column-map
826 ,fields
827 #'next-field)))
828 ,@body))))))
830 (defmacro do-query-dao (((type type-var) query) &body body)
831 "Like query-dao, but iterates over the results rather than returning them.
832 For each matching DAO, body is evaluated with type-var bound to the instance.
834 Example:
836 (do-query-dao (('user user)
837 (:order-by
838 (:select '* :from 'user :where (:> 'score 10000))
839 'name))
840 (pushnew user high-scorers))"
842 (let (args)
843 (when (and (consp query) (not (keywordp (first query))))
844 (setf args (cdr query) query (car query)))
845 `(query-dao% ,type ,(real-query query)
846 (dao-row-reader-with-body (,type ,type-var)
847 ,@body)
848 ,@args)))
850 (defun generate-dao-query (type &optional (test t) ordering)
851 (flet ((check-string (x)
852 (if (stringp x) `(:raw ,x) x)))
853 (let ((query `(:select '* :from (dao-table-name (find-class ,type))
854 :where ,(check-string test))))
855 (when ordering
856 (setf query `(:order-by ,query ,@(mapcar #'check-string ordering))))
857 query)))
859 (defmacro select-dao (type &optional (test t) &rest ordering)
860 "Select DAO objects for the rows in the associated table for which the given
861 test (either an S-SQL expression or a string) holds. When sorting arguments
862 are given, which can also be S-SQL forms or strings, these are used to sort
863 the result.
865 (Note that, if you want to sort, you have to pass the test argument.)
867 (select-dao 'user (:> 'score 10000) 'name)"
869 `(query-dao% ,type (sql ,(generate-dao-query type test ordering))
870 (dao-row-reader (find-class ,type))))
872 (defmacro do-select-dao (((type type-var) &optional (test t)
873 &rest ordering)
874 &body body)
875 "Like select-dao, but iterates over the results rather than returning them.
876 For each matching DAO, body is evaluated with type-var bound to the DAO
877 instance.
879 Example:
881 (do-select-dao (('user user) (:> 'score 10000) 'name)
882 (pushnew user high-scorers))"
883 `(query-dao% ,type (sql ,(generate-dao-query type test ordering))
884 (dao-row-reader-with-body (,type ,type-var)
885 ,@body)))
887 (defun list-to-column (col-type)
888 "If a col-type is a list, alist or plist, will set the Postgresql column to text."
889 (if (member col-type '(list alist plist))
890 'text
891 col-type))
893 (defun dao-table-definition (table)
894 "Given a DAO class, or the name of one, this will produce an SQL query
895 string with a definition of the table. This is just the bare simple definition,
896 so if you need any extra indices or or constraints, you'll have to write your
897 own queries to add them, in which case look to s-sql's create-table function."
898 (unless (typep table 'dao-class)
899 (setf table (find-class table)))
900 (unless (class-finalized-p table)
901 #+postmodern-thread-safe
902 (unless (class-finalized-p table)
903 (bordeaux-threads:with-lock-held (*class-finalize-lock*)
904 (unless (class-finalized-p table)
905 (finalize-inheritance table))))
906 #-postmodern-thread-safe
907 (finalize-inheritance table))
908 (sql-compile
909 `(:create-table ,(dao-table-name table)
910 ,(loop :for slot :in (dao-column-slots table)
911 :unless (ghost slot)
912 :collect `(,(slot-definition-name slot)
913 :type ,(list-to-column (column-type slot))
914 ,@(cond ((slot-boundp slot 'col-identity)
915 `(:primary-key "generated always as identity"))
916 ((slot-boundp slot 'col-primary-key)
917 `(:primary-key t)))
918 ,@(when (slot-boundp slot 'col-unique)
919 `(:unique t))
920 ,@(when (slot-boundp slot 'col-collate)
921 `(:collate ,(column-collate slot)))
922 ,@(when (slot-boundp slot 'col-interval)
923 `(:interval ,(column-interval slot)))
924 ,@(when (slot-boundp slot 'col-check)
925 `(:check ,(column-check slot)))
926 ,@(when (slot-boundp slot 'col-references)
927 `(:references ,(column-references slot)))
928 ,@(when (slot-boundp slot 'col-default)
929 `(:default ,(column-default slot)))))
930 ,@(when (and (dao-keys table) (not (find-primary-key-column table)))
931 `((:primary-key ,@(dao-keys table)))))))