101a02a06a0debfac257b5450cb1c287b55749e8
[umlisp.git] / sql-create.lisp
1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10; Package: umlisp -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:          sql-create
6 ;;;; Purpose:       Create SQL database for UMLisp
7 ;;;; Programmer:    Kevin M. Rosenberg
8 ;;;; Date Started:  Apr 2000
9 ;;;;
10 ;;;; $Id: sql-create.lisp,v 1.6 2003/05/04 08:55:52 kevin Exp $
11 ;;;;
12 ;;;; This file, part of UMLisp, is
13 ;;;;    Copyright (c) 2000-2002 by Kevin M. Rosenberg, M.D.
14 ;;;;
15 ;;;; UMLisp users are granted the rights to distribute and use this software
16 ;;;; as governed by the terms of the GNU General Public License.
17 ;;;; *************************************************************************
18
19 (in-package :umlisp)
20 (declaim (optimize (speed 3) (safety 1) (compilation-speed 0) (debug 3)))
21
22
23 (defun create-table-cmd-old (file)
24   "Return sql command to create a table"
25   (let ((col-func 
26          (lambda (c) 
27            (let ((sqltype (umls-col-sqltype c)))
28              (concatenate 'string (umls-col-col c)
29                           " "
30                           (if (or (string-equal sqltype "VARCHAR")
31                                   (string-equal sqltype "CHAR"))
32                               (format nil "~a (~a)" sqltype (umls-col-max c))
33                               sqltype)
34                           ",")))))
35     (format nil "CREATE TABLE ~a (~a)" (umls-file-table file)
36             (string-trim-last-character
37              (mapcar-append-string col-func (umls-cols-for-umls-file file))))))
38
39
40 (defun create-table-cmd (file)
41   "Return sql command to create a table"
42   (let ((col-func 
43          (lambda (c) 
44            (let ((sqltype (umls-col-sqltype c)))
45              (concatenate 'string
46                           (umls-col-col c)
47                           " "
48                           (if (or (string-equal sqltype "VARCHAR")
49                                   (string-equal sqltype "CHAR"))
50                               (format nil "~a (~a)" sqltype (umls-col-max c))
51                               sqltype))))))
52     (format nil "CREATE TABLE ~a (~{~a~^,~})" (umls-file-table file)
53             (mapcar col-func (umls-cols-for-umls-file file)))))
54
55 (defun create-custom-table-cmd (tablename sql-cmd)
56   "Return SQL command to create a custom table"
57   (format nil "CREATE TABLE ~a AS ~a;" tablename sql-cmd))
58
59 (defun insert-values-cmd-old (file values)
60   "Return sql insert command for a row of values"  
61   (let ((insert-func
62          (lambda (col value)
63            (concatenate
64             'string
65             (umls-col-quotechar col)
66             (if (null (umls-col-parsefunc col)) 
67                 value
68                 (format nil "~A" (funcall (umls-col-parsefunc col) value)))
69             (umls-col-quotechar col)
70             ","))))
71     (format
72      nil "INSERT INTO ~a (~a) VALUES (~a)"
73      (umls-file-table file)
74      (string-trim-last-character
75       (mapcar-append-string (lambda (c) (concatenate 'string c ","))
76                             (umls-file-fields file)))
77      (string-trim-last-character
78       (concatenate 'string
79                    (mapcar2-append-string insert-func
80                                           (remove-custom-cols (umls-file-colstructs file)) 
81                                           values)
82                    (custom-col-values-old (custom-colstructs-for-file file) values "," t)))
83      )))
84
85 (defun insert-col-value (col value)
86   (if (null (umls-col-parsefunc col)) 
87       value
88       (format nil "~A" (funcall (umls-col-parsefunc col) value))))
89
90 (defun insert-values-cmd (file values)
91   "Return sql insert command for a row of values"  
92   (let ((insert-func
93          (lambda (col value)
94            (let ((q (umls-col-quotechar col)))
95              (concatenate 'string q (insert-col-value col value) q)))))
96     (format
97      nil "INSERT INTO ~a (~{~a~^,~}) VALUES (~{~a~^,~})"
98      (umls-file-table file)
99      (umls-file-fields file)
100      (append
101       (mapcar insert-func (remove-custom-cols (umls-file-colstructs file)) values)
102       (custom-col-values (custom-colstructs-for-file file) values t)))))
103
104 (defun custom-col-values-old (colstructs values delim doquote)
105   "Returns a string of column values for SQL inserts for custom columns"
106   (let ((result ""))
107     (dolist (col colstructs)
108       (let* ((func (umls-col-custom-value-func col))
109              (custom-value (funcall func values)))
110         (string-append result 
111                        (if doquote (umls-col-quotechar col))
112                        (escape-backslashes custom-value)
113                        (if doquote (umls-col-quotechar col))
114                        delim)))
115     result))
116
117 (defun col-value (col doquote values)
118   (let ((custom-value (funcall (umls-col-custom-value-func col) values)))
119     (if doquote
120         (let ((q (umls-col-quotechar col)))
121           (concatenate 'string q (escape-backslashes custom-value) q))
122         (escape-backslashes custom-value))))
123
124 (defun custom-col-values (colstructs values doquote)
125   "Returns a list of string column values for SQL inserts for custom columns"
126   (loop for col in colstructs collect (col-value col doquote values)))
127
128
129 (defun remove-custom-cols (cols)
130   "Remove custom cols from a list col umls-cols"
131   (remove-if #'umls-col-custom-value-func cols))
132
133 (defun find-custom-cols-for-filename (filename)
134   (remove-if-not (lambda (x) (string-equal filename (car x))) +custom-cols+))
135
136 (defun find-custom-col (filename col)
137   (find-if (lambda (x) (and (string-equal filename (car x))
138                             (string-equal col (cadr x)))) +custom-cols+))
139
140
141 (defun custom-colnames-for-filename (filename)
142   (mapcar #'cadr (find-custom-cols-for-filename filename)))
143
144 (defun custom-colstructs-for-file (file)
145   (remove-if-not #'umls-col-custom-value-func (umls-file-colstructs file)))
146
147 (defun noneng-lang-index-files ()
148   (remove-if-not (lambda (f) (and (> (length (umls-file-fil f)) 4)
149                                   (string-equal (umls-file-fil f) "MRXW." :end1 5) 
150                                   (not (string-equal (umls-file-fil f) "MRXW.ENG"))
151                                   (not (string-equal (umls-file-fil f) "MRXW.NONENG"))))
152                  *umls-files*))
153
154 ;;; SQL Command Functions
155
156 (defun create-index-cmd (colname tablename length)
157   "Return sql create index command"
158   (format nil "CREATE INDEX ~a ON ~a (~a ~a)"
159           (concatenate 'string tablename "_" colname "_X") tablename colname
160           (if (integerp length)
161               (format nil "(~d)" length)
162               "")))
163
164 (defun create-all-tables-cmdfile ()
165   "Return sql commands to create all tables. Not need for automated SQL import"
166   (mapcar (lambda (f) (format nil "~a~%~%" (create-table-cmd f))) *umls-files*))
167
168
169 ;; SQL Execution functions
170
171 (defun sql-drop-tables (conn)
172   "SQL Databases: drop all tables"
173   (mapcar
174    (lambda (file)
175      (ignore-errors 
176        (sql-execute (format nil "DROP TABLE ~a" (umls-file-table file)) conn)))
177    *umls-files*))
178
179 (defun sql-create-tables (conn)
180   "SQL Databases: create all tables" 
181   (mapcar (lambda (file) (sql-execute (create-table-cmd file) conn)) *umls-files*))
182
183 (defun sql-create-custom-tables (conn)
184   "SQL Databases: create all custom tables"
185   (mapcar (lambda (ct)
186             (sql-execute (create-custom-table-cmd (car ct) (cadr ct)) conn))
187           +custom-tables+))
188   
189 (defun sql-insert-values (conn file)
190   "SQL Databases: inserts all values for a file"  
191   (with-umls-file (line (umls-file-fil file))
192     (sql-execute (insert-values-cmd file line) conn)))
193
194 (defun sql-insert-all-values (conn)
195   "SQL Databases: inserts all values for all files"  
196   (mapcar (lambda (file) (sql-insert-values conn file)) *umls-files*))
197
198 (defun sql-create-indexes (conn &optional (indexes +index-cols+))
199   "SQL Databases: create all indexes"
200   (mapcar 
201    (lambda (idx) 
202      (sql-execute (create-index-cmd (car idx) (cadr idx) (caddr idx)) conn)) 
203    indexes))
204
205 (defun make-usrl (conn)
206   (sql-execute "drop table if exists USRL" conn)
207   (sql-execute "create table USRL (sab varchar(80), srl integer)" conn)
208   (dolist (tuple (mutex-sql-query "select distinct SAB,SRL from MRSO order by SAB asc"))
209     (sql-execute (format nil "insert into USRL (sab,srl) values ('~a',~d)" 
210                          (car tuple) (ensure-integer (cadr tuple)))
211                  conn)))
212
213 (defun sql-create-special-tables (conn)
214   (make-usrl conn))
215
216 (defun create-umls-db-by-insert ()
217   "SQL Databases: initializes entire database via SQL insert commands"
218   (init-umls)
219   (init-hash-table)
220   (with-sql-connection (conn)
221     (sql-drop-tables conn)
222     (sql-create-tables conn)
223     (sql-insert-all-values conn)
224     (sql-create-indexes conn)
225     (sql-create-custom-tables conn)
226     (sql-create-indexes conn +custom-index-cols+)
227     (sql-create-special-tables conn)))
228
229 (defun create-umls-db (&optional (extension ".trans") 
230                        (copy-cmd #'mysql-copy-cmd))
231   "SQL Databases: initializes entire database via SQL copy commands. 
232 This is much faster that using create-umls-db-insert."
233   (init-umls)
234   (init-hash-table)
235   (translate-all-files extension)
236   (with-sql-connection (conn)
237     (sql-drop-tables conn)
238     (sql-create-tables conn)
239     (mapcar 
240      #'(lambda (file) (sql-execute (funcall copy-cmd file extension) conn)) 
241      *umls-files*)
242     (sql-create-indexes conn)
243     (sql-create-custom-tables conn)
244     (sql-create-indexes conn +custom-index-cols+)
245     (sql-create-special-tables conn)))
246
247 (defun translate-all-files (&optional (extension ".trans"))
248   "Copy translated files and return postgresql copy commands to import"
249   (make-noneng-index-file extension)
250   (mapcar (lambda (f) (translate-file f extension)) *umls-files*))
251
252 (defun translate-file (file extension)
253   "Translate a umls file into a format suitable for sql copy cmd"
254   (let ((path (umls-pathname (umls-file-fil file) extension)))
255     (if (probe-file path)
256         (progn
257           (format t "File ~A already exists: skipping~%" path)
258           nil)
259         (with-open-file (ostream path :direction :output)
260           (with-umls-file (line (umls-file-fil file))
261             (princ (umls-translate file line) ostream)
262             (princ #\newline ostream))
263           t))))
264
265 (defun make-noneng-index-file (extension)
266   "Make non-english index file"
267   (let* ((outfile (find-umls-file "MRXW.NONENG"))
268          (path (umls-pathname (umls-file-fil outfile) extension)))
269         
270     (if (probe-file path)
271         (progn
272           (format t "File ~A already exists: skipping~%" path)
273           nil)
274         (progn
275           (with-open-file (ostream path :direction :output)
276             (dolist (inputfile (noneng-lang-index-files))
277               (with-umls-file (line (umls-file-fil inputfile))
278                 (princ (umls-translate outfile line) ostream) ;; use outfile for custom cols
279                 (princ #\newline ostream))))
280           t))))
281
282 (defun pg-copy-cmd (file extension)
283   "Return postgresql copy statement for a file"  
284   (format nil "COPY ~a FROM '~a' using delimiters '|' with null as ''"
285           (umls-file-table file) (umls-pathname (umls-file-fil file) extension)))
286
287 (defun mysql-copy-cmd (file extension)
288   "Return mysql copy statement for a file"  
289   (format nil "LOAD DATA LOCAL INFILE \"~a\" INTO TABLE ~a FIELDS TERMINATED BY \"|\""
290           (umls-pathname (umls-file-fil file) extension) (umls-file-table file)))
291
292 (defun umls-translate-old (file line)
293   "Translate a single line for sql output"
294   (string-trim-last-character
295    (concatenate 'string
296                 (mapcar2-append-string 
297                  (lambda (col value)
298                    (concatenate
299                     'string
300                     (if (eq (umls-col-datatype col) 'sql-u)
301                         (format nil "~d" (parse-ui value ""))
302                         (escape-backslashes value))
303                     "|"))
304                  (remove-custom-cols (umls-file-colstructs file)) 
305                  line)
306                 (custom-col-values-old (custom-colstructs-for-file file) line "|" nil))))
307
308 (defun umls-translate (file line)
309   "Translate a single line for sql output"
310   (format nil "~{~A~^|~}"
311           (append
312            (mapcar
313             (lambda (col value)
314               (concatenate
315                'string
316                (if (eq (umls-col-datatype col) 'sql-u)
317                    (format nil "~d" (parse-ui value ""))
318                    (escape-backslashes value))))
319             (remove-custom-cols (umls-file-colstructs file)) 
320             line)
321           (custom-col-values (custom-colstructs-for-file file) line nil))))
322    
323
324 ;;; Routines for analyzing cost of fixed size storage
325
326
327 (defun umls-fixed-size-waste ()
328   "Display storage waste if using all fixed size storage"
329   (let ((totalwaste 0)
330         (totalunavoidable 0)
331         (totalavoidable 0)
332         (unavoidable '())
333         (avoidable '()))
334     (dolist (file *umls-files*)
335       (dolist (col (umls-file-colstructs file))
336         (let* ((avwaste (- (umls-col-max col) (umls-col-av col)))
337                (cwaste (* avwaste (umls-file-rws file))))
338           (unless (zerop cwaste)
339             (if (<= avwaste 6)
340                 (progn
341                   (incf totalunavoidable cwaste)
342                   (setq unavoidable (append unavoidable (list (list (umls-file-fil file) (umls-col-col col) avwaste cwaste)))))
343                 (progn
344                   (incf totalavoidable cwaste)
345                   (setq avoidable (append avoidable (list (list (umls-file-fil file) (umls-col-col col) avwaste cwaste))))))
346             (incf totalwaste cwaste)))))
347     (values totalwaste totalavoidable totalunavoidable avoidable unavoidable)))
348
349 (defun display-waste ()
350   (unless *umls-files*
351     (init-umls))
352   (multiple-value-bind (tw ta tu al ul) (umls-fixed-size-waste)
353     (format t "Total waste: ~d~%" tw)
354     (format t "Total avoidable: ~d~%" ta)
355     (format t "Total unavoidable: ~d~%" tu)
356     (format t "Avoidable:~%")
357     (dolist (w al)
358       (format t "  (~a,~a): ~a,~a~%" (car w) (cadr w) (caddr w) (cadddr w)))
359     (format t "Unavoidable:~%")
360     (dolist (w ul)
361       (format t "  (~a,~a): ~a,~a~%" (car w) (cadr w) (caddr w) (cadddr w)))
362     ))
363
364 (defun max-umls-field ()
365   "Return length of longest field"
366   (unless *umls-files*
367     (init-umls))
368   (let ((max 0))
369     (declare (fixnum max))
370     (dolist (col *umls-cols*)
371       (when (> (umls-col-max col) max)
372         (setq max (umls-col-max col))))
373     max))
374
375 (defun max-umls-row ()
376   "Return length of longest row"
377   (if t
378       6000 ;;; hack to use on systems without MRCOLS/MRFILES -- ok for UMLS2001
379       (progn
380         (unless *umls-files*
381           (init-umls))
382         (let ((rowsizes '()))
383           (dolist (file *umls-files*)
384             (let ((row 0)
385                   (fields (umls-file-colstructs file)))
386               (dolist (field fields)
387                 (incf row (1+ (umls-col-max field))))
388               (push row rowsizes)))
389           (car (sort rowsizes #'>))))))