packages feed

hsSqlite3-0.0.2: src/Database/Sqlite3/Low.hs

{-# options -fglasgow-exts -ffi #-}

module Database.Sqlite3.Low where

import Foreign
import Foreign.C

import System.IO
import System.IO.Unsafe
import System.IO.Error hiding (try)

import Control.Monad.Trans
import Control.Monad.Error
import Control.Monad.State.Lazy hiding (get,put)

import qualified Codec.Binary.UTF8.String as UTF8
import qualified Data.ByteString as B

import Data.StateLocal hiding (getST)

import Data.Typeable

type Void = Word8

foreign import ccall unsafe "sqlite3_open" c_open
 :: CString -> Ptr (Ptr Void) -> IO Int

foreign import ccall unsafe "sqlite3_close" c_close
 :: Ptr Void -> IO Int

foreign import ccall unsafe "sqlite3_errmsg" c_errmsg
 :: Ptr Void -> IO CString

foreign import ccall unsafe "sqlite3_prepare_v2" c_prepare
 :: Ptr Void -> CString -> Int -> Ptr (Ptr Void) -> Ptr CString -> IO Int

foreign import ccall unsafe "sqlite3_finalize" c_finalize
 :: Ptr Void -> IO Int

foreign import ccall unsafe "sqlite3_reset" c_reset
 :: Ptr Void -> IO Int

foreign import ccall unsafe "sqlite3_bind_int" c_bind_int
 :: Ptr Void -> Int -> Int -> IO Int

foreign import ccall unsafe "sqlite3_bind_text" c_bind_text
 :: Ptr Void -> Int -> CString -> Int -> Int -> IO Int

foreign import ccall unsafe "sqlite3_step" c_step
 :: Ptr Void -> IO Int

foreign import ccall unsafe "sqlite3_column_blob" c_column_blob
 :: Ptr Void -> Int -> IO CString

foreign import ccall unsafe "sqlite3_column_bytes" c_column_bytes
 :: Ptr Void -> Int -> IO Int

foreign import ccall unsafe "sqlite3_column_count" c_column_count
 :: Ptr Void -> IO Int

foreign import ccall unsafe "sqlite3_column_int" c_column_int
 :: Ptr Void -> Int -> IO Int

foreign import ccall unsafe "sqlite3_column_double" c_column_double
 :: Ptr Void -> Int -> IO Double

foreign import ccall unsafe "sqlite3_column_text" c_column_text
 :: Ptr Void -> Int -> IO CString

foreign import ccall unsafe "sqlite3_column_type" c_column_type
 :: Ptr Void -> Int -> IO Int

--

data StateDb = SD
 { dbP :: Maybe (Ptr Void)
 , stP :: Maybe (Ptr Void)
 } deriving (Show, Typeable)

instance ZeroState StateDb where
 zeroST = SD
  { dbP = Nothing
  , stP = Nothing }

setDB :: (Error e, MonadError e m, LocalState sg StateDb) => Ptr Void -> StateT sg m ()
setDB ptr = get >>= \st@(SD { dbP = dbP }) ->
 case dbP of
   Nothing -> put (st { dbP = Just ptr })
   Just _  -> lift $ throwError $ strMsg
    "Database is allready set"

setST :: (Error e, MonadError e m, LocalState sg StateDb) => Ptr Void -> StateT sg m ()
setST ptr = get >>= \st@(SD { stP = stP }) ->
    case stP of
      Nothing -> put (st { stP = Just ptr })
      Just _  -> lift $ throwError $ strMsg
        "Statement is allready set"

isSetDB :: (Error e, MonadError e m, LocalState sg StateDb) => StateT sg m Bool
isSetDB = get >>= \(SD { dbP = dbP }) ->
    case dbP of
      Nothing -> return False
      Just _ -> return True

getDB :: (Error e, MonadError e m, LocalState sg StateDb) => StateT sg m (Ptr Void)
getDB = get >>= \(SD { dbP = dbP }) ->
    case dbP of
      Just a  -> return a
      Nothing -> lift $ throwError $ strMsg
        "Database is not set"

getST :: (Error e, MonadError e m, LocalState sg StateDb) => StateT sg m (Ptr Void)
getST = get >>= \(SD { stP = stP }) ->
    case stP of
      Just a  -> return a
      Nothing -> lift $ throwError $ strMsg
        "Statement is not set"

clearDB :: (Error e, MonadError e m, LocalState sg StateDb) => StateT sg m ()
clearDB = get >>= \st@(SD { dbP = dbP }) ->
    case dbP of
      Just _  -> put (st { dbP = Nothing })
      Nothing -> lift $ throwError $ strMsg
        "Database is allready clear"

clearST :: (Error e, MonadError e m, LocalState sg StateDb) => StateT sg m ()
clearST = get >>= \st@(SD { stP = stP }) ->
    case stP of
      Just _  -> put (st { stP = Nothing })
      Nothing -> lift $ throwError $ strMsg
        "Statement is allready clear"

--

err :: String -> StateT s IO ()
err = liftIO .  throwError .  strMsg

errRc :: LocalState s StateDb => Int -> String -> StateT s IO ()
errRc 0 _ = return ()
errRc rc msg = do
 ans <- isSetDB
 case ans of
  True -> do
    ptr <- getDB
    cstr <- liftIO $ c_errmsg ptr
    reason <- liftIO $ peekCString cstr
    err $ "Foreign: "++msg++": (rc="++show rc++") "++reason
  False ->
    err $ "Foreign: "++msg++": (rc="++show rc++")"

errIf :: LocalState s StateDb => Bool -> String -> StateT s IO ()
errIf False _ = return ()
errIf True msg = do
 ans <- isSetDB
 case ans of
  True -> do
    ptr <- getDB
    cstr <- liftIO $ c_errmsg ptr
    reason <- liftIO $ peekCString cstr
    err $ "Foreign: "++msg++": "++reason
  False ->
    err $ "Foreign: "++msg


--debug a = liftIO $ hPutStr stderr $ "DEBUG: " ++ show a ++ "\n"

debug :: Show a => a -> StateT s IO ()
debug a = liftIO $ hPutStr stderr ("DEBUG: " ++ show a ++ "\n")

cont :: ((a -> IO (b,s)) -> IO (b,s)) -> (a -> StateT s IO b) -> StateT s IO b
cont f g = StateT $ \st -> liftIO $ f $ \a -> (runStateT (g a)) st

fin a b = catchError a (\e -> b >> throwError e)

try a = catchError (liftM Right a) (\e -> return $ Left e)

--

open :: LocalState s StateDb => String -> StateT s IO ()
open path =
 cont alloca $ \pptr ->
 cont (withCString path) $ \cstr -> do
  debug (cstr,pptr,"open")
  rc <- liftIO $ c_open cstr pptr
  ptr <- liftIO $ peek pptr
  setDB ptr
  fin (errRc rc "open") $ do
    r <- try close
    case r of
     Left _ -> clearDB
     Right () -> return ()

close :: LocalState s StateDb => StateT s IO ()
close = do
 ptr <- getDB
 debug (ptr,"close")
 rc <- liftIO $ c_close ptr
 errRc rc "close"
 clearDB

prepare :: LocalState s StateDb => String -> StateT s IO ()
prepare sql =
 cont alloca $ \pptr ->
 cont (withCStringLen sql) $ \(cstr,len) -> do
  ptr <- getDB
  debug (cstr,len,pptr,"prepare")
  rc <- liftIO $ c_prepare ptr cstr len pptr nullPtr
  sptr <- liftIO $ peek pptr
  setST sptr
  fin (errRc rc "prepare") $ do
   r <- try finalize
   case r of
    Left _ -> clearST
    Right () -> return ()

finalize :: LocalState s StateDb => StateT s IO ()
finalize = do
 ptr <- getST
 debug (ptr,"finalize")
 rc <- liftIO $ c_finalize ptr
 errRc rc "finalize"
 clearST

reset :: LocalState s StateDb => StateT s IO ()
reset = do
 ptr <- getST
 debug (ptr,"reset")
 rc <- liftIO $ c_reset ptr
 errRc rc "reset"

step :: LocalState s StateDb => StateT s IO Bool
step = do
 ptr <- getST
 debug (ptr,"step")
 rc <- liftIO $ c_step ptr
 errIf (rc /= 100 && rc /= 101) $ "step: "++show rc
 return (rc == 101)

bind_int :: LocalState s StateDb => (Int,Int) -> StateT s IO ()
bind_int (num,val) = do
 ptr <- getST
 debug (ptr,"bind_int")
 rc <- liftIO $ c_bind_int ptr num val
 errRc rc "bind_int"

bind_text :: LocalState s StateDb => (Int,String) -> StateT s IO ()
bind_text (num,val) =
 cont (withCStringLen val) $ \(cstr,len) -> do
  ptr <- getST
  debug (ptr,"bind_text")
  rc <- liftIO $ c_bind_text ptr num cstr len (-1)
  errRc rc "bind_text"

column_bytes :: LocalState s StateDb => Int -> StateT s IO Int
column_bytes num = do
 ptr <- getST
 debug (ptr,"column_bytes")
 liftIO $ c_column_bytes ptr num

column_count :: LocalState s StateDb => StateT s IO Int
column_count = do
 ptr <- getST
 debug (ptr,"column_count")
 liftIO $ c_column_count ptr

column_int :: LocalState s StateDb => Int -> StateT s IO Int
column_int num = do
 ptr <- getST
 debug (ptr,"column_int")
 liftIO $ c_column_int ptr num

column_double :: LocalState s StateDb => Int -> StateT s IO Double
column_double num = do
 ptr <- getST
 debug (ptr,"column_double")
 liftIO $ c_column_double ptr num

column_type :: LocalState s StateDb => Int -> StateT s IO Int
column_type num = do
 ptr <- getST
 debug (ptr,"column_type")
 liftIO $ c_column_type ptr num

column_text :: LocalState s StateDb => Int -> StateT s IO String
column_text num = do
 ptr <- getST
 by <- column_bytes num
 debug (ptr,by,"column_text")
 tx <- liftIO $ c_column_text ptr num
 utf <- liftIO $ peekCStringLen (tx,by)
 return $ UTF8.decodeString utf

column_blob :: LocalState s StateDb => Int -> StateT s IO B.ByteString
column_blob num = do
 ptr <- getST
 debug (ptr,"column_blob")
 by <- column_bytes num
 bl <- liftIO $ c_column_blob ptr num
 return $ B.packCStringLen (bl,by)