packages feed

hesql-0.3: HESQL/Translator.hs

module HESQL.Translator (generateCode) where

import Language.Haskell.Syntax
import Database.HDBC.PostgreSQL

import HESQL.Syntax 
import HESQL.Verifier

import System.FilePath



stmtRec = HsIdent "Stmts"
funarg = HsPVar . HsIdent
funargs = map funarg

srcloc = SrcLoc "<dummy>" 1 1

hsSimpleFun name vars body wheres =
   HsFunBind [HsMatch srcloc (HsIdent name) (funargs vars) body wheres]
    

generateCode :: FilePath -> String -> HesqlModule -> IO HsModule
generateCode fn db (HesqlModule modName decls) = do
    conn <- connectPostgreSQL db
    mapM_ (verifySql conn . declSQL) decls'
    return $ HsModule srcloc (Module modName) Nothing imports hsDecls
  where imports   = [HsImportDecl srcloc (Module "Database.HDBC") 
                       False Nothing Nothing]
        hsDecls     = [dataType, initFun decls'] ++ map declFun decls'
        dataType  = HsDataDecl srcloc [] stmtRec [] 
                    [HsRecDecl srcloc stmtRec recFields] []
        mkRecField decl = ([HsIdent $ stmtRecName decl], HsBangedTy (HsTyCon $ UnQual $ HsIdent"Statement"))
        recFields = map mkRecField  decls
        decls'    = map updateDeclSql decls


updateDeclSql decl = decl { declSQL = substituteVarsSql (declVars decl) (declSQL decl) }

hsapp fun = HsApp (HsVar $ UnQual $ HsSymbol fun)
hsapps fun exps = foldl HsApp (HsVar $ UnQual $ HsSymbol fun) exps
hsvar v = HsVar $ UnQual $ HsSymbol v

stmtRecName decl = "stmt_" ++ declName decl

initFun decls = 
    hsSimpleFun "init" ["conn"] body []
  where body    = HsUnGuardedRhs $ HsDo (mkStmts ++ [returnRec])
        mkStmts = map mkStmt decls
        mkStmt decl = HsGenerator srcloc (HsPVar $ HsIdent  $ declName decl)
                      (hsapps "prepare" [hsvar "conn", HsLit $  HsString sqlText])
             where sqlText = show $ declSQL decl
        returnRec = HsQualifier $ hsapp "return" $ HsRecConstr (UnQual $ stmtRec) fields
        fields    = map mkField decls
        mkField decl = HsFieldUpdate 
                         (UnQual $ HsIdent $ stmtRecName decl)
                         (HsVar $ UnQual $ HsIdent $ declName decl)


declFun decl =  -- TODO declLOC
          hsSimpleFun (declName decl) ("h":declVars decl) body (stmtDef : maybeTupleFun)
     where body      = HsUnGuardedRhs $ HsDo (bindAndExecute:result)
           bindAndExecute = HsQualifier $ hsapps "execute" [stmt, valList]
           selectFlag     = isSelect $ declSQL decl
           result 
               | selectFlag = [resultStmt (selectOpts $ declSQL decl) stmt]
               | otherwise               = []
           maybeTupleFun 
               | selectFlag = [tupleFun $ selectColumnLength $ selectColumns $ declSQL decl]
               | otherwise  = []
           stmt           = HsVar $ UnQual $ HsSymbol "stmt"
           valList        = HsList $ map mkSqlVal $ placeHoldersSql $ declSQL decl
           mkSqlVal  v    = hsapp "toSql" (hsvar v)
           stmtDef        = HsPatBind srcloc (HsPVar $ HsIdent "stmt") 
                               (HsUnGuardedRhs $ HsApp (HsVar $ UnQual $ HsIdent $ stmtRecName decl) (hsvar "h"))
                               []

resultStmt opts stmt = HsQualifier $ hsapps "fmap" [HsParen (hsapp "fmap" (hsvar "toTuple")), HsParen (hsapp (prefix++suffix) stmt)]
  where suffix 
          | opts == [Strict] = "'"
          | otherwise        = ""
        prefix
          | ReturnMaybe `elem` opts = "fetchRow"
          | otherwise               = "fetchAllRows"

          


tupleFun n = HsFunBind [
              HsMatch srcloc (HsIdent "toTuple") [HsPList $ map HsPVar lvars]
                      (HsUnGuardedRhs (HsTuple (map convVar lvars))) []
             , HsMatch srcloc (HsIdent "toTuple") [HsPWildCard]
                        (HsUnGuardedRhs (hsapp "error" $ HsLit $ HsString "hesql internal error"))
                        []
            ]

    where lvars  = map lvar [1..n]
          lvar i = HsIdent $ "v" ++ show i
          convVar v = hsapp "fromSql" (HsVar (UnQual  v))

placeHolderCols AllColumns = []
placeHolderCols (ExplicitColumns cols) =
    concatMap placeHoldersExp $ map fst cols

placeHoldersSql (SELECT _ cols from wh ord grp) =
    placeHolderCols cols ++ maybe [] placeHoldersExp wh

placeHoldersSql (INSERT _ _ vals) =
   concatMap placeHoldersExp vals

placeHoldersSql (UPDATE _ updates wh) =
   concatMap (placeHoldersExp . snd) updates  ++
    maybe [] placeHoldersExp wh

placeHoldersExp (SqlPlaceHolder ph) = [ph]
placeHoldersExp (SqlInfixApp e1 _ e2) = placeHoldersExp e1 ++ placeHoldersExp e2
placeHoldersExp (SqlColumn _) = []
placeHoldersExp (SqlFunApp n args) = concatMap placeHoldersExp args
placeHoldersExp (SqlLiteral _) = []
placeHoldersExp (SqlNot e) = placeHoldersExp e
           
    
substituteVarsExp vars (SqlInfixApp e1 op e2) = 
    SqlInfixApp e1' op e2'
  where e1' = substituteVarsExp vars e1
        e2' = substituteVarsExp vars e2

substituteVarsExp _ sql@(SqlPlaceHolder _) = sql
substituteVarsExp _ sql@(SqlLiteral _) = sql
substituteVarsExp vars sql@(SqlColumn col)  
    | col `elem` vars = SqlPlaceHolder col
    | otherwise        =  sql
substituteVarsExp vars (SqlNot e) = SqlNot (substituteVarsExp vars e)

substituteVarsExp vars (SqlFunApp n args) =
    SqlFunApp n (map (substituteVarsExp vars) args)

substituteVarsSql vars (SELECT opt cols from wh ord grp) =
    SELECT opt cols from 
               wh'
               ord grp
         where wh' :: Maybe SqlExp
               wh' = fmap (substituteVarsExp vars) wh

substituteVarsSql vars (INSERT tab spec vals) =
    INSERT tab spec $ map (substituteVarsExp vars) vals

substituteVarsSql vars (UPDATE tab updates wh) =
    UPDATE tab updates' $ fmap (substituteVarsExp vars) wh
  where updates'    = map (mapSnd $ substituteVarsExp vars)  updates



mapSnd f (a, b) = (a, f b)
        

selectColumnLength (ExplicitColumns cols) = length cols