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