packages feed

simplest-sqlite-0.0.0.2: src/Database/SmplstSQLite3.hs

{-# LANGUAGE TupleSections #-}

module Database.SmplstSQLite3 (
	SQLite3, withSQLite3, withPrepared, step, column) where

import Control.Applicative
import Control.Monad
import Control.Exception
import qualified Data.ByteString as BS
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Foreign
import Foreign.C.Types
import Foreign.C.String

import Database.SQLite3.Constants

data SQLite3 = SQLite3 (Ptr SQLite3) deriving Show
data SQLite3Stmt = SQLite3Stmt (Ptr SQLite3Stmt) deriving Show

foreign import ccall unsafe "sqlite3.h sqlite3_open" c_sqlite3_open ::
	CString -> Ptr (Ptr SQLite3) -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_close" c_sqlite3_close ::
	Ptr SQLite3 -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_errmsg" c_sqlite3_errmsg ::
	Ptr SQLite3 -> IO CString

withSQLite3 :: String -> (SQLite3 -> IO a) -> IO a
withSQLite3 fp = bracket (sqlite3Open fp) sqlite3Close

sqlite3Open :: String -> IO SQLite3
sqlite3Open fp = withCString fp $ \cfp -> alloca $ \ppDb -> do
	ret <- c_sqlite3_open cfp ppDb
	db <- peek ppDb
	let sq = SQLite3 db
	when (ret /= sQLITE_OK) $ do
		em <- sqlite3Errmsg sq
		sqlite3Close sq
		ioError . userError $ "Cannot open database: " ++ em
	return sq

sqlite3Close :: SQLite3 -> IO ()
sqlite3Close sq@(SQLite3 db) = do
	ret <- c_sqlite3_close db
	when (ret /= sQLITE_OK) $ do
		em <- sqlite3Errmsg sq
		ioError . userError $ "Cannot close database: " ++ em

sqlite3Errmsg :: SQLite3 -> IO String
sqlite3Errmsg (SQLite3 db) = peekCString =<< c_sqlite3_errmsg db

foreign import ccall unsafe "sqlite3.h sqlite3_prepare_v2"
	c_sqlite3_prepare_v2 ::
		Ptr SQLite3 -> CString -> CInt -> Ptr (Ptr SQLite3Stmt) ->
		(Ptr CString) -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_finalize" c_sqlite3_finalize ::
	Ptr SQLite3Stmt -> IO CInt

withPrepared :: SQLite3 -> String -> (SQLite3Stmt -> IO a) -> IO (a, String)
withPrepared db sql act =
	bracket (sqlite3PrepareV2 db sql) (sqlite3Finalize . fst) $
		\(sm, t) -> (, t) <$> act sm

sqlite3PrepareV2 :: SQLite3 -> String -> IO (SQLite3Stmt, String)
sqlite3PrepareV2 db@(SQLite3 pdb) sql =
	withCString sql $ \csql -> alloca $ \psm -> alloca $ \pt -> do
		ret <- c_sqlite3_prepare_v2 pdb csql (- 1) psm pt
		sm <- SQLite3Stmt <$> peek psm
		when (ret /= sQLITE_OK) $ do
			em <- sqlite3Errmsg db
			sqlite3Finalize sm
			ioError . userError $ "Cannot prepare: " ++ em
		(sm ,) <$> (peekCString =<< peek pt)

sqlite3Finalize :: SQLite3Stmt -> IO ()
sqlite3Finalize (SQLite3Stmt psm) = do
	ret <- c_sqlite3_finalize psm
	when (ret /= sQLITE_OK) $
		ioError . userError $
			"Cannot finalize stmt: error code (" ++ show ret ++ ")"

data ResultCode = SQLiteBusy | SQLiteRow | SQLiteDone deriving Show

foreign import ccall unsafe "sqlite3.h sqlite3_step" c_sqlite3_step ::
	Ptr SQLite3Stmt -> IO CInt

step :: SQLite3Stmt -> IO ResultCode
step (SQLite3Stmt psm) = do
	ret <- c_sqlite3_step psm
	case ret of
		_	| ret == sQLITE_BUSY -> return SQLiteBusy
			| ret == sQLITE_ROW -> return SQLiteRow
			| ret == sQLITE_DONE -> return SQLiteDone
		_ -> ioError . userError $
			"Error while step: error code (" ++ show ret ++ ")"

foreign import ccall unsafe "sqlite3.h sqlite3_column_int"
	c_sqlite3_column_int :: Ptr SQLite3Stmt -> CInt -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_column_text"
	c_sqlite3_column_text :: Ptr SQLite3Stmt -> CInt -> IO CString

class SQLite3DataList a where
	columnList :: SQLite3Stmt -> Int -> IO [a]

class SQLite3Data a where
	column :: SQLite3Stmt -> Int -> IO a

instance SQLite3DataList a => SQLite3Data [a] where
	column = columnList

instance SQLite3Data Int where
	column (SQLite3Stmt sm) i =
		fromIntegral <$> c_sqlite3_column_int sm (fromIntegral i)

instance SQLite3DataList Char where
	columnList (SQLite3Stmt sm) i =
		peekCString =<< c_sqlite3_column_text sm (fromIntegral i)

instance SQLite3Data BS.ByteString where
	column (SQLite3Stmt sm) i =
		BS.packCString =<< c_sqlite3_column_text sm (fromIntegral i)

instance SQLite3Data T.Text where
	column sm i = T.decodeUtf8 <$> column sm i