hsSqlite3-0.0.6: src/Database/HsSqlite3.hs
module Database.HsSqlite3
( module Database.Sqlite3.Low
, Val ( IntV, Int64V, DoubleV, TextV, BlobV, NullV )
, fetch
, bind
, row
, test
, sql
) where
import Data.Char
import Control.Monad
import Data.Int
import Database.Sqlite3.Low
import qualified Data.ByteString as B
import Control.Arrow
import Data.Typeable
import Control.Monad.Trans
import Control.Monad.Error
import Control.Monad.State.Lazy
import Data.Monoid
import Data.State hiding (fetch)
import qualified Codec.Binary.UTF8.String as UTF8
--
class ValID a where
valID :: a -> Int
class ValFetch a where
valFetch :: (Int,Int) -> DbIO a
class ValBind a where
valBind :: (Int,a) -> DbIO ()
class ValID a => ValCast a b where
valCast :: a -> b
data Val
= IntV Int
| Int64V Int64
| DoubleV Double
| TextV String
| BlobV B.ByteString
| NullV
deriving Show
instance ValID Val where
valID (IntV _) = 1
valID (Int64V _) = 1
valID (DoubleV _) = 2
valID (TextV _) = 3
valID (BlobV _) = 4
valID NullV = 5
instance ValFetch Val where
valFetch (t,n) = do
case t of
1 -> liftM IntV $ column_int n
2 -> liftM DoubleV $ column_double n
3 -> liftM TextV $ column_text n
4 -> liftM BlobV $ column_blob n
5 -> return NullV
instance ValBind Val where
valBind (t,v) = do
case v of
IntV a -> bind_int (t,a)
TextV a -> bind_text (t,a)
instance ValCast Val Int where
valCast (IntV a) = a
instance ValCast Val Double where
valCast (DoubleV a) = a
instance ValCast Val String where
valCast (TextV a) = a
instance ValCast Val B.ByteString where
valCast (BlobV a) = a
instance ValCast Val () where
valCast NullV = ()
instance ValID Int where valID _ = 1
instance ValID Double where valID _ = 2
instance ValID String where valID _ = 3
instance ValID B.ByteString where valID _ = 4
instance ValID () where valID _ = 5
reqType :: Int -> (Int -> DbIO a) -> (Int,Int) -> DbIO a
reqType t' a (t,v) = do
errIf (t'/=t) "ValFetch"
a v
instance ValFetch Int where
valFetch = reqType 1 column_int
instance ValFetch Double where
valFetch = reqType 2 column_double
instance ValFetch String where
valFetch = reqType 3 column_text
instance ValFetch B.ByteString where
valFetch = reqType 4 column_blob
instance ValFetch () where
valFetch = reqType 5 (\_ -> return ())
instance ValBind Int where
valBind = bind_int
instance ValBind String where
valBind = bind_text
instance ValCast Int Val where
valCast a = IntV a
instance ValCast Double Val where
valCast a = DoubleV a
instance ValCast String Val where
valCast a = TextV a
instance ValCast B.ByteString Val where
valCast a = BlobV a
instance ValCast () Val where
valCast a = NullV
--
fetch :: ValFetch a => String -> DbIO [[a]]
fetch sql = do
liftIO $ putStr $ UTF8.encodeString $ sql++"\n"
prepare sql
stat <- step
case stat of
True -> finalize >> return []
False -> do
cn <- column_count
ty <- mapM column_type [0..cn-1]
let ft = mapM valFetch (zip ty [0..])
x <- ft
xs <- fetchRow ft
finalize
return (x:xs)
where
fetchRow ft = do
end <- step
case end of
True -> return []
False -> do
x <- ft
xs <- fetchRow ft
return (x:xs)
sql :: String -> DbIO ()
sql sql = do
liftIO $ putStr $ UTF8.encodeString $ sql++"\n"
prepare sql
step
finalize
bind :: String -> [[Val]] -> DbIO ()
bind sql tab = do
liftIO $ putStr $ UTF8.encodeString $ sql++"\n"
debug tab
prepare sql
mapM_ (\row -> mapM_ (\(v,n) -> valBind (n,v)) (zip row [1..]) >> step >> reset) tab
finalize
fetchOne' :: ValFetch a => String -> Int -> DbIO [[a]]
fetchOne' sql num = fetch sql'
where sql' = concat [sql," LIMIT 1 OFFSET ", show num]
row :: ValFetch a => String -> Int -> DbIO [a]
row sql num = liftM head $ fetchOne' sql num
test :: String -> DbIO Bool
test sql = do
tab :: [[Val]] <- fetchOne' sql 0
case length tab of
1 -> return True
_ -> return False
Patch from Evgeny:
Utilization of types
For fetch
> tab :: [Nil :. Int :. Int :. Double] <- fetch "SELECT * FROM test01"
> liftIO $ mapM print tab
And for bind
> bind "INSERT INTO test01 VALUES(?,?,?)" $
> replicate 7 $
> Nil :. (1::Int) :. (2::Int) :. (3.0::Double)
This is stack with different types
> Nil :. (1::Int) :. (2::Int) :. (3.0::Double)
Definition of stack
> data Stack a where
> Nil :: Stack ()
> (:.) :: Stack a -> b -> Stack (Stack a, b)
This is class for new kinds of values.
"T" means used in type stack ... type manipulations ...
> class Cell a where
> bindT :: Int -> a -> Db ()
> columnT :: Int -> Db a
> idT :: a -> Int
> instance Cell Int where
> idT _ = 1
> columnT = columnInt
> bindT = bindInt
> instance Cell Double where
> idT _ = 2
> columnT = columnDouble
> bindT = bindDouble
Equality test of derived types and database types
> fetchBody derived = do
> cn <- columnCount
> dbTypes <- mapM columnType $ enumFromThenTo (cn-1) (cn-2) 0
> case dbTypes == derived of
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> False -> fail "different database and haskell types"
> True -> return ()
> x <- columnStack
> xs <- fetchTail
> finalize
> return (x:xs)
Middle level:
Abstract away from state and errors
> class DbError e where
> makeErr :: CInt -> e
> castErr :: e -> Maybe CInt
> class MonadIO m => MonadDb m where
> getDb :: m (Ptr L.Sqlite3)
> putDb :: Ptr L.Sqlite3 -> m ()
> cleanDb :: m ()
> isDbReady :: m Bool
> getSt :: m (Ptr L.Stmt)
> putSt :: Ptr L.Stmt -> m ()
> cleanSt :: m ()
> isStReady :: m Bool
Simple instance for MonadDb (in Sqlite3.hs)
> data SimpleState = SimpleState
> { database :: Maybe (Ptr L.Sqlite3)
> , statement :: Maybe (Ptr L.Stmt ) }
> ...
> instance MonadDb (StateT SimpleState (ErrorT (Either CInt String) IO)) where
> getDb = get >>= maybe (fail "Empty database") return . database
> getSt = get >>= maybe (fail "Empty statement") return . statement
> putDb x = modify $ \a -> a { database = Just x }
> putSt x = modify $ \a -> a { statement = Just x }
> cleanDb = modify $ \a -> a { database = Nothing }
> cleanSt = modify $ \a -> a { statement = Nothing }
> isDbReady = get >>= return . maybe False (const True) . database
> isStReady = get >>= return . maybe False (const True) . statement
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
type DbE a =
(Error e
,MonadError e m
,MonadState sg m
,LocalState sg StateDb
,Monad m
) => m a
type DbIO a =
(LocalState sg StateDb
,MonadIO m
,MonadState sg m
,MonadError e m
,Error e
,Monad m
) => m a
type E a =
(MonadError e m
,Error e
) => m a
data StateDb = SD
{ dbP :: Maybe (Ptr Void)
, stP :: Maybe (Ptr Void)
} deriving (Show, Typeable)
instance ZeroState StateDb where
zeroState = SD
{ dbP = Nothing
, stP = Nothing }
setDB :: Ptr Void -> DbE ()
setDB ptr = get >>= \st@(SD { dbP = dbP }) ->
case dbP of
Nothing -> put (st { dbP = Just ptr })
Just _ -> err "Database is allready set"
setST :: Ptr Void -> DbE ()
setST ptr = get >>= \st@(SD { stP = stP }) ->
case stP of
Nothing -> put (st { stP = Just ptr })
Just _ -> err "Statement is allready set"
isSetDB :: DbE Bool
isSetDB = get >>= \(SD { dbP = dbP }) ->
case dbP of
Nothing -> return False
Just _ -> return True
getDB :: DbE (Ptr Void)
getDB = get >>= \(SD { dbP = dbP }) ->
case dbP of
Just a -> return a
Nothing -> err "Database is not set"
getST :: DbE (Ptr Void)
getST = get >>= \(SD { stP = stP }) ->
case stP of
Just a -> return a
Nothing -> err "Statement is not set"
clearDB :: DbE ()
clearDB = get >>= \st@(SD { dbP = dbP }) ->
case dbP of
Just _ -> put (st { dbP = Nothing })
Nothing -> err "Database is allready clear"
clearST :: DbE ()
clearST = get >>= \st@(SD { stP = stP }) ->
case stP of
Just _ -> put (st { stP = Nothing })
Nothing -> err "Statement is allready clear"
--
err :: String -> E x
err a = throwError $ strMsg a
errRc :: Int -> String -> DbIO ()
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 :: Bool -> String -> DbIO ()
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 -> DbIO ()
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 :: String -> DbIO ()
open path = do
pptr <- i malloc
cstr <- i$ newCString path
debug (path,cstr,pptr,"open")
rc <- i$ c_open cstr pptr
ptr <- i$ peek pptr
setDB ptr
fin (errRc rc "open") $ do
r <- try close
case r of
Left _ -> clearDB
Right () -> return ()
where i = liftIO
close :: DbIO ()
close = do
ptr <- getDB
debug (ptr,"close")
rc <- liftIO $ c_close ptr
errRc rc "close"
clearDB
prepare :: String -> DbIO ()
prepare sql = do
pptr <- i malloc
(cstr,len) <- i$ newCStringLen sql
debug (sql,cstr,len,pptr,"prepare")
ptr <- getDB
rc <- i$ c_prepare ptr cstr len pptr nullPtr
sptr <- i$ peek pptr
setST sptr
fin (errRc rc "prepare") $ do
r <- try finalize
case r of
Left _ -> clearST
Right () -> return ()
where i = liftIO
finalize :: DbIO ()
finalize = do
ptr <- getST
debug (ptr,"finalize")
rc <- liftIO $ c_finalize ptr
errRc rc "finalize"
clearST
reset :: DbIO ()
reset = do
ptr <- getST
debug (ptr,"reset")
rc <- liftIO $ c_reset ptr
errRc rc "reset"
step :: DbIO 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 :: (Int,Int) -> DbIO ()
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 :: (Int,String) -> DbIO ()
bind_text (num,val) = do
(cstr,len) <- liftIO $ newCStringLen val
ptr <- getST
debug (ptr,"bind_text")
rc <- liftIO $ c_bind_text ptr num cstr len (-1)
errRc rc "bind_text"
column_bytes :: Int -> DbIO Int
column_bytes num = do
ptr <- getST
debug (ptr,"column_bytes")
liftIO $ c_column_bytes ptr num
column_count :: DbIO Int
column_count = do
ptr <- getST
debug (ptr,"column_count")
liftIO $ c_column_count ptr
column_int :: Int -> DbIO Int
column_int num = do
ptr <- getST
debug (ptr,"column_int")
liftIO $ c_column_int ptr num
column_double :: Int -> DbIO Double
column_double num = do
ptr <- getST
debug (ptr,"column_double")
liftIO $ c_column_double ptr num
column_type :: Int -> DbIO Int
column_type num = do
ptr <- getST
debug (ptr,"column_type")
liftIO $ c_column_type ptr num
column_text :: Int -> DbIO 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 :: Int -> DbIO 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)