Coverage report: /home/ati/workspace/perec/query/sql.lisp
Kind | Covered | All | % |
expression | 820 | 967 | 84.8 |
branch | 42 | 62 | 67.7 |
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.
13
;;;----------------------------------------------------------------------------
16
(defun sql-select-for-query (query)
17
"Emits code for the QUERY."
18
(ecase (action-of query)
21
:distinct ,(uniquep query)
22
:columns (list ,@(sql-select-list-for query))
23
:tables (list ,@(sql-table-references-for query))
24
:where ,(where-clause-of query)
25
:order-by (list ,@(order-by-of query))))
27
(bind ((variable (first (action-args-of query))))
30
:columns (list ,(sql-id-column-reference-for variable))
31
:tables (list ,@(sql-table-references-for query))
32
:where ,(where-clause-of query))))))
34
(defun sql-select-oids-for-class (class-name)
35
"Generates a select for the oids of instances of the class named CLASS-NAME."
36
(bind ((tables (rest (primary-tables-of (find-class class-name))))) ; TODO: APPEND/UNION
37
(sql-select-oids-from-tables tables 'sql-union)))
39
(defun sql-select-oids-from-tables (tables set-operation)
40
"Generates a select for the union or intersection of oids from TABLES."
41
(declare (type (member sql-union sql-intersect) set-operation))
44
(1 (sql-select-oids-from-table (first tables)))
45
(otherwise (apply set-operation (mapcar 'sql-select-oids-from-table tables)))))
47
(defgeneric sql-select-oids-from-table (table)
48
(:documentation "Generates a select for the oids in TABLE.")
49
(:method ((table table))
50
(sql-select :columns +oid-column-names+ :tables (list (name-of table))))
51
(:method ((table sql-table-alias))
52
(sql-select :columns +oid-column-names+ :tables (list table))))
54
;;;----------------------------------------------------------------------------
56
(defun sql-deletes-for-query (query)
57
"Returns two values, the first is a list of SQL statements that deletes the records, the second is a cleanup form."
58
(bind ((variable (first (action-args-of query)))
59
(type (xtype-of variable))
60
(tables (when (persistent-class-p type) (tables-for-delete type))))
61
(if (simple-purge-p query)
62
(bind ((table (first tables)))
63
`(list ,(sql-delete-from-table table :where (where-clause-of query))))
64
(bind ((temp-table (rdbms-name-for 'deleted-ids))
65
(create-table `(sql-create-table
68
:columns (list (sql-identifier :name ',+id-column-name+))
69
:as (sql-subquery :query ,(sql-select-for-query query))))
70
(delete-where `(sql-subquery
73
:columns (list ',+id-column-name+)
74
:tables (list ',temp-table))))
75
(deletes (mapcar #L(sql-delete-for-subselect !1 delete-where) tables))
76
(drop-table `(sql-drop-table
78
(values `(list ,create-table ,@deletes)
81
(defun simple-purge-p (query)
82
"The purge is simple if it deletes records from one table only and no need to join other tables for the condition."
83
(and (typep query 'simple-query)
84
(eq (action-of query) :purge)
85
(length=1 (query-variables-of query))
86
(persistent-class-p (xtype-of (first (query-variables-of query))))
87
(length=1 (tables-for-delete (xtype-of (first (query-variables-of query)))))))
89
(defun sql-delete-for-subselect (table subselect)
90
"Generate a delete command for records in TABLE whose oid is in the set returned by SUBSELECT."
91
(sql-delete-from-table
94
,(sql-id-column-reference-for table)
97
(defun sql-delete-from-table (table &key where)
98
"Generate a delete command for records in TABLE that satisfies the WHERE clause."
100
:table ,(sql-table-reference-for table nil)
103
(defun tables-for-delete (class)
104
"Returns the tables where instances of CLASS are stored."
105
(bind ((super-classes (persistent-effective-super-classes-of class))
106
(sub-classes (persistent-effective-sub-classes-of class))
107
(class-primary-table (primary-table-of class))
108
(super-primary-tables (mapcar 'primary-table-of super-classes))
109
(super-sub-primary-tables (mappend 'data-tables-of sub-classes)))
113
(list class-primary-table)
115
super-sub-primary-tables)))))
117
;;;----------------------------------------------------------------------------
120
(defvar *suppress-alias-names* nil)
122
(defgeneric sql-alias-for (element)
123
(:documentation "Generates a table alias for the given ELEMENT. Alias names may be supressed
124
by setting *SUPRESS-ALIAS-NAMES* to true.")
125
(:method ((name symbol))
126
(unless *suppress-alias-names*
127
(rdbms-name-for name)))
128
(:method ((variable query-variable))
129
(sql-alias-for (name-of variable)))
130
(:method ((class persistent-class))
131
(sql-alias-for (class-name class)))
132
(:method ((table table))
133
(unless *suppress-alias-names*
136
;;;----------------------------------------------------------------------------
139
(defgeneric sql-select-list-for (element)
140
(:method ((query query))
141
;; select oids and prefetched slots
142
;; TODO: if no prefetch, select the expressions in the collect clause
144
(lambda (variable) (sql-columns-for-variable variable (prefetchp query)))
145
(query-variables-of query))))
147
(defun sql-columns-for-variable (variable prefetchp)
148
(bind ((table-alias (sql-alias-for variable)))
151
(sql-oid-column-references-for variable)
152
(mapcan #L(sql-column-references-for !1 table-alias)
153
(prefetched-slots-for variable)))
154
(sql-oid-column-references-for variable))))
156
(defun prefetched-slots-for (variable)
157
(bind ((type (xtype-of variable)))
158
(when (persistent-class-p type)
159
(collect-if #L(eq (table-of !1) (primary-table-of type))
160
(prefetched-slots-of type)))))
162
;;;----------------------------------------------------------------------------
165
(defgeneric sql-table-references-for (element)
166
(:method ((query query))
167
(bind ((variables (query-variables-of query)))
168
(mapcar 'sql-table-reference-for variables variables))))
170
(defgeneric sql-table-reference-for (element alias)
171
(:method ((table table) (alias symbol))
172
(sql-table-alias :name (name-of table) :alias alias))
174
(:method ((subquery sql-subquery) (alias symbol))
175
(sql-derived-table :subquery subquery :alias alias))
177
(:method ((class persistent-class) (alias symbol))
178
(sql-table-reference-for-type class alias))
180
(:method ((class-name symbol) (alias symbol))
181
(aif (find-class class-name)
182
(sql-table-reference-for it alias)
183
(error "No persistent class named '~A~%" class-name)))
185
(:method ((variable query-variable) (alias symbol))
186
(assert (not (eq (xtype-of variable) +unknown-type+)))
187
(sql-table-reference-for-type (xtype-of variable) alias))
189
(:method ((syntax syntax-object) (alias symbol))
190
(make-function-call :fn 'sql-table-reference-for :args (list syntax alias)))
192
(:method (element (variable query-variable))
193
(sql-table-reference-for element (sql-alias-for variable)))
195
(:method (element (class persistent-class))
196
(sql-table-reference-for element (sql-alias-for class)))
198
(:method (element (syntax syntax-object))
199
(make-function-call :fn 'sql-table-reference-for :args (list element syntax))))
201
(defgeneric sql-table-reference-for-type (type &optional alias)
203
(:method ((class persistent-class) &optional alias)
204
(bind ((tables (rest (primary-tables-of class)))) ; TODO handle UNION/APPEND
205
(case (length tables)
206
(0 (error "No primary table for persistent class: ~A" class))
207
(1 (sql-table-reference-for (first tables) alias))
208
(t (sql-table-reference-for
209
(sql-subquery :query (sql-select-oids-from-tables tables 'sql-union))
212
(:method ((type-name symbol) &optional alias)
213
(bind ((class (find-class type-name #f)))
215
(persistent-class (sql-table-reference-for-type class alias))
218
(:method ((type syntax-object) &optional alias) ;; type unknown at compile time
220
:fn 'sql-table-reference-for-type
221
:args (list (backquote-type-syntax type) `',alias)))
223
(:method ((combined-type list) &optional alias)
224
(if (contains-syntax-p combined-type)
225
;; delay evaluation until run-time
227
:fn 'sql-table-reference-for-type
228
:args (list (backquote-type-syntax combined-type) `',alias))
230
(labels ((ensure-sql-query (table-ref)
231
(assert (not (syntax-object-p table-ref)))
233
(sql-table-alias (sql-subquery :query (sql-select-oids-from-table table-ref)))
234
(sql-derived-table (cl-rdbms::subquery-of table-ref)))) ; TODO missing export
235
(ensure-alias (table-ref)
236
(when (or (typep table-ref 'sql-table-alias)
237
(typep table-ref 'sql-derived-table))
238
(setf (cl-rdbms::alias-of table-ref) alias))
240
(combine-types (sql-set-operation types)
241
(bind ((operands (delete nil
242
(mapcar 'sql-table-reference-for-type types))))
243
(case (length operands)
245
(1 (ensure-alias (first operands)))
246
(t (sql-table-reference-for
248
:query (apply sql-set-operation
249
(mapcar #'ensure-sql-query operands)))
251
(case (car combined-type)
252
(or (combine-types 'sql-union (rest combined-type)))
253
(and (combine-types 'sql-intersect (rest combined-type)))
254
(not (not-yet-implemented))
255
(t (error "Unsupported type constructor in ~A" combined-type)))))))
257
;;;----------------------------------------------------------------------------
258
;;; Column references
260
(defgeneric sql-column-reference-for (element qualifier)
261
(:method ((column-name symbol) (qualifier symbol))
262
(sql-column-alias :column column-name :table qualifier))
264
(:method ((column column) qualifier)
265
(sql-column-reference-for (rdbms::name-of column) qualifier))
267
(:method ((association-end persistent-association-end-slot-definition) qualifier)
268
(sql-column-reference-for (id-column-of association-end) qualifier))
270
(:method ((slot persistent-slot-definition) qualifier)
271
(sql-column-reference-for (last1 (columns-of slot)) qualifier))
273
(:method (element qualifier)
274
(sql-column-reference-for element (sql-alias-for qualifier))))
276
(defun sql-id-column-reference-for (qualifier)
277
(sql-column-reference-for +id-column-name+ qualifier))
279
(defgeneric sql-column-references-for (element qualifier)
280
(:method ((column-names list) qualifier)
281
(mapcar #L(sql-column-reference-for !1 qualifier) column-names))
283
(:method ((slot persistent-slot-definition) qualifier)
284
(sql-column-references-for (columns-of slot) qualifier)))
286
(defun sql-oid-column-references-for (qualifier)
287
(sql-column-references-for +oid-column-names+ qualifier))
290
;;;----------------------------------------------------------------------------
293
(defun sql-exists-subselect-for-variable (variable type)
294
"Returns an sql expression which evaluates to true iff the query variable named VARIABLE-NAME
301
:tables (list ,(sql-table-reference-for-type type type))
302
:where ,(sql-join-condition-for variable type nil)))))
304
(defun sql-exists-subselect-for-association-end (variable association-end)
305
"Returns an sql expression which evaluates to true iff the query variable VARIABLE
306
has associated objects through ASSOCIATION-END."
307
(bind ((class (slot-definition-class (other-association-end-of association-end))))
313
:tables (list ,(sql-table-reference-for class (sql-alias-for class)))
314
:where ,(sql-join-condition-for variable class (other-association-end-of association-end)))))))
316
(defun sql-aggregate-subselect-for-variable (aggregate-function n-association-end 1-var)
317
(bind ((1-association-end (other-association-end-of n-association-end))
318
(n-class (slot-definition-class 1-association-end))
319
(n-var (make-query-variable :name (gensym (symbol-name (class-name n-class)))
324
:columns (list (,aggregate-function ,(sql-id-column-reference-for n-var)))
325
:tables (list ,(sql-table-reference-for n-var n-var))
326
:where ,(sql-join-condition-for 1-var n-var 1-association-end)))))
328
(defun sql-aggregate-subselect-for-m-n-association-end (aggregate-function association-end variable)
329
(bind ((other-end (other-association-end-of association-end))
330
(table (primary-table-of (association-of association-end))))
334
:columns (list (,aggregate-function ,(sql-column-reference-for association-end table)))
335
:tables (list (sql-identifier :name ',(name-of table)))
337
,(sql-column-reference-for other-end table)
338
,(sql-id-column-reference-for variable))))))
340
(defun sql-subselect-for-secondary-association-end (association-end variable)
341
(bind ((primary-association-end (other-association-end-of association-end))
342
(class (slot-definition-class primary-association-end)))
346
:columns (list ,(sql-id-column-reference-for nil))
347
:tables (list ,(sql-table-reference-for class nil))
349
,(sql-column-reference-for primary-association-end nil)
350
,(sql-id-column-reference-for variable))))))
352
(defun sql-subselect-for-m-n-association (association-end variable)
353
(bind ((other-end (other-association-end-of association-end))
354
(table (primary-table-of (association-of association-end))))
358
:columns (list ,(sql-column-reference-for association-end table))
359
:tables (list (sql-identifier :name ',(name-of table)))
361
,(sql-column-reference-for other-end table)
362
,(sql-id-column-reference-for variable))))))
366
;;;----------------------------------------------------------------------------
369
(defun sql-join-condition-for (object-1 object-2 association-end-2)
370
(if (not association-end-2)
372
(sql-id-column-reference-for object-1)
373
(sql-id-column-reference-for object-2))
374
(bind ((association (association-of association-end-2))
375
(association-kind (association-kind-of association)))
376
(case association-kind
378
(if (primary-association-end-p association-end-2)
380
(sql-id-column-reference-for object-1)
381
(sql-column-reference-for association-end-2 object-2))
383
(sql-id-column-reference-for object-2)
384
(sql-column-reference-for (other-association-end-of association-end-2) object-1))))
386
(if (to-one-association-end-p association-end-2) ; TODO should check if primary
388
(sql-id-column-reference-for object-1)
389
(sql-column-reference-for association-end-2 object-2))
391
(sql-id-column-reference-for object-2)
392
(sql-column-reference-for (other-association-end-of association-end-2) object-1))))
394
(bind ((table (primary-table-of association)))
397
(sql-id-column-reference-for object-1)
398
(sql-column-reference-for association-end-2 table))
400
(sql-id-column-reference-for object-2)
401
(sql-column-reference-for (other-association-end-of association-end-2) table)))))))))
403
(defun sql-join-condition-for-joined-variable (variable)
404
(sql-join-condition-for variable (object-of variable) (association-end-of variable)))
406
(defun sql-join-condition-for-m-n-association (object-1 object-2 association-end-2)
407
(bind ((table (primary-table-of (association-of association-end-2))))
413
:tables (list (sql-identifier :name ',(name-of table)))
414
:where ,(sql-join-condition-for object-1 object-2 association-end-2))))))
417
;;;----------------------------------------------------------------------------
420
(defvar *sql-operators* (make-hash-table)
421
"Map from lisp function names to the corresponding SQL operator.")
423
(defun sql-operator-p (operator)
424
(gethash operator *sql-operators*))
426
(defun sql-operator-for (operator)
427
(gethash operator *sql-operators*))
429
(defun define-sql-operator (lisp-operator sql-operator)
430
(setf (gethash lisp-operator *sql-operators*) sql-operator))
432
(defun chained-operator (binary-operator &optional default)
433
(lambda (first-arg &rest more-args)
437
(iter (for arg in more-args)
438
(for prev-arg previous arg initially first-arg)
439
(collect (funcall binary-operator prev-arg arg)))))))
441
(defun pairwise-operator (binary-operator &optional default)
442
(lambda (first-arg &rest more-args)
446
(iter outer (for rest-args on more-args)
447
(for first previous (car rest-args) initially first-arg)
448
(iter (for second in rest-args)
449
(in outer (collect (funcall binary-operator first second)))))))))
451
(define-sql-operator 'eq 'sql-equal)
452
(define-sql-operator 'eql 'sql-equal)
453
(define-sql-operator 'equal 'sql-equal)
454
(define-sql-operator '= (chained-operator 'sql-= #t))
455
(define-sql-operator 'string= 'sql-string=)
457
(define-sql-operator 'and 'sql-and)
458
(define-sql-operator 'or 'sql-or)
459
(define-sql-operator 'not 'sql-not)
461
(define-sql-operator '> (chained-operator 'sql-> #t))
462
(define-sql-operator '< (chained-operator 'sql-< #t))
463
(define-sql-operator '>= (chained-operator 'sql->= #t))
464
(define-sql-operator '<= (chained-operator 'sql-<= #t))
465
(define-sql-operator '/= (pairwise-operator 'sql-<> #t))
467
(define-sql-operator '+ 'sql-+)
468
(define-sql-operator '- 'sql--)
469
(define-sql-operator '* 'sql-*)
470
(define-sql-operator '/ 'sql-/)
471
(define-sql-operator 'mod 'sql-%)
472
(define-sql-operator 'expt 'sql-^)
473
(define-sql-operator 'sqrt 'sql-\|/)
474
(define-sql-operator 'abs 'sql-@)
476
(define-sql-operator 'subseq 'sql-subseq)
477
(define-sql-operator 'strcat 'sql-\|\|)
479
(define-sql-operator 'like 'sql-like)
480
(define-sql-operator 'scan 'sql-regex-match)
482
(define-sql-operator 'null 'sql-is-null)
484
(defun sql-equal (first second &key check-nils)
486
((sql-null-literal-p first)
487
`(sql-is-null ,second))
488
((sql-null-literal-p second)
489
`(sql-is-null ,first))
490
((or (sql-literal-p first) (sql-literal-p second) (not check-nils))
491
`(sql-= ,first ,second))
493
`(sql-or (sql-= ,first ,second) (sql-and (sql-is-null ,first) (sql-is-null ,second))))))
495
(defun sql-string= (string1 string2 &key (start1 0) end1 (start2 0) end2 check-nils)
497
((sql-null-literal-p string1)
498
`(sql-is-null ,string2))
499
((sql-null-literal-p string2)
500
`(sql-is-null ,string1))
501
((or (sql-literal-p string1) (sql-literal-p string2) (not check-nils))
502
`(sql-= ,(sql-subseq string1 start1 end1) ,(sql-subseq string2 start2 end2)))
504
`(sql-or (sql-and (sql-is-null ,string1) (sql-is-null ,string2))
505
(sql-= ,(sql-subseq string1 start1 end1) ,(sql-subseq string2 start2 end2))))))
507
(defun sql-subseq (seq start &optional end)
508
"TODO: other sequnce types"
509
(if (or (not (numberp start)) (> start 0) end)
510
(sql-substring seq (sql-+ start 1) (sql-- (sql-length seq) start))
513
(defun sql-substring (str start length)
514
(sql-function-call :name "substring" :arguments (list str start length)))
516
(defun sql-regex-match (regex target &key (start 0) end)
517
"TODO: this works for PostgreSQL only"
518
(sql-~ (sql-subseq target start end) regex))
520
(defun sql-length (str)
521
(sql-function-call :name "char_length" :arguments (list str)))
523
;;;----------------------------------------------------------------------------
524
;;; Aggregate functions
526
(defvar *aggregate-functions* (make-hash-table)
527
"Map from lisp function symbol to the corresponing SQL aggregate function.")
529
(defun sql-aggregate-function-name-p (function-name)
530
(gethash function-name *aggregate-functions*))
532
(defun sql-aggregate-function-for (function-name)
533
(gethash function-name *aggregate-functions*))
535
(defun define-aggregate-function (lisp-function-name sql-function-name)
536
(setf (gethash lisp-function-name *aggregate-functions*) sql-function-name))
538
(define-aggregate-function 'length 'sql-count)
539
(define-aggregate-function 'min 'sql-min)
540
(define-aggregate-function 'max 'sql-max)
541
(define-aggregate-function 'sum 'sql-sum)
542
(define-aggregate-function 'avg 'sql-avg)
544
;;;----------------------------------------------------------------------------
547
(defun sql-null-literal-p (sql)
549
(and (typep sql 'sql-literal)
550
(null (cl-rdbms::value-of sql))
551
(or (null (cl-rdbms::type-of sql))
552
(not (typep (cl-rdbms::type-of sql) 'cl-rdbms::sql-boolean-type))))))
554
(defun sql-literal-p (sql)
555
(typep sql 'cl-rdbms::sql-literal*))
557
(defun sql-false-literal ()
558
(sql-literal :value #f :type (make-instance 'cl-rdbms::sql-boolean-type)))
560
(defun sql-true-literal ()
561
(sql-literal :value #t :type (make-instance 'cl-rdbms::sql-booelan-type)))