-- | -- Module : Database.Oracle.OCIFunctions -- Copyright : (c) 2004 Oleg Kiselyov, Alistair Bayley -- License : BSD-style -- Maintainer : oleg@pobox.com, alistair@abayley.org -- Stability : experimental -- Portability : non-portable -- Simple wrappers for OCI functions (FFI). -- The functions in this file are simple wrappers for OCI functions. -- The wrappers add error detection and exceptions; -- functions in this module raise 'OCIException'. -- The next layer up traps these and turns them into 'Database.Enumerator.DBException'. -- Note that 'OCIException' /does not/ contain the error number and text -- returned by 'getOCIErrorMsg'. -- It is the job of the next layer (module) up to catch the 'OCIException' -- and then call 'getOCIErrorMsg' to get the actual error details. -- The 'OCIException' simply contains the error number returned by -- the OCI call, and some text identifying the wrapper function. -- See 'formatErrorCodeDesc' for the set of possible values for the OCI error numbers. {-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE DeriveDataTypeable #-} module Database.Oracle.OCIFunctions where import Prelude hiding (catch) import Database.Oracle.OCIConstants import Database.Util import Foreign import Foreign.C import Control.Monad import Control.Exception import Data.Dynamic import Data.Time import System.Time -- | -- * Each handle type has its own data type, to prevent stupid errors -- i.e. using the wrong handle at the wrong time. -- * In GHC you can simply say @data OCIStruct@ i.e. there's no need for @= OCIStruct@. -- I've decided to be more portable, as it doesn't cost much. -- * Use castPtr if you need to convert handles (say 'OCIHandle' to a more specific type, or vice versa). data OCIStruct = OCIStruct type OCIHandle = Ptr OCIStruct -- generic Handle for OCI functions that return Handles data OCIBuffer = OCIBuffer -- generic buffer. Could hold anything: value or pointer. type BufferPtr = Ptr OCIBuffer type BufferFPtr = ForeignPtr OCIBuffer type ColumnResultBuffer = ForeignPtr OCIBuffer -- use ForeignPtr to ensure GC'd -- triple of (nullind, buffer, size) type BindBuffer = (ForeignPtr CShort, ForeignPtr OCIBuffer, ForeignPtr CUShort) data Context = Context type ContextPtr = Ptr Context data EnvStruct = EnvStruct type EnvHandle = Ptr EnvStruct data ErrorStruct = ErrorStruct type ErrorHandle = Ptr ErrorStruct data ServerStruct = ServerStruct type ServerHandle = Ptr ServerStruct data UserStruct = UserStruct type UserHandle = Ptr UserStruct data ConnStruct = ConnStruct type ConnHandle = Ptr ConnStruct -- AKA Service Context data SessStruct = SessStruct type SessHandle = Ptr SessStruct data StmtStruct = StmtStruct type StmtHandle = Ptr StmtStruct data DefnStruct = DefnStruct type DefnHandle = Ptr DefnStruct data ParamStruct = ParamStruct type ParamHandle = Ptr ParamStruct data BindStruct = BindStruct type BindHandle = Ptr BindStruct type ColumnInfo = (DefnHandle, ColumnResultBuffer, ForeignPtr CShort, ForeignPtr CUShort) -- |Low-level, OCI library errors. data OCIException = OCIException CInt String deriving (Typeable, Show) -- If we can't derive Typeable then the following code should do the trick: -- > data OCIException = OCIException CInt String -- > ociExceptionTc :: TyCon -- > ociExceptionTc = mkTyCon "Database.Oracle.OCIFunctions.OCIException" -- > instance Typeable OCIException where typeOf _ = mkAppTy ociExceptionTc [] catchOCI :: IO a -> (OCIException -> IO a) -> IO a throwOCI :: OCIException -> a instance Exception OCIException catchOCI = catch throwOCI = throw mkCInt :: Int -> CInt mkCInt n = fromIntegral n mkCShort :: CInt -> CShort mkCShort n = fromIntegral n mkCUShort :: CInt -> CUShort mkCUShort n = fromIntegral n cStrLen :: CStringLen -> CInt cStrLen = mkCInt . snd cStr :: CStringLen -> CString cStr = fst -- --------------------------------------------------------------------------------- -- -- ** Foreign OCI functions -- --------------------------------------------------------------------------------- foreign import ccall "OCIEnvCreate" ociEnvCreate :: Ptr EnvHandle -> CInt -> Ptr a -> FunPtr a -> FunPtr a -> FunPtr a -> CInt -> Ptr (Ptr a) -> IO CInt foreign import ccall "OCIHandleAlloc" ociHandleAlloc :: OCIHandle -> Ptr OCIHandle -> CInt -> CInt -> Ptr a -> IO CInt foreign import ccall "oci.h OCIHandleFree" ociHandleFree :: OCIHandle -> CInt -> IO CInt foreign import ccall "oci.h OCIErrorGet" ociErrorGet :: OCIHandle -> CInt -> CString -> Ptr CInt -> CString -> CInt -> CInt -> IO CInt foreign import ccall "oci.h OCIParamGet" ociParamGet :: OCIHandle -> CInt -> ErrorHandle -> Ptr OCIHandle -> CInt -> IO CInt foreign import ccall "oci.h OCIAttrGet" ociAttrGet :: OCIHandle -> CInt -> BufferPtr -> Ptr CInt -> CInt -> ErrorHandle -> IO CInt foreign import ccall "oci.h OCIAttrSet" ociAttrSet :: OCIHandle -> CInt -> BufferPtr -> CInt -> CInt -> ErrorHandle -> IO CInt foreign import ccall "oci.h OCILogon" ociLogon :: EnvHandle -> ErrorHandle -> Ptr ConnHandle -> CString -> CInt -> CString -> CInt -> CString -> CInt -> IO CInt foreign import ccall "oci.h OCILogoff" ociLogoff :: ConnHandle -> ErrorHandle -> IO CInt foreign import ccall "oci.h OCISessionBegin" ociSessionBegin :: ConnHandle -> ErrorHandle -> SessHandle -> CInt -> CInt -> IO CInt foreign import ccall "oci.h OCISessionEnd" ociSessionEnd :: ConnHandle -> ErrorHandle -> SessHandle -> CInt -> IO CInt foreign import ccall "oci.h OCIServerAttach" ociServerAttach :: ServerHandle -> ErrorHandle -> CString -> CInt -> CInt -> IO CInt foreign import ccall "oci.h OCIServerDetach" ociServerDetach :: ServerHandle -> ErrorHandle -> CInt -> IO CInt foreign import ccall "oci.h OCITerminate" ociTerminate :: CInt -> IO CInt foreign import ccall "oci.h OCITransStart" ociTransStart :: ConnHandle -> ErrorHandle -> Word8 -> CInt -> IO CInt foreign import ccall "oci.h OCITransCommit" ociTransCommit :: ConnHandle -> ErrorHandle -> CInt -> IO CInt foreign import ccall "oci.h OCITransRollback" ociTransRollback :: ConnHandle -> ErrorHandle -> CInt -> IO CInt foreign import ccall "oci.h OCIStmtPrepare" ociStmtPrepare :: StmtHandle -> ErrorHandle -> CString -> CInt -> CInt -> CInt -> IO CInt foreign import ccall "oci.h OCIDefineByPos" ociDefineByPos :: StmtHandle -> Ptr DefnHandle -> ErrorHandle -> CInt -> BufferPtr -> CInt -> CUShort -> Ptr CShort -> Ptr CUShort -> Ptr CUShort -> CInt -> IO CInt foreign import ccall "oci.h OCIStmtExecute" ociStmtExecute :: ConnHandle -> StmtHandle -> ErrorHandle -> CInt -> CInt -> OCIHandle -> OCIHandle -> CInt -> IO CInt foreign import ccall "oci.h OCIStmtFetch" ociStmtFetch :: StmtHandle -> ErrorHandle -> CInt -> CShort -> CInt -> IO CInt -- stmt, ptr bindHdl, err, pos, valuePtr, sizeOfValue, -- datatype, indicatorPtr, lenArrayPtr, retCodeArrayPtr, -- plsqlArrayMaxLen, plsqlCurrEltPtr, mode foreign import ccall "oci.h OCIBindByPos" ociBindByPos :: StmtHandle -> Ptr BindHandle -> ErrorHandle -> CUInt -- ^ position -> BufferPtr -- ^ buffer containing data -> CInt -- ^ max size of buffer -> CUShort -- ^ SQL data type -> Ptr CShort -- ^ null indicator ptr -> Ptr CUShort -- ^ input + output size, or array of sizes -> Ptr CUShort -- ^ array of return codes -> CUInt -- ^ max array elements -> Ptr CUInt -- ^ number of array elements -> CUInt -- ^ mode -> IO CInt foreign import ccall "oci.h OCIBindDynamic" ociBindDynamic :: BindHandle -> ErrorHandle -> ContextPtr -> FunPtr OCICallbackInBind -> ContextPtr -> FunPtr OCICallbackOutBind -> IO CInt type OCICallbackInBind = ContextPtr -> BindHandle -> CInt -> CInt -> Ptr BufferPtr -> CInt -> Ptr Word8 -> Ptr CShort -> IO CInt type OCICallbackOutBind = ContextPtr -> BindHandle -> CInt -> CInt -> Ptr BufferPtr -> Ptr CInt -> Ptr Word8 -> Ptr CShort -> Ptr (Ptr CShort) -> IO CInt foreign import ccall "wrapper" mkOCICallbackInBind :: OCICallbackInBind -> IO (FunPtr OCICallbackInBind) foreign import ccall "wrapper" mkOCICallbackOutBind :: OCICallbackOutBind -> IO (FunPtr OCICallbackOutBind) -- --------------------------------------------------------------------------------- -- -- ** OCI error reporting -- --------------------------------------------------------------------------------- -- |This is just an auxiliary function for getOCIErrorMsg. getOCIErrorMsg2 :: OCIHandle -> CInt -> Ptr CInt -> CString -> CInt -> IO (CInt, String) getOCIErrorMsg2 ocihandle handleType errCodePtr errMsgBuf maxErrMsgLen = do rc <- ociErrorGet ocihandle 1 nullPtr errCodePtr errMsgBuf maxErrMsgLen handleType if rc < 0 then return (0, "Error message not available.") else do msg <- peekCString errMsgBuf e <- peek errCodePtr return (e, msg) getOCIErrorMsg :: OCIHandle -> CInt -> IO (CInt, String) getOCIErrorMsg ocihandle handleType = do let stringBufferLen = 1000 allocaBytes stringBufferLen $ \errMsg -> alloca $ \errCode -> getOCIErrorMsg2 ocihandle handleType errCode errMsg (mkCInt stringBufferLen) fromEnumOCIErrorCode :: CInt -> String fromEnumOCIErrorCode err | err == oci_SUCCESS = "OCI_SUCCESS" | err == oci_SUCCESS_WITH_INFO = "OCI_SUCCESS_WITH_INFO" | err == oci_NEED_DATA = "OCI_NEED_DATA" | err == oci_NO_DATA = "OCI_NO_DATA" | err == oci_INVALID_HANDLE = "OCI_INVALID_HANDLE" | err == oci_STILL_EXECUTING = "OCI_STILL_EXECUTING" | err == oci_CONTINUE = "OCI_CONTINUE" | err == oci_RESERVED_FOR_INT_USE = "OCI_RESERVED_FOR_INT_USE" | otherwise = "OCI_ERROR" formatErrorCodeDesc :: CInt -> String -> String formatErrorCodeDesc err desc | err == oci_ERROR = "" | otherwise = (fromEnumOCIErrorCode err) ++ " - " ++ desc -- |Given the two parts of an 'OCIException' (the error number and text) -- get the actual error message from the DBMS and construct an error message -- from all of these pieces. formatOCIMsg :: CInt -> String -> OCIHandle -> CInt -> IO (Int, String) formatOCIMsg e m ocihandle handleType = do (err, msg) <- getOCIErrorMsg ocihandle handleType --return (fromIntegral err, (formatErrorCodeDesc e m) ++ " : " ++ (show err) ++ " - " ++ msg) if msg == "" then return (fromIntegral err, (formatErrorCodeDesc e m)) else return (fromIntegral err, (formatErrorCodeDesc e m) ++ " : " ++ msg) -- |We have two format functions: 'formatEnvMsg' takes the 'EnvHandle', -- 'formatErrorMsg' takes the 'ErrorHandle'. -- They're just type-safe wrappers for 'formatMsgCommon'. formatMsgCommon :: OCIException -> OCIHandle -> CInt -> IO (Int, String) formatMsgCommon (OCIException e m) h handleType = do if e == 0 then return (0, "") else case () of _ | e == oci_ERROR -> do (formatOCIMsg e m h handleType) | e == oci_SUCCESS_WITH_INFO -> do (formatOCIMsg e m h handleType) | otherwise -> return (fromIntegral e, formatErrorCodeDesc e m) formatErrorMsg :: OCIException -> ErrorHandle -> IO (Int, String) formatErrorMsg exc err = formatMsgCommon exc (castPtr err) oci_HTYPE_ERROR formatEnvMsg :: OCIException -> EnvHandle -> IO (Int, String) formatEnvMsg exc err = formatMsgCommon exc (castPtr err) oci_HTYPE_ENV -- |The testForError functions are the only places where OCIException is thrown, -- so if you want to change or embellish it, your changes will be localised here. -- These functions factor out common error handling code -- from the OCI wrapper functions that follow. -- Typically an OCI wrapper function would look like: -- > handleAlloc handleType env = alloca ptr -> do -- > rc <- ociHandleAlloc env ptr handleType 0 nullPtr -- > if rc < 0 -- > then throwOCI (OCIException rc msg) -- > else return () -- where the code from @if rc < 0@ onwards was identical. -- 'testForError' replaces the code from @if rc < 0 ...@ onwards. testForError :: CInt -> String -> a -> IO a testForError rc msg retval = do if rc < 0 then throwOCI (OCIException rc msg) else return retval -- |Like 'testForError' but when the value you want to return -- is at the end of a pointer. -- Either there was an error, in which case the pointer probably isn't valid, -- or there is something at the end of the pointer to return. -- See 'dbLogon' and 'getHandleAttr' for example usage. testForErrorWithPtr :: Storable a => CInt -> String -> Ptr a -> IO a testForErrorWithPtr rc msg retval = do if rc < 0 then throwOCI (OCIException rc msg) else peek retval -- --------------------------------------------------------------------------------- -- -- ** Allocating Handles (i.e. creating OCI data structures, and memory management) -- --------------------------------------------------------------------------------- envCreate :: IO EnvHandle envCreate = alloca $ \ptr -> do rc <- ociEnvCreate ptr oci_DEFAULT nullPtr nullFunPtr nullFunPtr nullFunPtr 0 nullPtr testForErrorWithPtr rc "allocate initial end" ptr handleAlloc :: CInt -> OCIHandle -> IO OCIHandle handleAlloc handleType env = alloca $ \ptr -> do rc <- ociHandleAlloc env ptr handleType 0 nullPtr testForErrorWithPtr rc "allocate handle" ptr handleFree :: CInt -> OCIHandle -> IO () handleFree handleType ptr = do rc <- ociHandleFree ptr handleType testForError rc "free handle" () setHandleAttr :: ErrorHandle -> OCIHandle -> CInt -> Ptr a -> CInt -> IO () setHandleAttr err ocihandle handleType handleAttr attrType = do rc <- ociAttrSet ocihandle handleType (castPtr handleAttr) 0 attrType err testForError rc "setHandleAttr" () setHandleAttrString :: ErrorHandle -> OCIHandle -> CInt -> String -> CInt -> IO () setHandleAttrString err ocihandle handleType s attrType = do withCStringLen s $ \sC -> do rc <- ociAttrSet ocihandle handleType (castPtr (cStr sC)) (cStrLen sC) attrType err testForError rc "setHandleAttrString" () -- ociAttrGet returns a pointer to something - maybe a handle or a chunk of memory. -- Sometimes it's a pointer to a Handle, i.e. a Ptr to a Ptr to a Struct, -- so we want to peek it to get the Handle. -- Other times it's a pointer to (say) a few bytes which might contain a number or a string. -- Deref'ing it returns that value immediately, rather than a Ptr to that value. getHandleAttr :: (Storable a) => ErrorHandle -> OCIHandle -> CInt -> CInt -> IO a getHandleAttr err ocihandle handleType attrType = alloca $ \ptr -> do -- 3rd arg has type Ptr OCIBuffer. rc <- ociAttrGet ocihandle handleType (castPtr ptr) nullPtr attrType err testForErrorWithPtr rc "getAttrHandle" ptr getParam :: ErrorHandle -> StmtHandle -> Int -> IO ParamHandle getParam err stmt posn = alloca $ \ptr -> do rc <- ociParamGet (castPtr stmt) oci_HTYPE_STMT err ptr (mkCInt posn) testForErrorWithPtr rc "getParam" (castPtr ptr) -- --------------------------------------------------------------------------------- -- -- ** Connecting and detaching -- --------------------------------------------------------------------------------- -- |The OCI Logon function doesn't behave as you'd expect when the password is due to expire. -- 'ociLogon' returns 'Database.Oracle.OCIConstants.oci_SUCCESS_WITH_INFO', -- but the 'ConnHandle' returned is not valid. -- In this case we have to change 'Database.Oracle.OCIConstants.oci_SUCCESS_WITH_INFO' -- to 'Database.Oracle.OCIConstants.oci_ERROR', -- so that the error handling code will catch it and abort. -- I don't know why the handle returned isn't valid, -- as the logon process should be able to complete successfully in this case. dbLogon :: String -> String -> String -> EnvHandle -> ErrorHandle -> IO ConnHandle dbLogon user pswd db env err = withCStringLen user $ \userC -> withCStringLen pswd $ \pswdC -> withCStringLen db $ \dbC -> alloca $ \conn -> do rc <- ociLogon env err conn (cStr userC) (cStrLen userC) (cStr pswdC) (cStrLen pswdC) (cStr dbC) (cStrLen dbC) case () of _ | rc == oci_SUCCESS_WITH_INFO -> testForErrorWithPtr oci_ERROR "logon" conn | otherwise -> testForErrorWithPtr rc "logon" conn dbLogoff :: ErrorHandle -> ConnHandle -> IO () dbLogoff err conn = do rc <- ociLogoff conn err testForError rc "logoff" () terminate :: IO () terminate = do rc <- ociTerminate oci_DEFAULT testForError rc "terminate" () serverDetach :: ErrorHandle -> ServerHandle -> IO () serverDetach err server = do rc <- ociServerDetach server err oci_DEFAULT testForError rc "server detach" () serverAttach :: ErrorHandle -> ServerHandle -> String -> IO () serverAttach err server dblink = do withCStringLen dblink $ \s -> do rc <- ociServerAttach server err (cStr s) (cStrLen s) oci_DEFAULT testForError rc "server attach" () -- |Having established a connection (Service Context), now get the Session. -- You can have more than one session per connection, -- but I haven't implemented it yet. getSession :: ErrorHandle -> ConnHandle -> IO SessHandle getSession err conn = liftM castPtr (getHandleAttr err (castPtr conn) oci_HTYPE_SVCCTX oci_ATTR_SESSION) sessionBegin :: ErrorHandle -> ConnHandle -> SessHandle -> CInt -> IO () sessionBegin err conn sess cred = do rc <- ociSessionBegin conn err sess cred oci_DEFAULT testForError rc "session begin" () sessionEnd :: ErrorHandle -> ConnHandle -> SessHandle -> IO () sessionEnd err conn sess = do rc <- ociSessionEnd conn err sess oci_DEFAULT testForError rc "session end" () -- --------------------------------------------------------------------------------- -- -- ** Transactions -- --------------------------------------------------------------------------------- beginTrans :: ErrorHandle -> ConnHandle -> CInt -> IO () beginTrans err conn isolation = do rc <- ociTransStart conn err 0 isolation testForError rc "begin transaction" () commitTrans :: ErrorHandle -> ConnHandle -> IO () commitTrans err conn = do rc <- ociTransCommit conn err oci_DEFAULT testForError rc "commit" () rollbackTrans :: ErrorHandle -> ConnHandle -> IO () rollbackTrans err conn = do rc <- ociTransRollback conn err oci_DEFAULT testForError rc "rollback" () -- --------------------------------------------------------------------------------- -- -- ** Issuing queries -- --------------------------------------------------------------------------------- -- |With the OCI you do queries with these steps: -- * prepare your statement (it's just a String) - no communication with DBMS -- * execute it (this sends it to the DBMS for parsing etc) -- * allocate result set buffers by calling 'defineByPos' for each column -- * call fetch for each row. -- * call 'handleFree' for the 'StmtHandle' -- (I assume this is the approved way of terminating the query; -- the OCI docs aren't explicit about this.) stmtPrepare :: ErrorHandle -> StmtHandle -> String -> IO () stmtPrepare err stmt sqltext = do withCStringLen sqltext $ \sqltextC -> do rc <- ociStmtPrepare stmt err (cStr sqltextC) (cStrLen sqltextC) oci_NTV_SYNTAX oci_DEFAULT testForError rc "stmtPrepare" () stmtExecute :: ErrorHandle -> ConnHandle -> StmtHandle -> Int -> IO () stmtExecute err conn stmt iterations = do rc <- ociStmtExecute conn stmt err (mkCInt iterations) 0 nullPtr nullPtr oci_DEFAULT testForError rc "stmtExecute" () -- |defineByPos allocates memory for a single column value. -- The allocated components are: -- * the result (i.e. value) - you have to say how big with bufsize. -- * the null indicator (int16) -- * the size of the returned data (int16) -- Previously it was the caller's responsibility to free the memory after they're done with it. -- Now we use 'Foreign.ForeignPtr.mallocForeignPtr', so manual memory management is hopefully -- a thing of the past. -- The caller will also have to cast the data in bufferptr to the expected type -- (using 'Foreign.Ptr.castPtr'). defineByPos :: ErrorHandle -> StmtHandle -> Int -- ^ Position -> Int -- ^ Buffer size in bytes -> CInt -- ^ SQL Datatype (from "Database.Oracle.OCIConstants") -> IO ColumnInfo -- ^ tuple: (DefnHandle, Ptr to buffer, Ptr to null indicator, Ptr to size of value in buffer) defineByPos err stmt posn bufsize sqldatatype = do bufferFPtr <- mallocForeignPtrBytes bufsize nullIndFPtr <- mallocForeignPtr retSizeFPtr <- mallocForeignPtr alloca $ \defnPtr -> withForeignPtr bufferFPtr $ \bufferPtr -> withForeignPtr nullIndFPtr $ \nullIndPtr -> withForeignPtr retSizeFPtr $ \retSizePtr -> do rc <- ociDefineByPos stmt defnPtr err (mkCInt posn) bufferPtr (mkCInt bufsize) (mkCUShort sqldatatype) nullIndPtr retSizePtr nullPtr oci_DEFAULT defn <- peek defnPtr -- no need for caller to free defn; I think freeing the stmt handle does it. testForError rc "defineByPos" (defn, bufferFPtr, nullIndFPtr, retSizeFPtr) -- |Oracle only understands bind variable placeholders using syntax :x, -- where x is a number or a variable name. -- Most other DBMS's use ? as a placeholder, -- so we have this function to substitute ? with :n, -- where n starts at one and increases with each ?. -- We don't use this function in this library though; -- it's used in the higher-level implementation of Enumerator. -- We prefer to retain flexibility at this lower-level, -- and not force arbitrary implementation choices too soon. -- If you want to use this library and use :x style syntax, you can. substituteBindPlaceHolders sql = sbph sql 1 False "" sbph :: String -> Int -> Bool -> String -> String sbph [] _ _ acc = reverse acc sbph ('\'':cs) i inQuote acc = sbph cs i (not inQuote) ('\'':acc) sbph ('?':cs) i False acc = sbph cs (i+1) False ((reverse (show i)) ++ (':':acc)) sbph (c:cs) i inQuote acc = sbph cs i inQuote (c:acc) bindByPos :: ErrorHandle -> StmtHandle -> Int -- ^ Position -> CShort -- ^ Null ind: 0 == not null, -1 == null -> BufferPtr -- ^ payload -> Int -- ^ payload size in bytes -> CInt -- ^ SQL Datatype (from "Database.Oracle.OCIConstants") -> IO () bindByPos err stmt pos nullInd bufptr sze sqltype = do indFPtr <- mallocForeignPtr sizeFPtr <- mallocForeignPtr withForeignPtr indFPtr $ \p -> poke p nullInd -- You can't put any old junk in the return-size field, -- even if the parameter is IN-only. -- So tell it how big the input buffer is. withForeignPtr sizeFPtr $ \p -> poke p (fromIntegral sze) bufFPtr <- newForeignPtr_ bufptr bindOutputByPos err stmt pos (indFPtr, bufFPtr, sizeFPtr) sze sqltype return () -- Note that this function takes a ForeignPtr to the output-size -- (in the triple) and also a size parameter, which is the input size. -- We need to provide both, apparently, regardless of actual parameter -- direction. bindOutputByPos :: ErrorHandle -> StmtHandle -> Int -- ^ Position -> BindBuffer -- ^ triple of (null-ind, payload, input-size) -> Int -- ^ buffer max size in bytes -> CInt -- ^ SQL Datatype (from "Database.Oracle.OCIConstants") -> IO BindHandle bindOutputByPos err stmt pos (nullIndFPtr, bufFPtr, sizeFPtr) sze sqltype = alloca $ \bindHdl -> withForeignPtr nullIndFPtr $ \indPtr -> do withForeignPtr sizeFPtr $ \sizePtr -> withForeignPtr bufFPtr $ \bufPtr -> do rc <- ociBindByPos stmt bindHdl err (fromIntegral pos) bufPtr (fromIntegral sze) (fromIntegral sqltype) indPtr sizePtr nullPtr 0 nullPtr (fromIntegral oci_DEFAULT) testForError rc "bindOutputByPos" () bptr <- peek bindHdl return bptr -- | Fetch a single row into the buffers. -- If you have specified a prefetch count > 1 then the row -- might already be cached by the OCI library. stmtFetch :: ErrorHandle -> StmtHandle -> IO CInt stmtFetch err stmt = do let numRowsToFetch = 1 rc <- ociStmtFetch stmt err numRowsToFetch (mkCShort oci_FETCH_NEXT) oci_DEFAULT if rc == oci_NO_DATA then return rc else testForError rc "stmtFetch" rc -- From the "Bindind and Defining" chapter in the OCI docs: -- Binding RETURNING...INTO variables -- An OCI application implements the placeholders in the RETURNING clause -- as pure OUT bind variables. However, all binds in the RETURNING clause -- are initially IN and must be properly initialized. -- To provide a valid value, you can provide a NULL indicator -- and set that indicator to -1 (NULL). -- An application must adhere to the following rules when working with -- bind variables in a RETURNING clause: -- 1. Bind RETURNING clause placeholders in OCI_DATA_AT_EXEC mode using -- OCIBindByName() or OCIBindByPos(), followed by a call to -- OCIBindDynamic() for each placeholder. -- Note: The OCI only supports the callback mechanism for -- RETURNING clause binds. The polling mechanism is not supported. -- 2. When binding RETURNING clause placeholders, you must supply a valid out -- bind function as the ocbfp parameter of the OCIBindDynamic() call. -- This function must provide storage to hold the returned data. -- 3. The icbfp parameter of OCIBindDynamic() call should provide a -- "dummy" function which returns NULL values when called. -- 4. The piecep parameter of OCIBindDynamic() must be set to OCI_ONE_PIECE. -- 5. No duplicate binds are allowed in a DML statement with a -- RETURNING clause, such as no duplication between bind variables -- in the DML section and the RETURNING section of the statement. -- |Short-circuit null test: if the buffer contains a null then return Nothing. -- Otherwise, run the IO action to extract a value from the buffer and return Just it. maybeBufferNull :: ForeignPtr CShort -> Maybe a -> IO a -> IO (Maybe a) maybeBufferNull nullIndFPtr nullVal action = withForeignPtr nullIndFPtr $ \nullIndPtr -> do nullInd <- liftM cShort2Int (peek nullIndPtr) if (nullInd == -1) -- -1 == null, 0 == value then return nullVal else do v <- action return (Just v) nullByte :: CChar nullByte = 0 cShort2Int :: CShort -> Int cShort2Int n = fromIntegral n cUShort2Int :: CUShort -> Int cUShort2Int n = fromIntegral n cuCharToInt :: CUChar -> Int cuCharToInt c = fromIntegral c byteToInt :: Ptr CUChar -> Int -> IO Int byteToInt buffer n = do b <- peekByteOff buffer n return (cuCharToInt b) bufferToString :: ColumnInfo -> IO (Maybe String) bufferToString (_, bufFPtr, nullFPtr, sizeFPtr) = withForeignPtr nullFPtr $ \nullIndPtr -> do nullInd <- liftM cShort2Int (peek nullIndPtr) if (nullInd == -1) -- -1 == null, 0 == value then return Nothing else do -- Given a column buffer, extract a string of variable length -- (you have to terminate it yourself). withForeignPtr bufFPtr $ \bufferPtr -> withForeignPtr sizeFPtr $ \retSizePtr -> do retsize <- liftM cUShort2Int (peek retSizePtr) --putStrLn ("bufferToString: size = " ++ show retsize) pokeByteOff (castPtr bufferPtr) retsize nullByte val <- peekCString (castPtr bufferPtr) return (Just val) -- | Oracle's excess-something-or-other encoding for years: -- year = 100*(c - 100) + (y - 100), -- c = (year div 100) + 100, -- y = (year mod 100) + 100. -- +1999 -> 119, 199 -- +0100 -> 101, 100 -- +0001 -> 100, 101 -- -0001 -> 100, 99 -- -0100 -> 99, 100 -- -1999 -> 81, 1 makeYear :: Int -> Int -> Int makeYear c100 y100 = 100 * (c100 - 100) + (y100 - 100) makeYearByte :: Int -> Word8 makeYearByte y = fromIntegral ((rem y 100) + 100) makeCentByte :: Int -> Word8 makeCentByte y = fromIntegral ((quot y 100) + 100) dumpBuffer :: Ptr Word8 -> IO () dumpBuffer buf = do dumpByte 0 dumpByte 1 dumpByte 2 dumpByte 3 dumpByte 4 dumpByte 5 dumpByte 6 putStrLn "" where dumpByte n = do b <- (peekByteOff buf n :: IO Word8) putStr $ (show b) ++ " " bufferToCaltime :: ForeignPtr CShort -> BufferFPtr -> IO (Maybe CalendarTime) bufferToCaltime nullind fptr = maybeBufferNull nullind Nothing $ withForeignPtr fptr $ \bufferPtr -> do let buffer = castPtr bufferPtr --dumpBuffer (castPtr buffer) century100 <- byteToInt buffer 0 year100 <- byteToInt buffer 1 month <- byteToInt buffer 2 day <- byteToInt buffer 3 hour <- byteToInt buffer 4 minute <- byteToInt buffer 5 second <- byteToInt buffer 6 return $ CalendarTime { ctYear = makeYear century100 year100 , ctMonth = toEnum (month - 1) , ctDay = day , ctHour = hour - 1 , ctMin = minute - 1 , ctSec = second - 1 , ctPicosec = 0 , ctWDay = Sunday , ctYDay = -1 , ctTZName = "UTC" , ctTZ = 0 , ctIsDST = False } bufferToUTCTime :: ForeignPtr CShort -> BufferFPtr -> IO (Maybe UTCTime) bufferToUTCTime nullind fptr = maybeBufferNull nullind Nothing $ withForeignPtr fptr $ \bufferPtr -> do let buffer = castPtr bufferPtr --dumpBuffer (castPtr buffer) century100 <- byteToInt buffer 0 year100 <- byteToInt buffer 1 month <- byteToInt buffer 2 day <- byteToInt buffer 3 hour <- byteToInt buffer 4 minute <- byteToInt buffer 5 second <- byteToInt buffer 6 let year = makeYear century100 year100 return (mkUTCTime year month day (hour-1) (minute-1) (second-1)) setBufferByte :: BufferPtr -> Int -> Word8 -> IO () setBufferByte buf n v = pokeByteOff buf n v calTimeToBuffer :: BufferPtr -> CalendarTime -> IO () calTimeToBuffer buf ct = do setBufferByte buf 0 (makeCentByte (ctYear ct)) setBufferByte buf 1 (makeYearByte (ctYear ct)) setBufferByte buf 2 (fromIntegral ((fromEnum (ctMonth ct)) + 1)) setBufferByte buf 3 (fromIntegral (ctDay ct)) setBufferByte buf 4 (fromIntegral (ctHour ct + 1)) setBufferByte buf 5 (fromIntegral (ctMin ct + 1)) setBufferByte buf 6 (fromIntegral (ctSec ct + 1)) utcTimeToBuffer :: BufferPtr -> UTCTime -> IO () utcTimeToBuffer buf utc = do let (LocalTime ltday time) = utcToLocalTime (hoursToTimeZone 0) utc let (TimeOfDay hour minute second) = time let (year, month, day) = toGregorian ltday setBufferByte buf 0 (makeCentByte (fromIntegral year)) setBufferByte buf 1 (makeYearByte (fromIntegral year)) setBufferByte buf 2 (fromIntegral month) setBufferByte buf 3 (fromIntegral day) setBufferByte buf 4 (fromIntegral (hour+1)) setBufferByte buf 5 (fromIntegral (minute+1)) setBufferByte buf 6 (round (second+1)) bufferPeekValue :: (Storable a) => BufferFPtr -> IO a bufferPeekValue buffer = do v <- withForeignPtr buffer $ \bufferPtr -> peek $ castPtr bufferPtr return v bufferToA :: (Storable a) => ForeignPtr CShort -> BufferFPtr -> IO (Maybe a) bufferToA nullind buffer = maybeBufferNull nullind Nothing (bufferPeekValue buffer) bufferToCInt :: ForeignPtr CShort -> BufferFPtr -> IO (Maybe CInt) bufferToCInt = bufferToA bufferToInt :: ForeignPtr CShort -> BufferFPtr -> IO (Maybe Int) bufferToInt nullind b = do cint <- bufferToCInt nullind b return $ maybe Nothing (Just . fromIntegral) cint bufferToCDouble :: ForeignPtr CShort -> BufferFPtr -> IO (Maybe CDouble) bufferToCDouble = bufferToA bufferToDouble :: ForeignPtr CShort -> BufferFPtr -> IO (Maybe Double) bufferToDouble nullind b = do cdbl <- bufferToCDouble nullind b return $ maybe Nothing (Just . realToFrac) cdbl bufferToStmtHandle :: BufferFPtr -> IO StmtHandle bufferToStmtHandle buffer = do withForeignPtr buffer $ \bufferPtr -> do v <- peek (castPtr bufferPtr) return v