fix mutation returning when relationships are present (fix #1576) (#1703)

If returning field contains nested selections then mutation is performed in two steps
1. Mutation is performed with returning columns of any primary key and unique constraints
2. returning fields are queried on rows returned by selecting from table by filtering with column values returned in Step 1.

Since mutation takes two courses based on selecting relations in returning field, it is hard to maintain sequence of prepared arguments (PrepArg) generated while resolving returning field. So, we're using txtConverter instead of prepare to resolve mutation fields.
This commit is contained in:
Rakesh Emmadi
2019-03-07 15:54:07 +05:30
committed by Vamshi Surabhi
parent efc97c0b5c
commit 5f274b5527
23 changed files with 553 additions and 193 deletions

View File

@@ -181,6 +181,7 @@ library
, Hasura.RQL.DML.Delete
, Hasura.RQL.DML.Internal
, Hasura.RQL.DML.Insert
, Hasura.RQL.DML.Mutation
, Hasura.RQL.DML.Returning
, Hasura.RQL.DML.Select.Internal
, Hasura.RQL.DML.Select.Types

View File

@@ -57,13 +57,15 @@ data UpdOpCtx
, _uocHeaders :: ![T.Text]
, _uocFilter :: !AnnBoolExpSQL
, _uocPresetCols :: !PreSetCols
, _uocUniqCols :: !(Maybe [PGColInfo])
} deriving (Show, Eq)
data DelOpCtx
= DelOpCtx
{ _docTable :: !QualifiedTable
, _docHeaders :: ![T.Text]
, _docFilter :: !AnnBoolExpSQL
{ _docTable :: !QualifiedTable
, _docHeaders :: ![T.Text]
, _docFilter :: !AnnBoolExpSQL
, _docUniqCols :: !(Maybe [PGColInfo])
} deriving (Show, Eq)
data OpCtx

View File

@@ -22,7 +22,6 @@ import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.SQL.Value
import qualified Hasura.GraphQL.Resolve.Select as RS
import qualified Hasura.GraphQL.Transport.HTTP as TH
@@ -96,7 +95,6 @@ explainField userInfo gCtx sqlGenCtx fld =
return $ FieldPlan fName (Just txtSQL) $ Just planLines
where
fName = _fName fld
txtConverter = return . uncurry toTxtValue
opCtxMap = _gOpCtxMap gCtx
fldMap = _gFields gCtx

View File

@@ -24,6 +24,7 @@ module Hasura.GraphQL.Resolve.Context
, Convert
, runConvert
, prepare
, txtConverter
, module Hasura.GraphQL.Utils
) where
@@ -139,6 +140,9 @@ prepare (colTy, colVal) = do
put (preparedArgs Seq.|> binEncoder colVal)
return $ toPrepParam (Seq.length preparedArgs + 1) colTy
txtConverter :: Monad m => PrepFn m
txtConverter = return . uncurry toTxtValue
runConvert
:: (MonadError QErr m)
=> (FieldMap, OrdByCtx, InsCtxMap, SQLGenCtx)

View File

@@ -49,6 +49,7 @@ data InsCtx
, icSet :: !PreSetCols
, icRelations :: !RelationInfoMap
, icUpdPerm :: !(Maybe UpdPermForIns)
, icUniqCols :: !(Maybe [PGColInfo])
} deriving (Show, Eq)
type InsCtxMap = Map.HashMap QualifiedTable InsCtx

View File

