mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 12:31:52 +03:00
32ae105279
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).
234 lines
7.0 KiB
Haskell
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
|