graphql-engine/server/src-lib/Hasura/RQL/DDL/QueryTemplate.hs
Vamshi Surabhi 32ae105279 improved sql generation for select queries (closes #6, #121, #278) (#643)
Better SQL generation for select queries (the query plans will be the same but much more readable). This closes some long standing issues (#6, #121, #278).
2018-10-05 14:26:47 +05:30

234 lines
7.0 KiB
Haskell

{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Hasura.RQL.DDL.QueryTemplate
( createQueryTemplateP1
, createQueryTemplateP2
, delQTemplateFromCatalog
, TemplateParamConf(..)
, CreateQueryTemplate(..)
, DropQueryTemplate(..)
, QueryTP1
, SetQueryTemplateComment(..)
) where
import Hasura.Prelude
import Hasura.RQL.GBoolExp (txtRHSBuilder)
import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.SQL.Value
import qualified Database.PG.Query as Q
import qualified Hasura.RQL.DML.Count as R
import qualified Hasura.RQL.DML.Delete as R
import qualified Hasura.RQL.DML.Insert as R
import qualified Hasura.RQL.DML.Select as R
import qualified Hasura.RQL.DML.Update as R
import qualified Hasura.SQL.DML as PS
import Data.Aeson
import Data.Aeson.Casing
import Data.Aeson.TH
import Instances.TH.Lift ()
import Language.Haskell.TH.Syntax (Lift)
import qualified Data.HashMap.Strict as M
import qualified Data.Text as T
data TemplateParamConf
= TemplateParamConf
{ tpcParam :: !TemplateParam
, tpcDefault :: !(Maybe Value)
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''TemplateParamConf)
data CreateQueryTemplate
= CreateQueryTemplate
{ cqtName :: !TQueryName
, cqtTemplate :: !QueryT
, cqtComment :: !(Maybe T.Text)
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''CreateQueryTemplate)
validateParam
:: PGColType
-> Value
-> P1 PS.SQLExp
validateParam pct val =
case val of
Object _ -> do
tpc <- decodeValue val
withPathK "default" $
maybe (return ()) validateDefault $ tpcDefault tpc
return $ PS.SELit "NULL"
_ -> txtRHSBuilder pct val
where
validateDefault =
void . runAesonParser (convToBin pct)
mkSelQ :: SelectQueryT -> P1 SelectQuery
mkSelQ (DMLQuery tn (SelectG c w o lim offset)) = do
intLim <- withPathK "limit" $ maybe returnNothing parseAsInt lim
intOffset <- withPathK "offset" $ maybe returnNothing parseAsInt offset
return $ DMLQuery tn $ SelectG c w o intLim intOffset
where
returnNothing = return Nothing
parseAsInt v = case v of
Object _ -> do
tpc <- decodeValue v
withPathK "default" $
mapM decodeValue $ tpcDefault tpc
_ -> Just <$> decodeValue v
data QueryTP1
= QTP1Insert R.InsertQueryP1
| QTP1Select R.AnnSel
| QTP1Update R.UpdateQueryP1
| QTP1Delete R.DeleteQueryP1
| QTP1Count R.CountQueryP1
| QTP1Bulk [QueryTP1]
deriving (Show, Eq)
validateTQuery
:: QueryT
-> P1 QueryTP1
validateTQuery qt = withPathK "args" $ case qt of
QTInsert q -> QTP1Insert <$> R.convInsertQuery decodeInsObjs validateParam q
QTSelect q -> QTP1Select <$> (mkSelQ q >>= R.convSelectQuery validateParam)
QTUpdate q -> QTP1Update <$> R.convUpdateQuery validateParam q
QTDelete q -> QTP1Delete <$> R.convDeleteQuery validateParam q
QTCount q -> QTP1Count <$> R.countP1 validateParam q
QTBulk q -> QTP1Bulk <$> mapM validateTQuery q
where
decodeInsObjs val = do
tpc <- decodeValue val
mDefObjs <- mapM decodeValue $ tpcDefault tpc
return $ fromMaybe [] mDefObjs
collectDeps
:: QueryTP1 -> [SchemaDependency]
collectDeps qt = case qt of
QTP1Insert qp1 -> R.getInsertDeps qp1
QTP1Select qp1 -> R.getSelectDeps qp1
QTP1Update qp1 -> R.getUpdateDeps qp1
QTP1Delete qp1 -> R.getDeleteDeps qp1
QTP1Count qp1 -> R.getCountDeps qp1
QTP1Bulk qp1 -> concatMap collectDeps qp1
createQueryTemplateP1
:: (P1C m) => CreateQueryTemplate -> m QueryTemplateInfo
createQueryTemplateP1 (CreateQueryTemplate qtn qt _) = do
adminOnly
ui <- askUserInfo
sc <- askSchemaCache
withPathK "name" $ when (isJust $ M.lookup qtn $ scQTemplates sc) $
throw400 AlreadyExists $ "the query template already exists : " <>> qtn
let qCtx = QCtx ui sc
qtp1 <- withPathK "template" $ liftP1 qCtx $ validateTQuery qt
let deps = collectDeps qtp1
return $ QueryTemplateInfo qtn qt deps
addQTemplateToCatalog
:: CreateQueryTemplate
-> Q.TxE QErr ()
addQTemplateToCatalog (CreateQueryTemplate qtName qtDef mComment) =
Q.unitQE defaultTxErrorHandler [Q.sql|
INSERT INTO
hdb_catalog.hdb_query_template
(template_name, template_defn, comment)
VALUES ($1, $2 :: jsonb, $3)
|] (qtName, Q.AltJ qtDef, mComment) False
createQueryTemplateP2
:: (P2C m)
=> CreateQueryTemplate -> QueryTemplateInfo -> m RespBody
createQueryTemplateP2 cqt qti = do
addQTemplateToCache qti
liftTx $ addQTemplateToCatalog cqt
return successMsg
instance HDBQuery CreateQueryTemplate where
type Phase1Res CreateQueryTemplate = QueryTemplateInfo
phaseOne = createQueryTemplateP1
phaseTwo = createQueryTemplateP2
schemaCachePolicy = SCPReload
data DropQueryTemplate
= DropQueryTemplate
{ dqtName :: !TQueryName
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''DropQueryTemplate)
delQTemplateFromCatalog
:: TQueryName
-> Q.TxE QErr ()
delQTemplateFromCatalog qtn =
Q.unitQE defaultTxErrorHandler [Q.sql|
DELETE FROM
hdb_catalog.hdb_query_template
WHERE template_name = $1
|] (Identity qtn) False
instance HDBQuery DropQueryTemplate where
type Phase1Res DropQueryTemplate = ()
phaseOne (DropQueryTemplate qtn) =
withPathK "name" $ void $ askQTemplateInfo qtn
phaseTwo (DropQueryTemplate qtn) _ = do
delQTemplateFromCache qtn
liftTx $ delQTemplateFromCatalog qtn
return successMsg
schemaCachePolicy = SCPReload
data SetQueryTemplateComment
= SetQueryTemplateComment
{ sqtcName :: !TQueryName
, sqtcComment :: !(Maybe T.Text)
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 4 snakeCase) ''SetQueryTemplateComment)
setQueryTemplateCommentP1 :: (P1C m) => SetQueryTemplateComment -> m ()
setQueryTemplateCommentP1 (SetQueryTemplateComment qtn _) = do
adminOnly
void $ askQTemplateInfo qtn
setQueryTemplateCommentP2 :: (P2C m) => SetQueryTemplateComment -> m RespBody
setQueryTemplateCommentP2 apc = do
liftTx $ setQueryTemplateCommentTx apc
return successMsg
instance HDBQuery SetQueryTemplateComment where
type Phase1Res SetQueryTemplateComment = ()
phaseOne = setQueryTemplateCommentP1
phaseTwo q _ = setQueryTemplateCommentP2 q
schemaCachePolicy = SCPNoChange
setQueryTemplateCommentTx
:: SetQueryTemplateComment
-> Q.TxE QErr ()
setQueryTemplateCommentTx (SetQueryTemplateComment qtn comment) =
Q.unitQE defaultTxErrorHandler
[Q.sql|
UPDATE hdb_catalog.hdb_query_template
SET comment = $1
WHERE template_name = $2
|] (comment, qtn) False