mirror of
https://github.com/zhigang1992/graphql-engine.git
synced 2026-05-30 04:07:45 +08:00
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:
committed by
Vamshi Surabhi
parent
efc97c0b5c
commit
5f274b5527
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
108
server/src-lib/Hasura/RQL/DML/Mutation.hs
Normal file
108
server/src-lib/Hasura/RQL/DML/Mutation.hs
Normal 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
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user