8475f47f904883e96b31c5756d3fa07754579f2c
[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 errbuf)))
170            (uffi:free-foreign-object errcode)
171            (uffi:free-foreign-object 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 (* :short))
368 (uffi:def-type int-array (* :int))
369 (uffi:def-type double-array (* :double))
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                                 (defnp ':pointer-void))
645       (let ((buffer nil)
646             (sizeof nil))
647         (do ((icolumn 0 (1+ icolumn))
648              (cds-as-reversed-list nil))
649             ((not (eql (oci-param-get (deref-vp stmthp)
650                                       +oci-htype-stmt+
651                                       (deref-vp errhp)
652                                       parmdp
653                                       (1+ icolumn) :database database)
654                        +oci-success+))
655              (coerce (reverse cds-as-reversed-list) 'simple-vector))
656           ;; Decode type of ICOLUMNth column into a type we're prepared to
657           ;; handle in Lisp.
658           (oci-attr-get (deref-vp parmdp)
659                         +oci-dtype-param+
660                         dtype-foreign
661                         +unsigned-int-null-pointer+
662                         +oci-attr-data-type+
663                         (deref-vp errhp))
664           (let ((dtype (uffi:deref-pointer dtype-foreign :unsigned-short)))
665             (declare (fixnum dtype))
666             (case dtype
667               (#.SQLT-DATE
668                (setf buffer (acquire-foreign-resource :unsigned-char
669                                                       (* 32 +n-buf-rows+)))
670                (setf sizeof 32 dtype #.SQLT-STR))
671               (#.SQLT-NUMBER
672                (oci-attr-get (deref-vp parmdp)
673                              +oci-dtype-param+
674                              precision
675                              +unsigned-int-null-pointer+
676                              +oci-attr-precision+
677                              (deref-vp errhp))
678                (oci-attr-get (deref-vp parmdp)
679                              +oci-dtype-param+
680                              scale
681                              +unsigned-int-null-pointer+
682                              +oci-attr-scale+
683                              (deref-vp errhp))
684                (let ((*scale (uffi:deref-pointer scale :byte))
685                      (*precision (uffi:deref-pointer precision :short)))
686
687                  ;;(format t "scale=~d, precision=~d~%" *scale *precision)
688                  (cond
689                   ((or (and (minusp *scale) (zerop *precision))
690                        (and (zerop *scale) (plusp *precision)))
691                    (setf buffer (acquire-foreign-resource :int +n-buf-rows+)
692                          sizeof 4                       ;; sizeof(int)
693                          dtype #.SQLT-INT))
694                   (t
695                    (setf buffer (acquire-foreign-resource :double +n-buf-rows+)
696                          sizeof 8                   ;; sizeof(double)
697                          dtype #.SQLT-FLT)))))
698               ;; Default to SQL-STR
699               (t
700                (setf (uffi:deref-pointer colsize :unsigned-short) 0)
701                (setf dtype #.SQLT-STR)
702                (oci-attr-get (deref-vp parmdp)
703                              +oci-dtype-param+
704                              colsize
705                              +unsigned-int-null-pointer+
706                              +oci-attr-data-size+
707                              (deref-vp errhp))
708                (let ((colsize-including-null (1+ (uffi:deref-pointer colsize :unsigned-short))))
709                  (setf buffer (acquire-foreign-resource
710                                :unsigned-char (* +n-buf-rows+ colsize-including-null)))
711                  (setf sizeof colsize-including-null))))
712             (let ((retcodes (acquire-foreign-resource :unsigned-short +n-buf-rows+))
713                   (indicators (acquire-foreign-resource :short +n-buf-rows+))
714                   (colname-string ""))
715               (when field-names
716                 (oci-attr-get (deref-vp parmdp)
717                               +oci-dtype-param+
718                               colname
719                               colnamelen
720                               +oci-attr-name+
721                               (deref-vp errhp))
722                 (setq colname-string (uffi:convert-from-foreign-string
723                                       (uffi:deref-pointer colname '(* :unsigned-char))
724                                       :length (uffi:deref-pointer colnamelen 'ub4))))
725               (push (make-cd :name colname-string
726                              :sizeof sizeof
727                              :buffer buffer
728                              :oci-data-type dtype
729                              :retcodes retcodes
730                              :indicators indicators
731                              :result-type (cond
732                                            ((consp result-types)
733                                             (nth icolumn result-types))
734                                            ((null result-types)
735                                             :string)
736                                            (t
737                                             result-types)))
738                     cds-as-reversed-list)
739               (oci-define-by-pos (deref-vp stmthp)
740                                  defnp
741                                  (deref-vp errhp)
742                                  (1+ icolumn) ; OCI 1-based indexing again
743                                  (foreign-resource-buffer buffer)
744                                  sizeof
745                                  dtype
746                                  (foreign-resource-buffer indicators)
747                                  +unsigned-short-null-pointer+
748                                  (foreign-resource-buffer retcodes)
749                                  +oci-default+))))))))
750
751 ;; Release the resources associated with a QUERY-CURSOR.
752
753 (defun close-query (qc)
754   (oci-handle-free (deref-vp (qc-stmthp qc)) +oci-htype-stmt+)
755   (uffi:free-foreign-object (qc-stmthp qc))
756   (let ((cds (qc-cds qc)))
757     (dotimes (i (length cds))
758       (release-cd-resources (aref cds i))))
759   (values))
760
761
762 ;; Release the resources associated with a column description.
763
764 (defun release-cd-resources (cd)
765   (free-foreign-resource (cd-buffer cd))
766   (free-foreign-resource (cd-retcodes cd))
767   (free-foreign-resource (cd-indicators cd))
768   (values))
769
770
771 (defmethod database-name-from-spec (connection-spec (database-type (eql :oracle)))
772   (check-connection-spec connection-spec database-type (dsn user password))
773   (destructuring-bind (dsn user password) connection-spec
774     (declare (ignore password))
775     (concatenate 'string  dsn "/" user)))
776
777
778 (defmethod database-connect (connection-spec (database-type (eql :oracle)))
779   (check-connection-spec connection-spec database-type (dsn user password))
780   (destructuring-bind (data-source-name user password)
781       connection-spec
782     (let ((envhp (uffi:allocate-foreign-object :pointer-void))
783           (errhp (uffi:allocate-foreign-object :pointer-void))
784           (svchp (uffi:allocate-foreign-object :pointer-void))
785           (srvhp (uffi:allocate-foreign-object :pointer-void)))
786       ;; Requests to allocate environments and handles should never
787       ;; fail in normal operation, and they're done too early to
788       ;; handle errors very gracefully (since they're part of the
789       ;; error-handling mechanism themselves) so we just assert they
790       ;; work.
791
792       (setf (deref-vp envhp) +null-void-pointer+)
793
794       #-oci7
795       (oci-env-create envhp +oci-default+ +null-void-pointer+
796                       +null-void-pointer+ +null-void-pointer+
797                       +null-void-pointer+ 0 +null-void-pointer-pointer+)
798
799       #+oci7
800       (progn
801         (oci-initialize +oci-object+ +null-void-pointer+ +null-void-pointer+
802                         +null-void-pointer+ +null-void-pointer-pointer+)
803         (ignore-errors (oci-handle-alloc +null-void-pointer+ envhp
804                                          +oci-htype-env+ 0
805                                          +null-void-pointer-pointer+)) ;no testing return
806         (oci-env-init envhp +oci-default+ 0 +null-void-pointer-pointer+))
807
808       (oci-handle-alloc (deref-vp envhp) errhp
809                         +oci-htype-error+ 0 +null-void-pointer-pointer+)
810       (oci-handle-alloc (deref-vp envhp) srvhp
811                         +oci-htype-server+ 0 +null-void-pointer-pointer+)
812
813       (let ((db (make-instance 'oracle-database
814                   :name (database-name-from-spec connection-spec
815                                                  database-type)
816                   :connection-spec connection-spec
817                   :envhp envhp
818                   :errhp errhp
819                   :database-type :oracle
820                   :svchp svchp
821                   :dsn data-source-name
822                   :user user)))
823         (uffi:with-foreign-strings ((c-user user)
824                                     (c-password password)
825                                     (c-data-source-name data-source-name))
826           (oci-logon (deref-vp envhp)
827                      (deref-vp errhp)
828                      svchp
829                      c-user (length user)
830                      c-password (length password)
831                      c-data-source-name (length data-source-name)
832                      :database db))
833         ;; :date-format-length (1+ (length date-format)))))
834         (setf (slot-value db 'clsql-sys::state) :open)
835         (database-execute-command
836          (format nil "ALTER SESSION SET NLS_DATE_FORMAT='~A'" (date-format db)) db)
837         (let ((server-version
838                (caar (database-query
839                       "SELECT BANNER FROM V$VERSION WHERE BANNER LIKE '%Oracle%'" db nil nil))))
840           (setf (slot-value db 'server-version) server-version
841                 (slot-value db 'major-server-version) (major-client-version-from-string
842                                                        server-version)))
843         db))))
844
845
846 (defun major-client-version-from-string (str)
847   (cond
848     ((search " 10g " str)
849      10)
850     ((search "Oracle9i " str)
851      9)
852     ((search "Oracle8" str)
853      8)))
854
855 (defun major-server-version-from-string (str)
856   (when (> (length str) 2)
857     (cond
858       ((string= "10." (subseq str 0 3))
859        10)
860       ((string= "9." (subseq str 0 2))
861        9)
862       ((string= "8." (subseq str 0 2))
863        8))))
864
865
866 ;; Close a database connection.
867
868 (defmethod database-disconnect ((database oracle-database))
869   (osucc (oci-logoff (deref-vp (svchp database))
870                      (deref-vp (errhp database))))
871   (osucc (oci-handle-free (deref-vp (envhp database)) +oci-htype-env+))
872   ;; Note: It's neither required nor allowed to explicitly deallocate the
873   ;; ERRHP handle here, since it's owned by the ENVHP deallocated above,
874   ;; and was therefore automatically deallocated at the same time.
875   t)
876
877 ;;; Do the database operation described in SQL-STMT-STRING on database
878 ;;; DB and, if the command is a SELECT, return a representation of the
879 ;;; resulting table. The representation of the table is controlled by the
880 ;;; QUERY argument:
881 ;;;   * If QUERY is NIL, the table is returned as a list of rows, with
882 ;;;     each row represented by a list.
883 ;;;   * If QUERY is non-NIL, the result is returned as a QUERY-CURSOR
884 ;;;     suitable for FETCH-ROW and CLOSE-QUERY
885 ;;; The TYPES argument controls the type conversion method used
886 ;;; to construct the table. The Allegro version supports several possible
887 ;;; values for this argument, but we only support :AUTO.
888
889 (defmethod database-query (query-expression (database oracle-database) result-types field-names)
890   (let ((cursor (sql-stmt-exec query-expression database result-types field-names)))
891     ;; (declare (type (or query-cursor null) cursor))
892     (if (null cursor) ; No table was returned.
893         (values)
894       (do ((reversed-result nil))
895           (nil)
896         (let* ((eof-value :eof)
897                (row (fetch-row cursor nil eof-value)))
898           (when (eq row eof-value)
899             (close-query cursor)
900             (if field-names
901                 (return (values (nreverse reversed-result)
902                                 (loop for cd across (qc-cds cursor)
903                                     collect (cd-name cd))))
904               (return (nreverse reversed-result))))
905           (push row reversed-result))))))
906
907
908 (defmethod database-create-sequence (sequence-name (database oracle-database))
909   (execute-command
910    (concatenate 'string "CREATE SEQUENCE " (sql-escape sequence-name))
911    :database database))
912
913 (defmethod database-drop-sequence (sequence-name (database oracle-database))
914   (execute-command
915    (concatenate 'string "DROP SEQUENCE " (sql-escape sequence-name))
916    :database database))
917
918 (defmethod database-sequence-next (sequence-name (database oracle-database))
919   (caar (database-query
920          (concatenate 'string "SELECT "
921                       (sql-escape sequence-name)
922                       ".NEXTVAL FROM dual")
923          database :auto nil)))
924
925 (defmethod database-sequence-last (sequence-name (database oracle-database))
926   (caar (database-query
927          (concatenate 'string "SELECT "
928                       (sql-escape sequence-name)
929                       ".CURRVAL FROM dual")
930          database :auto nil)))
931
932 (defmethod database-set-sequence-position (name position (database oracle-database))
933   (without-interrupts
934    (let* ((next (database-sequence-next name database))
935           (incr (- position next)))
936      (unless (zerop incr)
937        (database-execute-command
938         (format nil "ALTER SEQUENCE ~A INCREMENT BY ~D" name incr)
939         database))
940      (database-sequence-next name database)
941      (database-execute-command
942       (format nil "ALTER SEQUENCE ~A INCREMENT BY 1" name)
943       database))))
944
945 (defmethod database-list-sequences ((database oracle-database) &key owner)
946   (let ((query
947          (cond ((null owner)
948                 "select sequence_name from user_sequences")
949                ((eq owner :all)
950                 "select sequence_name from all_sequences")
951                (t
952                 (format nil
953                         "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~)'"
954                         owner)))))
955     (mapcar #'car (database-query query database nil nil))))
956
957 (defmethod database-execute-command (sql-expression (database oracle-database))
958   (database-query sql-expression database nil nil)
959   (when (database-autocommit database)
960     (oracle-commit database))
961   t)
962
963
964 (defstruct (cd (:constructor make-cd)
965                (:print-function print-cd))
966   "a column descriptor: metadata about the data in a table"
967
968   ;; name of this column
969   (name (error "missing NAME") :type simple-string :read-only t)
970   ;; the size in bytes of a single element
971   (sizeof (error "missing SIZE") :type fixnum :read-only t)
972   ;; an array of +N-BUF-ROWS+ elements in C representation
973   (buffer (error "Missing BUFFER")
974           :type foreign-resource
975           :read-only t)
976   ;; an array of +N-BUF-ROWS+ OCI return codes in C representation.
977   ;; (There must be one return code for every element of every
978   ;; row in order to be able to represent nullness.)
979   (retcodes (error "Missing RETCODES")
980             :type foreign-resource
981             :read-only t)
982   (indicators (error "Missing INDICATORS")
983               :type foreign-resource
984               :read-only t)
985   ;; the OCI code for the data type of a single element
986   (oci-data-type (error "missing OCI-DATA-TYPE")
987                  :type fixnum
988                  :read-only t)
989   (result-type (error "missing RESULT-TYPE")
990                :read-only t))
991
992
993 (defun print-cd (cd stream depth)
994   (declare (ignore depth))
995   (print-unreadable-object (cd stream :type t)
996     (format stream
997             ":NAME ~S :OCI-DATA-TYPE ~S :OCI-DATA-SIZE ~S"
998             (cd-name cd)
999             (cd-oci-data-type cd)
1000             (cd-sizeof cd))))
1001
1002 (defun print-query-cursor (qc stream depth)
1003   (declare (ignore depth))
1004   (print-unreadable-object (qc stream :type t :identity t)
1005     (prin1 (qc-db qc) stream)))
1006
1007
1008 (defmethod database-query-result-set ((query-expression string)
1009                                       (database oracle-database)
1010                                       &key full-set result-types)
1011   (let ((cursor (sql-stmt-exec query-expression database result-types nil)))
1012     (if full-set
1013         (values cursor (length (qc-cds cursor)) nil)
1014         (values cursor (length (qc-cds cursor))))))
1015
1016
1017 (defmethod database-dump-result-set (result-set (database oracle-database))
1018   (close-query result-set))
1019
1020 (defmethod database-store-next-row (result-set (database oracle-database) list)
1021   (let* ((eof-value :eof)
1022          (row (fetch-row result-set nil eof-value)))
1023     (unless (eq eof-value row)
1024       (loop for i from 0 below (length row)
1025           do (setf (nth i list) (nth i row)))
1026       list)))
1027
1028 (defmethod database-start-transaction ((database oracle-database))
1029   (call-next-method)
1030   ;; Not needed with simple transaction
1031   #+ignore
1032   (with-slots (svchp errhp) database
1033     (oci-trans-start (deref-vp svchp)
1034                      (deref-vp errhp)
1035                      60
1036                      +oci-trans-new+))
1037   t)
1038
1039
1040 (defun oracle-commit (database)
1041   (with-slots (svchp errhp) database
1042     (osucc (oci-trans-commit (deref-vp svchp)
1043                              (deref-vp errhp)
1044                              0))))
1045
1046 (defmethod database-commit-transaction ((database oracle-database))
1047   (call-next-method)
1048   (oracle-commit database)
1049   t)
1050
1051 (defmethod database-abort-transaction ((database oracle-database))
1052   (call-next-method)
1053   (osucc (oci-trans-rollback (deref-vp (svchp database))
1054                              (deref-vp (errhp database))
1055                              0))
1056   t)
1057
1058 ;; Specifications
1059
1060 (defmethod db-type-has-bigint? ((type (eql :oracle)))
1061   nil)
1062
1063 (defmethod db-type-has-fancy-math? ((db-type (eql :oracle)))
1064   t)
1065
1066 (defmethod db-type-has-boolean-where? ((db-type (eql :oracle)))
1067   nil)
1068
1069 (when (clsql-sys:database-type-library-loaded :oracle)
1070   (clsql-sys:initialize-database-type :database-type :oracle))