{- Copyright (C) 2009 Andrejs Sisojevs All rights reserved. For license and copyright information, see the file COPYRIGHT -} -------------------------------------------------------------------------- -------------------------------------------------------------------------- {-# LANGUAGE MultiParamTypeClasses, DeriveDataTypeable, FlexibleContexts #-} -- | An attemt of generalization of \"read from DB\" operation. module Database.ReadableFromDB where import Control.Exception import Data.Convertible import Data.Char import qualified Data.Map as M import Data.Map (Map, (!)) import Data.MyHelpers import Data.Typeable import Database.HDBC import Database.HDBC.PostgreSQL -- * ReadableFromDB type FieldName = String -- | Type aliase for use by 'readOneFromDB'. type NoMoreThanOne_shoulditbe = Bool -- | It's recommended to use newtypes for subject type variable (\"readable\"), -- when it is composite (like tuple, Either, Map or other). Reason for that -- is described near the 'ShowAsPCSI' instaniation -- for 'AddressedReadFromDBError'. class (Typeable readable, Show pk) => ReadableFromDB readable pk where -- | Read rows from DB and parse each at once -- using 'parseDBrow' class method readFromDB :: Connection -> pk -> IO [Either AddressedReadFromDBError readable] parseDBrow :: Connection -> pk -> Map FieldName SqlValue -> IO (Either AddressedReadFromDBError readable) -- | Class method derived from 'readFromDB' and 'parseDBrow'. -- No need to overload it, if not for special cases. readOneFromDB :: Connection -> pk -> NoMoreThanOne_shoulditbe -> IO (Either AddressedReadFromDBError readable) readOneFromDB db_conn pk nomorethanone_shoulditbe = do results_list <- readFromDB db_conn pk return $ case results_list of [] -> wrapParseResult_1row pk $ Left NotFoundInDB_RFDBE err_or_readable : [] -> err_or_readable err_or_readable : _ -> case nomorethanone_shoulditbe of True -> wrapParseResult_1row pk $ Left $ TooManyResultsFoundInDB_RFDBE 1 False -> err_or_readable -- | Type aliase for use by 'TooManyResultsFoundInDB_RFDBE'. type AllowedMaximumOfRowsCount = Int -- | What kind of errors are possible in the result -- of \"read from DB\" operation data ReadFromDBError = RecieveError_RFDBE SqlError | NotFoundInDB_RFDBE | TooManyResultsFoundInDB_RFDBE AllowedMaximumOfRowsCount | RowParseError_RFDBE SomeException | SubReadError_RFDBE AddressedReadFromDBError deriving (Show, Typeable) -- | A wrapper around 'ReadFromDBError', added information about type of -- read subject and by showing the primary key. -- -- ___________ -- -- If PCLT is to be used for representation of 'AddressedReadFromDBError' -- then user must declare an additional localizable template in his PCLT -- catalog with ID of form: -- -- @\"ARFDBE_SDLR.\" ++ (show $ 'arfdbeEntityType' err)@ -- -- , where the @show 'arfdbeEntityType'@ will include full modules path -- of type. The templete content if not important - leave it empty, but what -- is important - is it's SDL requirement. This SDL requirement wil be in -- effect for the representation of 'AddressedReadFromDBError'. -- -- For more information on that, see description for @ShowAsPCSI@ instance for -- 'AddressedReadFromDBError' - in module "Database.ReadableFromDB__". data AddressedReadFromDBError = AddressedReadFromDBError { arfdbeEntityType :: TypeRep , arfdbePK :: String , arfdbeErr :: ReadFromDBError } deriving (Show, Typeable) -- | In case of error it wraps it 'ReadFromDBError' -- into 'AddressedReadFromDBError'. wrapParseResult_1row :: (Typeable readable, Show pk) => pk -> Either ReadFromDBError readable -> Either AddressedReadFromDBError readable wrapParseResult_1row pk err_or_readable = case err_or_readable of Right readable -> Right readable Left err -> Left AddressedReadFromDBError { arfdbeEntityType = rightType_formReadResOrErr err_or_readable , arfdbePK = show pk , arfdbeErr = err } -- | Wrapper around 'wrapParseResult_1row'. wrapParseResult_Nrows :: (Typeable a, Show pk) => pk -> [Either ReadFromDBError a] -> [Either AddressedReadFromDBError a] wrapParseResult_Nrows pk err_or_a__list = map (wrapParseResult_1row pk) err_or_a__list ----------------------------------------------- -- * Helpers -- | Return the 'TypeRep' of what can be under @Right@ in @Either@ rightType_formReadResOrErr :: (Typeable left, Typeable right) => Either left right -> TypeRep rightType_formReadResOrErr left_or_right = let _ : right_type : [] = typeRepArgs (typeOf left_or_right) in right_type -- | This is better than @row ! field_name@, because if an error occurs -- (field not found), then it returns in error message this field name. takeFieldValue :: Convertible SqlValue a => FieldName -> Map FieldName SqlValue -> a takeFieldValue f_name row_map = let mb_sqlv = M.lookup f_name row_map in case mb_sqlv of Just v -> fromSql v Nothing -> error ("Function 'takeFieldValue' failed!\nField '" ++ f_name ++ "' not found.\nRow: " ++ show row_map) -- | Uppercase given field name, and apply it with 'takeFieldValue'. takeUFieldValue :: Convertible SqlValue a => FieldName -> Map FieldName SqlValue -> a takeUFieldValue f_name row_map = takeFieldValue (map toUpper f_name) row_map -- | WARNING !!! -- HDBC lowercases field names, when using @fetchAllRowsMap@. uppercaseMapKeys :: Map String a -> Map String a uppercaseMapKeys = M.mapKeys (map toUpper) -- | -- -- > liftInList a = a : [] liftInList :: a -> [a] liftInList a = a : []