/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

  1. Template Haskell code to return database queries as lists of tuples.
  2. > {-# LANGUAGE TemplateHaskell #-}
  3. >
  4. > module Database.HsSqlPpp.Examples.Wrappers.ThTupleWrapper
  5. > (withConn
  6. > ,sqlQuery
  7. > ,IConnection) where
  8. >
  9. > import Language.Haskell.TH
  10. > import Data.Maybe
  11. > import Control.Applicative
  12. > import Control.Monad.Error
  13. > import Database.HDBC
  14. > import System.IO.Unsafe
  15. > import Data.IORef
  16. > -- the select relation from the library returns strings, but
  17. > -- we want the completely pointless wrapper which gives us sqlvalues,
  18. > -- which we can cast better
  19. > import Database.HsSqlPpp.Utils.DbmsCommon hiding (selectRelation)
  20. > import Database.HsSqlPpp.Examples.Wrappers.SelectRelation
  21. > import qualified Database.HsSqlPpp.SqlTypes as Sql
  22. > import Database.HsSqlPpp.Catalog
  23. > import Database.HsSqlPpp.TypeChecker
  24. > import Database.HsSqlPpp.Parser
  25. > import Database.HsSqlPpp.Annotation
  26. > import Database.HsSqlPpp.Utils.Utils
  27. >
  28. > sqlQuery:: String -> String -> Q Exp
  29. > sqlQuery dbName sqlStr = do
  30. > (StatementHaskellType inA outA) <- liftStType
  31. > let cnName = mkName "cn"
  32. > argNames <- getNNewNames "a" $ length inA
  33. > lamE (map varP (cnName : argNames))
  34. > [| selectRelation $(varE cnName) sqlStr
  35. > $(ListE <$> zipWithM toSqlIt argNames inA) >>=
  36. > return . map $(mapTupleFromSql $ map snd outA)|]
  37. >
  38. > where
  39. > liftStType :: Q StatementHaskellType
  40. > liftStType = runIO stType >>= (either (error . show) toH)
  41. >
  42. > stType :: IO (Either String StatementType)
  43. > stType = runErrorT $ do
  44. > cat <- getCat
  45. > tsl (getStatementType cat sqlStr)
  46. >
  47. > getCat :: ErrorT String IO Catalog
  48. > getCat = do
  49. > -- bad code to avoid reading the catalog multiple times
  50. > c1 <- liftIO $ readIORef globalCachedCatalog
  51. > case c1 of
  52. > Just c -> return c
  53. > Nothing -> do
  54. > c <- liftIO (readCatalogFromDatabase dbName) >>=
  55. > (tsl . updateCatalog defaultCatalog)
  56. > liftIO $ writeIORef globalCachedCatalog (Just c)
  57. > return c
  58. >
  59. > -- lambda which does [SqlValue] -> (T1, T2, ...)
  60. > mapTupleFromSql :: [Type] -> Q Exp
  61. > mapTupleFromSql outT = do
  62. > retNames <- getNNewNames "r" $ length outT
  63. > lamE [listP (map varP retNames)]
  64. > (tupE $ zipWith fromSqlIt retNames outT)
  65. >
  66. > toSqlIt :: Name -> Type -> Q Exp
  67. > toSqlIt n t = [| toSql $(castName n t)|]
  68. >
  69. > fromSqlIt :: Name -> Type -> Q Exp
  70. > fromSqlIt n t = do
  71. > n1 <- [| fromSql $(varE n) |]
  72. > cast n1 t
  73. >
  74. > cast :: Exp -> Type -> Q Exp
  75. > cast e = return . SigE e
  76. >
  77. > castName :: Name -> Type -> Q Exp
  78. > castName = cast . VarE
  79. >
  80. > getNNewNames :: String -> Int -> Q [Name]
  81. > getNNewNames i n = forM [1..n] $ const $ newName i
  82. evil hack to avoid reading the catalog from the database for each call
  83. to sqlStmt. Atm this means that you can only read the catalog from one
  84. database at compile time, but this should be an easy fix if too
  85. limiting. TODO: make this change, in case the catalog ends up being
  86. cached in ghci meaning if you change the database whilst developing in
  87. emacs it will go wrong
  88. > globalCachedCatalog :: IORef (Maybe Catalog)
  89. > {-# NOINLINE globalCachedCatalog #-}
  90. > globalCachedCatalog = unsafePerformIO (newIORef Nothing)
  91. -------------------------------------------------------------------------------
  92. sql parsing and typechecking
  93. ----------------------------
  94. This is the demonstration of using the type checker to get the
  95. information needed.
  96. Get the input and output types for a parameterized sql statement:
  97. > getStatementType :: Catalog -> String -> Either String StatementType
  98. > getStatementType cat sql = do
  99. > ast <- tsl $ parseSql "" sql
  100. > let (_,aast) = typeCheck cat ast
  101. > let a = getTopLevelInfos aast
  102. > return $ fromJust $ head a
  103. convert sql statement type to equivalent with sql types replaced with
  104. haskell equivalents - HDBC knows how to convert the actual values using
  105. toSql and fromSql as long as we add in the appropriate casts
  106. > data StatementHaskellType = StatementHaskellType [Type] [(String,Type)]
  107. >
  108. > toH :: StatementType -> Q StatementHaskellType
  109. > toH (StatementType i o) = do
  110. > ih <- mapM sqlTypeToHaskell i
  111. > oht <- mapM (sqlTypeToHaskell . snd) o
  112. > return $ StatementHaskellType ih $ zip (map fst o) oht
  113. > where
  114. > sqlTypeToHaskell :: Sql.Type -> TypeQ
  115. > sqlTypeToHaskell t =
  116. > case t of
  117. > Sql.ScalarType "text" -> [t| Maybe String |]
  118. > Sql.ScalarType "int4" -> [t| Maybe Int |]
  119. > Sql.ScalarType "int8" -> [t| Maybe Int |]
  120. > Sql.ScalarType "bool" -> [t| Maybe Bool |]
  121. > Sql.DomainType _ -> [t| Maybe String |]
  122. > x -> error $ show x