packages feed

hsSqlite3-0.0.3: src/Database/Sqlite3.hs

{-# options -fglasgow-exts -farrows #-}

module Database.Sqlite3
 ( module Database.Sqlite3.Low
 , Val ( IntV, Int64V, DoubleV, TextV, BlobV, NullV )
 , fetch
 , bind
 , fetchOne
 , fetchOK
 , 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 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 :: LocalState s StateDb => (Int,Int) -> StateT s IO a

class ValBind a where
 valBind :: LocalState s StateDb => (Int,a) -> StateT s IO ()

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 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 ::
 forall a s sql .
 ( ValFetch a
 --, GetCString sql IO
 , LocalState s StateDb
 --, Show sql
 )
 => String -> StateT s IO [[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 :: ({-Show sql,-} {-GetCString sql IO,-} LocalState s StateDb) => String -> StateT s IO ()
sql sql = do
 liftIO $ putStr $ UTF8.encodeString $ sql++"\n"
 prepare sql
 step
 finalize

bind :: ({-Show sql, GetCString sql IO,-} LocalState s StateDb) => String -> [[Val]] -> StateT s IO ()
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' :: forall a s .  (ValFetch a, LocalState s StateDb) => String -> Int -> StateT s IO [[a]]
fetchOne' sql num = fetch sql'
 where sql' = concat [sql," LIMIT 1 OFFSET ", show num]

fetchOne :: forall a s .  (ValFetch a, LocalState s StateDb) => String -> Int -> StateT s IO [a]
fetchOne sql num = liftM head $ fetchOne' sql num

fetchOK :: LocalState s StateDb => String -> StateT s IO Bool
fetchOK sql = do
 tab :: [[Val]] <- fetchOne' sql 0
 case length tab of
  1 -> return True
  _ -> return False