/src-extra/tosort/examples/wrappers/ThTupleWrapper.lhs
http://github.com/JakeWheat/hssqlppp · Haskell · 133 lines · 120 code · 11 blank · 2 comment · 5 complexity · 7b35cabcd6afb1f8c928dfee3602a2f4 MD5 · raw file
- Template Haskell code to return database queries as lists of tuples.
- > {-# LANGUAGE TemplateHaskell #-}
- >
- > module Database.HsSqlPpp.Examples.Wrappers.ThTupleWrapper
- > (withConn
- > ,sqlQuery
- > ,IConnection) where
- >
- > import Language.Haskell.TH
- > import Data.Maybe
- > import Control.Applicative
- > import Control.Monad.Error
- > import Database.HDBC
- > import System.IO.Unsafe
- > import Data.IORef
- > -- the select relation from the library returns strings, but
- > -- we want the completely pointless wrapper which gives us sqlvalues,
- > -- which we can cast better
- > import Database.HsSqlPpp.Utils.DbmsCommon hiding (selectRelation)
- > import Database.HsSqlPpp.Examples.Wrappers.SelectRelation
- > import qualified Database.HsSqlPpp.SqlTypes as Sql
- > import Database.HsSqlPpp.Catalog
- > import Database.HsSqlPpp.TypeChecker
- > import Database.HsSqlPpp.Parser
- > import Database.HsSqlPpp.Annotation
- > import Database.HsSqlPpp.Utils.Utils
- >
- > sqlQuery:: String -> String -> Q Exp
- > sqlQuery dbName sqlStr = do
- > (StatementHaskellType inA outA) <- liftStType
- > let cnName = mkName "cn"
- > argNames <- getNNewNames "a" $ length inA
- > lamE (map varP (cnName : argNames))
- > [| selectRelation $(varE cnName) sqlStr
- > $(ListE <$> zipWithM toSqlIt argNames inA) >>=
- > return . map $(mapTupleFromSql $ map snd outA)|]
- >
- > where
- > liftStType :: Q StatementHaskellType
- > liftStType = runIO stType >>= (either (error . show) toH)
- >
- > stType :: IO (Either String StatementType)
- > stType = runErrorT $ do
- > cat <- getCat
- > tsl (getStatementType cat sqlStr)
- >
- > getCat :: ErrorT String IO Catalog
- > getCat = do
- > -- bad code to avoid reading the catalog multiple times
- > c1 <- liftIO $ readIORef globalCachedCatalog
- > case c1 of
- > Just c -> return c
- > Nothing -> do
- > c <- liftIO (readCatalogFromDatabase dbName) >>=
- > (tsl . updateCatalog defaultCatalog)
- > liftIO $ writeIORef globalCachedCatalog (Just c)
- > return c
- >
- > -- lambda which does [SqlValue] -> (T1, T2, ...)
- > mapTupleFromSql :: [Type] -> Q Exp
- > mapTupleFromSql outT = do
- > retNames <- getNNewNames "r" $ length outT
- > lamE [listP (map varP retNames)]
- > (tupE $ zipWith fromSqlIt retNames outT)
- >
- > toSqlIt :: Name -> Type -> Q Exp
- > toSqlIt n t = [| toSql $(castName n t)|]
- >
- > fromSqlIt :: Name -> Type -> Q Exp
- > fromSqlIt n t = do
- > n1 <- [| fromSql $(varE n) |]
- > cast n1 t
- >
- > cast :: Exp -> Type -> Q Exp
- > cast e = return . SigE e
- >
- > castName :: Name -> Type -> Q Exp
- > castName = cast . VarE
- >
- > getNNewNames :: String -> Int -> Q [Name]
- > getNNewNames i n = forM [1..n] $ const $ newName i
- evil hack to avoid reading the catalog from the database for each call
- to sqlStmt. Atm this means that you can only read the catalog from one
- database at compile time, but this should be an easy fix if too
- limiting. TODO: make this change, in case the catalog ends up being
- cached in ghci meaning if you change the database whilst developing in
- emacs it will go wrong
- > globalCachedCatalog :: IORef (Maybe Catalog)
- > {-# NOINLINE globalCachedCatalog #-}
- > globalCachedCatalog = unsafePerformIO (newIORef Nothing)
- -------------------------------------------------------------------------------
- sql parsing and typechecking
- ----------------------------
- This is the demonstration of using the type checker to get the
- information needed.
- Get the input and output types for a parameterized sql statement:
- > getStatementType :: Catalog -> String -> Either String StatementType
- > getStatementType cat sql = do
- > ast <- tsl $ parseSql "" sql
- > let (_,aast) = typeCheck cat ast
- > let a = getTopLevelInfos aast
- > return $ fromJust $ head a
- convert sql statement type to equivalent with sql types replaced with
- haskell equivalents - HDBC knows how to convert the actual values using
- toSql and fromSql as long as we add in the appropriate casts
- > data StatementHaskellType = StatementHaskellType [Type] [(String,Type)]
- >
- > toH :: StatementType -> Q StatementHaskellType
- > toH (StatementType i o) = do
- > ih <- mapM sqlTypeToHaskell i
- > oht <- mapM (sqlTypeToHaskell . snd) o
- > return $ StatementHaskellType ih $ zip (map fst o) oht
- > where
- > sqlTypeToHaskell :: Sql.Type -> TypeQ
- > sqlTypeToHaskell t =
- > case t of
- > Sql.ScalarType "text" -> [t| Maybe String |]
- > Sql.ScalarType "int4" -> [t| Maybe Int |]
- > Sql.ScalarType "int8" -> [t| Maybe Int |]
- > Sql.ScalarType "bool" -> [t| Maybe Bool |]
- > Sql.DomainType _ -> [t| Maybe String |]
- > x -> error $ show x