Coverage report: /home/ati/workspace/perec/query/compiler.lisp
| Kind | Covered | All | % |
| expression | 1073 | 1301 | 82.5 |
| branch | 70 | 100 | 70.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;; -*- mode: Lisp; Syntax: Common-Lisp; -*-
3
;;; Copyright (c) 2006 by the authors.
5
;;; See LICENCE for details.
11
(enable-pattern-reader #\M)
13
;;;; TODO: sorting and grouping
14
;;;; TODO: embedded SQL in queries
15
;;;; TODO: return nil if there is contradiction between asserts
16
;;;; TODO: eliminate tautologies from asserts
17
;;;; TODO: n-m associations
18
;;;; TODO: delete and update operations
19
;;;; TODO: recursive selects
24
(defvar *compile-query-counter* 0
25
"Number of calls to COMPILE-QUERY. (FOR TESTING)")
27
(defvar *test-query-compiler* nil
28
"When true, the compiled form performs a runtime check by comparing the result of the query
29
with the result of the naively compiled query.")
31
(defun reset-compile-query-counter ()
32
(setf *compile-query-counter* 0))
34
(defmethod compile-query :before (query)
35
(incf *compile-query-counter*))
37
(defmethod compile-query ((query query))
38
(%compile-query (make-instance 'trivial-query-compiler) query))
40
(defmethod compile-query ((query simple-query))
41
(if *test-query-compiler*
42
(%compile-query (make-instance 'debug-query-compiler) query)
43
(%compile-query (make-instance 'simple-query-compiler) query)))
45
(defclass* query-compiler ()
47
(:documentation "Generic query compiler, which can transform to sql to any select form."))
49
(defgeneric %compile-query (compiler query)
50
(:documentation "Compiles the query with the specified compiler."))
52
(defmethod %compile-query ((compiler query-compiler) (query query))
62
(defgeneric transform-query (compiler query)
63
(:documentation "TODO")
65
(:method (compiler query)
68
(defgeneric emit-query (compiler query)
69
(:documentation "TODO"))
71
(defgeneric optimize-query (compiler syntax)
72
(:documentation "TODO")
74
(:method (compiler syntax)
78
;;;; Trivial query compiler
80
(defclass* trivial-query-compiler (query-compiler)
82
(:documentation "Query compiler that can compile any select form, but does not optimize sql queries."))
84
(defmethod emit-query ((compiler trivial-query-compiler) query)
85
(bind ((lexical-variables (lexical-variables-of query))
86
(variables (get-query-variable-names query))
87
(body (body-of query))
88
(body (mapcar 'query-macroexpand body))
89
(persistent-object-literals (collect-persistent-object-literals body))
90
(persistent-object-variables (mapcar #L(gensym (symbol-name (class-name (class-of !1))))
91
persistent-object-literals)))
92
(with-unique-names (objects result-list)
93
`(lambda ,lexical-variables
94
(declare (ignorable ,@lexical-variables))
95
,@(mapcar #L(`(load-instance ,!1)) persistent-object-literals)
96
(let (,@(mapcar #L(`(,!1 (load-instance ,!2))) persistent-object-variables persistent-object-literals)
97
(,objects (mapcar 'cache-object
98
(execute ,(sql-select-oids-for-class 'persistent-object))))
100
(flet ((assert! (cond) (when (not cond) (throw 'fail nil)))
101
(collect (&rest exprs) (push exprs ,result-list))
102
(purge (&rest objects) (mapc 'make-transient objects))
103
(order-by (&rest order-spec) nil)) ; TODO
104
(declare (ignorable (function assert!) (function collect) (function purge) (function order-by)))
105
(bind-cartesian-product ((,@variables) ,objects)
109
(cons '(assert . assert!)
110
(mapcar 'cons persistent-object-literals persistent-object-variables))
112
,(add-conversion-to-result-type
116
`(make-list-result-set (nreverse ,result-list)))))))))
119
;;;; Debug query compiler
121
(defclass* debug-query-compiler (query-compiler)
123
(:documentation "Generic query compiler, which can transform to sql to any select form and
124
wraps the compiled code with a runtime check of the result."))
126
(defmethod %compile-query ((compiler debug-query-compiler) (query query))
127
"Emits code that checks that the result of COMPILED-FORM equals
128
to the result of the PREDICATE-FORM."
129
(let* ((predicate-form (%compile-query (make-instance 'trivial-query-compiler) query))
130
(compiled-form (%compile-query (make-instance 'simple-query-compiler) query))
131
(lexical-variables (lexical-variables-of query)))
132
(with-unique-names (result expected result-list expected-list)
133
(unparse-query-syntax
134
`(lambda ,lexical-variables
135
(declare (ignorable ,@lexical-variables))
136
(bind ((,result (funcall ,compiled-form ,@lexical-variables))
137
(,expected (funcall ,predicate-form ,@lexical-variables))
138
(,result-list (to-list ,result))
139
(,expected-list (to-list ,expected)))
140
;; TODO: set-exclusive-or is not ok for comparing the results, because
141
;; the result is not a set and (set-exclusive-or '(a b b) '(a a b))
143
(when (set-exclusive-or ,result-list ,expected-list :test 'equal)
144
(cerror "Return the expected result." 'query-error
145
:query ,query :result ,result-list :expected ,expected-list))
148
(define-condition query-error ()
149
((query :initarg :query)
150
(result :initarg :result)
151
(expected :initarg :expected))
153
(:report (lambda (condition stream)
154
(format stream "Query ~S failed. Result is ~:W, but expected ~:W."
155
(slot-value condition 'query)
156
(slot-value condition 'result)
157
(slot-value condition 'expected))))
159
(:documentation "Condition signalling that the runtime check of the query failed."))
161
;;;;---------------------------------------------------------------------------
162
;;;; Simple query compiler
164
(defclass* simple-query-compiler (query-compiler)
166
(:documentation "Query compiler that can transform to sql to simple select forms."))
168
(defmethod emit-query ((compiler simple-query-compiler) (query simple-query))
169
(ecase (action-of query)
170
(:collect (emit-select query))
171
(:purge (emit-purge query))))
173
(defun emit-purge (query)
174
(bind ((lexical-variables (lexical-variables-of query))
175
(asserts (asserts-of query))
176
(variables (query-variables-of query))
177
(purge-var (first (action-args-of query)))
178
(type (xtype-of purge-var))
179
(substitutions (generate-persistent-object-substitutions query)))
181
;; execute deletes from lisp filter
182
(with-unique-names (row)
183
`(lambda ,lexical-variables
184
(declare (ignorable ,@lexical-variables))
185
(let (,@(emit-persistent-object-bindings substitutions))
187
`(execute ,(sql-select-for-query query)
190
(let ,(emit-query-variable-bindings variables row #f)
191
,(emit-ignorable-variables-declaration variables)
192
(when (and ,@asserts)
193
(make-transient ,(first (action-args-of query)))))))
195
;; execute deletes from sql
196
(bind (((values deletes cleanup) (sql-deletes-for-query query)))
197
`(lambda ,lexical-variables
198
(declare (ignorable ,@lexical-variables))
199
(let (,@(emit-persistent-object-bindings substitutions))
200
(invalidate-persistent-flag-of-cached-objects (find-persistent-class* ,type))
203
`(unwind-protect (mapc 'execute ,deletes) (execute ,cleanup))
204
`(mapc 'execute ,deletes))
207
(defun emit-select (query)
208
"Emits code that for the compiled query."
209
(bind ((lexical-variables (lexical-variables-of query)))
210
(if (contradictory-p query)
211
`(lambda ,lexical-variables
212
(declare (ignorable ,@lexical-variables))
213
,(empty-result query))
214
(bind ((substitutions (generate-persistent-object-substitutions query)))
215
`(lambda ,lexical-variables
216
(declare (ignorable ,@lexical-variables))
217
(let (,@(emit-persistent-object-bindings substitutions))
219
(add-conversion-to-result-type
223
(add-mapping-for-collects
225
(add-sorter-for-order-by
227
(add-filter-for-asserts
230
',(result-type-of query)
231
,(partial-eval (sql-select-for-query query) query)))))))
234
(defun empty-result (query)
235
(ecase (result-type-of query)
237
(scroll '(make-instance 'simple-scroll))))
239
(defun add-filter-for-asserts (query form)
240
(bind ((variables (query-variables-of query))
241
(asserts (asserts-of query))
242
(prefetchp (prefetchp query)))
243
(with-unique-names (row)
245
`(make-filtered-result-set
248
(let (,@(emit-query-variable-bindings variables row prefetchp))
249
,(emit-ignorable-variables-declaration variables)
253
(defun add-sorter-for-order-by (query form)
254
(bind ((variables (query-variables-of query))
255
(order-by (order-by-of query))
256
(prefetchp (prefetchp query)))
257
(labels ((rename-query-variables (expr suffix)
258
"Adds the SUFFIX to each query variable symbol in EXPR."
259
(sublis (mapcar #L(cons (name-of !1) (concatenate-symbol (name-of !1) suffix))
262
(generate-variable-bindings (row suffix)
263
(rename-query-variables (emit-query-variable-bindings variables row prefetchp)
265
(generate-lessp-body (order-by)
266
"Builds the body of the lessp predicate."
267
(bind ((lessp (ecase (first order-by) (:asc 'lessp) (:desc 'greaterp)))
268
(expr1 (rename-query-variables (second order-by) "1"))
269
(expr2 (rename-query-variables (second order-by) "2")))
270
(if (null (cddr order-by))
271
`(,lessp ,expr1 ,expr2)
272
(with-unique-names (obj1 obj2)
273
`(let ((,obj1 ,expr1)
279
,(generate-lessp-body (cddr order-by))))))))))
280
(if (and order-by (or (member :asc order-by) (member :desc order-by)))
281
(with-unique-names (row1 row2)
282
`(make-ordered-result-set
284
(lambda (,row1 ,row2)
285
(let (,@(generate-variable-bindings row1 "1")
286
,@(generate-variable-bindings row2 "2"))
287
,(generate-lessp-body order-by)))))
290
(defun add-mapping-for-collects (query form)
291
(bind ((variables (query-variables-of query))
292
(collects (collects-of query))
293
(prefetchp (prefetchp query)))
294
(with-unique-names (row)
295
`(make-mapped-result-set
298
(let (,@(emit-query-variable-bindings variables row prefetchp))
299
,(emit-ignorable-variables-declaration variables)
300
(list ,@collects)))))))
302
(defun add-unique-filter (query form)
304
`(make-unique-result-set ,form)
307
(defun add-conversion-to-result-type (query form)
308
(ecase (result-type-of query)
309
(list `(to-list ,form ,(flatp query)))
310
(scroll `(to-scroll ,form))))
312
(defun emit-query-variable-bindings (variables row prefetchp)
313
(iter (for variable in variables)
314
(for slots = (when prefetchp (prefetched-slots-for variable)))
315
(for column-count = (reduce '+ slots
316
:key 'column-count-of
317
:initial-value (length +oid-column-names+)))
318
(for i initially 0 then (+ i column-count))
319
(collect `(,(name-of variable) (cache-object-with-prefetched-slots ,row ,i ',slots)))))
321
(defun emit-ignorable-variables-declaration (variables)
322
`(declare (ignorable ,@(mapcar 'name-of variables))))
324
(defun generate-persistent-object-substitutions (query)
325
(bind ((objects (collect-persistent-object-literals query))
326
(variables (mapcar #L(gensym (symbol-name (class-name (class-of !1)))) objects)))
327
(mapcar 'cons objects variables)))
329
(defun emit-persistent-object-bindings (substitutions)
330
(mapcar #L(`(,(cdr !1) (load-instance ,(car !1)))) substitutions))
332
;;;;---------------------------------------------------------------------------
335
(defmethod transform-query ((compiler simple-query-compiler) (query simple-query))
336
"Transforms the QUERY by pushing down the asserts to the SQL query."
337
(macroexpand-query query)
339
(normalize-query query)
341
(introduce-joined-variables query)
342
(partial-eval-asserts query)
343
(when (not (contradictory-p query))
344
(let ((*suppress-alias-names* (simple-purge-p query)))
348
(defun macroexpand-query (query)
349
"Expands query macros in QUERY."
350
(setf (asserts-of query)
351
(mapcar 'query-macroexpand (asserts-of query))))
353
(defun parse-query (query)
354
(bind ((variables (get-variables query)))
355
(setf (asserts-of query) (mapcar #L(parse-query-form !1 variables) (asserts-of query)))
356
(setf (action-args-of query) (mapcar #L(parse-query-form !1 variables) (action-args-of query)))
357
(setf (order-by-of query) (iter (for (dir expr) on (order-by-of query) by 'cddr)
358
(nconcing (list dir (parse-query-form expr variables)))))))
360
(defun normalize-query (query)
361
(setf (asserts-of query)
362
(mappend #L(conjuncts-of
363
(simplify-boolean-syntax
365
(partial-eval !1 query))))
366
(asserts-of query))))
368
(defun conjuncts-of (syntax)
369
"Return a list of the conjuncts in SYNTAX."
371
(#M(macro-call :macro and) (args-of syntax))
372
(#M(literal-value :value #t) nil)
373
(?otherwise (list syntax))))
375
(defgeneric normalize-syntax (syntax)
376
(:documentation "Normalizes type asserts to (typep ...) forms to ease further processing:
377
(typep <object> '<class-name>) -> (typep <object> <class>)
378
(subtypep (class-of <obj>) '<class-name>) -> (typep <object> <class>)
379
(subtypep (class-of <obj>) <type>) -> (typep <object> <type>)
382
(eq (<primary-assoc-end-accessor> <obj1>) <obj2>) ->
383
(eq (secondary-assoc-end-accessor <obj2>) <obj1>)")
387
(:method ((form compound-form))
388
(setf (operands-of form)
389
(mapcar 'normalize-syntax (operands-of form)))
391
(:method ((call function-call))
394
(#M(function-call :fn typep
395
:args (?obj #M(literal-value :value (?is ?class persistent-class-p))))
397
(#M(function-call :fn typep
398
:args (?obj #M(literal-value :value (?is ?name persistent-class-name-p))))
399
(setf (second (args-of call)) (make-literal-value :value (find-class ?name)))
401
(#M(function-call :fn subtypep
402
:args (#M(function-call :fn class-of :args (?object))
403
#M(literal-value :value (?is ?name persistent-class-name-p))))
404
(make-function-call :fn 'typep
406
(make-literal-value :value (find-class ?name)))))
407
(#M(function-call :fn subtypep
408
:args (#M(function-call :fn class-of :args (?object)) ?type))
409
(make-function-call :fn 'typep :args (list ?object ?type)))
410
;; TODO reverse 1-1 association-end
414
(defun introduce-joined-variables (query)
415
(mapc #L(introduce-joined-variables-for !1 query) (asserts-of query))
416
(mapc #L(introduce-joined-variables-for !1 query) (action-args-of query))
417
(mapc #L(when (syntax-object-p !1) (introduce-joined-variables-for !1 query)) (order-by-of query)))
419
(defgeneric introduce-joined-variables-for (syntax query)
420
(:documentation "Substitutes the arguments of slot accessor forms with joined variables.")
422
(:method (syntax query)
424
;; recurse on compound forms
425
(:method ((syntax compound-form) query)
426
(mapc #L(introduce-joined-variables-for !1 query) (operands-of syntax)))
427
;; slot access -> ensure that arg is a query variable with the correct type
428
(:method ((access slot-access) query)
430
(when (association-end-access-p (arg-of access))
431
(setf (arg-of access)
432
(joined-variable-for-association-end-access query (arg-of access))))
433
(when (slot-of access)
434
(setf (arg-of access)
435
(ensure-type query (arg-of access) (slot-definition-class (slot-of access)))))
438
(defun partial-eval-asserts (query)
439
(setf (asserts-of query)
440
(mapcar #L(partial-eval !1 query) (asserts-of query))))
442
(defun contradictory-p (query)
444
(simplify-boolean-syntax
445
(make-macro-call :macro 'and :args (asserts-of query)))))
447
(defun build-sql (query)
448
"Converts assert conditions and order by specifications to SQL."
449
(iter (for variable in (query-variables-of query))
450
(when (joined-variable-p variable)
451
(add-where-clause query (sql-join-condition-for-joined-variable variable))))
452
(setf (asserts-of query)
453
(iter (for condition in (asserts-of query))
454
(bind (((values sql success) (transform-to-sql condition)))
456
(add-where-clause query sql)
457
(collect condition)))))
458
(bind ((new-order-by (iter (for (dir expr) on (order-by-of query) by 'cddr)
459
(bind (((values sort-key success) (transform-to-sql expr))
460
(ordering (ecase dir (:asc :ascending) (:desc :descending))))