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))))
462
(collect `(sql-sort-spec :sort-key ,sort-key :ordering ,ordering))
465
(setf (order-by-of query) new-order-by))))
467
;;;----------------------------------------------------------------------------
470
(defmethod optimize-query ((compiler simple-query-compiler) syntax)
471
"Optimize the compiled form."
472
;(simplify-class-references syntax)
473
;(partial-eval syntax)
476
(defun simplify-class-references (syntax)
478
(#M(function-call :fn find-class
479
:args (#M(function-call :fn name-of
480
:args ((? and ?inner #M(function-call :fn class-of
481
:args (?object)))))))
482
(simplify-class-references ?inner))
484
(setf (operands-of syntax)
485
(mapcar 'simplify-class-references (operands-of syntax)))
491
;;;----------------------------------------------------------------------------
494
(defun transform-to-sql (condition)
495
"Transforms the CONDITION of an assert to an SQL expression."
498
(catch 'sql-map-failed
499
(setf sql (syntax-to-sql condition))
501
(values sql success)))
503
(defun sql-map-failed ()
504
(throw 'sql-map-failed nil))
507
(defgeneric syntax-to-sql (syntax)
508
(:documentation "Maps a lisp form to SQL.")
511
(if (free-of-query-variables-p syntax)
515
(:method ((literal literal-value))
516
(literal-to-sql (value-of literal) (xtype-of literal) literal))
518
(:method ((variable lexical-variable))
519
`(value->sql-literal ,(name-of variable) ,(backquote-type-syntax (xtype-of variable))))
521
(:method ((variable query-variable))
522
(sql-id-column-reference-for variable))
524
(:method ((access slot-access))
525
(slot-access-to-sql (accessor-of access) (arg-of access) access))
527
(:method ((call function-call))
528
(bind ((fn (fn-of call))
529
(args (args-of call)))
530
(function-call-to-sql fn (length args) (first args) (second args) call)))
532
(:method ((call macro-call))
533
(bind ((macro (macro-of call))
534
(args (args-of call)))
535
(macro-call-to-sql macro (length args) (first args) (second args) call))))
537
(defgeneric literal-to-sql (value type literal)
538
(:documentation "Maps a literal value to SQL.")
540
(:method (value type literal)
542
((keywordp value) value)
543
((syntax-object-p type) `(value->sql-literal ,literal ,(backquote-type-syntax type)))
544
(t (value->sql-literal value type)))))
546
(defgeneric slot-access-to-sql (accessor arg access)
547
(:method (accessor arg access)
548
(if (free-of-query-variables-p access)
549
`(value->sql-literal ,access ,(backquote-type-syntax (xtype-of access)))
552
(:method (accessor (variable query-variable) (access slot-access))
554
(bind ((slot (slot-of access)))
555
(if (and slot (persistent-slot-p slot))
556
(sql-column-reference-for slot variable)
559
(:method (accessor (variable query-variable) (access association-end-access))
560
;; association-end accessor
561
(if (association-end-of access)
562
(bind ((association-end (association-end-of access))
563
(association (association-of association-end)))
564
(ecase (association-kind-of association)
566
(if (primary-association-end-p association-end)
567
(sql-column-reference-for association-end variable)
568
(sql-subselect-for-secondary-association-end association-end variable)))
570
(if (to-one-association-end-p association-end)
571
(sql-column-reference-for association-end variable)
572
(sql-subselect-for-secondary-association-end association-end variable)))
574
(sql-subselect-for-m-n-association association-end variable))))
578
(defgeneric function-call-to-sql (fn n-args arg1 arg2 call)
580
(:method (fn n-args arg1 arg2 call)
582
;; (<aggregate-fn> (<n-ary-association-end-accessor> <query-var>))
583
;; e.g. (length (messages-of topic)) -->
584
;; (select count(_m.id) from _message _m where _m.topic_id = _topic.id)
585
((and (sql-aggregate-function-name-p fn) (= n-args 1)
586
(association-end-access-p arg1) (association-end-of arg1)
587
(query-variable-p (arg-of arg1)))
588
(ecase (association-kind-of (association-of (association-end-of arg1)))
592
(sql-aggregate-subselect-for-variable
593
(sql-aggregate-function-for fn)
594
(association-end-of arg1)
597
(sql-aggregate-subselect-for-m-n-association-end
598
(sql-aggregate-function-for fn)
599
(association-end-of arg1)
601
;; eq,eql and friends: compare with NULL can be true
602
((member fn '(eq eql equal))
606
:check-nils (and (maybe-null-subtype-p (xtype-of arg1))
607
(maybe-null-subtype-p (xtype-of arg2)))))
612
:check-nils (and (maybe-null-subtype-p (xtype-of arg1))
613
(maybe-null-subtype-p (xtype-of arg2)))))
614
;; (<fn> <arg> ...), where <fn> has SQL counterpart
615
;; e.g. (+ 1 2) --> (1 + 2)
617
`(funcall ',(sql-operator-for fn) ,@(mapcar 'syntax-to-sql (args-of call))))
618
;; When the function call does not depend on query variables
619
;; evaluate it at runtime and insert its value into the SQL query.
620
;; The persistent-objects in the value are converted to object ids.
621
((every 'free-of-query-variables-p (args-of call))
622
`(value->sql-literal ,call ,(backquote-type-syntax (xtype-of call))))
623
;; Otherwise the assert containing the function call cannot be mapped to SQL.
627
;; member form -> in (ignore keyword args, TODO)
628
(:method ((fn (eql 'member)) n-args arg1 arg2 call)
630
((literal-value-p arg2)
631
(if (null (value-of arg2))
633
`(sql-in ,(syntax-to-sql arg1) ,(syntax-to-sql arg2))))
634
((free-of-query-variables-p arg2)
635
`(if (null ,(unparse-query-syntax arg2))
637
,(unparse-query-syntax `(sql-in ,(syntax-to-sql arg1) ,(syntax-to-sql arg2)))))
638
(t `(sql-in ,(syntax-to-sql arg1) ,(syntax-to-sql arg2)))))
640
;; (member <object> (<association-end-accessor> <query-variable>))
641
;; e.g. (member m1 (messages-of topic)) --> (_m1.topic_id = _topic.id)
642
(:method ((fn (eql 'member)) (n-args (eql 2)) (object query-variable) (access association-end-access) call)
643
;; member form -> join
645
;; (member m (messages-of t)) -> m.topic_id = t.id
646
(if (or (not (query-variable-p (arg-of access)))
647
(not (association-end-of access)))
649
(bind ((association-end (association-end-of access))
650
(variable (arg-of access))
651
(association (association-of association-end)))
652
(ecase (association-kind-of association)
656
(sql-join-condition-for object variable association-end))
658
(sql-join-condition-for-m-n-association object variable association-end))))))
662
;; (eq (topic-of message) topic) -> message.topic_id = topic.id
663
;; (eq (wife-of man) woman) -> man.wife_id = woman.id
664
;; (eq (husband-of woman) man) -> man.wife_id = woman.id
665
(:method ((fn (eql 'eq)) (n-args (eql 2)) (access association-end-access) object call)
667
(if (not (association-end-of access))
669
(bind ((association-end (association-end-of access))
670
(other-end (other-association-end-of association-end))
671
(variable (arg-of access))
672
(association (association-of association-end)))
673
(ecase (association-kind-of association)
675
(if (primary-association-end-p association-end)
678
(make-function-call ;; reverse
680
:args (list (make-association-end-access :association-end other-end
681
:accessor (first (slot-definition-readers (first (direct-slots-of other-end))))
687
(sql-map-failed))))))
689
(:method ((fn (eql 'eq)) (n-args (eql 2)) object (access association-end-access) call)
690
(function-call-to-sql fn 2 access object call))
694
;; (typep o #<class user>) -> exists(select 1 from user u where u.id = o.id)
695
(:method ((fn (eql 'typep)) (n-args (eql 2)) (variable query-variable) (type literal-value) call)
696
(if (persistent-class-p (value-of type))
697
(sql-exists-subselect-for-variable variable (value-of type))
701
(defgeneric macro-call-to-sql (macro n-args arg1 arg2 call)
702
(:method (macro n-args arg1 arg2 call)
704
((sql-operator-p macro)
705
`(funcall ',(sql-operator-for macro) ,@(mapcar 'syntax-to-sql (args-of call))))
706
((every 'free-of-query-variables-p (args-of call))
707
`(value->sql-literal ,call ,(backquote-type-syntax (xtype-of call))))
711
(defun free-of-query-variables-p (syntax)
714
(unparsed-form (free-of-query-variables-p (form-of syntax)))
715
(compound-form (every 'free-of-query-variables-p (operands-of syntax)))
716
(cons (and (free-of-query-variables-p (car syntax))
717
(free-of-query-variables-p (cdr syntax))))
720
;;;----------------------------------------------------------------------------
724
(defun joined-variable-for-association-end-access (query access)
725
(ensure-joined-variable
728
(association-end-of access)
729
(normalized-type-for* (xtype-of access))))
731
(defun ensure-joined-variable (query object association-end type)
732
(or (and (query-variable-p object) (eq (xtype-of object) type) object)
733
(find-joined-variable-by-definition query object association-end type)
734
(make-new-joined-variable query object association-end type)))
736
(defun ensure-type (query object type)
737
(if (eq (xtype-of object) +unknown-type+)
738
(progn (setf (xtype-of object) type) object)
739
(or (and (eq (xtype-of object) type) object)
740
(find-joined-variable-by-definition query object nil type)
741
(make-new-joined-variable query object nil type))))
743
(defun find-joined-variable-by-definition (query object association-end type)
745
#L(and (typep !1 'joined-variable)
746
(eq association-end (association-end-of !1))
747
(equal object (object-of !1))
748
(eq (xtype-of !1) type))
749
(query-variables-of query)))
751
(defun make-new-joined-variable (query object association-end type)
752
"Creates a new joined variable."
753
(bind ((name (generate-joined-variable-name type))
754
(variable (make-joined-variable :name name :object object
755
:association-end association-end :xtype type)))
756
(add-joined-variable query variable)
759
(defun generate-joined-variable-name (type)
760
"Generates a name for a joined variable of type TYPE."
762
(persistent-class (gensym (symbol-name (class-name type))))
763
(symbol (gensym (symbol-name type)))
764
(otherwise (gensym "joined"))))
766
(defgeneric collect-persistent-object-literals (element &optional result)
767
(:method ((query simple-query) &optional result)
768
(collect-persistent-object-literals
770
(collect-persistent-object-literals
771
(action-args-of query)
772
(collect-persistent-object-literals
776
(:method ((element t) &optional result)
779
(:method ((object persistent-object) &optional result)
780
(adjoin object result))
782
(:method ((literal literal-value) &optional result)
783
(collect-persistent-object-literals (value-of literal) result))
785
(:method ((cons cons) &optional result)
786
(collect-persistent-object-literals
788
(collect-persistent-object-literals
792
(:method ((form unparsed-form) &optional result)
793
(collect-persistent-object-literals (form-of form) result))
795
(:method ((form compound-form) &optional result)
796
(collect-persistent-object-literals (operands-of form) result)))