@@ -18,7 +18,6 @@ import qualified Language.GraphQL.Draft.Syntax as G
import qualified Database.PG.Query as Q
import qualified Hasura.RQL.DML.Insert as RI
import qualified Hasura.RQL.DML.Returning as RR
import qualified Hasura.RQL.DML.Select as RS
import qualified Hasura.RQL.GBoolExp as RB
import qualified Hasura.SQL.DML as S
@@ -30,6 +29,7 @@ import Hasura.GraphQL.Resolve.Select
import Hasura.GraphQL.Validate.Field
import Hasura.GraphQL.Validate.Types
import Hasura.RQL.DML.Internal (dmlTxErrorHandler)
import Hasura.RQL.DML.Mutation
import Hasura.RQL.GBoolExp (toSQLBoolExp)
import Hasura.RQL.Types
import Hasura.SQL.Types
@@ -48,11 +48,18 @@ data AnnIns a
, _aiView :: !QualifiedTable
, _aiTableCols :: ![PGColInfo]
, _aiDefVals :: !(Map.HashMap PGCol S.SQLExp)
} deriving (Show, Eq)
, _aiUniqCols :: !(Maybe [PGColInfo])
} deriving (Show, Eq, Functor, Foldable, Traversable)
type SingleObjIns = AnnIns AnnInsObj
type MultiObjIns = AnnIns [AnnInsObj]
singleToMulti :: SingleObjIns -> MultiObjIns
singleToMulti = fmap pure
multiToSingles :: MultiObjIns -> [SingleObjIns]
multiToSingles = sequenceA
data RelIns a
= RelIns
{ _riAnnIns :: !a
@@ -64,11 +71,10 @@ type ArrRelIns = RelIns MultiObjIns
type PGColWithValue = (PGCol, PGColValue)
data InsWithExp
= InsWithExp
{ _iweExp :: !S.CTE
, _iweConflictCtx :: !(Maybe RI.ConflictCtx)
, _iwePrepArgs :: !(Seq.Seq Q.PrepArg)
data CTEExp
= CTEExp
{ _iweExp :: !S.CTE
, _iwePrepArgs :: !(Seq.Seq Q.PrepArg)
} deriving (Show, Eq)
data AnnInsObj
@@ -114,14 +120,14 @@ traverseInsObj rim (gName, annVal) defVal@(AnnInsObj cols objRels arrRels) =
throw500 $ "relation " <> relName <<> " not found"
let rTable = riRTable relInfo
InsCtx rtView rtCols rtDefVals rtRelInfoMap rtUpdPerm <- getInsCtx rTable
InsCtx rtView rtCols rtDefVals rtRelInfoMap rtUpdPerm rtUniqCols <- getInsCtx rTable
withPathK (G.unName gName) $ case riType relInfo of
ObjRel -> do
dataObj <- asObject dataVal
annDataObj <- mkAnnInsObj rtRelInfoMap dataObj
ccM <- forM onConflictM $ parseOnConflict rTable rtUpdPerm
let singleObjIns = AnnIns annDataObj ccM rtView rtCols rtDefVals
let singleObjIns = AnnIns annDataObj ccM rtView rtCols rtDefVals rtUniqCols
objRelIns = RelIns singleObjIns relInfo
return (AnnInsObj cols (objRelIns:objRels) arrRels)
@@ -132,7 +138,7 @@ traverseInsObj rim (gName, annVal) defVal@(AnnInsObj cols objRels arrRels) =
dataObj <- asObject arrDataVal
mkAnnInsObj rtRelInfoMap dataObj
ccM <- forM onConflictM $ parseOnConflict rTable rtUpdPerm
let multiObjIns = AnnIns annDataObjs ccM rtView rtCols rtDefVals
let multiObjIns = AnnIns annDataObjs ccM rtView rtCols rtDefVals rtUniqCols
arrRelIns = RelIns multiObjIns relInfo
return (AnnInsObj cols objRels (arrRelIns:arrRels))
-- if array relation insert input data has empty objects
@@ -182,17 +188,17 @@ mkSQLRow defVals withPGCol =
mkInsertQ :: MonadError QErr m => QualifiedTable
-> Maybe RI.ConflictClauseP1 -> [(PGCol, AnnGValue)]
-> [PGCol] -> Map.HashMap PGCol S.SQLExp -> RoleName
-> m InsWithExp
-> m (CTEExp, Maybe RI.ConflictCtx)
mkInsertQ vn onConflictM insCols tableCols defVals role = do
(givenCols, args) <- flip runStateT Seq.Empty $ toSQLExps insCols
let sqlConflict = RI.toSQLConflict <$> onConflictM
sqlExps = mkSQLRow defVals givenCols
sqlInsert = S.SQLInsert vn tableCols [sqlExps] sqlConflict $ Just S.returningStar
adminIns = return $ InsWithExp (S.CTEInsert sqlInsert) Nothing args
adminIns = return (CTEExp (S.CTEInsert sqlInsert) args, Nothing)
nonAdminInsert = do
ccM <- mapM RI.extractConflictCtx onConflictM
let cteIns = S.CTEInsert sqlInsert{S.siConflict=Nothing}
return $ InsWithExp cteIns ccM args
return (CTEExp cteIns args, ccM)
bool nonAdminInsert adminIns $ isAdmin role
@@ -200,6 +206,7 @@ mkBoolExp
:: (MonadError QErr m, MonadState PrepArgs m)
=> QualifiedTable -> [(PGColInfo, PGColValue)]
-> m S.BoolExp
mkBoolExp _ [] = return $ S.BELit False
mkBoolExp tn colInfoVals =
RB.toSQLBoolExp (S.mkQual tn) . BoolAnd <$>
mapM (fmap BoolFld . uncurry f) colInfoVals
@@ -207,54 +214,58 @@ mkBoolExp tn colInfoVals =
f ci@(PGColInfo _ colTy _) colVal =
AVCol ci . pure . AEQ True <$> prepare (colTy, colVal)
mkSelQ :: MonadError QErr m => QualifiedTable
-> [PGColInfo] -> [PGColWithValue] -> m InsWithExp
mkSelQ tn allColInfos pgColsWithVal = do
(whereExp, args) <- flip runStateT Seq.Empty $ mkBoolExp tn colWithInfos
asSingleObject
:: MonadError QErr m
=> [ColVals] -> m (Maybe ColVals)
asSingleObject = \case
[] -> return Nothing
[a] -> return $ Just a
_ -> throw500 "more than one row returned"
fetchFromColVals
:: MonadError QErr m
=> ColVals
-> [PGColInfo]
-> (PGColInfo -> a)
-> m [(a, PGColValue)]
fetchFromColVals colVal reqCols f =
forM reqCols $ \ci -> do
let valM = Map.lookup (pgiName ci) colVal
val <- onNothing valM $ throw500 $ "column "
<> pgiName ci <<> " not found in given colVal"
pgColVal <- RB.pgValParser (pgiType ci) val
return (f ci, pgColVal)
mkSelCTE
:: MonadError QErr m
=> QualifiedTable
-> [PGColInfo]
-> Maybe ColVals
-> m CTEExp
mkSelCTE tn uniqCols colValM = do
(whereExp, args) <- case colValM of
Nothing -> return (S.BELit False, Seq.empty)
Just colVal -> do
colInfoWithVals <- fetchFromColVals colVal uniqCols id
flip runStateT Seq.Empty $ mkBoolExp tn colInfoWithVals
let sqlSel = S.mkSelect { S.selExtr = [S.selectStar]
, S.selFrom = Just $ S.mkSimpleFromExp tn
, S.selWhere = Just $ S.WhereFrag whereExp
}
return $ InsWithExp (S.CTESelect sqlSel) Nothing args
where
colWithInfos = mergeListsWith pgColsWithVal allColInfos
(\(c, _) ci -> c == pgiName ci)
(\(_, v) ci -> (ci, v))
return $ CTEExp (S.CTESelect sqlSel) args
execWithExp
execCTEExp
:: Bool
-> QualifiedTable
-> InsWithExp
-> CTEExp
-> RR.MutFlds
-> Q.TxE QErr RespBody
execWithExp strfyNum tn (InsWithExp withExp ccM args) flds = do
RI.setConflictCtx ccM
execCTEExp strfyNum tn (CTEExp cteExp args) flds =
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder sqlBuilder) (toList args) True
where
sqlBuilder = toSQL $ RR.mkSelWith tn withExp flds True strfyNum
insertAndRetCols
:: Bool
-> QualifiedTable
-> InsWithExp
-> T.Text
-> [PGColInfo]
-> Q.TxE QErr [PGColWithValue]
insertAndRetCols strfyNum tn withExp errMsg retCols = do
resBS <- execWithExp strfyNum tn withExp [("response", RR.MRet annSelFlds)]
insResp <- decodeFromBS resBS
resObj <- onNothing (_irResponse insResp) $ throwVE errMsg
forM retCols $ \(PGColInfo col colty _) -> do
val <- onNothing (Map.lookup (getPGColTxt col) resObj) $
throw500 $ "column " <> col <<> "not returned by postgres"
pgColVal <- RB.pgValParser colty val
return (col, pgColVal)
where
annSelFlds = flip map retCols $ \pgci ->
(fromPGCol $ pgiName pgci, RS.FCol pgci)
sqlBuilder = toSQL $ RR.mkSelWith tn cteExp flds True strfyNum
-- | validate an insert object based on insert columns,
-- | insert object relations and additional columns from parent
@@ -291,23 +302,33 @@ insertObjRel
-> Q.TxE QErr (Int, [PGColWithValue])
insertObjRel strfyNum role objRelIns =
withPathK relNameTxt $ do
(aRows, withExp) <- insertObj strfyNum role tn singleObjIns []
let errMsg = "cannot proceed to insert object relation "
<> relName <<> " since insert to table " <> tn <<> " affects zero rows"
retColsWithVals <- insertAndRetCols strfyNum tn withExp errMsg $
getColInfos rCols allCols
resp <- insertMultipleObjects strfyNum role tn multiObjIns [] mutFlds "data"
MutateResp aRows colVals <- decodeFromBS resp
colValM <- asSingleObject colVals
colVal <- onNothing colValM $ throw400 NotSupported errMsg
retColsWithVals <- fetchFromColVals colVal rColInfos pgiName
let c = mergeListsWith mapCols retColsWithVals
(\(_, rCol) (col, _) -> rCol == col)
(\(lCol, _) (_, colVal) -> (lCol, colVal))
(\(lCol, _) (_, cVal) -> (lCol, cVal))
return (aRows, c)
where
RelIns singleObjIns relInfo = objRelIns
multiObjIns = singleToMulti singleObjIns
relName = riName relInfo
relNameTxt = getRelTxt relName
mapCols = riMapping relInfo
tn = riRTable relInfo
rCols = map snd mapCols
allCols = _aiTableCols singleObjIns
rCols = map snd mapCols
rColInfos = getColInfos rCols allCols
errMsg = "cannot proceed to insert object relation "
<> relName <<> " since insert to table "
<> tn <<> " affects zero rows"
mutFlds = [ ("affected_rows", RR.MCount)
, ( "returning_columns"
, RR.MRet $ RR.pgColsToSelFlds rColInfos
)
]
-- | insert an array relationship and return affected rows
insertArrRel
@@ -340,7 +361,7 @@ insertObj
-> QualifiedTable
-> SingleObjIns
-> [PGColWithValue] -- ^ additional fields
-> Q.TxE QErr (Int, InsWithExp)
-> Q.TxE QErr (Int, CTEExp)
insertObj strfyNum role tn singleObjIns addCols = do
-- validate insert
validateInsert (map _1 cols) (map _riRelInfo objRels) $ map fst addCols
@@ -349,7 +370,7 @@ insertObj strfyNum role tn singleObjIns addCols = do
objInsRes <- forM objRels $ insertObjRel strfyNum role
-- prepare final insert columns
let objInsAffRows = sum $ map fst objInsRes
let objRelAffRows = sum $ map fst objInsRes
objRelDeterminedCols = concatMap snd objInsRes
objRelInsCols = mkPGColWithTypeAndVal allCols objRelDeterminedCols
addInsCols = mkPGColWithTypeAndVal allCols addCols
@@ -360,29 +381,31 @@ insertObj strfyNum role tn singleObjIns addCols = do
arrDepColsWithInfo = getColInfos arrDepCols allCols
-- prepare insert query as with expression
insQ <- mkInsertQ vn onConflictM finalInsCols (map pgiName allCols) defVals role
(CTEExp cte insPArgs, ccM) <-
mkInsertQ vn onConflictM finalInsCols (map pgiName allCols) defVals role
uniqCols <- onNothing uniqColsM $ throw500 "unique columns not found in relational insert"
let preArrRelInsAffRows = objInsAffRows + 1
insertWithArrRels = withArrRels preArrRelInsAffRows insQ
arrDepColsWithInfo
insertWithoutArrRels = return (preArrRelInsAffRows, insQ)
RI.setConflictCtx ccM
MutateResp affRows colVals <-
mutateAndFetchCols tn (uniqCols `union` arrDepColsWithInfo) (cte, insPArgs) strfyNum
colValM <- asSingleObject colVals
cteExp <- mkSelCTE tn uniqCols colValM
-- insert object
bool insertWithArrRels insertWithoutArrRels $ null arrDepColsWithInfo
arrRelAffRows <- bool (withArrRels arrDepColsWithInfo colValM) (return 0) $ null arrRels
let totAffRows = objRelAffRows + affRows + arrRelAffRows
return (totAffRows, cteExp)
where
AnnIns annObj onConflictM vn allCols defVals = singleObjIns
AnnIns annObj onConflictM vn allCols defVals uniqColsM = singleObjIns
AnnInsObj cols objRels arrRels = annObj
withArrRels preAffRows insQ arrDepColsWithType = do
arrDepColsWithVal <-
insertAndRetCols strfyNum tn insQ cannotInsArrRelErr arrDepColsWithType
withArrRels arrDepCols colValM = do
colVal <- onNothing colValM $ throw400 NotSupported cannotInsArrRelErr
arrDepColsWithVal <- fetchFromColVals colVal arrDepCols pgiName
arrInsARows <- forM arrRels $ insertArrRel strfyNum role arrDepColsWithVal
let totalAffRows = preAffRows + sum arrInsARows
selQ <- mkSelQ tn allCols arrDepColsWithVal
return (totalAffRows, selQ)
return $ sum arrInsARows
cannotInsArrRelErr =
"cannot proceed to insert array relations since insert to table "
@@ -402,9 +425,8 @@ insertMultipleObjects
insertMultipleObjects strfyNum role tn multiObjIns addCols mutFlds errP =
bool withoutRelsInsert withRelsInsert anyRelsToInsert
where
AnnIns insObjs onConflictM vn tableColInfos defVals = multiObjIns
singleObjInserts = flip map insObjs $ \o ->
AnnIns o onConflictM vn tableColInfos defVals
AnnIns insObjs onConflictM vn tableColInfos defVals uniqCols = multiObjIns
singleObjInserts = multiToSingles multiObjIns
insCols = map _aioColumns insObjs
allInsObjRels = concatMap _aioObjRels insObjs
allInsArrRels = concatMap _aioArrRels insObjs
@@ -425,7 +447,7 @@ insertMultipleObjects strfyNum role tn multiObjIns addCols mutFlds errP =
rowsWithCol <- mapM (toSQLExps . map pgColToAnnGVal) withAddCols
return $ map (mkSQLRow defVals) rowsWithCol
let insQP1 = RI.InsertQueryP1 tn vn tableCols sqlRows onConflictM mutFlds
let insQP1 = RI.InsertQueryP1 tn vn tableCols sqlRows onConflictM mutFlds uniqCols
p1 = (insQP1, prepArgs)
bool (RI.nonAdminInsert strfyNum p1) (RI.insertP2 strfyNum p1) $ isAdmin role
@@ -435,10 +457,10 @@ insertMultipleObjects strfyNum role tn multiObjIns addCols mutFlds errP =
insertObj strfyNum role tn objIns addCols
let affRows = sum $ map fst insResps
withExps = map snd insResps
cteExps = map snd insResps
retFlds = mapMaybe getRet mutFlds
rawResps <- forM withExps
$ \withExp -> execWithExp strfyNum tn withExp retFlds
rawResps <- forM cteExps
$ \cteExp -> execCTEExp strfyNum tn cteExp retFlds
respVals :: [J.Object] <- mapM decodeFromBS rawResps
respTups <- forM mutFlds $ \(t, mutFld) -> do
jsonVal <- case mutFld of
@@ -468,11 +490,11 @@ convertInsert role tn fld = prefixErrPath fld $ do
bool (withNonEmptyObjs annVals mutFlds) (buildEmptyMutResp mutFlds) $ null annVals
where
withNonEmptyObjs annVals mutFlds = do
InsCtx vn tableCols defValMap relInfoMap updPerm <- getInsCtx tn
InsCtx vn tableCols defValMap relInfoMap updPerm uniqCols <- getInsCtx tn
annObjs <- mapM asObject annVals
annInsObjs <- forM annObjs $ mkAnnInsObj relInfoMap
conflictClauseM <- forM onConflictM $ parseOnConflict tn updPerm
let multiObjIns = AnnIns annInsObjs conflictClauseM vn tableCols defValMap
let multiObjIns = AnnIns annInsObjs conflictClauseM vn tableCols defValMap uniqCols
strfyNum <- stringifyNum <$> asks getter
return $ prefixErrPath fld $ insertMultipleObjects strfyNum role tn
multiObjIns [] mutFlds "objects"

View File

@@ -38,7 +38,7 @@ convertMutResp ty selSet =
"__typename" -> return $ RR.MExp $ G.unName $ G.unNamedType ty
"affected_rows" -> return RR.MCount
"returning" -> fmap RR.MRet $
fromSelSet prepare (_fType fld) $ _fSelSet fld
fromSelSet txtConverter (_fType fld) $ _fSelSet fld
G.Name t -> throw500 $ "unexpected field in mutation resp : " <> t
convertRowObj
@@ -129,14 +129,14 @@ convertUpdate opCtx fld = do
"atleast any one of _set, _inc, _append, _prepend, _delete_key, _delete_elem and "
<> " _delete_at_path operator is expected"
strfyNum <- stringifyNum <$> asks getter
let p1 = RU.UpdateQueryP1 tn setItems (filterExp, whereExp) mutFlds
let p1 = RU.UpdateQueryP1 tn setItems (filterExp, whereExp) mutFlds uniqCols
whenNonEmptyItems = return $ RU.updateQueryToTx strfyNum (p1, prepArgs)
whenEmptyItems = buildEmptyMutResp mutFlds
-- if there are not set items then do not perform
-- update and return empty mutation response
bool whenNonEmptyItems whenEmptyItems $ null setItems
where
UpdOpCtx tn _ filterExp preSetCols = opCtx
UpdOpCtx tn _ filterExp preSetCols uniqCols = opCtx
args = _fArguments fld
preSetItems = Map.toList preSetCols
@@ -148,11 +148,11 @@ convertDelete opCtx fld = do
whereExp <- withArg (_fArguments fld) "where" (parseBoolExp prepare)
mutFlds <- convertMutResp (_fType fld) $ _fSelSet fld
args <- get
let p1 = RD.DeleteQueryP1 tn (filterExp, whereExp) mutFlds
let p1 = RD.DeleteQueryP1 tn (filterExp, whereExp) mutFlds uniqCols
strfyNum <- stringifyNum <$> asks getter
return $ RD.deleteQueryToTx strfyNum (p1, args)
where
DelOpCtx tn _ filterExp = opCtx
DelOpCtx tn _ filterExp uniqCols = opCtx
-- | build mutation response for empty objects
buildEmptyMutResp :: Monad m => RR.MutFlds -> m RespTx

View File

@@ -97,9 +97,9 @@ getValidCols = fst . validPartitionFieldInfoMap
getValidRels :: FieldInfoMap -> [RelInfo]
getValidRels = snd . validPartitionFieldInfoMap
mkValidConstraints :: [ConstraintName] -> [ConstraintName]
mkValidConstraints :: [TableConstraint] -> [TableConstraint]
mkValidConstraints =
filter (isValidName . G.Name . getConstraintTxt)
filter (isValidName . G.Name . getConstraintTxt . tcName)
isRelNullable :: FieldInfoMap -> RelInfo -> Bool
isRelNullable fim ri = isNullable
@@ -149,6 +149,10 @@ mkTableTy :: QualifiedTable -> G.NamedType
mkTableTy =
G.NamedType . qualObjectToName
mkTableColTy :: QualifiedTable -> G.NamedType
mkTableColTy tn =
G.NamedType $ qualObjectToName tn <> "_columns"
mkTableAggTy :: QualifiedTable -> G.NamedType
mkTableAggTy tn =
G.NamedType $ qualObjectToName tn <> "_aggregate"
@@ -256,6 +260,26 @@ mkTableObj tn allowedFlds =
mkRelFld allowAgg relInfo isNullable
desc = G.Description $ "columns and relationships of " <>> tn
{-
type table_columns {
col1: colty1
.
.
coln: coltyn
}
-}
mkTableColObj
:: QualifiedTable
-> [PGColInfo]
-> ObjTyInfo
mkTableColObj tn allowedCols =
mkHsraObjTyInfo (Just desc) (mkTableColTy tn) Set.empty $
mapFromL _fiName flds
where
flds = map mkPGColFld allowedCols
desc = G.Description $ "columns of " <>> tn
{-
type table_aggregate {
agg: table_aggregate_fields
@@ -479,8 +503,9 @@ type table_mutation_response {
mkMutRespObj
:: QualifiedTable
-> Bool -- is sel perm defined
-> Bool -- is nested allowed
-> ObjTyInfo
mkMutRespObj tn sel =
mkMutRespObj tn sel nestAlwd =
mkHsraObjTyInfo (Just objDesc) (mkMutRespTy tn) Set.empty $ mapFromL _fiName
$ affectedRowsFld : bool [] [returningFld] sel
where
@@ -493,9 +518,10 @@ mkMutRespObj tn sel =
desc = "number of affected rows by the mutation"
returningFld =
mkHsraObjFldInfo (Just desc) "returning" Map.empty $
G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkTableTy tn
G.toGT $ G.toNT $ G.toLT $ G.toNT retTy
where
desc = "data of the affected rows by the mutation"
retTy = bool (mkTableColTy tn) (mkTableTy tn) nestAlwd
-- table_bool_exp
mkBoolExpInp
@@ -928,10 +954,11 @@ mkInsInp
:: QualifiedTable -> InsCtx -> InpObjTyInfo
mkInsInp tn insCtx =
mkHsraInpTyInfo (Just desc) (mkInsInpTy tn) $ fromInpValL $
map mkPGColInp insCols <> relInps
map mkPGColInp insCols <> bool [] relInps alwNestedIns
where
desc = G.Description $
"input type for inserting data into table " <>> tn
alwNestedIns = isJust $ icUniqCols insCtx
cols = icColumns insCtx
setCols = Map.keys $ icSet insCtx
insCols = flip filter cols $ \ci -> pgiName ci `notElem` setCols
@@ -1216,6 +1243,8 @@ mkOnConflictTypes tn uniqueOrPrimaryCons cols =
mkGCtxRole'
:: QualifiedTable
-- all columns
-> [PGColInfo]
-- insert perm
-> Maybe (InsCtx, Bool)
-- select permission
@@ -1227,21 +1256,24 @@ mkGCtxRole'
-- primary key columns
-> [PGColInfo]
-- constraints
-> [ConstraintName]
-> [TableConstraint]
-> Maybe ViewInfo
-- all functions
-> [FunctionInfo]
-> TyAgg
mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM funcs =
TyAgg (mkTyInfoMap allTypes) fieldMap scalars ordByCtx
mkGCtxRole' tn allCols insPermM selPermM updColsM
delPermM pkeyCols constraints viM funcs =
TyAgg (mkTyInfoMap allTypes) fieldMap scalars ordByCtx
where
ordByCtx = fromMaybe Map.empty ordByCtxM
upsertPerm = or $ fmap snd insPermM
isUpsertable = upsertable constraints upsertPerm $ isJust viM
alwNestMutFld = isJust $ getUniqCols allCols constraints
constNames = map tcName constraints
isUpsertable = upsertable constNames upsertPerm $ isJust viM
updatableCols = maybe [] (map pgiName) updColsM
onConflictTypes = mkOnConflictTypes tn constraints updatableCols isUpsertable
onConflictTypes = mkOnConflictTypes tn constNames updatableCols isUpsertable
jsonOpTys = fromMaybe [] updJSONOpInpObjTysM
relInsInpObjTys = maybe [] (map TIInpObj) $
mutHelper viIsInsertable relInsInpObjsM
@@ -1256,6 +1288,7 @@ mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM func
[ TIInpObj <$> boolExpInpObjM
, TIInpObj <$> ordByInpObjM
, TIObj <$> selObjM
, TIObj <$> selColObjM
]
aggQueryTypes = map TIObj aggObjs <> map TIInpObj aggOrdByInps
@@ -1273,6 +1306,7 @@ mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM func
fieldMap = Map.unions $ catMaybes
[ insInpObjFldsM, updSetInpObjFldsM
, boolExpInpObjFldsM , selObjFldsM
, selColObjFldsM
]
scalars = Set.unions [selByPkScalarSet, funcArgScalarSet]
@@ -1338,7 +1372,7 @@ mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM func
-- mut resp obj
mutRespObjM =
if isMut
then Just $ mkMutRespObj tn $ isJust selFldsM
then Just $ mkMutRespObj tn (isJust selFldsM) alwNestMutFld
else Nothing
isMut = (isJust insColsM || isJust updColsM || isJust delPermM)
@@ -1346,6 +1380,11 @@ mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM func
-- table obj
selObjM = mkTableObj tn <$> selFldsM
-- table columns obj
selColObjM = if not alwNestMutFld then
(mkTableColObj tn . lefts) <$> selFldsM
else Nothing
-- aggregate objs and order by inputs
(aggObjs, aggOrdByInps) = case selPermM of
Just (True, selFlds) ->
@@ -1376,6 +1415,10 @@ mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM func
in numFldsObjs <> compFldsObjs
-- the fields used in table object
selObjFldsM = mkFldMap (mkTableTy tn) <$> selFldsM
-- the fields used in table_columns object
selColObjFldsM = if not alwNestMutFld then
(mkColFldMap (mkTableColTy tn) . lefts) <$> selFldsM
else Nothing
-- the scalar set for table_by_pk arguments
selByPkScalarSet = Set.fromList $ map pgiType pkeyCols
@@ -1387,7 +1430,7 @@ mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM func
getRootFldsRole'
:: QualifiedTable
-> [PGCol]
-> [ConstraintName]
-> [TableConstraint]
-> FieldInfoMap
-> [FunctionInfo]
-> Maybe ([T.Text], Bool) -- insert perm
@@ -1418,19 +1461,21 @@ getRootFldsRole' tn primCols constraints fields funcs insM selM updM delM viM =
bool Nothing (getDet <$> mutM) $ isMutable f viM
colInfos = fst $ validPartitionFieldInfoMap fields
constNames = map tcName constraints
uniqCols = getUniqCols colInfos constraints
getInsDet (hdrs, upsertPerm) =
let isUpsertable = upsertable constraints upsertPerm $ isJust viM
let isUpsertable = upsertable constNames upsertPerm $ isJust viM
in ( OCInsert $ InsOpCtx tn $ hdrs `union` maybe [] (\(_, _, _, x) -> x) updM
, Right $ mkInsMutFld tn isUpsertable
)
getUpdDet (updCols, preSetCols, updFltr, hdrs) =
( OCUpdate $ UpdOpCtx tn hdrs updFltr preSetCols
( OCUpdate $ UpdOpCtx tn hdrs updFltr preSetCols uniqCols
, Right $ mkUpdMutFld tn $ getColInfos updCols colInfos
)
getDelDet (delFltr, hdrs) =
( OCDelete $ DelOpCtx tn hdrs delFltr
( OCDelete $ DelOpCtx tn hdrs delFltr uniqCols
, Right $ mkDelMutFld tn
)
getSelDet (selFltr, pLimit, hdrs, _) =
@@ -1504,8 +1549,13 @@ getSelPerm tableCache fields role selPermInfo = do
mkInsCtx
:: MonadError QErr m
=> RoleName
-> TableCache -> FieldInfoMap -> InsPermInfo -> Maybe UpdPermInfo -> m InsCtx
mkInsCtx role tableCache fields insPermInfo updPermM = do
-> TableCache
-> FieldInfoMap
-> InsPermInfo
-> Maybe [PGColInfo]
-> Maybe UpdPermInfo
-> m InsCtx
mkInsCtx role tableCache fields insPermInfo uniqCols updPermM = do
relTupsM <- forM rels $ \relInfo -> do
let remoteTable = riRTable relInfo
relName = riName relInfo
@@ -1516,7 +1566,7 @@ mkInsCtx role tableCache fields insPermInfo updPermM = do
isInsertable insPermM viewInfoM
let relInfoMap = Map.fromList $ catMaybes relTupsM
return $ InsCtx iView cols setCols relInfoMap updPermForIns
return $ InsCtx iView cols setCols relInfoMap updPermForIns uniqCols
where
cols = getValidCols fields
rels = getValidRels fields
@@ -1531,8 +1581,12 @@ mkInsCtx role tableCache fields insPermInfo updPermM = do
mkAdminInsCtx
:: MonadError QErr m
=> QualifiedTable -> TableCache -> FieldInfoMap -> m InsCtx
mkAdminInsCtx tn tc fields = do
=> QualifiedTable
-> TableCache
-> FieldInfoMap
-> Maybe [PGColInfo]
-> m InsCtx
mkAdminInsCtx tn tc fields uniqCols = do
relTupsM <- forM rels $ \relInfo -> do
let remoteTable = riRTable relInfo
relName = riName relInfo
@@ -1544,7 +1598,7 @@ mkAdminInsCtx tn tc fields = do
let relInfoMap = Map.fromList $ catMaybes relTupsM
updPerm = UpdPermForIns (map pgiName cols) noFilter Map.empty
return $ InsCtx tn cols Map.empty relInfoMap $ Just updPerm
return $ InsCtx tn cols Map.empty relInfoMap (Just updPerm) uniqCols
where
cols = getValidCols fields
rels = getValidRels fields
@@ -1555,7 +1609,7 @@ mkGCtxRole
-> QualifiedTable
-> FieldInfoMap
-> [PGCol]
-> [ConstraintName]
-> [TableConstraint]
-> [FunctionInfo]
-> Maybe ViewInfo
-> RoleName
@@ -1564,24 +1618,26 @@ mkGCtxRole
mkGCtxRole tableCache tn fields pCols constraints funcs viM role permInfo = do
selPermM <- mapM (getSelPerm tableCache fields role) $ _permSel permInfo
tabInsCtxM <- forM (_permIns permInfo) $ \ipi -> do
tic <- mkInsCtx role tableCache fields ipi $ _permUpd permInfo
tic <- mkInsCtx role tableCache fields ipi uniqCols $ _permUpd permInfo
return (tic, isJust $ _permUpd permInfo)
let updColsM = filterColInfos . upiCols <$> _permUpd permInfo
tyAgg = mkGCtxRole' tn tabInsCtxM selPermM updColsM
tyAgg = mkGCtxRole' tn allCols tabInsCtxM selPermM updColsM
(void $ _permDel permInfo) pColInfos constraints viM funcs
rootFlds = getRootFldsRole tn pCols constraints fields funcs viM permInfo
insCtxMap = maybe Map.empty (Map.singleton tn) $ fmap fst tabInsCtxM
return (tyAgg, rootFlds, insCtxMap)
where
allCols = getCols fields
uniqCols = getUniqCols allCols constraints
colInfos = getValidCols fields
pColInfos = getColInfos pCols colInfos
pColInfos = getColInfos pCols allCols
filterColInfos allowedSet =
filter ((`Set.member` allowedSet) . pgiName) colInfos
getRootFldsRole
:: QualifiedTable
-> [PGCol]
-> [ConstraintName]
-> [TableConstraint]
-> FieldInfoMap
-> [FunctionInfo]
-> Maybe ViewInfo
@@ -1610,19 +1666,21 @@ mkGCtxMapTable
-> FunctionCache
-> TableInfo
-> m (Map.HashMap RoleName (TyAgg, RootFlds, InsCtxMap))
mkGCtxMapTable tableCache funcCache (TableInfo tn _ fields rolePerms constraints pkeyCols viewInfo _) = do
mkGCtxMapTable tableCache funcCache tabInfo = do
m <- Map.traverseWithKey
(mkGCtxRole tableCache tn fields pkeyCols validConstraints tabFuncs viewInfo) rolePerms
adminInsCtx <- mkAdminInsCtx tn tableCache fields
let adminCtx = mkGCtxRole' tn (Just (adminInsCtx, True))
adminInsCtx <- mkAdminInsCtx tn tableCache fields $ getUniqCols allCols constraints
let adminCtx = mkGCtxRole' tn allCols (Just (adminInsCtx, True))
(Just (True, selFlds)) (Just colInfos) (Just ())
pkeyColInfos validConstraints viewInfo tabFuncs
adminInsCtxMap = Map.singleton tn adminInsCtx
return $ Map.insert adminRole (adminCtx, adminRootFlds, adminInsCtxMap) m
where
TableInfo tn _ fields rolePerms constraints pkeyCols viewInfo _ = tabInfo
validConstraints = mkValidConstraints constraints
allCols = getCols fields
colInfos = getValidCols fields
allCols = map pgiName colInfos
validColNames = map pgiName colInfos
pkeyColInfos = getColInfos pkeyCols colInfos
tabFuncs = filter (isValidObjectName . fiName) $
getFuncsOfTable tn funcCache
@@ -1632,7 +1690,7 @@ mkGCtxMapTable tableCache funcCache (TableInfo tn _ fields rolePerms constraints
adminRootFlds =
getRootFldsRole' tn pkeyCols validConstraints fields tabFuncs
(Just ([], True)) (Just (noFilter, Nothing, [], True))
(Just (allCols, mempty, noFilter, [])) (Just (noFilter, []))
(Just (validColNames, mempty, noFilter, [])) (Just (noFilter, []))
viewInfo
noFilter :: AnnBoolExpSQL

View File

@@ -49,6 +49,7 @@ data ConstraintMeta
{ cmName :: !ConstraintName
, cmOid :: !Int
, cmType :: !ConstraintType
, cmCols :: ![PGCol]
} deriving (Show, Eq)
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''ConstraintMeta)
@@ -100,11 +101,27 @@ fetchTableMeta = do
json_build_object(
'name', tc.constraint_name,
'oid', r.oid::integer,
'type', tc.constraint_type
'type', tc.constraint_type,
'cols', tc.columns
)
) as constraints
FROM
information_schema.table_constraints tc
(
SELECT table_name, table_schema,
constraint_name, columns,
'PRIMARY KEY' as constraint_type
FROM hdb_catalog.hdb_primary_key
UNION ALL
SELECT table_name, table_schema,
constraint_name, columns,
'UNIQUE' as constraint_type
FROM hdb_catalog.hdb_unique_constraint
UNION ALL
SELECT table_name, table_schema,
constraint_name, '[]'::json as columns,
'FOREIGN KEY' as constraint_type
FROM hdb_catalog.hdb_foreign_key_constraint
) tc
JOIN pg_catalog.pg_constraint r
ON tc.constraint_name = r.conname
GROUP BY
@@ -115,8 +132,8 @@ fetchTableMeta = do
AND t.table_schema <> 'information_schema'
AND t.table_schema <> 'hdb_catalog'
|] () False
forM res $ \(ts, tn, toid, cols, constrnts) ->
return $ TableMeta toid (QualifiedObject ts tn) (Q.getAltJ cols) (Q.getAltJ constrnts)
forM res $ \(ts, tn, toid, cols, constrnts)
-> return $ TableMeta toid (QualifiedObject ts tn) (Q.getAltJ cols) (Q.getAltJ constrnts)
getOverlap :: (Eq k, Hashable k) => (v -> k) -> [v] -> [v] -> [(v, v)]
getOverlap getKey left right =
@@ -140,7 +157,7 @@ data TableDiff
-- The final list of uniq/primary constraint names
-- used for generating types on_conflict clauses
-- TODO: this ideally should't be part of TableDiff
, _tdUniqOrPriCons :: ![ConstraintName]
, _tdUniqOrPriCons :: ![TableConstraint]
} deriving (Show, Eq)
getTableDiff :: TableMeta -> TableMeta -> TableDiff
@@ -153,7 +170,9 @@ getTableDiff oldtm newtm =
newCols = tmColumns newtm
uniqueOrPrimaryCons =
[cmName cm | cm <- tmConstraints newtm, isUniqueOrPrimary $ cmType cm]
[ TableConstraint (cmType cm) (cmName cm) (cmCols cm)
| cm <- tmConstraints newtm, isUniqueOrPrimary (cmType cm)
]
droppedCols =
map pcmColumnName $ getDifference pcmOrdinalPosition oldCols newCols
@@ -241,7 +260,7 @@ funcFromMeta :: FunctionMeta -> QualifiedFunction
funcFromMeta fm = QualifiedObject (fmSchema fm) (fmName fm)
fetchFunctionMeta :: Q.Tx [FunctionMeta]
fetchFunctionMeta = do
fetchFunctionMeta =
map (Q.getAltJ . runIdentity) <$> Q.listQ [Q.sql|
SELECT
json_build_object(

View File

@@ -52,8 +52,8 @@ getTableInfo qt@(QualifiedObject sn tn) isSystemDefined = do
Q.listQ $(Q.sqlFromFile "src-rsr/table_info.sql")(sn, tn) True
case tableData of
[] -> throw400 NotExists $ "no such table/view exists in postgres : " <>> qt
[(Q.AltJ cols, Q.AltJ pkeyCols, Q.AltJ cons, Q.AltJ viewInfoM)] ->
return $ mkTableInfo qt isSystemDefined cons cols pkeyCols viewInfoM
[(Q.AltJ cols, Q.AltJ cons, Q.AltJ viewInfoM)] ->
return $ mkTableInfo qt isSystemDefined cons cols viewInfoM
_ -> throw500 $ "more than one row found for: " <>> qt
newtype TrackTable

View File

@@ -14,6 +14,7 @@ import qualified Data.Sequence as DS
import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Mutation
import Hasura.RQL.DML.Returning
import Hasura.RQL.GBoolExp
import Hasura.RQL.Types
@@ -24,15 +25,16 @@ import qualified Hasura.SQL.DML as S
data DeleteQueryP1
= DeleteQueryP1
{ dqp1Table :: !QualifiedTable
, dqp1Where :: !(AnnBoolExpSQL, AnnBoolExpSQL)
, dqp1MutFlds :: !MutFlds
{ dqp1Table :: !QualifiedTable
, dqp1Where :: !(AnnBoolExpSQL, AnnBoolExpSQL)
, dqp1MutFlds :: !MutFlds
, dqp1UniqCols :: !(Maybe [PGColInfo])
} deriving (Show, Eq)
mkSQLDelete
:: Bool -> DeleteQueryP1 -> S.SelectWith
mkSQLDelete strfyNum (DeleteQueryP1 tn (fltr, wc) mutFlds) =
mkSelWith tn (S.CTEDelete delete) mutFlds False strfyNum
mkDeleteCTE
:: DeleteQueryP1 -> S.CTE
mkDeleteCTE (DeleteQueryP1 tn (fltr, wc) _ _) =
S.CTEDelete delete
where
delete = S.SQLDelete tn Nothing tableFltr $ Just S.returningStar
tableFltr = Just $ S.WhereFrag $
@@ -40,10 +42,12 @@ mkSQLDelete strfyNum (DeleteQueryP1 tn (fltr, wc) mutFlds) =
getDeleteDeps
:: DeleteQueryP1 -> [SchemaDependency]
getDeleteDeps (DeleteQueryP1 tn (_, wc) mutFlds) =
mkParentDep tn : whereDeps <> retDeps
getDeleteDeps (DeleteQueryP1 tn (_, wc) mutFlds uniqCols) =
mkParentDep tn : uniqColDeps <> whereDeps <> retDeps
where
whereDeps = getBoolExpDeps tn wc
uniqColDeps = map (mkColDep "on_type" tn) $
maybe [] (map pgiName) uniqCols
retDeps = map (mkColDep "untyped" tn . fst) $
pgColsFromMutFlds mutFlds
@@ -70,6 +74,8 @@ validateDeleteQWith prepValBuilder (DeleteQuery tableName rqlBE mRetCols) = do
askSelPermInfo tableInfo
let fieldInfoMap = tiFieldInfoMap tableInfo
uniqCols = getUniqCols (getCols fieldInfoMap) $
tiUniqOrPrimConstraints tableInfo
-- convert the returning cols into sql returing exp
mAnnRetCols <- forM mRetCols $ \retCols ->
@@ -81,7 +87,7 @@ validateDeleteQWith prepValBuilder (DeleteQuery tableName rqlBE mRetCols) = do
return $ DeleteQueryP1 tableName
(dpiFilter delPerm, annSQLBoolExp)
(mkDefaultMutFlds mAnnRetCols)
(mkDefaultMutFlds mAnnRetCols) uniqCols
where
selNecessaryMsg =
@@ -97,10 +103,10 @@ validateDeleteQ =
deleteQueryToTx :: Bool -> (DeleteQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
deleteQueryToTx strfyNum (u, p) =
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder deleteSQL) (toList p) True
runMutation $ Mutation (dqp1Table u) (deleteCTE, p)
(dqp1MutFlds u) (dqp1UniqCols u) strfyNum
where
deleteSQL = toSQL $ mkSQLDelete strfyNum u
deleteCTE = mkDeleteCTE u
runDelete
:: (QErrM m, UserInfoM m, CacheRM m, MonadTx m, HasSQLGenCtx m)

View File

@@ -11,6 +11,7 @@ import qualified Data.Text.Lazy as LT
import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Mutation
import Hasura.RQL.DML.Returning
import Hasura.RQL.GBoolExp
import Hasura.RQL.Instances ()
@@ -38,11 +39,12 @@ data InsertQueryP1
, iqp1Tuples :: ![[S.SQLExp]]
, iqp1Conflict :: !(Maybe ConflictClauseP1)
, iqp1MutFlds :: !MutFlds
, iqp1UniqCols :: !(Maybe [PGColInfo])
} deriving (Show, Eq)
mkSQLInsert :: Bool -> InsertQueryP1 -> S.SelectWith
mkSQLInsert strfyNum (InsertQueryP1 tn vn cols vals c mutFlds) =
mkSelWith tn (S.CTEInsert insert) mutFlds False strfyNum
mkInsertCTE :: InsertQueryP1 -> S.CTE
mkInsertCTE (InsertQueryP1 _ vn cols vals c _ _) =
S.CTEInsert insert
where
insert =
S.SQLInsert vn cols vals (toSQLConflict <$> c) $ Just S.returningStar
@@ -66,7 +68,7 @@ mkDefValMap cim =
getInsertDeps
:: InsertQueryP1 -> [SchemaDependency]
getInsertDeps (InsertQueryP1 tn _ _ _ _ mutFlds) =
getInsertDeps (InsertQueryP1 tn _ _ _ _ mutFlds _) =
mkParentDep tn : retDeps
where
retDeps = map (mkColDep "untyped" tn . fst) $
@@ -145,7 +147,8 @@ buildConflictClause tableInfo inpCols (OnConflict mTCol mTCons act) =
\pgCol -> askPGType fieldInfoMap pgCol ""
validateConstraint c = do
let tableConsNames = tiUniqOrPrimConstraints tableInfo
let tableConsNames = map tcName $
tiUniqOrPrimConstraints tableInfo
withPathK "constraint" $
unless (c `elem` tableConsNames) $
throw400 Unexpected $ "constraint " <> getConstraintTxt c
@@ -186,6 +189,8 @@ convInsertQuery objsParser prepFn (InsertQuery tableName val oC mRetCols) = do
let fieldInfoMap = tiFieldInfoMap tableInfo
setInsVals = ipiSet insPerm
uniqCols = getUniqCols (getCols fieldInfoMap) $
tiUniqOrPrimConstraints tableInfo
-- convert the returning cols into sql returing exp
mAnnRetCols <- forM mRetCols $ \retCols -> do
@@ -214,7 +219,7 @@ convInsertQuery objsParser prepFn (InsertQuery tableName val oC mRetCols) = do
buildConflictClause tableInfo inpCols c
return $ InsertQueryP1 tableName insView insCols sqlExps
conflictClause mutFlds
conflictClause mutFlds uniqCols
where
selNecessaryMsg =
@@ -237,10 +242,10 @@ convInsQ =
insertP2 :: Bool -> (InsertQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
insertP2 strfyNum (u, p) =
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder insertSQL) (toList p) True
runMutation $ Mutation (iqp1Table u) (insertCTE, p)
(iqp1MutFlds u) (iqp1UniqCols u) strfyNum
where
insertSQL = toSQL $ mkSQLInsert strfyNum u
insertCTE = mkInsertCTE u
data ConflictCtx
= CCUpdate !ConstraintName ![PGCol] !PreSetCols !S.BoolExp

View File

@@ -0,0 +1,108 @@
module Hasura.RQL.DML.Mutation
( Mutation(..)
, runMutation
, mutateAndFetchCols
)
where
import qualified Data.Sequence as DS
import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Returning
import Hasura.RQL.DML.Select
import Hasura.RQL.Instances ()
import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.SQL.Value
import qualified Data.HashMap.Strict as Map
import qualified Database.PG.Query as Q
import qualified Hasura.SQL.DML as S
data Mutation
= Mutation
{ _mTable :: !QualifiedTable
, _mQuery :: !(S.CTE, DS.Seq Q.PrepArg)
, _mFields :: !MutFlds
, _mUniqCols :: !(Maybe [PGColInfo])
, _mStrfyNum :: !Bool
} deriving (Show, Eq)
runMutation :: Mutation -> Q.TxE QErr RespBody
runMutation mut =
bool (mutateAndReturn mut) (mutateAndSel mut) $
hasNestedFld $ _mFields mut
mutateAndReturn :: Mutation -> Q.TxE QErr RespBody
mutateAndReturn (Mutation qt (cte, p) mutFlds _ strfyNum) =
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder $ toSQL selWith)
(toList p) True
where
selWith = mkSelWith qt cte mutFlds False strfyNum
mutateAndSel :: Mutation -> Q.TxE QErr RespBody
mutateAndSel (Mutation qt q mutFlds mUniqCols strfyNum) = do
uniqCols <- onNothing mUniqCols $
throw500 "uniqCols not found in mutateAndSel"
let colMap = Map.fromList $ flip map uniqCols $
\ci -> (pgiName ci, ci)
-- Perform mutation and fetch unique columns
MutateResp _ colVals <- mutateAndFetchCols qt uniqCols q strfyNum
colExps <- mapM (colValToColExp colMap) colVals
let selWhere = S.mkBoolExpWithColVal mkQIdenExp colExps
selCTE = S.CTESelect $
S.mkSelect
{ S.selExtr = [S.selectStar]
, S.selFrom = Just $ S.FromExp [S.FISimple qt Nothing]
, S.selWhere = Just $ S.WhereFrag selWhere
}
selWith = mkSelWith qt selCTE mutFlds False strfyNum
-- Perform select query and fetch returning fields
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder $ toSQL selWith) [] True
where
colValToColExp colMap colVal =
fmap Map.fromList $ forM (Map.toList colVal) $
\(pgCol, val) -> do
colInfo <- onNothing (Map.lookup pgCol colMap) $
throw500 "colInfo not found; colValToColExp"
sqlExp <- runAesonParser (convToTxt (pgiType colInfo)) val
return (pgCol, sqlExp)
mkQIdenExp col =
S.SEQIden $ S.QIden (S.mkQual qt) $ Iden $ getPGColTxt col
mutateAndFetchCols
:: QualifiedTable
-> [PGColInfo]
-> (S.CTE, DS.Seq Q.PrepArg)
-> Bool
-> Q.TxE QErr MutateResp
mutateAndFetchCols qt cols (cte, p) strfyNum =
Q.getAltJ . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder sql) (toList p) True
where
aliasIden = Iden $ qualObjectToText qt <> "__mutation_result"
tabFrom = TableFrom qt $ Just aliasIden
tabPerm = TablePerm annBoolExpTrue Nothing
selFlds = flip map cols $
\ci -> (fromPGCol $ pgiName ci, FCol ci)
sql = toSQL selectWith
selectWith = S.SelectWith [(S.Alias aliasIden, cte)] select
select = S.mkSelect {S.selExtr = [S.Extractor extrExp Nothing]}
extrExp = S.applyJsonBuildObj
[ S.SELit "affected_rows", affRowsSel
, S.SELit "returning_columns", colSel
]
affRowsSel = S.SESelect $
S.mkSelect
{ S.selExtr = [S.Extractor S.countStar Nothing]
, S.selFrom = Just $ S.FromExp [S.FIIden aliasIden]
}
colSel = S.SESelect $ mkSQLSelect False $
AnnSelG selFlds tabFrom tabPerm noTableArgs strfyNum

View File

@@ -19,6 +19,17 @@ data MutFld
type MutFlds = [(T.Text, MutFld)]
hasNestedFld :: MutFlds -> Bool
hasNestedFld = any isNestedMutFld
where
isNestedMutFld (_, mutFld) = case mutFld of
MRet annFlds -> any isNestedAnnFld annFlds
_ -> False
isNestedAnnFld (_, annFld) = case annFld of
FObj _ -> True
FArr _ -> True
_ -> False
pgColsFromMutFld :: MutFld -> [(PGCol, PGColType)]
pgColsFromMutFld = \case
MCount -> []
@@ -31,14 +42,17 @@ pgColsFromMutFld = \case
pgColsFromMutFlds :: MutFlds -> [(PGCol, PGColType)]
pgColsFromMutFlds = concatMap (pgColsFromMutFld . snd)
pgColsToSelFlds :: [PGColInfo] -> [(FieldName, AnnFld)]
pgColsToSelFlds cols =
flip map cols $
\pgColInfo -> (fromPGCol $ pgiName pgColInfo, FCol pgColInfo)
mkDefaultMutFlds :: Maybe [PGColInfo] -> MutFlds
mkDefaultMutFlds = \case
Nothing -> mutFlds
Just cols -> ("returning", MRet $ pgColsToSelFlds cols):mutFlds
where
mutFlds = [("affected_rows", MCount)]
pgColsToSelFlds cols = flip map cols $ \pgColInfo ->
(fromPGCol $ pgiName pgColInfo, FCol pgColInfo)
qualTableToAliasIden :: QualifiedTable -> Iden
qualTableToAliasIden qt =

View File

@@ -15,6 +15,7 @@ import qualified Data.Sequence as DS
import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Mutation
import Hasura.RQL.DML.Returning
import Hasura.RQL.GBoolExp
import Hasura.RQL.Instances ()
@@ -26,16 +27,17 @@ import qualified Hasura.SQL.DML as S
data UpdateQueryP1
= UpdateQueryP1
{ uqp1Table :: !QualifiedTable
, uqp1SetExps :: ![(PGCol, S.SQLExp)]
, uqp1Where :: !(AnnBoolExpSQL, AnnBoolExpSQL)
, pqp1MutFlds :: !MutFlds
{ uqp1Table :: !QualifiedTable
, uqp1SetExps :: ![(PGCol, S.SQLExp)]
, uqp1Where :: !(AnnBoolExpSQL, AnnBoolExpSQL)
, uqp1MutFlds :: !MutFlds
, uqp1UniqCols :: !(Maybe [PGColInfo])
} deriving (Show, Eq)
mkSQLUpdate
:: Bool -> UpdateQueryP1 -> S.SelectWith
mkSQLUpdate strfyNum (UpdateQueryP1 tn setExps (permFltr, wc) mutFlds) =
mkSelWith tn (S.CTEUpdate update) mutFlds False strfyNum
mkUpdateCTE
:: UpdateQueryP1 -> S.CTE
mkUpdateCTE (UpdateQueryP1 tn setExps (permFltr, wc) _ _) =
S.CTEUpdate update
where
update = S.SQLUpdate tn setExp Nothing tableFltr $ Just S.returningStar
setExp = S.SetExp $ map S.SetExpItem setExps
@@ -45,10 +47,12 @@ mkSQLUpdate strfyNum (UpdateQueryP1 tn setExps (permFltr, wc) mutFlds) =
getUpdateDeps
:: UpdateQueryP1
-> [SchemaDependency]
getUpdateDeps (UpdateQueryP1 tn setExps (_, wc) mutFlds) =
mkParentDep tn : colDeps <> whereDeps <> retDeps
getUpdateDeps (UpdateQueryP1 tn setExps (_, wc) mutFlds uniqCols) =
mkParentDep tn : colDeps <> uniqColDeps <> whereDeps <> retDeps
where
colDeps = map (mkColDep "on_type" tn . fst) setExps
uniqColDeps = map (mkColDep "on_type" tn) $
maybe [] (map pgiName) uniqCols
whereDeps = getBoolExpDeps tn wc
retDeps = map (mkColDep "untyped" tn . fst) $
pgColsFromMutFlds mutFlds
@@ -140,6 +144,8 @@ validateUpdateQueryWith f uq = do
let fieldInfoMap = tiFieldInfoMap tableInfo
preSetObj = upiSet updPerm
preSetCols = M.keys preSetObj
uniqCols = getUniqCols (getCols fieldInfoMap) $
tiUniqOrPrimConstraints tableInfo
-- convert the object to SQL set expression
setItems <- withPathK "$set" $
@@ -173,6 +179,7 @@ validateUpdateQueryWith f uq = do
setExpItems
(upiFilter updPerm, annSQLBoolExp)
(mkDefaultMutFlds mAnnRetCols)
uniqCols
where
mRetCols = uqReturning uq
selNecessaryMsg =
@@ -186,12 +193,13 @@ validateUpdateQuery
validateUpdateQuery =
liftDMLP1 . validateUpdateQueryWith binRHSBuilder
updateQueryToTx :: Bool -> (UpdateQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
updateQueryToTx
:: Bool -> (UpdateQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
updateQueryToTx strfyNum (u, p) =
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder updateSQL) (toList p) True
runMutation $ Mutation (uqp1Table u) (updateCTE, p)
(uqp1MutFlds u) (uqp1UniqCols u) strfyNum
where
updateSQL = toSQL $ mkSQLUpdate strfyNum u
updateCTE = mkUpdateCTE u
runUpdate
:: (QErrM m, UserInfoM m, CacheRWM m, MonadTx m, HasSQLGenCtx m)

View File

@@ -16,6 +16,7 @@ module Hasura.RQL.Types.Common
, WithTable(..)
, ColVals
, PreSetCols
, MutateResp(..)
) where
import Hasura.Prelude
@@ -140,3 +141,10 @@ instance (ToAesonPairs a) => ToJSON (WithTable a) where
type ColVals = HM.HashMap PGCol Value
type PreSetCols = HM.HashMap PGCol S.SQLExp
data MutateResp
= MutateResp
{ _mrAffectedRows :: !Int
, _mrReturningColumns :: ![ColVals]
} deriving (Show, Eq)
$(deriveJSON (aesonDrop 3 snakeCase) ''MutateResp)

View File

@@ -7,6 +7,7 @@ module Hasura.RQL.Types.SchemaCache
, emptySchemaCache
, TableInfo(..)
, TableConstraint(..)
, getUniqCols
, ConstraintType(..)
, ViewInfo(..)
, isMutable
@@ -152,8 +153,8 @@ onlyComparableCols :: [PGColInfo] -> [PGColInfo]
onlyComparableCols = filter (isComparableType . pgiType)
getColInfos :: [PGCol] -> [PGColInfo] -> [PGColInfo]
getColInfos cols allColInfos = flip filter allColInfos $ \ci ->
pgiName ci `elem` cols
getColInfos cols allColInfos =
flip filter allColInfos $ \ci -> pgiName ci `elem` cols
type WithDeps a = (a, [SchemaDependency])
@@ -309,10 +310,32 @@ data TableConstraint
= TableConstraint
{ tcType :: !ConstraintType
, tcName :: !ConstraintName
, tcCols :: ![PGCol]
} deriving (Show, Eq)
$(deriveJSON (aesonDrop 2 snakeCase) ''TableConstraint)
getUniqCols :: [PGColInfo] -> [TableConstraint] -> Maybe [PGColInfo]
getUniqCols allCols = travConstraints
where
colsNotNull = all (not . pgiIsNullable)
travConstraints [] = Nothing
travConstraints (h:t) =
let cols = getColInfos (tcCols h) allCols
in case tcType h of
CTPRIMARYKEY -> Just cols
CTUNIQUE -> if colsNotNull cols then Just cols
else travConstraints t
_ -> travConstraints t
getAllPkeyCols :: [TableConstraint] -> [PGCol]
getAllPkeyCols constraints =
flip concatMap constraints $
\c -> case tcType c of
CTPRIMARYKEY -> tcCols c
_ -> []
data ViewInfo
= ViewInfo
{ viIsUpdatable :: !Bool
@@ -339,7 +362,7 @@ data TableInfo
, tiSystemDefined :: !Bool
, tiFieldInfoMap :: !FieldInfoMap
, tiRolePermInfoMap :: !RolePermInfoMap
, tiUniqOrPrimConstraints :: ![ConstraintName]
, tiUniqOrPrimConstraints :: ![TableConstraint]
, tiPrimaryKeyCols :: ![PGCol]
, tiViewInfo :: !(Maybe ViewInfo)
, tiEventTriggerInfoMap :: !EventTriggerInfoMap
@@ -350,14 +373,14 @@ $(deriveToJSON (aesonDrop 2 snakeCase) ''TableInfo)
mkTableInfo
:: QualifiedTable
-> Bool
-> [ConstraintName]
-> [TableConstraint]
-> [PGColInfo]
-> [PGCol]
-> Maybe ViewInfo -> TableInfo
mkTableInfo tn isSystemDefined uniqCons cols pcols mVI =
mkTableInfo tn isSystemDefined uniqCons cols mVI =
TableInfo tn isSystemDefined colMap (M.fromList [])
uniqCons pcols mVI (M.fromList [])
uniqCons pCols mVI (M.fromList [])
where
pCols = getAllPkeyCols uniqCons
colMap = M.fromList $ map f cols
f colInfo = (fromPGCol $ pgiName colInfo, FIColumn colInfo)

View File

@@ -267,6 +267,7 @@ data SQLExp
| SEBool !BoolExp
| SEExcluded !T.Text
| SEArray ![SQLExp]
| SETuples ![SQLExp]
| SECount !CountType
deriving (Show, Eq)
@@ -323,6 +324,7 @@ instance ToSQL SQLExp where
<> toSQL (PGCol t)
toSQL (SEArray exps) = "ARRAY" <> TB.char '['
<> (", " <+> exps) <> TB.char ']'
toSQL (SETuples exps) = paren $ ", " <+> exps
toSQL (SECount ty) = "COUNT" <> paren (toSQL ty)
intToSQLExp :: Int -> SQLExp
@@ -497,6 +499,19 @@ mkExists fromItem whereFrag =
, selWhere = Just $ WhereFrag whereFrag
}
mkBoolExpWithColVal
:: (PGCol -> SQLExp)
-> [HM.HashMap PGCol SQLExp]
-> BoolExp
mkBoolExpWithColVal f colValMaps =
case colValMaps of
[] -> BELit False
l@(h:_) ->
let cols = map f $ HM.keys h
colTup = SETuples cols
valTups = map (SETuples . HM.elems) l
in BEIN colTup valTups
instance ToSQL BoolExp where
toSQL (BELit True) = TB.text $ T.squote "true"
toSQL (BELit False) = TB.text $ T.squote "false"

View File

@@ -171,6 +171,8 @@ uSqlExp = restoringIdens . \case
S.SEExcluded <$> return t
S.SEArray l ->
S.SEArray <$> mapM uSqlExp l
S.SETuples l ->
S.SEArray <$> mapM uSqlExp l
S.SECount cty -> return $ S.SECount cty
where
uQual = \case

View File

@@ -176,7 +176,7 @@ convToTxt :: PGColType
-> Value
-> AT.Parser S.SQLExp
convToTxt ty val =
txtEncoder <$> parsePGValue ty val
toTxtValue ty <$> parsePGValue ty val
readEitherTxt :: (Read a) => T.Text -> Either String a
readEitherTxt = readEither . T.unpack

View File

@@ -1,6 +1,5 @@
select
coalesce(columns.columns, '[]') as columns,
coalesce(pk.columns, '[]') as primary_key_columns,
coalesce(constraints.constraints, '[]') as constraints,
coalesce(views.view_info, 'null') as view_info
from
@@ -28,25 +27,32 @@ from
tables.table_schema = columns.table_schema
AND tables.table_name = columns.table_name
)
left outer join (
select * from hdb_catalog.hdb_primary_key
) pk on (
tables.table_schema = pk.table_schema
AND tables.table_name = pk.table_name
)
left outer join (
select
c.table_schema,
c.table_name,
json_agg(constraint_name) as constraints
cm.table_schema,
cm.table_name,
json_agg(
json_build_object(
'type', cm.constraint_type,
'name', cm.constraint_name,
'cols', cm.columns
)
) as constraints
from
information_schema.table_constraints c
where
c.constraint_type = 'UNIQUE'
or c.constraint_type = 'PRIMARY KEY'
(
select table_name, table_schema,
constraint_name, columns,
'PRIMARY KEY' as constraint_type
from hdb_catalog.hdb_primary_key
union all
select table_name, table_schema,
constraint_name, columns,
'UNIQUE' as constraint_type
from hdb_catalog.hdb_unique_constraint
) cm
group by
c.table_schema,
c.table_name
cm.table_schema,
cm.table_name
) constraints on (
tables.table_schema = constraints.table_schema
AND tables.table_name = constraints.table_name

View File

@@ -0,0 +1,57 @@
description: Insert article along with author and returning author with articles as relationship
url: /v1alpha1/graphql
status: 200
response:
data:
insert_article:
affected_rows: 2
returning:
- content: Content for Article 5
author:
name: Author 5
articles:
- content: Content for Article 5
id: 5
title: Article by Author 5
id: 5
articles_aggregate:
aggregate:
count: 1
id: 5
title: Article by Author 5
query:
query: |
mutation {
insert_article(
objects: [{
id: 5
title: "Article by Author 5"
content: "Content for Article 5"
author: {
data: {
id: 5
name: "Author 5"
}
}
}]
){
affected_rows
returning{
id
title
content
author{
id
name
articles_aggregate{
aggregate{count}
}
articles{
id
title
content
}
}
}
}
}

View File

@@ -229,6 +229,9 @@ class TestGraphqlNestedInserts(DefaultTestQueries):
def test_articles_author_upsert_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/articles_author_upsert_fail.yaml")
def test_articles_with_author_returning(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/articles_with_author_returning.yaml")
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/nested"