module Database.MetaHDBC.SqlExpr
( runStmt, prepareStmt, strict
, ExprParts(..), Parameter(..), PrepareParts(..)
, rethrowDoing, sqlInfo, makeExprParts
, runStmtLHS, runStmtRHS
, makePrepStmtParts, prepStmtLHS, prepStmtQ, execPrepStmtRHS, returnExecPrepStmtLHS
, runStmtML, multiline
)
where
import Database.MetaHDBC.Connection
import Database.MetaHDBC.SqlTypeIdExpQ
import Database.MetaHDBC.SimpleSqlParser
import Database.MetaHDBC.OdbcInferTypes
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Database.HDBC
import Control.Monad(when)
strict :: IO [a] -> IO [a]
strict xs = do xs' <- xs
strictList xs'
data ExprParts = ExprParts
{ parameters :: [Parameter]
, returnTypes :: [SqlColDesc]
, dbSqlExpr :: String
, connectionName :: Name
}
data Parameter = Parameter
{ parmName :: Name
, typeID :: SqlColDesc
, isBound :: Bool
}
data PrepareParts = PrepareParts
{ exprParts :: ExprParts
, stmtName :: Name
}
unboundParameters :: ExprParts -> [Parameter]
unboundParameters parts = filter (not . isBound) $ parameters parts
makeExprParts :: String -> String -> Q ExprParts
makeExprParts dsn extendedSql =
do (varNames, sqlExpr, paramInfo, returnInfo) <- runIO $ inferTypes dsn extendedSql
parameters' <- sequence $ zipWith3 makeParameter [0..] varNames paramInfo
connName <- newName "connection"
return $ ExprParts parameters' returnInfo sqlExpr connName
where
makeParameter :: Int -> String -> SqlColDesc -> Q Parameter
makeParameter pos "" typeID' = do n <- newName ("x" ++ show pos)
return $ Parameter n typeID' False
makeParameter _ xs typeID' = return $ Parameter (mkName xs) typeID' True
runStmt :: String
-> String
-> ExpQ
runStmt dsn extendedSql =
do parts <- makeExprParts dsn extendedSql
runStmtLHS parts (runStmtRHS parts)
runStmtLHS :: ExprParts
-> ExpQ
-> ExpQ
runStmtLHS parts expr = lamE (map varP (connectionName parts:sqlExprParms)) expr
where sqlExprParms = map parmName $ unboundParameters parts
runStmtRHS :: ExprParts -> ExpQ
runStmtRHS parts =
let sql = dbSqlExpr parts
in if null (returnTypes parts)
then [| run $(varE (connectionName parts)) sql $(convertParams parts) |]
else [| do rows <- quickQuery $(varE (connectionName parts)) sql $(convertParams parts)
return (map $(fromRow (returnTypes parts)) rows)
|]
prepareStmt :: String
-> String
-> ExpQ
prepareStmt dsn extendedSql =
do parts <- makePrepStmtParts dsn extendedSql
prepStmtLHS parts [ prepStmtQ parts
, returnExecPrepStmtLHS parts [execPrepStmtRHS parts]
]
makePrepStmtParts :: String -> String -> Q PrepareParts
makePrepStmtParts dsn extendedSql =
do parts <- makeExprParts dsn extendedSql
preStmtName <- newName "preStmt"
return $ PrepareParts parts preStmtName
prepStmtLHS :: PrepareParts -> [StmtQ] -> ExpQ
prepStmtLHS (PrepareParts parts _) stmtQs =
lam1E (varP (connectionName parts)) (doE stmtQs)
prepStmtQ :: PrepareParts
-> StmtQ
prepStmtQ (PrepareParts parts preStmtName) =
let sql = dbSqlExpr parts
in bindS (varP preStmtName)
[| prepare $(varE (connectionName parts)) sql `rethrowDoing` "calling prepare" |]
execPrepStmtRHS :: PrepareParts -> StmtQ
execPrepStmtRHS (PrepareParts parts preStmtName) =
let sql = dbSqlExpr parts
expr = if null (returnTypes parts)
then [| execute $(varE preStmtName) $(convertParams parts) |]
else [| do rows <- fetchRows $(varE preStmtName) $(convertParams parts)
return (map $(fromRow (returnTypes parts)) rows)
|]
in noBindS expr
returnExecPrepStmtLHS :: PrepareParts -> [StmtQ] -> StmtQ
returnExecPrepStmtLHS (PrepareParts parts _) statements =
noBindS $ appE [|return|] (lamE pattern (doE statements))
where pattern = map (varP . parmName) $ unboundParameters parts
convertParams :: ExprParts
-> ExpQ
convertParams parts = listE $ map convertParam (parameters parts)
where convertParam p = appE (toSqlColDesc $ typeID p) (varE $ parmName p)
sqlInfo :: String -> String -> IO String
sqlInfo dsn extendedSql =
do (varNames, sqlExpr, paramInfo, columnInfo) <- inferTypes dsn extendedSql
let varsString = show $ zip varNames paramInfo
columnInfoString = show columnInfo
return ("Extended sql: " ++ extendedSql ++ "\n" ++
"Parsed sql: " ++ sqlExpr ++ "\n" ++
"Variables: " ++ varsString ++ "\n" ++
"Column info: " ++ columnInfoString ++ "\n"
)
fetchRows :: Statement -> [SqlValue] -> IO [[SqlValue]]
fetchRows preStmt params =
do _ <- execute preStmt params `rethrowDoing` "executing prepared statement"
fetchAllRows preStmt `rethrowDoing` "fetch all rows"
rethrowDoing :: IO a -> String -> IO a
rethrowDoing command doing =
command `catchSql` (\e -> fail ("Exception when trying \"" ++ doing ++
"\" : " ++ seErrorMsg e))
inferTypes :: String -> String
-> IO ([String], String, [SqlColDesc], [SqlColDesc])
inferTypes dsn extendedSql =
do let (varNames, sqlExpr) = simpleSqlParser extendedSql
(paramInfo, columnInfo) <- dbInferTypes dsn sqlExpr
when (length varNames /= length paramInfo)
(fail "Database server and MetaHDBC disagrees about number of placeholder arguments")
return (varNames, sqlExpr, paramInfo, columnInfo)
fromRow :: [SqlColDesc] -> ExpQ
fromRow xs =
do es <- mapM fromSqlColDesc xs
names <- mapM (\i -> newName ("p" ++ show i)) [0..(length es 1)]
return $ LamE [ListP (map VarP names)] (TupE $ map (\(e, n) -> AppE e (VarE n)) $ zip es names)
runStmtML :: String -> QuasiQuoter
runStmtML conn = QuasiQuoter
{ quoteExp = \s -> runStmt conn s
, quotePat = error "Not implemented"
, quoteType = error "Not implemented"
, quoteDec = error "Not implemented"
}
multiline :: QuasiQuoter
multiline = QuasiQuoter
{ quoteExp = litE . stringL
, quotePat = error "Not implemented"
, quoteType = error "Not implemented"
, quoteDec = error "Not implemented"
}
cachingStmt :: String
-> String
-> ExpQ
cachingStmt dsn extendedSql =
do (conn, params, prepareStmtQ, executeExpQ) <- prepareParts' dsn extendedSql
lamE (map varP (conn:params)) (doE [prepareStmtQ, executeExpQ])
prepareParts' :: String
-> String
-> Q (Name, [Name], StmtQ, StmtQ)
prepareParts' dsn extendedSql =
do (varNames, sqlExpr, paramInfo, columnInfo) <- runIO $ inferTypes dsn extendedSql
(parmNames, parmExpr) <- fromParams' (zip varNames paramInfo)
connName <- newName "connection"
preStmtName <- newName "preStmt"
let prepareExpQ =
bindS (varP preStmtName) [| cachingPrepare $(varE connName) sqlExpr
`rethrowDoing` "calling cachingPrepare" |]
executeExpQ = noBindS $
[| do rows <- fetchRows $(varE preStmtName) $( parmExpr )
$( if null columnInfo
then [| return () |]
else [| return $ map ( $(fromRow columnInfo) ) rows |]
)
|]
return (connName, parmNames, prepareExpQ, executeExpQ)
fromParams' :: [(String, SqlColDesc)]
-> Q ([Name], ExpQ)
fromParams' xs =
do toFuns <- mapM toSqlColDesc (map snd xs)
let newNameOrBoundVar ("", i) = do n <- newName ("x" ++ show i)
return (n, True)
newNameOrBoundVar (ys, _) = return (mkName ys, False)
names <- mapM newNameOrBoundVar $ zip (map fst xs) [0::Int ..]
return ( map fst $ filter snd names
, listE $ map (\(n, f) -> appE f (varE n)) (zip (map fst names) (map return toFuns)))