X-Git-Url: http://git.kpe.io/?p=clsql.git;a=blobdiff_plain;f=db-oracle%2Foracle-sql.lisp;h=9460011db0d7f1a687d4faafa513eef7eeee9c0c;hp=b5876d44cbff9d96a6358d544af5e1728d65a43c;hb=2ba41ebdcd4963728c8d5460e389a5381b8e2293;hpb=1df8353cc15bcbf98078605cb6955aafa622ecea diff --git a/db-oracle/oracle-sql.lisp b/db-oracle/oracle-sql.lisp index b5876d4..9460011 100644 --- a/db-oracle/oracle-sql.lisp +++ b/db-oracle/oracle-sql.lisp @@ -4,8 +4,6 @@ ;;;; ;;;; Name: oracle-sql.lisp ;;;; -;;;; $Id$ -;;;; ;;;; This file is part of CLSQL. ;;;; ;;;; CLSQL users are granted the rights to distribute and use this software @@ -37,15 +35,15 @@ likely that we'll have to worry about the CMUCL limit.")) (defmacro deref-vp (foreign-object) `(the vp-type (uffi:deref-pointer (the vpp-type ,foreign-object) :pointer-void))) -;; constants - from OCI? - -(defvar +unsigned-char-null-pointer+ +(uffi:def-pointer-var +unsigned-char-null-pointer+ (uffi:make-null-pointer :unsigned-char)) -(defvar +unsigned-short-null-pointer+ +(uffi:def-pointer-var +unsigned-short-null-pointer+ (uffi:make-null-pointer :unsigned-short)) -(defvar +unsigned-int-null-pointer+ +(uffi:def-pointer-var +unsigned-int-null-pointer+ (uffi:make-null-pointer :unsigned-int)) +;; constants - from OCI? + (defconstant +var-not-in-list+ 1007) (defconstant +no-data-found+ 1403) (defconstant +null-value-returned+ 1405) @@ -64,7 +62,7 @@ likely that we'll have to worry about the CMUCL limit.")) ;;; database. Thus, there's no obstacle to having any number of DB ;;; objects referring to the same database. -(uffi:def-type pointer-pointer-void '(* :pointer-void)) +(uffi:def-type pointer-pointer-void (* :pointer-void)) (defclass oracle-database (database) ; was struct db ((envhp @@ -99,7 +97,7 @@ likely that we'll have to worry about the CMUCL limit.")) (date-format :initarg :date-format :reader date-format - :initform "YYYY-MM-DD HH24:MI:SS\"+00\"") + :initform "YYYY-MM-DD HH24:MI:SS\".0\"") (date-format-length :type number :documentation @@ -107,7 +105,7 @@ likely that we'll have to worry about the CMUCL limit.")) output format. In order to extract date strings from output buffers holding multiple date strings in fixed-width fields, we need to know the length of that format.") - (server-version + (server-version :type (or null string) :initarg :server-version :reader server-version @@ -118,57 +116,74 @@ the length of that format.") :initarg :major-server-version :reader major-server-version :documentation - "The major version number of the Oracle server, should be 8, 9, or 10") - (client-version - :type (or null string) - :initarg :client-version - :reader client-version - :documentation - "Version string of Oracle client.") - (major-client-version - :type (or null fixnum) - :initarg :major-client-version - :reader major-client-version - :documentation - "The major version number of the Oracle client, should be 8, 9, or 10"))) - + "The major version number of the Oracle server, should be 8, 9, or 10"))) + +;;; Handle a non-successful result from an OCI function. +(defun handle-oci-result (result database nulls-ok) + (case result + (#.+oci-success+ + +oci-success+) + (#.+oci-error+ + (handle-oci-error :database database :nulls-ok nulls-ok)) + (#.+oci-no-data+ + (error 'sql-database-error :message "OCI No Data Found")) + (#.+oci-success-with-info+ + (error 'sql-database-error :message "internal error: unexpected +oci-success-with-info")) + (#.+oci-invalid-handle+ + (error 'sql-database-error :message "OCI Invalid Handle")) + (#.+oci-need-data+ + (error 'sql-database-error :message "OCI Need Data")) + (#.+oci-still-executing+ + (error 'sql-temporary-error :message "OCI Still Executing")) + (#.+oci-continue+ + (error 'sql-database-error :message "OCI Continue")) + (1804 + (error 'sql-database-error :message "Check ORACLE_HOME and NLS settings.")) + (t + (error 'sql-database-error + :message + (format nil "OCI unknown error, code=~A" result))))) ;;; Handle the messy case of return code=+oci-error+, querying the ;;; system for subcodes and reporting them as appropriate. ERRHP and ;;; NULLS-OK are as in the OERR function. (defun handle-oci-error (&key database nulls-ok) - (cond (database - (with-slots (errhp) - database - (uffi:with-foreign-objects ((errbuf '(:array :unsigned-char - #.+errbuf-len+)) - (errcode :long)) - ;; ensure errbuf empty string - (setf (uffi:deref-array errbuf '(:array :unsigned-char) 0) - (uffi:ensure-char-storable (code-char 0))) - - (setf (uffi:deref-pointer errcode :long) 0) - (uffi:with-cstring (sqlstate nil) - (oci-error-get (deref-vp errhp) 1 - sqlstate - errcode - (uffi:char-array-to-pointer errbuf) - +errbuf-len+ +oci-htype-error+)) - (let ((subcode (uffi:deref-pointer errcode :long))) - (unless (and nulls-ok (= subcode +null-value-returned+)) - (error 'sql-database-error - :database database - :error-id subcode - :message (uffi:convert-from-foreign-string errbuf))))))) - (nulls-ok - (error 'sql-database-error - :database database - :message "can't handle NULLS-OK without ERRHP")) - (t - (error 'sql-database-error - :database database - :message "OCI Error (and no ERRHP available to find subcode)")))) + (cond + (database + (with-slots (errhp) database + (let ((errcode (uffi:allocate-foreign-object 'sb4)) + (errbuf (uffi:allocate-foreign-string #.+errbuf-len+))) + ;; ensure errbuf empty string + (setf (uffi:deref-array errbuf '(:array :unsigned-char) 0) + (uffi:ensure-char-storable (code-char 0))) + (setf (uffi:deref-pointer errcode 'sb4) 0) + + (uffi:with-cstring (sqlstate nil) + (oci-error-get (deref-vp errhp) 1 + sqlstate + errcode + (uffi:char-array-to-pointer errbuf) + +errbuf-len+ +oci-htype-error+)) + (let ((subcode (uffi:deref-pointer errcode 'sb4)) + (errstr (uffi:convert-from-foreign-string + errbuf + :encoding (when database (encoding database))))) + (uffi:free-foreign-object errcode) + (uffi:free-foreign-object errbuf) + (unless (and nulls-ok (= subcode +null-value-returned+)) + (error 'sql-database-error + :database database + :error-id subcode + :message errstr)))))) + (nulls-ok + (error 'sql-database-error + :database database + :message "can't handle NULLS-OK without ERRHP")) + (t + (error 'sql-database-error + :database database + :message "OCI Error (and no ERRHP available to find subcode)")))) ;;; Require an OCI success code. ;;; @@ -184,13 +199,13 @@ the length of that format.") (declare (type fixnum code)) (unless (= code +oci-success+) (error 'sql-database-error - :message (format nil "unexpected OCI failure, code=~S" code)))) + :message (format nil "unexpected OCI failure, code=~S" code)))) ;;; Enabling this can be handy for low-level debugging. #+nil (progn - (trace oci-initialize #+oci-8-1-5 oci-env-create oci-handle-alloc oci-logon + (trace #-oci7 oci-env-create oci-initialize oci-handle-alloc oci-logon oci-error-get oci-stmt-prepare oci-stmt-execute oci-param-get oci-logon oci-attr-get oci-define-by-pos oci-stmt-fetch) (setf debug::*debug-print-length* nil)) @@ -202,14 +217,15 @@ the length of that format.") (uffi:def-type string-pointer (* :unsigned-char)) -(defun deref-oci-string (arrayptr string-index size) +(defun deref-oci-string (arrayptr string-index size encoding) (declare (type string-pointer arrayptr)) (declare (type (mod #.+n-buf-rows+) string-index)) (declare (type (and unsigned-byte fixnum) size)) - (let ((str (uffi:convert-from-foreign-string - (uffi:make-pointer - (+ (uffi:pointer-address arrayptr) (* string-index size)) - :unsigned-char)))) + (let ((str (uffi:convert-from-foreign-string + (uffi:make-pointer + (+ (uffi:pointer-address arrayptr) (* string-index size)) + :unsigned-char) + :encoding encoding))) (if (string-equal str "NULL") nil str))) ;; the OCI library, part Z: no-longer used logic to convert from @@ -224,95 +240,115 @@ the length of that format.") #+nil (defun deref-oci-date (arrayptr index) - (oci-date->universal-time (uffi:pointer-address - (uffi:deref-array arrayptr - '(:array :unsigned-char) - (* index +oci-date-bytes+))))) + (oci-date->universal-time (uffi:pointer-address + (uffi:deref-array arrayptr + '(:array :unsigned-char) + (* index +oci-date-bytes+))))) #+nil (defun oci-date->universal-time (oci-date) (declare (type (alien (* :unsigned-char)) oci-date)) (flet (;; a character from OCI-DATE, interpreted as an unsigned byte - (ub (i) - (declare (type (mod #.+oci-date-bytes+) i)) - (mod (uffi:deref-array oci-date string-array i) 256))) + (ub (i) + (declare (type (mod #.+oci-date-bytes+) i)) + (mod (uffi:deref-array oci-date string-array i) 256))) (let* ((century (* (- (ub 0) 100) 100)) - (year (+ century (- (ub 1) 100))) - (month (ub 2)) - (day (ub 3)) - (hour (1- (ub 4))) - (minute (1- (ub 5))) - (second (1- (ub 6)))) + (year (+ century (- (ub 1) 100))) + (month (ub 2)) + (day (ub 3)) + (hour (1- (ub 4))) + (minute (1- (ub 5))) + (second (1- (ub 6)))) (encode-universal-time second minute hour day month year)))) (defmethod database-list-tables ((database oracle-database) &key owner) (let ((query - (if owner - (format nil - "select user_tables.table_name from user_tables,all_tables where user_tables.table_name=all_tables.table_name and all_tables.owner='~:@(~A~)'" - owner) - "select table_name from user_tables"))) + (cond ((null owner) + "select table_name from user_tables") + ((eq owner :all) + "select table_name from all_tables") + (t + (format nil + "select user_tables.table_name from user_tables,all_tables where user_tables.table_name=all_tables.table_name and all_tables.owner='~:@(~A~)'" + owner))))) (mapcar #'car (database-query query database nil nil)))) (defmethod database-list-views ((database oracle-database) &key owner) (let ((query - (if owner - (format nil - "select user_views.view_name from user_views,all_views where user_views.view_name=all_views.view_name and all_views.owner='~:@(~A~)'" - owner) - "select view_name from user_views"))) + (cond ((null owner) + "select view_name from user_views") + ((eq owner :all) + "select view_name from all_views") + (t + (format nil + "select user_views.view_name from user_views,all_views where user_views.view_name=all_views.view_name and all_views.owner='~:@(~A~)'" + owner))))) (mapcar #'car - (database-query query database nil nil)))) + (database-query query database nil nil)))) (defmethod database-list-indexes ((database oracle-database) &key (owner nil)) (let ((query - (if owner - (format nil - "select user_indexes.index_name from user_indexes,all_indexes where user_indexes.index_name=all_indexes.index_name and all_indexes.owner='~:@(~A~)'" - owner) - "select index_name from user_indexes"))) + (cond ((null owner) + "select index_name from user_indexes") + ((eq owner :all) + "select index_name from all_indexes") + (t (format nil + "select user_indexes.index_name from user_indexes,all_indexes where user_indexes.index_name=all_indexes.index_name and all_indexes.owner='~:@(~A~)'" + owner))))) (mapcar #'car (database-query query database nil nil)))) (defmethod database-list-table-indexes (table (database oracle-database) - &key (owner nil)) + &key (owner nil)) (let ((query - (if owner - (format nil - "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~)'" - table owner) - (format nil "select index_name from user_indexes where table_name='~A'" - table)))) + (cond ((null owner) + (format nil "select index_name from user_indexes where table_name='~A'" + table)) + ((eq owner :all) + (format nil "select index_name from all_indexes where table_name='~A'" + table)) + (t + (format nil + "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~)'" + table owner))))) (mapcar #'car (database-query query database nil nil)))) (defmethod database-list-attributes (table (database oracle-database) &key owner) (let ((query - (if owner - (format nil - "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~)'" - table owner) - (format nil - "select column_name from user_tab_columns where table_name='~A'" - table)))) + (cond ((null owner) + (format nil "select column_name from user_tab_columns where table_name='~A'" + table)) + ((eq owner :all) + (format nil "select column_name from all_tab_columns where table_name='~A'" + table)) + (t + (format nil + "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~)'" + table owner))))) (mapcar #'car (database-query query database nil nil)))) (defmethod database-attribute-type (attribute (table string) - (database oracle-database) - &key (owner nil)) - (let ((query - (if owner - (format nil - "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~)'" - table attribute owner) - (format nil - "select data_type,data_length,data_scale,nullable from user_tab_columns where table_name='~A' and column_name='~A'" - table attribute)))) + (database oracle-database) + &key (owner nil)) + (let ((query + (cond ((null owner) + (format nil + "select data_type,data_length,data_scale,nullable from user_tab_columns where table_name='~A' and column_name='~A'" + table attribute)) + ((eq owner :all) + (format nil + "select data_type,data_length,data_scale,nullable from all_tab_columns where table_name='~A' and column_name='~A'" + table attribute)) + (t + (format nil + "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~)'" + table attribute owner))))) (destructuring-bind (type length scale nullable) (car (database-query query database :auto nil)) - (values (ensure-keyword type) length scale - (if (char-equal #\Y (schar nullable 0)) 1 0))))) - + (values (ensure-keyword type) length scale + (if (char-equal #\Y (schar nullable 0)) 1 0))))) + ;; Return one row of the table referred to by QC, represented as a ;; list; or if there are no more rows, signal an error if EOF-ERRORP, ;; or return EOF-VALUE otherwise. @@ -331,9 +367,11 @@ the length of that format.") ;; STREAM which has no more data, and QC is not a STREAM, we signal ;; DBI-ERROR instead. -(uffi:def-type short-array '(:array :short)) -(uffi:def-type int-pointer '(* :int)) -(uffi:def-type double-pointer '(* :double)) +(uffi:def-type short-array (* :short)) +(uffi:def-type int-array (* :int)) +(uffi:def-type double-array (* :double)) +(uffi:def-type int-pointer (* :int)) +(uffi:def-type double-pointer (* :double)) ;;; the result of a database query: a cursor through a table (defstruct (oracle-result-set (:print-function print-query-cursor) @@ -343,12 +381,12 @@ the length of that format.") :type oracle-database :read-only t) (stmthp (error "missing STMTHP") ; the statement handle used to create -;; :type alien ; this table. owned by the QUERY-CURSOR +;; :type alien ; this table. owned by the QUERY-CURSOR :read-only t) ; object, deallocated on CLOSE-QUERY (cds) ; (error "missing CDS") ; column descriptors ; :type (simple-array cd 1) - ; :read-only t) - (n-from-oci + ; :read-only t) + (n-from-oci 0 ; buffered rows: number of rows recv'd :type (integer 0 #.+n-buf-rows+)) ; from the database on the last read (n-to-dbi @@ -367,45 +405,51 @@ the length of that format.") ; from it after that.. -(defun fetch-row (qc &optional (eof-errorp t) eof-value) - ;;(declare (optimize (speed 3))) +(defun fetch-row (qc &optional (eof-errorp t) eof-value encoding) + (declare (optimize (speed 3))) (cond ((zerop (qc-n-from-oci qc)) - (if eof-errorp - (error 'sql-database-error :message - (format nil "no more rows available in ~S" qc)) - eof-value)) - ((>= (qc-n-to-dbi qc) - (qc-n-from-oci qc)) - (refill-qc-buffers qc) - (fetch-row qc nil eof-value)) - (t - (let ((cds (qc-cds qc)) - (reversed-result nil) - (irow (qc-n-to-dbi qc))) - (dotimes (icd (length cds)) - (let* ((cd (aref cds icd)) - (b (foreign-resource-buffer (cd-buffer cd))) - (value - (let* ((arb (foreign-resource-buffer (cd-indicators cd))) - (indicator (uffi:deref-array arb '(:array :short) irow))) - ;;(declare (type short-array arb)) - (unless (= indicator -1) - (ecase (cd-oci-data-type cd) - (#.SQLT-STR - (deref-oci-string b irow (cd-sizeof cd))) - (#.SQLT-FLT - (uffi:deref-array b '(:array :double) irow)) - (#.SQLT-INT - (uffi:deref-array b '(:array :int) irow)) - (#.SQLT-DATE - (deref-oci-string b irow (cd-sizeof cd)))))))) - (when (and (eq :string (cd-result-type cd)) - value - (not (stringp value))) - (setq value (write-to-string value))) - (push value reversed-result))) - (incf (qc-n-to-dbi qc)) - (nreverse reversed-result))))) + (if eof-errorp + (error 'sql-database-error :message + (format nil "no more rows available in ~S" qc)) + eof-value)) + ((>= (qc-n-to-dbi qc) + (qc-n-from-oci qc)) + (refill-qc-buffers qc) + (fetch-row qc nil eof-value encoding)) + (t + (let ((cds (qc-cds qc)) + (reversed-result nil) + (irow (qc-n-to-dbi qc))) + (dotimes (icd (length cds)) + (let* ((cd (aref cds icd)) + (b (foreign-resource-buffer (cd-buffer cd))) + (value + (let* ((arb (foreign-resource-buffer (cd-indicators cd))) + (indicator (uffi:deref-array arb '(:array :short) irow))) + (declare (type short-array arb)) + (unless (= indicator -1) + (ecase (cd-oci-data-type cd) + (#.SQLT-STR + (deref-oci-string b irow (cd-sizeof cd) encoding)) + (#.SQLT-FLT + (locally + (declare (type double-array b)) + (uffi:deref-array b '(:array :double) irow))) + (#.SQLT-INT + (ecase (cd-sizeof cd) + (4 + (locally + (declare (type int-array b)) + (uffi:deref-array b '(:array :int) irow))))) + (#.SQLT-DATE + (deref-oci-string b irow (cd-sizeof cd) encoding))))))) + (when (and (eq :string (cd-result-type cd)) + value + (not (stringp value))) + (setq value (write-to-string value))) + (push value reversed-result))) + (incf (qc-n-to-dbi qc)) + (nreverse reversed-result))))) (defun refill-qc-buffers (qc) (with-slots (errhp) (qc-db qc) @@ -413,31 +457,31 @@ the length of that format.") (cond ((qc-oci-end-seen-p qc) (setf (qc-n-from-oci qc) 0)) (t - (let ((oci-code (%oci-stmt-fetch - (deref-vp (qc-stmthp qc)) - (deref-vp errhp) - +n-buf-rows+ - +oci-fetch-next+ +oci-default+))) + (let ((oci-code (%oci-stmt-fetch + (deref-vp (qc-stmthp qc)) + (deref-vp errhp) + +n-buf-rows+ + +oci-fetch-next+ +oci-default+))) (ecase oci-code (#.+oci-success+ (values)) (#.+oci-no-data+ (setf (qc-oci-end-seen-p qc) t) (values)) (#.+oci-error+ (handle-oci-error :database (qc-db qc) :nulls-ok t)))) - (uffi:with-foreign-object (rowcount :long) + (uffi:with-foreign-object (rowcount 'ub4) (oci-attr-get (deref-vp (qc-stmthp qc)) - +oci-htype-stmt+ - rowcount - +unsigned-int-null-pointer+ - +oci-attr-row-count+ + +oci-htype-stmt+ + rowcount + +unsigned-int-null-pointer+ + +oci-attr-row-count+ (deref-vp errhp)) (setf (qc-n-from-oci qc) - (- (uffi:deref-pointer rowcount :long) - (qc-total-n-from-oci qc))) + (- (uffi:deref-pointer rowcount 'ub4) + (qc-total-n-from-oci qc))) (when (< (qc-n-from-oci qc) +n-buf-rows+) (setf (qc-oci-end-seen-p qc) t)) (setf (qc-total-n-from-oci qc) - (uffi:deref-pointer rowcount :long))))) + (uffi:deref-pointer rowcount 'ub4))))) (values))) ;; the guts of the SQL function @@ -456,39 +500,48 @@ the length of that format.") ;; freeing the STMTHP when it is no longer needed. (defun sql-stmt-exec (sql-stmt-string db result-types field-names) - (with-slots (envhp svchp errhp) - db - (let ((stmthp (uffi:allocate-foreign-object :pointer-void))) - (uffi:with-foreign-object (stmttype :unsigned-short) - - (oci-handle-alloc (deref-vp envhp) - stmthp - +oci-htype-stmt+ 0 +null-void-pointer-pointer+) - (oci-stmt-prepare (deref-vp stmthp) - (deref-vp errhp) - (uffi:convert-to-cstring sql-stmt-string) - (length sql-stmt-string) - +oci-ntv-syntax+ +oci-default+ :database db) - (oci-attr-get (deref-vp stmthp) - +oci-htype-stmt+ - stmttype - +unsigned-int-null-pointer+ - +oci-attr-stmt-type+ - (deref-vp errhp) - :database db) - (let* ((select-p (= (uffi:deref-pointer stmttype :unsigned-short) 1)) - (iters (if select-p 0 1))) - - (oci-stmt-execute (deref-vp svchp) - (deref-vp stmthp) - (deref-vp errhp) - iters 0 +null-void-pointer+ +null-void-pointer+ +oci-default+ - :database db) - (cond (select-p - (make-query-cursor db stmthp result-types field-names)) - (t - (oci-handle-free (deref-vp stmthp) +oci-htype-stmt+) - nil))))))) + (with-slots (envhp svchp errhp) db + (uffi:with-foreign-strings ((c-stmt-string sql-stmt-string)) + (let ((stmthp (uffi:allocate-foreign-object :pointer-void)) + select-p) + + (uffi:with-foreign-object (stmttype :unsigned-short) + (unwind-protect + (progn + (oci-handle-alloc (deref-vp envhp) + stmthp + +oci-htype-stmt+ 0 +null-void-pointer-pointer+) + (oci-stmt-prepare (deref-vp stmthp) + (deref-vp errhp) + c-stmt-string + (uffi:foreign-string-length c-stmt-string) + +oci-ntv-syntax+ +oci-default+ :database db) + (oci-attr-get (deref-vp stmthp) + +oci-htype-stmt+ + stmttype + +unsigned-int-null-pointer+ + +oci-attr-stmt-type+ + (deref-vp errhp) + :database db) + + (setq select-p (= (uffi:deref-pointer stmttype :unsigned-short) 1)) + (let ((iters (if select-p 0 1))) + + (oci-stmt-execute (deref-vp svchp) + (deref-vp stmthp) + (deref-vp errhp) + iters 0 +null-void-pointer+ +null-void-pointer+ +oci-default+ + :database db))) + ;; free resources unless a query + (unless select-p + (oci-handle-free (deref-vp stmthp) +oci-htype-stmt+) + (uffi:free-foreign-object stmthp)))) + + (cond + (select-p + (make-query-cursor db stmthp result-types field-names)) + (t + nil)))))) ;; Return a QUERY-CURSOR representing the table returned from the OCI @@ -498,10 +551,10 @@ the length of that format.") (defun make-query-cursor (db stmthp result-types field-names) (let ((qc (%make-query-cursor :db db - :stmthp stmthp - :cds (make-query-cursor-cds db stmthp - result-types - field-names)))) + :stmthp stmthp + :cds (make-query-cursor-cds db stmthp + result-types + field-names)))) (refill-qc-buffers qc) qc)) @@ -519,6 +572,12 @@ the length of that format.") ;; 21 bytes. See pp. 3-10, 3-26, and 6-13 of OCI documentation ;; for more details. +;; Mac OS X Note: According to table 6-8 in the Oracle 9i OCI +;; documentation, PRECISION may actually be an sb2 instead of a +;; single byte if performing an "implicit describe". Using a +;; signed short instead of an unsigned byte fixes a Mac OS X bug +;; where PRECISION is always zero. -- JJB 20040713 + ;; When calling OCI C code to handle the conversion, we have ;; only two numeric types available to pass the return value: ;; double-float and signed-long. It would be possible to @@ -565,136 +624,139 @@ the length of that format.") ;; below, beware!) try setting this value into COLSIZE, calling OCI, ;; then looking at the value in COLSIZE. (setf colsize #x12345678) ;; debugging only - + +;; Mac OS X Note: This workaround fails on a bigendian platform so +;; I've changed the data type of COLNAME to :unsigned-short as per +;; the Oracle 9i OCI documentation. -- JJB 20040713 (uffi:def-type byte-pointer (* :byte)) -(uffi:def-type ulong-pointer (* :unsigned-long)) (uffi:def-type void-pointer-pointer (* :void-pointer)) (defun make-query-cursor-cds (database stmthp result-types field-names) (declare (optimize (safety 3) #+nil (speed 3)) - (type oracle-database database) - (type pointer-pointer-void stmthp)) + (type oracle-database database) + (type pointer-pointer-void stmthp)) (with-slots (errhp) database (uffi:with-foreign-objects ((dtype-foreign :unsigned-short) - (parmdp :pointer-void) - (precision :byte) - (scale :byte) - (colname '(* :unsigned-char)) - (colnamelen :unsigned-long) - (colsize :unsigned-long) - (colsizesize :unsigned-long) - (defnp ':pointer-void)) + (parmdp :pointer-void) + (precision :short) + (scale :byte) + (colname '(* :unsigned-char)) + (colnamelen 'ub4) + (colsize 'ub2) + (defnp ':pointer-void)) (let ((buffer nil) - (sizeof nil)) - (do ((icolumn 0 (1+ icolumn)) - (cds-as-reversed-list nil)) - ((not (eql (oci-param-get (deref-vp stmthp) - +oci-htype-stmt+ - (deref-vp errhp) - parmdp - (1+ icolumn) :database database) - +oci-success+)) - (coerce (reverse cds-as-reversed-list) 'simple-vector)) - ;; Decode type of ICOLUMNth column into a type we're prepared to - ;; handle in Lisp. - (oci-attr-get (deref-vp parmdp) - +oci-dtype-param+ - dtype-foreign - +unsigned-int-null-pointer+ - +oci-attr-data-type+ - (deref-vp errhp)) - (let ((dtype (uffi:deref-pointer dtype-foreign :unsigned-short))) - (declare (fixnum dtype)) - (case dtype - (#.SQLT-DATE - (setf buffer (acquire-foreign-resource :unsigned-char - (* 32 +n-buf-rows+))) - (setf sizeof 32 dtype #.SQLT-STR)) - (#.SQLT-NUMBER - (oci-attr-get (deref-vp parmdp) - +oci-dtype-param+ - precision - +unsigned-int-null-pointer+ - +oci-attr-precision+ - (deref-vp errhp)) - (oci-attr-get (deref-vp parmdp) - +oci-dtype-param+ - scale - +unsigned-int-null-pointer+ - +oci-attr-scale+ - (deref-vp errhp)) - (let ((*scale (uffi:deref-pointer scale :byte)) - (*precision (uffi:deref-pointer precision :byte))) - - ;; (format t "scale=~d, precision=~d~%" *scale *precision) - (cond - ((or (and (zerop *scale) (not (zerop *precision))) - (and (minusp *scale) (< *precision 10))) - (setf buffer (acquire-foreign-resource :int +n-buf-rows+) - sizeof 4 ;; sizeof(int) - dtype #.SQLT-INT)) - (t - (setf buffer (acquire-foreign-resource :double +n-buf-rows+) - sizeof 8 ;; sizeof(double) - dtype #.SQLT-FLT))))) - ;; Default to SQL-STR - (t - (setf (uffi:deref-pointer colsize :unsigned-long) 0) - (setf dtype #.SQLT-STR) - (oci-attr-get (deref-vp parmdp) - +oci-dtype-param+ - colsize - +unsigned-int-null-pointer+ - +oci-attr-data-size+ - (deref-vp errhp)) - (let ((colsize-including-null (1+ (uffi:deref-pointer colsize :unsigned-long)))) - (setf buffer (acquire-foreign-resource - :unsigned-char (* +n-buf-rows+ colsize-including-null))) - (setf sizeof colsize-including-null)))) - (let ((retcodes (acquire-foreign-resource :unsigned-short +n-buf-rows+)) - (indicators (acquire-foreign-resource :short +n-buf-rows+)) - (colname-string "")) - (when field-names - (oci-attr-get (deref-vp parmdp) - +oci-dtype-param+ - colname - colnamelen - +oci-attr-name+ - (deref-vp errhp)) - (setq colname-string (uffi:convert-from-foreign-string - (uffi:deref-pointer colname '(* :unsigned-char)) - :length (uffi:deref-pointer colnamelen :unsigned-long)))) - (push (make-cd :name colname-string - :sizeof sizeof - :buffer buffer - :oci-data-type dtype - :retcodes retcodes - :indicators indicators - :result-type (cond - ((consp result-types) - (nth icolumn result-types)) - ((null result-types) - :string) - (t - result-types))) - cds-as-reversed-list) - (oci-define-by-pos (deref-vp stmthp) - defnp - (deref-vp errhp) - (1+ icolumn) ; OCI 1-based indexing again - (foreign-resource-buffer buffer) - sizeof - dtype - (foreign-resource-buffer indicators) - +unsigned-short-null-pointer+ - (foreign-resource-buffer retcodes) - +oci-default+)))))))) - + (sizeof nil)) + (do ((icolumn 0 (1+ icolumn)) + (cds-as-reversed-list nil)) + ((not (eql (oci-param-get (deref-vp stmthp) + +oci-htype-stmt+ + (deref-vp errhp) + parmdp + (1+ icolumn) :database database) + +oci-success+)) + (coerce (reverse cds-as-reversed-list) 'simple-vector)) + ;; Decode type of ICOLUMNth column into a type we're prepared to + ;; handle in Lisp. + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + dtype-foreign + +unsigned-int-null-pointer+ + +oci-attr-data-type+ + (deref-vp errhp)) + (let ((dtype (uffi:deref-pointer dtype-foreign :unsigned-short))) + (declare (fixnum dtype)) + (case dtype + (#.SQLT-DATE + (setf buffer (acquire-foreign-resource :unsigned-char + (* 32 +n-buf-rows+))) + (setf sizeof 32 dtype #.SQLT-STR)) + (#.SQLT-NUMBER + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + precision + +unsigned-int-null-pointer+ + +oci-attr-precision+ + (deref-vp errhp)) + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + scale + +unsigned-int-null-pointer+ + +oci-attr-scale+ + (deref-vp errhp)) + (let ((*scale (uffi:deref-pointer scale :byte)) + (*precision (uffi:deref-pointer precision :short))) + + ;;(format t "scale=~d, precision=~d~%" *scale *precision) + (cond + ((or (and (minusp *scale) (zerop *precision)) + (and (zerop *scale) (plusp *precision))) + (setf buffer (acquire-foreign-resource :int +n-buf-rows+) + sizeof 4 ;; sizeof(int) + dtype #.SQLT-INT)) + (t + (setf buffer (acquire-foreign-resource :double +n-buf-rows+) + sizeof 8 ;; sizeof(double) + dtype #.SQLT-FLT))))) + ;; Default to SQL-STR + (t + (setf (uffi:deref-pointer colsize :unsigned-short) 0) + (setf dtype #.SQLT-STR) + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + colsize + +unsigned-int-null-pointer+ + +oci-attr-data-size+ + (deref-vp errhp)) + (let ((colsize-including-null (1+ (uffi:deref-pointer colsize :unsigned-short)))) + (setf buffer (acquire-foreign-resource + :unsigned-char (* +n-buf-rows+ colsize-including-null))) + (setf sizeof colsize-including-null)))) + (let ((retcodes (acquire-foreign-resource :unsigned-short +n-buf-rows+)) + (indicators (acquire-foreign-resource :short +n-buf-rows+)) + (colname-string "")) + (when field-names + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + colname + colnamelen + +oci-attr-name+ + (deref-vp errhp)) + (setq colname-string (uffi:convert-from-foreign-string + (uffi:deref-pointer colname '(* :unsigned-char)) + :length (uffi:deref-pointer colnamelen 'ub4) + :encoding (encoding database)))) + (push (make-cd :name colname-string + :sizeof sizeof + :buffer buffer + :oci-data-type dtype + :retcodes retcodes + :indicators indicators + :result-type (cond + ((consp result-types) + (nth icolumn result-types)) + ((null result-types) + :string) + (t + result-types))) + cds-as-reversed-list) + (oci-define-by-pos (deref-vp stmthp) + defnp + (deref-vp errhp) + (1+ icolumn) ; OCI 1-based indexing again + (foreign-resource-buffer buffer) + sizeof + dtype + (foreign-resource-buffer indicators) + +unsigned-short-null-pointer+ + (foreign-resource-buffer retcodes) + +oci-default+)))))))) + ;; Release the resources associated with a QUERY-CURSOR. (defun close-query (qc) (oci-handle-free (deref-vp (qc-stmthp qc)) +oci-htype-stmt+) + (uffi:free-foreign-object (qc-stmthp qc)) (let ((cds (qc-cds qc))) (dotimes (i (length cds)) (release-cd-resources (aref cds i)))) @@ -730,98 +792,63 @@ the length of that format.") ;; handle errors very gracefully (since they're part of the ;; error-handling mechanism themselves) so we just assert they ;; work. + (setf (deref-vp envhp) +null-void-pointer+) - #+oci-8-1-5 - (progn - (oci-env-create envhp +oci-default+ +null-void-pointer+ - +null-void-pointer+ +null-void-pointer+ - +null-void-pointer+ 0 +null-void-pointer-pointer+) - (oci-handle-alloc envhp - (deref-vp errhp) - +oci-htype-error+ 0 - +null-void-pointer-pointer+)) - #-oci-8-1-5 + + #-oci7 + (oci-env-create envhp +oci-default+ +null-void-pointer+ + +null-void-pointer+ +null-void-pointer+ + +null-void-pointer+ 0 +null-void-pointer-pointer+) + + #+oci7 (progn - (oci-initialize +oci-object+ +null-void-pointer+ +null-void-pointer+ - +null-void-pointer+ +null-void-pointer-pointer+) + (oci-initialize +oci-object+ +null-void-pointer+ +null-void-pointer+ + +null-void-pointer+ +null-void-pointer-pointer+) (ignore-errors (oci-handle-alloc +null-void-pointer+ envhp - +oci-htype-env+ 0 - +null-void-pointer-pointer+)) ;no testing return - (oci-env-init envhp +oci-default+ 0 +null-void-pointer-pointer+) - (oci-handle-alloc (deref-vp envhp) errhp - +oci-htype-error+ 0 +null-void-pointer-pointer+) - (oci-handle-alloc (deref-vp envhp) srvhp - +oci-htype-server+ 0 +null-void-pointer-pointer+) - (uffi:with-cstring (dblink nil) - (oci-server-attach (deref-vp srvhp) - (deref-vp errhp) - dblink - 0 +oci-default+)) - (oci-handle-alloc (deref-vp envhp) svchp - +oci-htype-svcctx+ 0 +null-void-pointer-pointer+) - (oci-attr-set (deref-vp svchp) - +oci-htype-svcctx+ - (deref-vp srvhp) 0 +oci-attr-server+ - (deref-vp errhp)) - ;; oci-handle-alloc((dvoid *)encvhp, (dvoid **)&stmthp, OCI_HTYPE_STMT, 0, 0); - ;;#+nil - ) - ;; Actually, oci-server-version returns the client version, not the server versions - ;; will use "SELECT VERSION FROM V$INSTANCE" to get actual server version. - (let (db server-version client-version) - (declare (ignorable server-version)) - (uffi:with-foreign-object (buf '(:array :unsigned-char #.+errbuf-len+)) - (oci-server-version (deref-vp svchp) - (deref-vp errhp) - (uffi:char-array-to-pointer buf) - +errbuf-len+ +oci-htype-svcctx+) - (setf client-version (uffi:convert-from-foreign-string buf)) - ;; This returns the client version, not the server version, so diable it - #+ignore - (oci-server-version (deref-vp srvhp) - (deref-vp errhp) - (uffi:char-array-to-pointer buf) - +errbuf-len+ +oci-htype-server+) - #+ignore - (setf server-version (uffi:convert-from-foreign-string buf))) - (setq db (make-instance 'oracle-database - :name (database-name-from-spec connection-spec - database-type) - :connection-spec connection-spec - :envhp envhp - :errhp errhp - :database-type :oracle - :svchp svchp - :dsn data-source-name - :user user - :client-version client-version - :server-version server-version - :major-client-version (major-client-version-from-string - client-version) - :major-server-version (major-client-version-from-string - server-version))) - (oci-logon (deref-vp envhp) - (deref-vp errhp) - svchp - (uffi:convert-to-cstring user) (length user) - (uffi:convert-to-cstring password) (length password) - (uffi:convert-to-cstring data-source-name) (length data-source-name) - :database db) - ;; :date-format-length (1+ (length date-format))))) - (setf (slot-value db 'clsql-sys::state) :open) + +oci-htype-env+ 0 + +null-void-pointer-pointer+)) ;no testing return + (oci-env-init envhp +oci-default+ 0 +null-void-pointer-pointer+)) + + (oci-handle-alloc (deref-vp envhp) errhp + +oci-htype-error+ 0 +null-void-pointer-pointer+) + (oci-handle-alloc (deref-vp envhp) srvhp + +oci-htype-server+ 0 +null-void-pointer-pointer+) + + (let ((db (make-instance 'oracle-database + :name (database-name-from-spec connection-spec + database-type) + :connection-spec connection-spec + :envhp envhp + :errhp errhp + :database-type :oracle + :svchp svchp + :dsn data-source-name + :user user))) + (uffi:with-foreign-strings ((c-user user) + (c-password password) + (c-data-source-name data-source-name)) + (oci-logon (deref-vp envhp) + (deref-vp errhp) + svchp + c-user (length user) + c-password (length password) + c-data-source-name (length data-source-name) + :database db)) + ;; :date-format-length (1+ (length date-format))))) + (setf (slot-value db 'clsql-sys::state) :open) (database-execute-command - (format nil "ALTER SESSION SET NLS_DATE_FORMAT='~A'" (date-format db)) db) - (let ((server-version - (caar (database-query - "SELECT BANNER FROM V$VERSION WHERE BANNER LIKE '%Oracle%'" db nil nil)))) - (setf (slot-value db 'server-version) server-version - (slot-value db 'major-server-version) (major-client-version-from-string - server-version))) + (format nil "ALTER SESSION SET NLS_DATE_FORMAT='~A'" (date-format db)) db) + (let ((server-version + (caar (database-query + "SELECT BANNER FROM V$VERSION WHERE BANNER LIKE '%Oracle%'" db nil nil)))) + (setf (slot-value db 'server-version) server-version + (slot-value db 'major-server-version) (major-client-version-from-string + server-version))) db)))) (defun major-client-version-from-string (str) - (cond + (cond ((search " 10g " str) 10) ((search "Oracle9i " str) @@ -831,7 +858,7 @@ the length of that format.") (defun major-server-version-from-string (str) (when (> (length str) 2) - (cond + (cond ((string= "10." (subseq str 0 3)) 10) ((string= "9." (subseq str 0 2)) @@ -844,7 +871,7 @@ the length of that format.") (defmethod database-disconnect ((database oracle-database)) (osucc (oci-logoff (deref-vp (svchp database)) - (deref-vp (errhp database)))) + (deref-vp (errhp database)))) (osucc (oci-handle-free (deref-vp (envhp database)) +oci-htype-env+)) ;; Note: It's neither required nor allowed to explicitly deallocate the ;; ERRHP handle here, since it's owned by the ENVHP deallocated above, @@ -867,19 +894,19 @@ the length of that format.") (let ((cursor (sql-stmt-exec query-expression database result-types field-names))) ;; (declare (type (or query-cursor null) cursor)) (if (null cursor) ; No table was returned. - (values) + (values) (do ((reversed-result nil)) - (nil) - (let* ((eof-value :eof) - (row (fetch-row cursor nil eof-value))) - (when (eq row eof-value) - (close-query cursor) - (if field-names - (return (values (nreverse reversed-result) - (loop for cd across (qc-cds cursor) - collect (cd-name cd)))) - (return (nreverse reversed-result)))) - (push row reversed-result)))))) + (nil) + (let* ((eof-value :eof) + (row (fetch-row cursor nil eof-value (encoding database)))) + (when (eq row eof-value) + (close-query cursor) + (if field-names + (return (values (nreverse reversed-result) + (loop for cd across (qc-cds cursor) + collect (cd-name cd)))) + (return (nreverse reversed-result)))) + (push row reversed-result)))))) (defmethod database-create-sequence (sequence-name (database oracle-database)) @@ -893,21 +920,27 @@ the length of that format.") :database database)) (defmethod database-sequence-next (sequence-name (database oracle-database)) - (caar - (database-query - (concatenate 'string "SELECT " - (sql-escape sequence-name) - ".NEXTVAL FROM dual" - ) - database :auto nil))) + (caar (database-query + (concatenate 'string "SELECT " + (sql-escape sequence-name) + ".NEXTVAL FROM dual") + database :auto nil))) + +(defmethod database-sequence-last (sequence-name (database oracle-database)) + (caar (database-query + (concatenate 'string "SELECT " + (sql-escape sequence-name) + ".CURRVAL FROM dual") + database :auto nil))) (defmethod database-set-sequence-position (name position (database oracle-database)) (without-interrupts (let* ((next (database-sequence-next name database)) - (incr (- position next))) - (database-execute-command - (format nil "ALTER SEQUENCE ~A INCREMENT BY ~D" name incr) - database) + (incr (- position next))) + (unless (zerop incr) + (database-execute-command + (format nil "ALTER SEQUENCE ~A INCREMENT BY ~D" name incr) + database)) (database-sequence-next name database) (database-execute-command (format nil "ALTER SEQUENCE ~A INCREMENT BY 1" name) @@ -915,22 +948,25 @@ the length of that format.") (defmethod database-list-sequences ((database oracle-database) &key owner) (let ((query - (if owner - (format nil - "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~)'" - owner) - "select sequence_name from user_sequences"))) + (cond ((null owner) + "select sequence_name from user_sequences") + ((eq owner :all) + "select sequence_name from all_sequences") + (t + (format nil + "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~)'" + owner))))) (mapcar #'car (database-query query database nil nil)))) (defmethod database-execute-command (sql-expression (database oracle-database)) (database-query sql-expression database nil nil) - ;; HACK HACK HACK - (database-query "commit" database nil nil) + (when (database-autocommit database) + (oracle-commit database)) t) (defstruct (cd (:constructor make-cd) - (:print-function print-cd)) + (:print-function print-cd)) "a column descriptor: metadata about the data in a table" ;; name of this column @@ -939,33 +975,33 @@ the length of that format.") (sizeof (error "missing SIZE") :type fixnum :read-only t) ;; an array of +N-BUF-ROWS+ elements in C representation (buffer (error "Missing BUFFER") - :type foreign-resource - :read-only t) + :type foreign-resource + :read-only t) ;; an array of +N-BUF-ROWS+ OCI return codes in C representation. ;; (There must be one return code for every element of every ;; row in order to be able to represent nullness.) (retcodes (error "Missing RETCODES") - :type foreign-resource - :read-only t) + :type foreign-resource + :read-only t) (indicators (error "Missing INDICATORS") - :type foreign-resource - :read-only t) + :type foreign-resource + :read-only t) ;; the OCI code for the data type of a single element (oci-data-type (error "missing OCI-DATA-TYPE") - :type fixnum - :read-only t) + :type fixnum + :read-only t) (result-type (error "missing RESULT-TYPE") - :read-only t)) + :read-only t)) (defun print-cd (cd stream depth) (declare (ignore depth)) (print-unreadable-object (cd stream :type t) (format stream - ":NAME ~S :OCI-DATA-TYPE ~S :OCI-DATA-SIZE ~S" - (cd-name cd) - (cd-oci-data-type cd) - (cd-sizeof cd)))) + ":NAME ~S :OCI-DATA-TYPE ~S :OCI-DATA-SIZE ~S" + (cd-name cd) + (cd-oci-data-type cd) + (cd-sizeof cd)))) (defun print-query-cursor (qc stream depth) (declare (ignore depth)) @@ -974,50 +1010,53 @@ the length of that format.") (defmethod database-query-result-set ((query-expression string) - (database oracle-database) - &key full-set result-types) + (database oracle-database) + &key full-set result-types) (let ((cursor (sql-stmt-exec query-expression database result-types nil))) (if full-set - (values cursor (length (qc-cds cursor)) nil) - (values cursor (length (qc-cds cursor)))))) + (values cursor (length (qc-cds cursor)) nil) + (values cursor (length (qc-cds cursor)))))) (defmethod database-dump-result-set (result-set (database oracle-database)) - (close-query result-set)) + (close-query result-set)) (defmethod database-store-next-row (result-set (database oracle-database) list) (let* ((eof-value :eof) - (row (fetch-row result-set nil eof-value))) + (row (fetch-row result-set nil eof-value (encoding database)))) (unless (eq eof-value row) (loop for i from 0 below (length row) - do (setf (nth i list) (nth i row))) + do (setf (nth i list) (nth i row))) list))) -(defmethod clsql-sys:database-start-transaction ((database oracle-database)) +(defmethod database-start-transaction ((database oracle-database)) (call-next-method) - ) + ;; Not needed with simple transaction + #+ignore + (with-slots (svchp errhp) database + (oci-trans-start (deref-vp svchp) + (deref-vp errhp) + 60 + +oci-trans-new+)) + t) -;;(with-slots (svchp errhp) database -;; (osucc (oci-trans-start (uffi:deref-pointer svchp) -;; (uffi:deref-pointer errhp) -;; 60 -;; +oci-trans-new+))) -;; t) - -(defmethod clsql-sys:database-commit-transaction ((database oracle-database)) - (call-next-method) +(defun oracle-commit (database) (with-slots (svchp errhp) database - (osucc (oci-trans-commit (deref-vp svchp) - (deref-vp errhp) - 0))) + (osucc (oci-trans-commit (deref-vp svchp) + (deref-vp errhp) + 0)))) + +(defmethod database-commit-transaction ((database oracle-database)) + (call-next-method) + (oracle-commit database) t) -(defmethod clsql-sys:database-abort-transaction ((database oracle-database)) +(defmethod database-abort-transaction ((database oracle-database)) (call-next-method) (osucc (oci-trans-rollback (deref-vp (svchp database)) - (deref-vp (errhp database)) - 0)) + (deref-vp (errhp database)) + 0)) t) ;; Specifications