f8f5836fab84ba29182f2aa6a0667191f3ad5c0b
[clsql.git] / db-oracle / oracle-sql.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:          oracle-sql.lisp
6 ;;;;
7 ;;;; This file is part of CLSQL.
8 ;;;;
9 ;;;; CLSQL users are granted the rights to distribute and use this software
10 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
11 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
12 ;;;; *************************************************************************
13
14 (in-package #:clsql-oracle)
15
16 (defmethod database-initialize-database-type ((database-type (eql :oracle)))
17   t)
18
19 ;;;; arbitrary parameters, tunable for performance or other reasons
20
21 (eval-when (:compile-toplevel :load-toplevel :execute)
22   (defconstant +errbuf-len+ 512
23     "the number of characters that we allocate for an error message buffer")
24   (defconstant +n-buf-rows+ 200
25     "the number of table rows that we buffer at once when reading a table.
26 CMUCL has a compiled-in limit on how much C data can be allocated
27 (through malloc() and friends) at any given time, typically 8 Mb.
28 Setting this constant to a moderate value should make it less
29 likely that we'll have to worry about the CMUCL limit."))
30
31
32 (uffi:def-type vp-type :pointer-void)
33 (uffi:def-type vpp-type (* :pointer-void))
34
35 (defmacro deref-vp (foreign-object)
36   `(the vp-type (uffi:deref-pointer (the vpp-type ,foreign-object) :pointer-void)))
37
38 (uffi:def-pointer-var +unsigned-char-null-pointer+
39   (uffi:make-null-pointer :unsigned-char))
40 (uffi:def-pointer-var +unsigned-short-null-pointer+
41   (uffi:make-null-pointer :unsigned-short))
42 (uffi:def-pointer-var +unsigned-int-null-pointer+
43   (uffi:make-null-pointer :unsigned-int))
44
45 ;; constants - from OCI?
46
47 (defconstant +var-not-in-list+       1007)
48 (defconstant +no-data-found+         1403)
49 (defconstant +null-value-returned+   1405)
50 (defconstant +field-truncated+       1406)
51
52 (eval-when (:compile-toplevel :load-toplevel :execute)
53   (defconstant SQLT-NUMBER 2)
54   (defconstant SQLT-INT 3)
55   (defconstant SQLT-FLT 4)
56   (defconstant SQLT-STR 5)
57   (defconstant SQLT-DATE 12))
58
59 ;;; Note that despite the suggestive class name (and the way that the
60 ;;; *DEFAULT-DATABASE* variable holds an object of this class), a DB
61 ;;; object is not actually a database but is instead a connection to a
62 ;;; database. Thus, there's no obstacle to having any number of DB
63 ;;; objects referring to the same database.
64
65 (uffi:def-type pointer-pointer-void (* :pointer-void))
66
67 (defclass oracle-database (database)    ; was struct db
68   ((envhp
69     :reader envhp
70     :initarg :envhp
71     :type pointer-pointer-void
72     :documentation
73     "OCI environment handle")
74    (errhp
75     :reader errhp
76     :initarg :errhp
77     :type pointer-pointer-void
78     :documentation
79     "OCI error handle")
80    (svchp
81     :reader svchp
82     :initarg :svchp
83     :type pointer-pointer-void
84     :documentation
85     "OCI service context handle")
86    (data-source-name
87     :initarg :dsn
88     :initform nil
89     :documentation
90     "optional data source name (used only for debugging/printing)")
91    (user
92     :initarg :user
93     :reader user
94     :type string
95     :documentation
96     "the \"user\" value given when data source connection was made")
97    (date-format
98     :initarg :date-format
99     :reader date-format
100     :initform "YYYY-MM-DD HH24:MI:SS\".0\"")
101    (date-format-length
102     :type number
103     :documentation
104     "Each database connection can be configured with its own date
105 output format.  In order to extract date strings from output buffers
106 holding multiple date strings in fixed-width fields, we need to know
107 the length of that format.")
108    (server-version
109     :type (or null string)
110     :initarg :server-version
111     :reader server-version
112     :documentation
113     "Version string of Oracle server.")
114    (major-server-version
115     :type (or null fixnum)
116     :initarg :major-server-version
117     :reader major-server-version
118     :documentation
119     "The major version number of the Oracle server, should be 8, 9, or 10")))
120
121 ;;; Handle a non-successful result from an OCI function.
122 (defun handle-oci-result (result database nulls-ok)
123   (case result
124     (#.+oci-success+
125      +oci-success+)
126     (#.+oci-error+
127      (handle-oci-error :database database :nulls-ok nulls-ok))
128     (#.+oci-no-data+
129      (error 'sql-database-error :message "OCI No Data Found"))
130     (#.+oci-success-with-info+
131      (error 'sql-database-error :message "internal error: unexpected +oci-success-with-info"))
132     (#.+oci-invalid-handle+
133      (error 'sql-database-error :message "OCI Invalid Handle"))
134     (#.+oci-need-data+
135      (error 'sql-database-error :message "OCI Need Data"))
136     (#.+oci-still-executing+
137      (error 'sql-temporary-error :message "OCI Still Executing"))
138     (#.+oci-continue+
139      (error 'sql-database-error :message "OCI Continue"))
140     (1804
141      (error 'sql-database-error :message "Check ORACLE_HOME and NLS settings."))
142     (t
143      (error 'sql-database-error
144             :message
145             (format nil "OCI unknown error, code=~A" result)))))
146
147 ;;; Handle the messy case of return code=+oci-error+, querying the
148 ;;; system for subcodes and reporting them as appropriate. ERRHP and
149 ;;; NULLS-OK are as in the OERR function.
150
151 (defun handle-oci-error (&key database nulls-ok)
152   (cond
153     (database
154      (with-slots (errhp) database
155        (let ((errcode (uffi:allocate-foreign-object 'sb4))
156              (errbuf (uffi:allocate-foreign-string #.+errbuf-len+)))
157          ;; ensure errbuf empty string
158          (setf (uffi:deref-array errbuf '(:array :unsigned-char) 0)
159                (uffi:ensure-char-storable (code-char 0)))
160          (setf (uffi:deref-pointer errcode 'sb4) 0)
161
162          (uffi:with-cstring (sqlstate nil)
163            (oci-error-get (deref-vp errhp) 1
164                           sqlstate
165                           errcode
166                           (uffi:char-array-to-pointer errbuf)
167                           +errbuf-len+ +oci-htype-error+))
168          (let ((subcode (uffi:deref-pointer errcode 'sb4))
169                (errstr (uffi:convert-from-foreign-string
170                         errbuf
171                         :encoding (when database (encoding database)))))
172            (uffi:free-foreign-object errcode)
173            (uffi:free-foreign-object errbuf)
174            (unless (and nulls-ok (= subcode +null-value-returned+))
175              (error 'sql-database-error
176                     :database database
177                     :error-id subcode
178                     :message errstr))))))
179     (nulls-ok
180      (error 'sql-database-error
181             :database database
182             :message "can't handle NULLS-OK without ERRHP"))
183     (t
184      (error 'sql-database-error
185             :database database
186             :message "OCI Error (and no ERRHP available to find subcode)"))))
187
188 ;;; Require an OCI success code.
189 ;;;
190 ;;; (The ordinary OCI error reporting mechanisms uses a fair amount of
191 ;;; machinery (environments and other handles). In order to get to
192 ;;; where we can use these mechanisms, we have to be able to allocate
193 ;;; the machinery. The functions for allocating the machinery can
194 ;;; return errors (e.g. out of memory) but shouldn't. Wrapping this function
195 ;;; around function calls to such have-to-succeed functions enforces
196 ;;; this condition.)
197
198 (defun osucc (code)
199   (declare (type fixnum code))
200   (unless (= code +oci-success+)
201     (error 'sql-database-error
202            :message (format nil "unexpected OCI failure, code=~S" code))))
203
204
205 ;;; Enabling this can be handy for low-level debugging.
206 #+nil
207 (progn
208   (trace #-oci7 oci-env-create oci-initialize oci-handle-alloc oci-logon
209          oci-error-get oci-stmt-prepare oci-stmt-execute
210          oci-param-get oci-logon oci-attr-get oci-define-by-pos oci-stmt-fetch)
211   (setf debug::*debug-print-length* nil))
212
213
214 ;; Return the INDEXth string of the OCI array, represented as Lisp
215 ;; SIMPLE-STRING. SIZE is the size of the fixed-width fields used by
216 ;; Oracle to store strings within the array.
217
218 (uffi:def-type string-pointer (* :unsigned-char))
219
220 (defun deref-oci-string (arrayptr string-index size encoding)
221   (declare (type string-pointer arrayptr))
222   (declare (type (mod #.+n-buf-rows+) string-index))
223   (declare (type (and unsigned-byte fixnum) size))
224   (let ((str (uffi:convert-from-foreign-string
225               (uffi:make-pointer
226                (+ (uffi:pointer-address arrayptr) (* string-index size))
227                :unsigned-char)
228               :encoding encoding)))
229     (if (string-equal str "NULL") nil str)))
230
231 ;; the OCI library, part Z: no-longer used logic to convert from
232 ;; Oracle's binary date representation to Common Lisp's native date
233 ;; representation
234
235 #+nil
236 (defvar +oci-date-bytes+ 7)
237
238 ;;; Return the INDEXth date in the OCI array, represented as
239 ;;; a Common Lisp "universal time" (i.e. seconds since 1900).
240
241 #+nil
242 (defun deref-oci-date (arrayptr index)
243   (oci-date->universal-time (uffi:pointer-address
244                              (uffi:deref-array arrayptr
245                                                '(:array :unsigned-char)
246                                                (* index +oci-date-bytes+)))))
247 #+nil
248 (defun oci-date->universal-time (oci-date)
249   (declare (type (alien (* :unsigned-char)) oci-date))
250   (flet (;; a character from OCI-DATE, interpreted as an unsigned byte
251          (ub (i)
252            (declare (type (mod #.+oci-date-bytes+) i))
253            (mod (uffi:deref-array oci-date string-array i) 256)))
254     (let* ((century (* (- (ub 0) 100) 100))
255            (year    (+ century (- (ub 1) 100)))
256            (month   (ub 2))
257            (day     (ub 3))
258            (hour    (1- (ub 4)))
259            (minute  (1- (ub 5)))
260            (second  (1- (ub 6))))
261       (encode-universal-time second minute hour day month year))))
262
263
264 (defmethod database-list-tables ((database oracle-database) &key owner)
265   (let ((query
266           (cond ((null owner)
267                  "select table_name from user_tables")
268                 ((eq owner :all)
269                  "select table_name from all_tables")
270                 (t
271                  (format nil
272                          "select user_tables.table_name from user_tables,all_tables where user_tables.table_name=all_tables.table_name and all_tables.owner='~:@(~A~)'"
273                       owner)))))
274     (mapcar #'car (database-query query database nil nil))))
275
276
277 (defmethod database-list-views ((database oracle-database) &key owner)
278   (let ((query
279           (cond ((null owner)
280                  "select view_name from user_views")
281                 ((eq owner :all)
282                  "select view_name from all_views")
283                 (t
284                  (format nil
285                          "select user_views.view_name from user_views,all_views where user_views.view_name=all_views.view_name and all_views.owner='~:@(~A~)'"
286                          owner)))))
287     (mapcar #'car
288             (database-query query database nil nil))))
289
290 (defmethod database-list-indexes ((database oracle-database)
291                                   &key (owner nil))
292   (let ((query
293          (cond ((null owner)
294                 "select index_name from user_indexes")
295                ((eq owner :all)
296                 "select index_name from all_indexes")
297                (t (format nil
298                           "select user_indexes.index_name from user_indexes,all_indexes where user_indexes.index_name=all_indexes.index_name and all_indexes.owner='~:@(~A~)'"
299                           owner)))))
300     (mapcar #'car (database-query query database nil nil))))
301
302 (defmethod database-list-table-indexes (table (database oracle-database)
303                                         &key (owner nil))
304   (let ((query
305          (cond ((null owner)
306                 (format nil "select index_name from user_indexes where table_name='~A'"
307                         table))
308                ((eq owner :all)
309                 (format nil "select index_name from all_indexes where table_name='~A'"
310                         table))
311                (t
312                 (format nil
313                         "select user_indexes.index_name from user_indexes,all_indexes where user_indexes.table_name='~A' and user_indexes.index_name=all_indexes.index_name and all_indexes.owner='~:@(~A~)'"
314                         table owner)))))
315     (mapcar #'car (database-query query database nil nil))))
316
317
318 (defmethod database-list-attributes (table (database oracle-database) &key owner)
319   (let ((query
320          (cond ((null owner)
321                 (format nil "select column_name from user_tab_columns where table_name='~A'"
322                         table))
323                ((eq owner :all)
324                 (format nil "select column_name from all_tab_columns where table_name='~A'"
325                         table))
326                (t
327                 (format nil
328                         "select user_tab_columns.column_name from user_tab_columns,all_tables where user_tab_columns.table_name='~A' and all_tables.table_name=user_tab_columns.table_name and all_tables.owner='~:@(~A~)'"
329                         table owner)))))
330     (mapcar #'car (database-query query database nil nil))))
331
332 (defmethod database-attribute-type (attribute (table string)
333                                          (database oracle-database)
334                                          &key (owner nil))
335   (let ((query
336          (cond ((null owner)
337                 (format nil
338                         "select data_type,data_length,data_scale,nullable from user_tab_columns where table_name='~A' and column_name='~A'"
339                         table attribute))
340                ((eq owner :all)
341                 (format nil
342                         "select data_type,data_length,data_scale,nullable from all_tab_columns where table_name='~A' and column_name='~A'"
343                         table attribute))
344                (t
345                 (format nil
346                         "select data_type,data_length,data_scale,nullable from user_tab_columns,all_tables where user_tab_columns.table_name='~A' and column_name='~A' and all_tables.table_name=user_tab_columns.table_name and all_tables.owner='~:@(~A~)'"
347                         table attribute owner)))))
348     (destructuring-bind (type length scale nullable) (car (database-query query database :auto nil))
349       (values (ensure-keyword type) length scale
350               (if (char-equal #\Y (schar nullable 0)) 1 0)))))
351
352 ;; Return one row of the table referred to by QC, represented as a
353 ;; list; or if there are no more rows, signal an error if EOF-ERRORP,
354 ;; or return EOF-VALUE otherwise.
355
356 ;; KLUDGE: This CASE statement is a strong sign that the code would be
357 ;; cleaner if CD were made into an abstract class, we made variant
358 ;; classes for CD-for-column-of-strings, CD-for-column-of-floats,
359 ;; etc., and defined virtual functions to handle operations like
360 ;; get-an-element-from-column. (For a small special purpose module
361 ;; like this, would arguably be overkill, so I'm not going to do it
362 ;; now, but if this code ends up getting more complicated in
363 ;; maintenance, it would become a really good idea.)
364
365 ;; Arguably this would be a good place to signal END-OF-FILE, but
366 ;; since the ANSI spec specifically says that END-OF-FILE means a
367 ;; STREAM which has no more data, and QC is not a STREAM, we signal
368 ;; DBI-ERROR instead.
369
370 (uffi:def-type short-array (* :short))
371 (uffi:def-type int-array (* :int))
372 (uffi:def-type double-array (* :double))
373 (uffi:def-type int-pointer (* :int))
374 (uffi:def-type double-pointer (* :double))
375
376 ;;; the result of a database query: a cursor through a table
377 (defstruct (oracle-result-set (:print-function print-query-cursor)
378                               (:conc-name qc-)
379                               (:constructor %make-query-cursor))
380   (db (error "missing DB")   ; db conn. this table is associated with
381     :type oracle-database
382     :read-only t)
383   (stmthp (error "missing STMTHP")      ; the statement handle used to create
384 ;;  :type alien                 ; this table. owned by the QUERY-CURSOR
385     :read-only t)                       ; object, deallocated on CLOSE-QUERY
386   (cds) ;  (error "missing CDS")            ; column descriptors
387 ;    :type (simple-array cd 1)
388                                         ;    :read-only t)
389   (n-from-oci
390    0                         ; buffered rows: number of rows recv'd
391    :type (integer 0 #.+n-buf-rows+))   ; from the database on the last read
392   (n-to-dbi
393    0                           ; number of buffered rows returned, i.e.
394    :type (integer 0 #.+n-buf-rows+))   ; the index, within the buffered rows,
395                                         ; of the next row which hasn't already
396                                         ; been returned
397   (total-n-from-oci
398    0                   ; total number of bytes recv'd from OCI
399    :type unsigned-byte)                ; in all reads
400   (oci-end-seen-p nil))                 ; Have we seen the end of OCI
401                                         ; data, i.e. OCI returning
402                                         ; less data than we requested?
403                                         ; OCI doesn't seem to like us
404                                         ; to try to read more data
405                                         ; from it after that..
406
407
408 (defun fetch-row (qc &optional (eof-errorp t) eof-value encoding)
409   (declare (optimize (speed 3)))
410   (cond ((zerop (qc-n-from-oci qc))
411          (if eof-errorp
412              (error 'sql-database-error :message
413                     (format nil "no more rows available in ~S" qc))
414            eof-value))
415         ((>= (qc-n-to-dbi qc)
416              (qc-n-from-oci qc))
417          (refill-qc-buffers qc)
418          (fetch-row qc nil eof-value encoding))
419         (t
420          (let ((cds (qc-cds qc))
421                (reversed-result nil)
422                (irow (qc-n-to-dbi qc)))
423            (dotimes (icd (length cds))
424              (let* ((cd (aref cds icd))
425                     (b (foreign-resource-buffer (cd-buffer cd)))
426                     (value
427                      (let* ((arb (foreign-resource-buffer (cd-indicators cd)))
428                             (indicator (uffi:deref-array arb '(:array :short) irow)))
429                        (declare (type short-array arb))
430                        (unless (= indicator -1)
431                          (ecase (cd-oci-data-type cd)
432                            (#.SQLT-STR
433                             (deref-oci-string b irow (cd-sizeof cd) encoding))
434                            (#.SQLT-FLT
435                             (locally
436                                 (declare (type double-array b))
437                               (uffi:deref-array b '(:array :double) irow)))
438                            (#.SQLT-INT
439                             (ecase (cd-sizeof cd)
440                               (4
441                                (locally
442                                    (declare (type int-array b))
443                                  (uffi:deref-array b '(:array :int) irow)))))
444                            (#.SQLT-DATE
445                             (deref-oci-string b irow (cd-sizeof cd) encoding)))))))
446                (when (and (eq :string (cd-result-type cd))
447                           value
448                           (not (stringp value)))
449                    (setq value (write-to-string value)))
450                (push value reversed-result)))
451            (incf (qc-n-to-dbi qc))
452            (nreverse reversed-result)))))
453
454 (defun refill-qc-buffers (qc)
455   (with-slots (errhp) (qc-db qc)
456     (setf (qc-n-to-dbi qc) 0)
457     (cond ((qc-oci-end-seen-p qc)
458            (setf (qc-n-from-oci qc) 0))
459           (t
460            (let ((oci-code (%oci-stmt-fetch
461                             (deref-vp (qc-stmthp qc))
462                             (deref-vp errhp)
463                             +n-buf-rows+
464                             +oci-fetch-next+ +oci-default+)))
465              (ecase oci-code
466                (#.+oci-success+ (values))
467                (#.+oci-no-data+ (setf (qc-oci-end-seen-p qc) t)
468                                 (values))
469                (#.+oci-error+ (handle-oci-error :database (qc-db qc)
470                                                 :nulls-ok t))))
471            (uffi:with-foreign-object (rowcount 'ub4)
472              (oci-attr-get (deref-vp (qc-stmthp qc))
473                            +oci-htype-stmt+
474                            rowcount
475                            +unsigned-int-null-pointer+
476                            +oci-attr-row-count+
477                            (deref-vp errhp))
478              (setf (qc-n-from-oci qc)
479                    (- (uffi:deref-pointer rowcount 'ub4)
480                       (qc-total-n-from-oci qc)))
481              (when (< (qc-n-from-oci qc) +n-buf-rows+)
482                (setf (qc-oci-end-seen-p qc) t))
483              (setf (qc-total-n-from-oci qc)
484                    (uffi:deref-pointer rowcount 'ub4)))))
485     (values)))
486
487 ;; the guts of the SQL function
488 ;;
489 ;; (like the SQL function, but with the QUERY argument hardwired to T, so
490 ;; that the return value is always a cursor instead of a list)
491
492 ;; Is this a SELECT statement?  SELECT statements are handled
493 ;; specially by OCIStmtExecute().  (Non-SELECT statements absolutely
494 ;; require a nonzero iteration count, while the ordinary choice for a
495 ;; SELECT statement is a zero iteration count.
496
497 ;; SELECT statements are the only statements which return tables.  We
498 ;; don't free STMTHP in this case, but instead give it to the new
499 ;; QUERY-CURSOR, and the new QUERY-CURSOR becomes responsible for
500 ;; freeing the STMTHP when it is no longer needed.
501
502 (defun sql-stmt-exec (sql-stmt-string db result-types field-names)
503   (with-slots (envhp svchp errhp) db
504     (uffi:with-foreign-strings ((c-stmt-string sql-stmt-string))
505       (let ((stmthp (uffi:allocate-foreign-object :pointer-void))
506             select-p)
507
508         (uffi:with-foreign-object (stmttype :unsigned-short)
509           (unwind-protect
510                (progn
511                  (oci-handle-alloc (deref-vp envhp)
512                                    stmthp
513                                    +oci-htype-stmt+ 0 +null-void-pointer-pointer+)
514                  (oci-stmt-prepare (deref-vp stmthp)
515                                    (deref-vp errhp)
516                                    c-stmt-string
517                                    (uffi:foreign-string-length c--stmt-string)
518                                    +oci-ntv-syntax+ +oci-default+ :database db)
519                  (oci-attr-get (deref-vp stmthp)
520                                +oci-htype-stmt+
521                                stmttype
522                                +unsigned-int-null-pointer+
523                                +oci-attr-stmt-type+
524                                (deref-vp errhp)
525                                :database db)
526
527                  (setq select-p (= (uffi:deref-pointer stmttype :unsigned-short) 1))
528                  (let ((iters (if select-p 0 1)))
529
530                    (oci-stmt-execute (deref-vp svchp)
531                                      (deref-vp stmthp)
532                                      (deref-vp errhp)
533                                      iters 0 +null-void-pointer+ +null-void-pointer+ +oci-default+
534                                      :database db)))
535             ;; free resources unless a query
536             (unless select-p
537               (oci-handle-free (deref-vp stmthp) +oci-htype-stmt+)
538               (uffi:free-foreign-object stmthp))))
539
540         (cond
541           (select-p
542            (make-query-cursor db stmthp result-types field-names))
543           (t
544            nil))))))
545
546
547 ;; Return a QUERY-CURSOR representing the table returned from the OCI
548 ;; operation done through STMTHP.  TYPES is the argument of the same
549 ;; name from the external SQL function, controlling type conversion
550 ;; of the returned arguments.
551
552 (defun make-query-cursor (db stmthp result-types field-names)
553   (let ((qc (%make-query-cursor :db db
554                                 :stmthp stmthp
555                                 :cds (make-query-cursor-cds db stmthp
556                                                             result-types
557                                                             field-names))))
558     (refill-qc-buffers qc)
559     qc))
560
561
562 ;; the hairy part of MAKE-QUERY-CURSOR: Ask OCI for information
563 ;; about table columns, translate the information into a Lisp
564 ;; vector of column descriptors, and return it.
565
566 ;; Allegro defines several flavors of type conversion, but this
567 ;; implementation only supports the :AUTO flavor.
568
569 ;; A note of explanation: OCI's internal number format uses 21
570 ;; bytes (42 decimal digits). 2 separate (?) one-byte fields,
571 ;; scale and precision, are used to deduce the nature of these
572 ;; 21 bytes. See pp. 3-10, 3-26, and 6-13 of OCI documentation
573 ;; for more details.
574
575 ;; Mac OS X Note: According to table 6-8 in the Oracle 9i OCI
576 ;; documentation, PRECISION may actually be an sb2 instead of a
577 ;; single byte if performing an "implicit describe".  Using a
578 ;; signed short instead of an unsigned byte fixes a Mac OS X bug
579 ;; where PRECISION is always zero. -- JJB 20040713
580
581 ;; When calling OCI C code to handle the conversion, we have
582 ;; only two numeric types available to pass the return value:
583 ;; double-float and signed-long. It would be possible to
584 ;; bypass the OCI conversion functions and write Lisp code
585 ;; which reads the 21-byte field directly and decodes
586 ;; it. However this is left as an exercise for the reader. :-)
587
588 ;; The following table describes the mapping, based on the implicit
589 ;; assumption that C's "signed long" type is a 32-bit integer.
590 ;;
591 ;;   Internal Values                     SQL Type        C Return Type
592 ;;   ===============                     ========        =============
593 ;;   Precision > 0        SCALE = -127   FLOAT       --> double-float
594 ;;   Precision > 0 && <=9 SCALE = 0      INTEGER     --> signed-long
595 ;;   Precision = 0 || > 9 SCALE = 0      BIG INTEGER --> double-float
596 ;;   Precision > 0        SCALE > 0      DECIMAL     --> double-float
597
598 ;; (OCI uses 1-based indexing here.)
599
600 ;; KLUDGE: This should work for all other data types except those
601 ;; which don't actually fit in their fixed-width field (BLOBs and the
602 ;; like). As Winton says, we (Cadabra) don't need to worry much about
603 ;; those, since we can't reason with them, so we don't use them. But
604 ;; for a more general application it'd be good to have a more
605 ;; selective and rigorously correct test here for whether we can
606 ;; actually handle the given DEREF-DTYPE value. -- WHN 20000106
607
608 ;; Note: The OCI documentation doesn't seem to say whether the COLNAME
609 ;; value returned here is a newly-allocated copy which we're
610 ;; responsible for freeing, or a pointer into some system copy which
611 ;; will be freed when the system itself is shut down.  But judging
612 ;; from the way that the result is used in the cdemodsa.c example
613 ;; program, it looks like the latter: we should make our own copy of
614 ;; the value, but not try to free it.
615
616 ;; WORKAROUND: OCI seems to return ub2 values for the
617 ;; +oci-attr-data-size+ attribute even though its documentation claims
618 ;; that it returns a ub4, and even though the associated "sizep" value
619 ;; is 4, not 2.  In order to make the code here work reliably, without
620 ;; having to patch it later if OCI is ever fixed to match its
621 ;; documentation, we pre-zero COLSIZE before making the call into OCI.
622
623 ;; To exercise the weird OCI behavior (thereby blowing up the code
624 ;; below, beware!) try setting this value into COLSIZE, calling OCI,
625 ;; then looking at the value in COLSIZE.  (setf colsize #x12345678)
626 ;; debugging only
627
628 ;; Mac OS X Note: This workaround fails on a bigendian platform so
629 ;; I've changed the data type of COLNAME to :unsigned-short as per
630 ;; the Oracle 9i OCI documentation. -- JJB 20040713
631
632 (uffi:def-type byte-pointer (* :byte))
633 (uffi:def-type void-pointer-pointer (* :void-pointer))
634
635 (defun make-query-cursor-cds (database stmthp result-types field-names)
636   (declare (optimize (safety 3) #+nil (speed 3))
637            (type oracle-database database)
638            (type pointer-pointer-void stmthp))
639   (with-slots (errhp) database
640     (uffi:with-foreign-objects ((dtype-foreign :unsigned-short)
641                                 (parmdp :pointer-void)
642                                 (precision :short)
643                                 (scale :byte)
644                                 (colname '(* :unsigned-char))
645                                 (colnamelen 'ub4)
646                                 (colsize 'ub2)
647                                 (defnp ':pointer-void))
648       (let ((buffer nil)
649             (sizeof nil))
650         (do ((icolumn 0 (1+ icolumn))
651              (cds-as-reversed-list nil))
652             ((not (eql (oci-param-get (deref-vp stmthp)
653                                       +oci-htype-stmt+
654                                       (deref-vp errhp)
655                                       parmdp
656                                       (1+ icolumn) :database database)
657                        +oci-success+))
658              (coerce (reverse cds-as-reversed-list) 'simple-vector))
659           ;; Decode type of ICOLUMNth column into a type we're prepared to
660           ;; handle in Lisp.
661           (oci-attr-get (deref-vp parmdp)
662                         +oci-dtype-param+
663                         dtype-foreign
664                         +unsigned-int-null-pointer+
665                         +oci-attr-data-type+
666                         (deref-vp errhp))
667           (let ((dtype (uffi:deref-pointer dtype-foreign :unsigned-short)))
668             (declare (fixnum dtype))
669             (case dtype
670               (#.SQLT-DATE
671                (setf buffer (acquire-foreign-resource :unsigned-char
672                                                       (* 32 +n-buf-rows+)))
673                (setf sizeof 32 dtype #.SQLT-STR))
674               (#.SQLT-NUMBER
675                (oci-attr-get (deref-vp parmdp)
676                              +oci-dtype-param+
677                              precision
678                              +unsigned-int-null-pointer+
679                              +oci-attr-precision+
680                              (deref-vp errhp))
681                (oci-attr-get (deref-vp parmdp)
682                              +oci-dtype-param+
683                              scale
684                              +unsigned-int-null-pointer+
685                              +oci-attr-scale+
686                              (deref-vp errhp))
687                (let ((*scale (uffi:deref-pointer scale :byte))
688                      (*precision (uffi:deref-pointer precision :short)))
689
690                  ;;(format t "scale=~d, precision=~d~%" *scale *precision)
691                  (cond
692                   ((or (and (minusp *scale) (zerop *precision))
693                        (and (zerop *scale) (plusp *precision)))
694                    (setf buffer (acquire-foreign-resource :int +n-buf-rows+)
695                          sizeof 4                       ;; sizeof(int)
696                          dtype #.SQLT-INT))
697                   (t
698                    (setf buffer (acquire-foreign-resource :double +n-buf-rows+)
699                          sizeof 8                   ;; sizeof(double)
700                          dtype #.SQLT-FLT)))))
701               ;; Default to SQL-STR
702               (t
703                (setf (uffi:deref-pointer colsize :unsigned-short) 0)
704                (setf dtype #.SQLT-STR)
705                (oci-attr-get (deref-vp parmdp)
706                              +oci-dtype-param+
707                              colsize
708                              +unsigned-int-null-pointer+
709                              +oci-attr-data-size+
710                              (deref-vp errhp))
711                (let ((colsize-including-null (1+ (uffi:deref-pointer colsize :unsigned-short))))
712                  (setf buffer (acquire-foreign-resource
713                                :unsigned-char (* +n-buf-rows+ colsize-including-null)))
714                  (setf sizeof colsize-including-null))))
715             (let ((retcodes (acquire-foreign-resource :unsigned-short +n-buf-rows+))
716                   (indicators (acquire-foreign-resource :short +n-buf-rows+))
717                   (colname-string ""))
718               (when field-names
719                 (oci-attr-get (deref-vp parmdp)
720                               +oci-dtype-param+
721                               colname
722                               colnamelen
723                               +oci-attr-name+
724                               (deref-vp errhp))
725                 (setq colname-string (uffi:convert-from-foreign-string
726                                       (uffi:deref-pointer colname '(* :unsigned-char))
727                                       :length (uffi:deref-pointer colnamelen 'ub4)
728                                       :encoding (encoding database))))
729               (push (make-cd :name colname-string
730                              :sizeof sizeof
731                              :buffer buffer
732                              :oci-data-type dtype
733                              :retcodes retcodes
734                              :indicators indicators
735                              :result-type (cond
736                                            ((consp result-types)
737                                             (nth icolumn result-types))
738                                            ((null result-types)
739                                             :string)
740                                            (t
741                                             result-types)))
742                     cds-as-reversed-list)
743               (oci-define-by-pos (deref-vp stmthp)
744                                  defnp
745                                  (deref-vp errhp)
746                                  (1+ icolumn) ; OCI 1-based indexing again
747                                  (foreign-resource-buffer buffer)
748                                  sizeof
749                                  dtype
750                                  (foreign-resource-buffer indicators)
751                                  +unsigned-short-null-pointer+
752                                  (foreign-resource-buffer retcodes)
753                                  +oci-default+))))))))
754
755 ;; Release the resources associated with a QUERY-CURSOR.
756
757 (defun close-query (qc)
758   (oci-handle-free (deref-vp (qc-stmthp qc)) +oci-htype-stmt+)
759   (uffi:free-foreign-object (qc-stmthp qc))
760   (let ((cds (qc-cds qc)))
761     (dotimes (i (length cds))
762       (release-cd-resources (aref cds i))))
763   (values))
764
765
766 ;; Release the resources associated with a column description.
767
768 (defun release-cd-resources (cd)
769   (free-foreign-resource (cd-buffer cd))
770   (free-foreign-resource (cd-retcodes cd))
771   (free-foreign-resource (cd-indicators cd))
772   (values))
773
774
775 (defmethod database-name-from-spec (connection-spec (database-type (eql :oracle)))
776   (check-connection-spec connection-spec database-type (dsn user password))
777   (destructuring-bind (dsn user password) connection-spec
778     (declare (ignore password))
779     (concatenate 'string  dsn "/" user)))
780
781
782 (defmethod database-connect (connection-spec (database-type (eql :oracle)))
783   (check-connection-spec connection-spec database-type (dsn user password))
784   (destructuring-bind (data-source-name user password)
785       connection-spec
786     (let ((envhp (uffi:allocate-foreign-object :pointer-void))
787           (errhp (uffi:allocate-foreign-object :pointer-void))
788           (svchp (uffi:allocate-foreign-object :pointer-void))
789           (srvhp (uffi:allocate-foreign-object :pointer-void)))
790       ;; Requests to allocate environments and handles should never
791       ;; fail in normal operation, and they're done too early to
792       ;; handle errors very gracefully (since they're part of the
793       ;; error-handling mechanism themselves) so we just assert they
794       ;; work.
795
796       (setf (deref-vp envhp) +null-void-pointer+)
797
798       #-oci7
799       (oci-env-create envhp +oci-default+ +null-void-pointer+
800                       +null-void-pointer+ +null-void-pointer+
801                       +null-void-pointer+ 0 +null-void-pointer-pointer+)
802
803       #+oci7
804       (progn
805         (oci-initialize +oci-object+ +null-void-pointer+ +null-void-pointer+
806                         +null-void-pointer+ +null-void-pointer-pointer+)
807         (ignore-errors (oci-handle-alloc +null-void-pointer+ envhp
808                                          +oci-htype-env+ 0
809                                          +null-void-pointer-pointer+)) ;no testing return
810         (oci-env-init envhp +oci-default+ 0 +null-void-pointer-pointer+))
811
812       (oci-handle-alloc (deref-vp envhp) errhp
813                         +oci-htype-error+ 0 +null-void-pointer-pointer+)
814       (oci-handle-alloc (deref-vp envhp) srvhp
815                         +oci-htype-server+ 0 +null-void-pointer-pointer+)
816
817       (let ((db (make-instance 'oracle-database
818                   :name (database-name-from-spec connection-spec
819                                                  database-type)
820                   :connection-spec connection-spec
821                   :envhp envhp
822                   :errhp errhp
823                   :database-type :oracle
824                   :svchp svchp
825                   :dsn data-source-name
826                   :user user)))
827         (uffi:with-foreign-strings ((c-user user)
828                                     (c-password password)
829                                     (c-data-source-name data-source-name))
830           (oci-logon (deref-vp envhp)
831                      (deref-vp errhp)
832                      svchp
833                      c-user (length user)
834                      c-password (length password)
835                      c-data-source-name (length data-source-name)
836                      :database db))
837         ;; :date-format-length (1+ (length date-format)))))
838         (setf (slot-value db 'clsql-sys::state) :open)
839         (database-execute-command
840          (format nil "ALTER SESSION SET NLS_DATE_FORMAT='~A'" (date-format db)) db)
841         (let ((server-version
842                (caar (database-query
843                       "SELECT BANNER FROM V$VERSION WHERE BANNER LIKE '%Oracle%'" db nil nil))))
844           (setf (slot-value db 'server-version) server-version
845                 (slot-value db 'major-server-version) (major-client-version-from-string
846                                                        server-version)))
847         db))))
848
849
850 (defun major-client-version-from-string (str)
851   (cond
852     ((search " 10g " str)
853      10)
854     ((search "Oracle9i " str)
855      9)
856     ((search "Oracle8" str)
857      8)))
858
859 (defun major-server-version-from-string (str)
860   (when (> (length str) 2)
861     (cond
862       ((string= "10." (subseq str 0 3))
863        10)
864       ((string= "9." (subseq str 0 2))
865        9)
866       ((string= "8." (subseq str 0 2))
867        8))))
868
869
870 ;; Close a database connection.
871
872 (defmethod database-disconnect ((database oracle-database))
873   (osucc (oci-logoff (deref-vp (svchp database))
874                      (deref-vp (errhp database))))
875   (osucc (oci-handle-free (deref-vp (envhp database)) +oci-htype-env+))
876   ;; Note: It's neither required nor allowed to explicitly deallocate the
877   ;; ERRHP handle here, since it's owned by the ENVHP deallocated above,
878   ;; and was therefore automatically deallocated at the same time.
879   t)
880
881 ;;; Do the database operation described in SQL-STMT-STRING on database
882 ;;; DB and, if the command is a SELECT, return a representation of the
883 ;;; resulting table. The representation of the table is controlled by the
884 ;;; QUERY argument:
885 ;;;   * If QUERY is NIL, the table is returned as a list of rows, with
886 ;;;     each row represented by a list.
887 ;;;   * If QUERY is non-NIL, the result is returned as a QUERY-CURSOR
888 ;;;     suitable for FETCH-ROW and CLOSE-QUERY
889 ;;; The TYPES argument controls the type conversion method used
890 ;;; to construct the table. The Allegro version supports several possible
891 ;;; values for this argument, but we only support :AUTO.
892
893 (defmethod database-query (query-expression (database oracle-database) result-types field-names)
894   (let ((cursor (sql-stmt-exec query-expression database result-types field-names)))
895     ;; (declare (type (or query-cursor null) cursor))
896     (if (null cursor) ; No table was returned.
897         (values)
898       (do ((reversed-result nil))
899           (nil)
900         (let* ((eof-value :eof)
901                (row (fetch-row cursor nil eof-value (encoding database))))
902           (when (eq row eof-value)
903             (close-query cursor)
904             (if field-names
905                 (return (values (nreverse reversed-result)
906                                 (loop for cd across (qc-cds cursor)
907                                     collect (cd-name cd))))
908               (return (nreverse reversed-result))))
909           (push row reversed-result))))))
910
911
912 (defmethod database-create-sequence (sequence-name (database oracle-database))
913   (execute-command
914    (concatenate 'string "CREATE SEQUENCE " (sql-escape sequence-name))
915    :database database))
916
917 (defmethod database-drop-sequence (sequence-name (database oracle-database))
918   (execute-command
919    (concatenate 'string "DROP SEQUENCE " (sql-escape sequence-name))
920    :database database))
921
922 (defmethod database-sequence-next (sequence-name (database oracle-database))
923   (caar (database-query
924          (concatenate 'string "SELECT "
925                       (sql-escape sequence-name)
926                       ".NEXTVAL FROM dual")
927          database :auto nil)))
928
929 (defmethod database-sequence-last (sequence-name (database oracle-database))
930   (caar (database-query
931          (concatenate 'string "SELECT "
932                       (sql-escape sequence-name)
933                       ".CURRVAL FROM dual")
934          database :auto nil)))
935
936 (defmethod database-set-sequence-position (name position (database oracle-database))
937   (without-interrupts
938    (let* ((next (database-sequence-next name database))
939           (incr (- position next)))
940      (unless (zerop incr)
941        (database-execute-command
942         (format nil "ALTER SEQUENCE ~A INCREMENT BY ~D" name incr)
943         database))
944      (database-sequence-next name database)
945      (database-execute-command
946       (format nil "ALTER SEQUENCE ~A INCREMENT BY 1" name)
947       database))))
948
949 (defmethod database-list-sequences ((database oracle-database) &key owner)
950   (let ((query
951          (cond ((null owner)
952                 "select sequence_name from user_sequences")
953                ((eq owner :all)
954                 "select sequence_name from all_sequences")
955                (t
956                 (format nil
957                         "select user_sequences.sequence_name from user_sequences,all_sequences where user_sequences.sequence_name=all_sequences.sequence_name and all_sequences.sequence_owner='~:@(~A~)'"
958                         owner)))))
959     (mapcar #'car (database-query query database nil nil))))
960
961 (defmethod database-execute-command (sql-expression (database oracle-database))
962   (database-query sql-expression database nil nil)
963   (when (database-autocommit database)
964     (oracle-commit database))
965   t)
966
967
968 (defstruct (cd (:constructor make-cd)
969                (:print-function print-cd))
970   "a column descriptor: metadata about the data in a table"
971
972   ;; name of this column
973   (name (error "missing NAME") :type simple-string :read-only t)
974   ;; the size in bytes of a single element
975   (sizeof (error "missing SIZE") :type fixnum :read-only t)
976   ;; an array of +N-BUF-ROWS+ elements in C representation
977   (buffer (error "Missing BUFFER")
978           :type foreign-resource
979           :read-only t)
980   ;; an array of +N-BUF-ROWS+ OCI return codes in C representation.
981   ;; (There must be one return code for every element of every
982   ;; row in order to be able to represent nullness.)
983   (retcodes (error "Missing RETCODES")
984             :type foreign-resource
985             :read-only t)
986   (indicators (error "Missing INDICATORS")
987               :type foreign-resource
988               :read-only t)
989   ;; the OCI code for the data type of a single element
990   (oci-data-type (error "missing OCI-DATA-TYPE")
991                  :type fixnum
992                  :read-only t)
993   (result-type (error "missing RESULT-TYPE")
994                :read-only t))
995
996
997 (defun print-cd (cd stream depth)
998   (declare (ignore depth))
999   (print-unreadable-object (cd stream :type t)
1000     (format stream
1001             ":NAME ~S :OCI-DATA-TYPE ~S :OCI-DATA-SIZE ~S"
1002             (cd-name cd)
1003             (cd-oci-data-type cd)
1004             (cd-sizeof cd))))
1005
1006 (defun print-query-cursor (qc stream depth)
1007   (declare (ignore depth))
1008   (print-unreadable-object (qc stream :type t :identity t)
1009     (prin1 (qc-db qc) stream)))
1010
1011
1012 (defmethod database-query-result-set ((query-expression string)
1013                                       (database oracle-database)
1014                                       &key full-set result-types)
1015   (let ((cursor (sql-stmt-exec query-expression database result-types nil)))
1016     (if full-set
1017         (values cursor (length (qc-cds cursor)) nil)
1018         (values cursor (length (qc-cds cursor))))))
1019
1020
1021 (defmethod database-dump-result-set (result-set (database oracle-database))
1022   (close-query result-set))
1023
1024 (defmethod database-store-next-row (result-set (database oracle-database) list)
1025   (let* ((eof-value :eof)
1026          (row (fetch-row result-set nil eof-value (encoding database))))
1027     (unless (eq eof-value row)
1028       (loop for i from 0 below (length row)
1029           do (setf (nth i list) (nth i row)))
1030       list)))
1031
1032 (defmethod database-start-transaction ((database oracle-database))
1033   (call-next-method)
1034   ;; Not needed with simple transaction
1035   #+ignore
1036   (with-slots (svchp errhp) database
1037     (oci-trans-start (deref-vp svchp)
1038                      (deref-vp errhp)
1039                      60
1040                      +oci-trans-new+))
1041   t)
1042
1043
1044 (defun oracle-commit (database)
1045   (with-slots (svchp errhp) database
1046     (osucc (oci-trans-commit (deref-vp svchp)
1047                              (deref-vp errhp)
1048                              0))))
1049
1050 (defmethod database-commit-transaction ((database oracle-database))
1051   (call-next-method)
1052   (oracle-commit database)
1053   t)
1054
1055 (defmethod database-abort-transaction ((database oracle-database))
1056   (call-next-method)
1057   (osucc (oci-trans-rollback (deref-vp (svchp database))
1058                              (deref-vp (errhp database))
1059                              0))
1060   t)
1061
1062 ;; Specifications
1063
1064 (defmethod db-type-has-bigint? ((type (eql :oracle)))
1065   nil)
1066
1067 (defmethod db-type-has-fancy-math? ((db-type (eql :oracle)))
1068   t)
1069
1070 (defmethod db-type-has-boolean-where? ((db-type (eql :oracle)))
1071   nil)
1072
1073 (when (clsql-sys:database-type-library-loaded :oracle)
1074   (clsql-sys:initialize-database-type :database-type :oracle))