From 92a994442ecafa9d6b9fa1b65cfc892dc8496f2b Mon Sep 17 00:00:00 2001 From: rakeshkky Date: Fri, 24 Aug 2018 21:47:56 +0530 Subject: [PATCH 1/2] select fields by primary key col values as argument values, fix #304 --- server/src-lib/Hasura/GraphQL/Resolve.hs | 3 + .../src-lib/Hasura/GraphQL/Resolve/BoolExp.hs | 30 ++++++- .../src-lib/Hasura/GraphQL/Resolve/Select.hs | 19 ++++- server/src-lib/Hasura/GraphQL/Schema.hs | 81 +++++++++++++++---- server/src-lib/Hasura/RQL/DDL/Schema/Table.hs | 19 ++++- server/src-lib/Hasura/RQL/DML/Select.hs | 53 ++++++------ .../src-lib/Hasura/RQL/Types/SchemaCache.hs | 12 ++- server/src-lib/Hasura/SQL/DML.hs | 79 ++++++++++-------- server/test/Spec.hs | 1 + .../select_query_author_by_pkey.yaml | 16 ++++ 10 files changed, 227 insertions(+), 86 deletions(-) create mode 100644 server/test/testcases/select_query_author_by_pkey.yaml diff --git a/server/src-lib/Hasura/GraphQL/Resolve.hs b/server/src-lib/Hasura/GraphQL/Resolve.hs index 675df8f86fb77..342bbab8d7412 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve.hs @@ -34,6 +34,9 @@ buildTx userInfo gCtx fld = do OCSelect tn permFilter permLimit hdrs -> validateHdrs hdrs >> RS.convertSelect tn permFilter permLimit fld + + OCSelectPkey tn permFilter hdrs -> + validateHdrs hdrs >> RS.convertSelectByPKey tn permFilter fld -- RS.convertSelect tn permFilter fld OCInsert tn vn cols hdrs -> validateHdrs hdrs >> RM.convertInsert roleName (tn, vn) cols fld diff --git a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs index d7f73a0058487..cc17788a8adda 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs @@ -6,6 +6,7 @@ module Hasura.GraphQL.Resolve.BoolExp ( parseBoolExp + , argsMapToBoolExp , convertBoolExp , prepare ) where @@ -22,6 +23,7 @@ import qualified Hasura.SQL.DML as S import Hasura.GraphQL.Resolve.Context import Hasura.GraphQL.Resolve.InputValue +import Hasura.GraphQL.Validate.Field import Hasura.GraphQL.Validate.Types import Hasura.RQL.Types @@ -70,16 +72,24 @@ parseOpExps annVal = do AGScalar _ _ -> throw500 "boolean value is expected" _ -> tyMismatch "pgvalue" v +parseAsPGColVal + :: (MonadError QErr m) + => AnnGValue -> m [RA.OpExp] +parseAsPGColVal annVal = do + annValOpExp <- RA.AEQ <$> asPGColVal annVal + return $ [RA.OEVal annValOpExp] + parseColExp :: (MonadError QErr m, MonadReader r m, Has FieldMap r) => G.NamedType -> G.Name -> AnnGValue + -> (AnnGValue -> m [RA.OpExp]) -> m RA.AnnVal -parseColExp nt n val = do +parseColExp nt n val expParser = do fldInfo <- getFldInfo nt n case fldInfo of - Left pgColInfo -> RA.AVCol pgColInfo <$> parseOpExps val + Left pgColInfo -> RA.AVCol pgColInfo <$> expParser val Right (relInfo, permExp, _, _) -> do relBoolExp <- parseBoolExp val return $ RA.AVRel relInfo relBoolExp permExp @@ -95,7 +105,7 @@ parseBoolExp annGVal = do | k == "_or" -> BoolOr . fromMaybe [] <$> parseMany parseBoolExp v | k == "_and" -> BoolAnd . fromMaybe [] <$> parseMany parseBoolExp v | k == "_not" -> BoolNot <$> parseBoolExp v - | otherwise -> BoolCol <$> parseColExp nt k v + | otherwise -> BoolCol <$> parseColExp nt k v parseOpExps return $ BoolAnd $ fromMaybe [] boolExpsM convertBoolExp @@ -105,3 +115,17 @@ convertBoolExp convertBoolExp tn whereArg = do whereExp <- parseBoolExp whereArg RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) whereExp + +argsMapToBoolExp + :: QualifiedTable + -> ArgsMap + -> Convert (GBoolExp RG.AnnSQLBoolExp) +argsMapToBoolExp tn argsMap = do + colExps <- forM args $ \(name, val) -> do + (ty, _) <- asPGColVal val + let namedTy = mkScalarTy ty + BoolCol <$> parseColExp namedTy name val parseAsPGColVal + let whereExp = BoolAnd colExps + RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) whereExp + where + args = Map.toList argsMap diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Select.hs b/server/src-lib/Hasura/GraphQL/Resolve/Select.hs index 13e4a3fd49d50..fb5d6c0d14141 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/Select.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/Select.hs @@ -7,6 +7,7 @@ module Hasura.GraphQL.Resolve.Select ( convertSelect + , convertSelectByPKey ) where import Data.Has @@ -63,10 +64,18 @@ fromField tn permFilter permLimit fld = fieldAsPath fld $ do offsetExpM <- withArgM args "offset" $ asPGColVal >=> prepare annFlds <- fromSelSet (_fType fld) $ _fSelSet fld return $ RS.SelectData annFlds tn (permFilter, whereExpM) ordByExpM - [] limitExpM offsetExpM + [] limitExpM offsetExpM False where args = _fArguments fld +fromFieldByPKey + :: QualifiedTable -> S.BoolExp -> Field -> Convert RS.SelectData +fromFieldByPKey tn permFilter fld = fieldAsPath fld $ do + boolExp <- argsMapToBoolExp tn $ _fArguments fld + annFlds <- fromSelSet (_fType fld) $ _fSelSet fld + return $ RS.SelectData annFlds tn (permFilter, Just boolExp) + Nothing [] Nothing Nothing True + getEnumInfo :: ( MonadError QErr m , MonadReader r m @@ -123,3 +132,11 @@ convertSelect qt permFilter permLimit fld = do fromField qt permFilter permLimit fld prepArgs <- get return $ RS.selectP2 (selData, prepArgs) + +convertSelectByPKey + :: QualifiedTable -> S.BoolExp -> Field -> Convert RespTx +convertSelectByPKey qt permFilter fld = do + selData <- withPathK "selectionSet" $ + fromFieldByPKey qt permFilter fld + prepArgs <- get + return $ RS.selectP2 (selData, prepArgs) diff --git a/server/src-lib/Hasura/GraphQL/Schema.hs b/server/src-lib/Hasura/GraphQL/Schema.hs index 7450b5ecc303b..ce107a6167db9 100644 --- a/server/src-lib/Hasura/GraphQL/Schema.hs +++ b/server/src-lib/Hasura/GraphQL/Schema.hs @@ -47,6 +47,8 @@ data OpCtx = OCInsert QualifiedTable QualifiedTable [PGCol] [T.Text] -- tn, filter exp, limit, req hdrs | OCSelect QualifiedTable S.BoolExp (Maybe Int) [T.Text] + -- tn, filter exp, reqt hdrs + | OCSelectPkey QualifiedTable S.BoolExp [T.Text] -- tn, filter exp, req hdrs | OCUpdate QualifiedTable S.BoolExp [T.Text] -- tn, filter exp, req hdrs @@ -119,9 +121,12 @@ isRelNullable fim ri = isNullable where lCols = map fst $ riMapping ri allCols = getCols fim - lColInfos = flip filter allCols $ \ci -> pgiName ci `elem` lCols + lColInfos = getColInfos lCols allCols isNullable = any pgiIsNullable lColInfos +mkColName :: PGCol -> G.Name +mkColName (PGCol n) = G.Name n + mkCompExpName :: PGColType -> G.Name mkCompExpName pgColTy = G.Name $ T.pack (show pgColTy) <> "_comparison_exp" @@ -142,6 +147,9 @@ mkTableTy :: QualifiedTable -> G.NamedType mkTableTy = G.NamedType . qualTableToName +mkTableByPKeyTy :: QualifiedTable -> G.NamedType +mkTableByPKeyTy tn = G.NamedType $ qualTableToName tn <> "_by_pkey" + mkCompExpInp :: PGColType -> InpObjTyInfo mkCompExpInp colTy = InpObjTyInfo (Just tyDesc) (mkCompExpTy colTy) $ fromInpValL $ concat @@ -275,6 +283,27 @@ mkSelFld tn = fldName = qualTableToName tn args = fromInpValL $ mkSelArgs tn ty = G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkTableTy tn +{- +table_by_pkey( + col1: value1!, + . . + . . + coln: valuen! +): table! +-} +mkSelFldPKey + :: QualifiedTable -> [PGColInfo] + -> ObjFldInfo +mkSelFldPKey tn cols = + ObjFldInfo (Just desc) fldName args ty + where + desc = G.Description $ "fetch data from the table: " <> tn + <<> " with primary key columns as input fields" + fldName = qualTableToName tn <> "_by_pkey" + args = fromInpValL $ map colInpVal cols + ty = G.toGT $ G.toNT $ mkTableTy tn + colInpVal (PGColInfo n typ _) = + InpValInfo Nothing (mkColName n) $ G.toGT $ G.toNT $ mkScalarTy typ -- table_mutation_response mkMutRespTy :: QualifiedTable -> G.NamedType @@ -813,10 +842,12 @@ mkGCtxRole' -> Maybe [PGColInfo] -- delete cols -> Maybe () + -- primary key columns + -> [PGColInfo] -- constraints -> [TableConstraint] -> TyAgg -mkGCtxRole' tn insColsM selFldsM updColsM delPermM constraints = +mkGCtxRole' tn insColsM selFldsM updColsM delPermM pkeyCols constraints = TyAgg (mkTyInfoMap allTypes) fieldMap ordByEnums where @@ -838,7 +869,7 @@ mkGCtxRole' tn insColsM selFldsM updColsM delPermM constraints = fieldMap = Map.unions $ catMaybes [ insInpObjFldsM, updSetInpObjFldsM, boolExpInpObjFldsM - , noRelsObjFldsM, selObjFldsM + , noRelsObjFldsM, selObjFldsM, Just selByPKeyObjFlds ] nameFromSelFld = \case @@ -895,6 +926,9 @@ mkGCtxRole' tn insColsM selFldsM updColsM delPermM constraints = selObjM = mkTableObj tn <$> selFldsM -- the fields used in table object selObjFldsM = mkFldMap (mkTableTy tn) <$> selFldsM + -- the field used in table_by_pkey object + selByPKeyObjFlds = Map.fromList $ flip map pkeyCols $ + \pgi@(PGColInfo col ty _) -> ((mkScalarTy ty, mkColName col), Left pgi) ordByEnumsCtxM = mkOrdByCtx tn . lefts <$> selFldsM @@ -904,6 +938,7 @@ mkGCtxRole' tn insColsM selFldsM updColsM delPermM constraints = getRootFldsRole' :: QualifiedTable + -> [PGCol] -> [TableConstraint] -> FieldInfoMap -> Maybe (QualifiedTable, [T.Text]) -- insert view @@ -911,26 +946,31 @@ getRootFldsRole' -> Maybe ([PGCol], S.BoolExp, [T.Text]) -- update filter -> Maybe (S.BoolExp, [T.Text]) -- delete filter -> RootFlds -getRootFldsRole' tn constraints fields insM selM updM delM = +getRootFldsRole' tn primCols constraints fields insM selM updM delM = RootFlds mFlds where - getUpdColInfos cols = flip filter (getCols fields) $ \c -> - pgiName c `elem` cols mFlds = mapFromL (either _fiName _fiName . snd) $ catMaybes [ getInsDet <$> insM, getSelDet <$> selM - , getUpdDet <$> updM, getDelDet <$> delM] + , getUpdDet <$> updM, getDelDet <$> delM + , getPKeySelDet selM $ getColInfos primCols colInfos + ] colInfos = fst $ validPartitionFieldInfoMap fields getInsDet (vn, hdrs) = (OCInsert tn vn (map pgiName colInfos) hdrs, Right $ mkInsMutFld tn constraints) getUpdDet (updCols, updFltr, hdrs) = ( OCUpdate tn updFltr hdrs - , Right $ mkUpdMutFld tn $ getUpdColInfos updCols + , Right $ mkUpdMutFld tn $ getColInfos updCols colInfos ) getDelDet (delFltr, hdrs) = (OCDelete tn delFltr hdrs, Right $ mkDelMutFld tn) getSelDet (selFltr, pLimit, hdrs) = (OCSelect tn selFltr pLimit hdrs, Left $ mkSelFld tn) + getPKeySelDet Nothing _ = Nothing + getPKeySelDet _ [] = Nothing + getPKeySelDet (Just (selFltr, _, hdrs)) pCols = Just + (OCSelectPkey tn selFltr hdrs, Left $ mkSelFldPKey tn pCols) + -- getRootFlds -- :: TableCache -- -> Map.HashMap RoleName RootFlds @@ -975,31 +1015,34 @@ mkGCtxRole => TableCache -> QualifiedTable -> FieldInfoMap + -> [PGCol] -> [TableConstraint] -> RoleName -> RolePermInfo -> m (TyAgg, RootFlds) -mkGCtxRole tableCache tn fields constraints role permInfo = do +mkGCtxRole tableCache tn fields pCols constraints role permInfo = do selFldsM <- mapM (getSelFlds tableCache fields role) $ _permSel permInfo let insColsM = const colInfos <$> _permIns permInfo updColsM = filterColInfos . upiCols <$> _permUpd permInfo tyAgg = mkGCtxRole' tn insColsM selFldsM updColsM - (void $ _permDel permInfo) constraints - rootFlds = getRootFldsRole tn constraints fields permInfo + (void $ _permDel permInfo) pColInfos constraints + rootFlds = getRootFldsRole tn pCols constraints fields permInfo return (tyAgg, rootFlds) where colInfos = fst $ validPartitionFieldInfoMap fields + pColInfos = getColInfos pCols colInfos filterColInfos allowedSet = filter ((`Set.member` allowedSet) . pgiName) colInfos getRootFldsRole :: QualifiedTable + -> [PGCol] -> [TableConstraint] -> FieldInfoMap -> RolePermInfo -> RootFlds -getRootFldsRole tn constraints fields (RolePermInfo insM selM updM delM) = - getRootFldsRole' tn constraints fields +getRootFldsRole tn pCols constraints fields (RolePermInfo insM selM updM delM) = + getRootFldsRole' tn pCols constraints fields (mkIns <$> insM) (mkSel <$> selM) (mkUpd <$> updM) (mkDel <$> delM) where @@ -1016,21 +1059,25 @@ mkGCtxMapTable => TableCache -> TableInfo -> m (Map.HashMap RoleName (TyAgg, RootFlds)) -mkGCtxMapTable tableCache (TableInfo tn _ fields rolePerms constraints) = do - m <- Map.traverseWithKey (mkGCtxRole tableCache tn fields validConstraints) rolePerms +mkGCtxMapTable tableCache (TableInfo tn _ fields rolePerms constraints pkeyCols) = do + m <- Map.traverseWithKey + (mkGCtxRole tableCache tn fields pkeyCols validConstraints) rolePerms let adminCtx = mkGCtxRole' tn (Just colInfos) - (Just selFlds) (Just colInfos) (Just ()) validConstraints + (Just selFlds) (Just colInfos) (Just ()) + pkeyColInfos validConstraints return $ Map.insert adminRole (adminCtx, adminRootFlds) m where validConstraints = mkValidConstraints constraints colInfos = fst $ validPartitionFieldInfoMap fields allCols = map pgiName colInfos + pkeyColInfos = getColInfos pkeyCols colInfos selFlds = flip map (toValidFieldInfos fields) $ \case FIColumn pgColInfo -> Left pgColInfo FIRelationship relInfo -> Right (relInfo, noFilter, Nothing, isRelNullable fields relInfo) noFilter = S.BELit True adminRootFlds = - getRootFldsRole' tn constraints fields (Just (tn, [])) (Just (noFilter, Nothing, [])) + getRootFldsRole' tn pkeyCols constraints fields + (Just (tn, [])) (Just (noFilter, Nothing, [])) (Just (allCols, noFilter, [])) (Just (noFilter, [])) mkScalarTyInfo :: PGColType -> ScalarTyInfo diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs index 6c4bf0154e55f..2d68f979e789b 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs @@ -68,6 +68,15 @@ getTableInfo qt@(QualifiedTable sn tn) isSystemDefined = do AND table_name = $2 |] (sn, tn) False + -- Fetch primary key columns + rawPrimaryCols <- Q.listQE defaultTxErrorHandler [Q.sql| + SELECT columns + FROM hdb_catalog.hdb_primary_key + WHERE table_schema = $1 + AND table_name = $2 + |] (sn, tn) False + pkeyCols <- mkPKeyCols rawPrimaryCols + -- Fetch the constraint details rawConstraints <- Q.catchE defaultTxErrorHandler $ Q.listQ [Q.sql| SELECT constraint_type, constraint_name @@ -75,9 +84,13 @@ getTableInfo qt@(QualifiedTable sn tn) isSystemDefined = do WHERE table_schema = $1 AND table_name = $2 |] (sn, tn) False - return $ mkTableInfo qt isSystemDefined rawConstraints $ - flip map colData $ \(colName, Q.AltJ colTy, isNull) - -> (colName, colTy, isNull) + let colDetails = flip map colData $ \(colName, Q.AltJ colTy, isNull) + -> (colName, colTy, isNull) + return $ mkTableInfo qt isSystemDefined rawConstraints colDetails pkeyCols + where + mkPKeyCols [] = return [] + mkPKeyCols [Identity (Q.AltJ pkeyCols)] = return pkeyCols + mkPKeyCols _ = throw500 "found multiple rows for a table in hdb_primary_key" newtype TrackTable = TrackTable diff --git a/server/src-lib/Hasura/RQL/DML/Select.hs b/server/src-lib/Hasura/RQL/DML/Select.hs index 752d592b4e3eb..6079faa88a8a0 100644 --- a/server/src-lib/Hasura/RQL/DML/Select.hs +++ b/server/src-lib/Hasura/RQL/DML/Select.hs @@ -67,13 +67,14 @@ data AnnRel = AnnRel data SelectData = SelectData -- Nested annotated columns - { sdFlds :: !(HM.HashMap FieldName AnnFld) - , sdTable :: !QualifiedTable -- Postgres table name - , sdWhere :: !(S.BoolExp, Maybe (GBoolExp AnnSQLBoolExp)) - , sdOrderBy :: !(Maybe S.OrderByExp) - , sdAddCols :: ![PGCol] -- additional order by columns - , sdLimit :: !(Maybe S.SQLExp) - , sdOffset :: !(Maybe S.SQLExp) + { sdFlds :: !(HM.HashMap FieldName AnnFld) + , sdTable :: !QualifiedTable -- Postgres table name + , sdWhere :: !(S.BoolExp, Maybe (GBoolExp AnnSQLBoolExp)) + , sdOrderBy :: !(Maybe S.OrderByExp) + , sdAddCols :: ![PGCol] -- additional order by columns + , sdLimit :: !(Maybe S.SQLExp) + , sdOffset :: !(Maybe S.SQLExp) + , asSingleObject :: !Bool } deriving (Show, Eq) convSelCol :: (P1C m) @@ -177,18 +178,18 @@ processOrderByElem annFlds [colTxt] = case HM.lookup (FieldName colTxt) annFlds of Just (FCol (_, ty)) -> if ty == PGGeography || ty == PGGeometry then throw400 UnexpectedPayload $ mconcat - [ (PGCol colTxt) <<> " has type 'geometry'" + [ PGCol colTxt <<> " has type 'geometry'" , " and cannot be used in order_by" ] else return annFlds Just (FRel _) -> throw400 UnexpectedPayload $ mconcat - [ (PGCol colTxt) <<> " is a" + [ PGCol colTxt <<> " is a" , " relationship and should be expanded" ] Just (FExp t) -> throw500 $ " found __typename in order_by?: " <> t Nothing -> throw400 UnexpectedPayload $ mconcat - [ (PGCol colTxt) <<> " should be" + [ PGCol colTxt <<> " should be" , " included in 'columns'" ] processOrderByElem annFlds (colTxt:xs) = @@ -199,24 +200,24 @@ processOrderByElem annFlds (colTxt:xs) = relFlds = sdFlds relSelData newRelFlds <- processOrderByElem relFlds xs let newRelSelData = relSelData - { sdAddCols = (PGCol $ T.intercalate "__" xs):(sdAddCols relSelData) + { sdAddCols = (PGCol $ T.intercalate "__" xs):sdAddCols relSelData , sdFlds = newRelFlds } newAnnRel = annRel { arSelData = newRelSelData } return $ HM.insert (FieldName colTxt) (FRel newAnnRel) annFlds ArrRel -> throw400 UnexpectedPayload $ mconcat - [ (RelName colTxt) <<> " is an array relationship" + [ RelName colTxt <<> " is an array relationship" ," and can't be used in 'order_by'" ] Just (FCol _) -> throw400 UnexpectedPayload $ mconcat - [ (PGCol colTxt) <<> " is a Postgres column" + [ PGCol colTxt <<> " is a Postgres column" , " and cannot be chained further" ] Just (FExp t) -> throw500 $ " found __typename in order_by?: " <> t Nothing -> throw400 UnexpectedPayload $ mconcat - [ (PGCol colTxt) <<> " should be" + [ PGCol colTxt <<> " should be" , " included in 'columns'" ] @@ -312,7 +313,7 @@ convSelectQ fieldInfoMap selPermInfo selQ prepValBuilder = do offsetExp = S.intToSQLExp <$> mQueryOffset return $ SelectData newAnnFlds (spiTable selPermInfo) - (spiFilter selPermInfo, wClause) sqlOrderBy [] limitExp offsetExp + (spiFilter selPermInfo, wClause) sqlOrderBy [] limitExp offsetExp False where mQueryOffset = sqOffset selQ @@ -453,7 +454,7 @@ mkObjRelExtr compCol relName flds = -- | Generates -- -- IF (first(r.__r_col) IS NULL) THEN '[]' ELSE json_agg(..) -mkArrRelExtr :: (Maybe S.OrderByExp) -> PGCol -> RelName -> [S.Extractor] -> S.Extractor +mkArrRelExtr :: Maybe S.OrderByExp -> PGCol -> RelName -> [S.Extractor] -> S.Extractor mkArrRelExtr mOb compCol relName flds = let refCol = S.SEFnApp "hdb_catalog.first" [ S.mkQIdenExp (TableName "r") compCol ] Nothing @@ -488,7 +489,7 @@ selDataToSQL :: [S.Extractor] -- ^ Parent's RCol -> S.BoolExp -- ^ Join Condition if any -> SelectData -- ^ Select data -> S.Select -- ^ SQL Select (needs wrapping) -selDataToSQL parRCols joinCond (SelectData annFlds tn (fltr, mWc) ob _ lt offst) = +selDataToSQL parRCols joinCond (SelectData annFlds tn (fltr, mWc) ob _ lt offst _) = let (sCols, relCols) = partAnnFlds $ HM.elems annFlds -- relCols = HM.elems relColsMap @@ -508,7 +509,7 @@ selDataToSQL parRCols joinCond (SelectData annFlds tn (fltr, mWc) ob _ lt offst) , S.selFrom = Just $ S.mkSimpleFromExp tn , S.selWhere = Just $ injectJoinCond joinCond finalWC } - joinedSel = foldr ($) baseSel $ map annRelColToSQL relCols + joinedSel = foldr annRelColToSQL baseSel relCols in joinedSel { S.selOrderBy = ob , S.selLimit = S.LimitExp <$> lt @@ -589,8 +590,8 @@ annRelColToSQL ar leftSel = -- Group by exp to aggregate relationship as json_array - grpByExp = S.GroupByExp $ map (S.mkQIdenExp (TableName "l") . mkLJCol relName) $ - map fst $ arMapping ar + grpByExp = S.GroupByExp $ map ((S.mkQIdenExp (TableName "l") . mkLJCol relName) . fst) + (arMapping ar) in S.mkSelect { S.selExtr = relExtr:qLSelCols , S.selFrom = Just fromExp @@ -600,7 +601,7 @@ annRelColToSQL ar leftSel = qualifyOrderByItem (S.OrderByItem e t n) = let qe = case e of Left c -> Right $ S.mkQIden (TableName "r") c - Right c -> Right $ c + Right c -> Right c in S.OrderByItem qe t n qualifyOrderBy (S.OrderByExp items) = S.OrderByExp $ map qualifyOrderByItem items @@ -638,7 +639,7 @@ wrapFinalSel initSel extCols = getSelectDeps :: SelectData -> [SchemaDependency] -getSelectDeps (SelectData flds tn (_, annWc) _ _ _ _) = +getSelectDeps (SelectData flds tn (_, annWc) _ _ _ _ _) = mkParentDep tn : fromMaybe [] whereDeps <> colDeps @@ -665,8 +666,12 @@ getSelectDeps (SelectData flds tn (_, annWc) _ _ _ _) = mkSQLSelect :: SelectData -> S.Select mkSQLSelect selData = - wrapFinalSel (selDataToSQL [] (S.BELit True) selData) $ - HM.toList $ sdFlds selData + bool finalSelect singleObjSel $ asSingleObject selData + where + singleObjSel = S.selectAsSingleObj finalSelect + finalSelect = + wrapFinalSel (selDataToSQL [] (S.BELit True) selData) $ + HM.toList $ sdFlds selData -- convSelectQuery -- :: (P1C m) diff --git a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs index 63a2616ea0558..556adb9f0a6a1 100644 --- a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs +++ b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs @@ -35,6 +35,7 @@ module Hasura.RQL.Types.SchemaCache , PGColInfo(..) , isPGColInfo + , getColInfos , RelInfo(..) , addFldToCache , delFldFromCache @@ -192,6 +193,10 @@ onlyIntCols = filter (isIntegerType . pgiType) onlyJSONBCols :: [PGColInfo] -> [PGColInfo] onlyJSONBCols = filter (isJSONBType . pgiType) +getColInfos :: [PGCol] -> [PGColInfo] -> [PGColInfo] +getColInfos cols allColInfos = flip filter allColInfos $ \ci -> + pgiName ci `elem` cols + data RelInfo = RelInfo { riName :: !RelName @@ -367,12 +372,13 @@ data TableInfo , tiFieldInfoMap :: !FieldInfoMap , tiRolePermInfoMap :: !RolePermInfoMap , tiConstraints :: ![TableConstraint] + , tiPrimaryKeyCols :: ![PGCol] } deriving (Show, Eq) $(deriveToJSON (aesonDrop 2 snakeCase) ''TableInfo) mkTableInfo :: QualifiedTable -> Bool -> [(ConstraintType, ConstraintName)] - -> [(PGCol, PGColType, Bool)] -> TableInfo + -> [(PGCol, PGColType, Bool)] -> [PGCol] -> TableInfo mkTableInfo tn isSystemDefined rawCons cols = TableInfo tn isSystemDefined colMap (M.fromList []) constraints where @@ -630,13 +636,13 @@ getDependentObjsOfTableWith f objId ti = getDependentRelsOfTable :: (T.Text -> Bool) -> SchemaObjId -> TableInfo -> [SchemaObjId] -getDependentRelsOfTable rsnFn objId (TableInfo tn _ fim _ _) = +getDependentRelsOfTable rsnFn objId (TableInfo tn _ fim _ _ _) = map (SOTableObj tn . TORel . riName) $ filter (isDependentOn rsnFn objId) $ getRels fim getDependentPermsOfTable :: (T.Text -> Bool) -> SchemaObjId -> TableInfo -> [SchemaObjId] -getDependentPermsOfTable rsnFn objId (TableInfo tn _ _ rpim _) = +getDependentPermsOfTable rsnFn objId (TableInfo tn _ _ rpim _ _) = concat $ flip M.mapWithKey rpim $ \rn rpi -> map (SOTableObj tn . TOPerm rn) $ getDependentPerms' rsnFn objId rpi diff --git a/server/src-lib/Hasura/SQL/DML.hs b/server/src-lib/Hasura/SQL/DML.hs index c5ca8c7dba560..c6c503b58ca08 100644 --- a/server/src-lib/Hasura/SQL/DML.hs +++ b/server/src-lib/Hasura/SQL/DML.hs @@ -15,7 +15,7 @@ import qualified Data.Text.Extended as T infixr 6 <-> (<->) :: BB.Builder -> BB.Builder -> BB.Builder -(<->) l r = l <> (BB.char7 ' ') <> r +(<->) l r = l <> BB.char7 ' ' <> r {-# INLINE (<->) #-} paren :: BB.Builder -> BB.Builder @@ -156,15 +156,15 @@ instance ToSQL WhereFrag where instance ToSQL Select where toSQL sel = BB.string7 "SELECT" - <-> (toSQL $ selDistinct sel) + <-> toSQL (selDistinct sel) <-> (", " <+> selExtr sel) - <-> (toSQL $ selFrom sel) - <-> (toSQL $ selWhere sel) - <-> (toSQL $ selGroupBy sel) - <-> (toSQL $ selHaving sel) - <-> (toSQL $ selOrderBy sel) - <-> (toSQL $ selLimit sel) - <-> (toSQL $ selOffset sel) + <-> toSQL (selFrom sel) + <-> toSQL (selWhere sel) + <-> toSQL (selGroupBy sel) + <-> toSQL (selHaving sel) + <-> toSQL (selOrderBy sel) + <-> toSQL (selLimit sel) + <-> toSQL (selOffset sel) mkSIdenExp :: (IsIden a) => a -> SQLExp mkSIdenExp = SEIden . toIden @@ -172,6 +172,15 @@ mkSIdenExp = SEIden . toIden mkQIdenExp :: (IsIden a, IsIden b) => a -> b -> SQLExp mkQIdenExp q t = SEQIden $ mkQIden q t +selectAsSingleObj :: Select -> Select +selectAsSingleObj s = mkSelect{selExtr = [extr]} + where + annSelect = SETyAnn (SESelect s) jsonType + withOp = mkSQLOpExp (SQLOp "->") annSelect (SEUnsafe "0") + nullE = SETyAnn (SELit "null") jsonType + handleNull = SEFnApp "coalesce" [withOp, nullE] Nothing + extr = Extractor handleNull Nothing + data Qual = QualIden !Iden | QualTable !QualifiedTable @@ -269,7 +278,7 @@ instance ToSQL SQLExp where TE.encodeUtf8Builder t toSQL (SESelect se) = paren $ toSQL se - toSQL (SEStar) = + toSQL SEStar = BB.char7 '*' toSQL (SEIden iden) = toSQL iden @@ -315,10 +324,10 @@ toEmptyArrWhenNull e = SEFnApp "coalesce" [e, SELit "[]"] Nothing getExtrAlias :: Extractor -> Maybe Alias getExtrAlias (Extractor _ ma) = ma -mkAliasedExtr :: (IsIden a, IsIden b) => a -> (Maybe b) -> Extractor +mkAliasedExtr :: (IsIden a, IsIden b) => a -> Maybe b -> Extractor mkAliasedExtr t = mkAliasedExtrFromExp (mkSIdenExp t) -mkAliasedExtrFromExp :: (IsIden a) => SQLExp -> (Maybe a) -> Extractor +mkAliasedExtrFromExp :: (IsIden a) => SQLExp -> Maybe a -> Extractor mkAliasedExtrFromExp sqlExp ma = Extractor sqlExp (aliasF <$> ma) where aliasF = Alias . toIden @@ -372,10 +381,10 @@ data JoinExpr instance ToSQL JoinExpr where toSQL je = - (toSQL $ tjeLeft je) - <-> (toSQL $ tjeType je) - <-> (toSQL $ tjeRight je) - <-> (toSQL $ tjeJC je) + toSQL (tjeLeft je) + <-> toSQL (tjeType je) + <-> toSQL (tjeRight je) + <-> toSQL (tjeJC je) data JoinType = Inner | LeftOuter @@ -395,7 +404,7 @@ data JoinCond = JoinOn !BoolExp instance ToSQL JoinCond where toSQL (JoinOn be) = - BB.string7 "ON" <-> (paren $ toSQL be) + BB.string7 "ON" <-> paren (toSQL be) toSQL (JoinUsing cols) = BB.string7 "USING" <-> paren ("," <+> cols) @@ -420,17 +429,17 @@ instance ToSQL BoolExp where toSQL (BELit True) = TE.encodeUtf8Builder $ T.squote "true" toSQL (BELit False) = TE.encodeUtf8Builder $ T.squote "false" toSQL (BEBin bo bel ber) = - (paren $ toSQL bel) <-> (toSQL bo) <-> (paren $ toSQL ber) + paren (toSQL bel) <-> toSQL bo <-> paren (toSQL ber) toSQL (BENot be) = - BB.string7 "NOT" <-> (paren $ toSQL be) + BB.string7 "NOT" <-> paren (toSQL be) toSQL (BECompare co vl vr) = - (paren $ toSQL vl) <-> (toSQL co) <-> (paren $ toSQL vr) + paren (toSQL vl) <-> toSQL co <-> paren (toSQL vr) toSQL (BENull v) = - (paren $ toSQL v) <-> BB.string7 "IS NULL" + paren (toSQL v) <-> BB.string7 "IS NULL" toSQL (BENotNull v) = - (paren $ toSQL v) <-> BB.string7 "IS NOT NULL" + paren (toSQL v) <-> BB.string7 "IS NOT NULL" toSQL (BEExists sel) = - BB.string7 "EXISTS " <-> (paren $ toSQL sel) + BB.string7 "EXISTS " <-> paren (toSQL sel) data BinOp = AndOp | OrOp @@ -531,18 +540,18 @@ instance ToSQL RetExp where instance ToSQL SQLDelete where toSQL sd = BB.string7 "DELETE FROM" - <-> (toSQL $ delTable sd) - <-> (toSQL $ delUsing sd) - <-> (toSQL $ delWhere sd) - <-> (toSQL $ delRet sd) + <-> toSQL (delTable sd) + <-> toSQL (delUsing sd) + <-> toSQL (delWhere sd) + <-> toSQL (delRet sd) instance ToSQL SQLUpdate where toSQL a = BB.string7 "UPDATE" - <-> (toSQL $ upTable a) - <-> (toSQL $ upSet a) - <-> (toSQL $ upFrom a) - <-> (toSQL $ upWhere a) - <-> (toSQL $ upRet a) + <-> toSQL (upTable a) + <-> toSQL (upSet a) + <-> toSQL (upFrom a) + <-> toSQL (upWhere a) + <-> toSQL (upRet a) instance ToSQL SetExp where toSQL (SetExp cvs) = @@ -593,13 +602,13 @@ instance ToSQL SQLInsert where BB.string7 "(" <-> (", " <+> tupVals) <-> BB.string7 ")" insConflict = maybe (BB.string7 "") toSQL in "INSERT INTO" - <-> (toSQL $ siTable si) + <-> toSQL (siTable si) <-> BB.string7 "(" <-> (", " <+> siCols si) <-> BB.string7 ") VALUES" <-> (", " <+> insTuples) - <-> (insConflict $ siConflict si) - <-> (toSQL $ siRet si) + <-> insConflict (siConflict si) + <-> toSQL (siRet si) data CTE = CTESelect !Select diff --git a/server/test/Spec.hs b/server/test/Spec.hs index 48f4af64f46ce..38e29721f1917 100644 --- a/server/test/Spec.hs +++ b/server/test/Spec.hs @@ -49,6 +49,7 @@ gqlSpecFiles = [ "introspection.yaml" , "insert_mutation/author.yaml" , "simple_select_query_author.yaml" + , "select_query_author_by_pkey.yaml" , "insert_mutation/article.yaml" , "insert_mutation/article_on_conflict.yaml" , "insert_mutation/article_on_conflict_ignore.yaml" diff --git a/server/test/testcases/select_query_author_by_pkey.yaml b/server/test/testcases/select_query_author_by_pkey.yaml new file mode 100644 index 0000000000000..3a3fb4169f249 --- /dev/null +++ b/server/test/testcases/select_query_author_by_pkey.yaml @@ -0,0 +1,16 @@ +description: select query on author with id = 1 +url: /v1alpha1/graphql +status: 200 +response: + data: + author_by_pkey: + name: Author 1 + id: 1 +query: + query: | + query { + author_by_pkey(id: 1){ + id + name + } + } From 9459d66ad18cbf278425406e819d334600a8d415 Mon Sep 17 00:00:00 2001 From: rakeshkky Date: Mon, 27 Aug 2018 18:20:58 +0530 Subject: [PATCH 2/2] change field name 'table_by_pkey' to 'table_by_pk' --- .../src-lib/Hasura/GraphQL/Resolve/BoolExp.hs | 23 ++++++++++--------- .../src-lib/Hasura/GraphQL/Resolve/Select.hs | 2 +- server/src-lib/Hasura/GraphQL/Schema.hs | 14 +++++------ .../select_query_author_by_pkey.yaml | 4 ++-- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs index cc17788a8adda..20e90466d96c1 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs @@ -6,7 +6,7 @@ module Hasura.GraphQL.Resolve.BoolExp ( parseBoolExp - , argsMapToBoolExp + , pgColValToBoolExp , convertBoolExp , prepare ) where @@ -23,7 +23,6 @@ import qualified Hasura.SQL.DML as S import Hasura.GraphQL.Resolve.Context import Hasura.GraphQL.Resolve.InputValue -import Hasura.GraphQL.Validate.Field import Hasura.GraphQL.Validate.Types import Hasura.RQL.Types @@ -72,12 +71,12 @@ parseOpExps annVal = do AGScalar _ _ -> throw500 "boolean value is expected" _ -> tyMismatch "pgvalue" v -parseAsPGColVal +parseAsEqOp :: (MonadError QErr m) => AnnGValue -> m [RA.OpExp] -parseAsPGColVal annVal = do +parseAsEqOp annVal = do annValOpExp <- RA.AEQ <$> asPGColVal annVal - return $ [RA.OEVal annValOpExp] + return [RA.OEVal annValOpExp] parseColExp :: (MonadError QErr m, MonadReader r m, Has FieldMap r) @@ -116,16 +115,18 @@ convertBoolExp tn whereArg = do whereExp <- parseBoolExp whereArg RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) whereExp -argsMapToBoolExp +type PGColValMap = Map.HashMap G.Name AnnGValue + +pgColValToBoolExp :: QualifiedTable - -> ArgsMap + -> PGColValMap -> Convert (GBoolExp RG.AnnSQLBoolExp) -argsMapToBoolExp tn argsMap = do - colExps <- forM args $ \(name, val) -> do +pgColValToBoolExp tn colValMap = do + colExps <- forM colVals $ \(name, val) -> do (ty, _) <- asPGColVal val let namedTy = mkScalarTy ty - BoolCol <$> parseColExp namedTy name val parseAsPGColVal + BoolCol <$> parseColExp namedTy name val parseAsEqOp let whereExp = BoolAnd colExps RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) whereExp where - args = Map.toList argsMap + colVals = Map.toList colValMap diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Select.hs b/server/src-lib/Hasura/GraphQL/Resolve/Select.hs index fb5d6c0d14141..8daca515493cc 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/Select.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/Select.hs @@ -71,7 +71,7 @@ fromField tn permFilter permLimit fld = fieldAsPath fld $ do fromFieldByPKey :: QualifiedTable -> S.BoolExp -> Field -> Convert RS.SelectData fromFieldByPKey tn permFilter fld = fieldAsPath fld $ do - boolExp <- argsMapToBoolExp tn $ _fArguments fld + boolExp <- pgColValToBoolExp tn $ _fArguments fld annFlds <- fromSelSet (_fType fld) $ _fSelSet fld return $ RS.SelectData annFlds tn (permFilter, Just boolExp) Nothing [] Nothing Nothing True diff --git a/server/src-lib/Hasura/GraphQL/Schema.hs b/server/src-lib/Hasura/GraphQL/Schema.hs index ce107a6167db9..f4563c6d6edd0 100644 --- a/server/src-lib/Hasura/GraphQL/Schema.hs +++ b/server/src-lib/Hasura/GraphQL/Schema.hs @@ -147,8 +147,8 @@ mkTableTy :: QualifiedTable -> G.NamedType mkTableTy = G.NamedType . qualTableToName -mkTableByPKeyTy :: QualifiedTable -> G.NamedType -mkTableByPKeyTy tn = G.NamedType $ qualTableToName tn <> "_by_pkey" +mkTableByPKeyTy :: QualifiedTable -> G.Name +mkTableByPKeyTy tn = qualTableToName tn <> "_by_pk" mkCompExpInp :: PGColType -> InpObjTyInfo mkCompExpInp colTy = @@ -284,12 +284,12 @@ mkSelFld tn = args = fromInpValL $ mkSelArgs tn ty = G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkTableTy tn {- -table_by_pkey( +table_by_pk( col1: value1!, . . . . coln: valuen! -): table! +): table -} mkSelFldPKey :: QualifiedTable -> [PGColInfo] @@ -298,10 +298,10 @@ mkSelFldPKey tn cols = ObjFldInfo (Just desc) fldName args ty where desc = G.Description $ "fetch data from the table: " <> tn - <<> " with primary key columns as input fields" - fldName = qualTableToName tn <> "_by_pkey" + <<> " using primary key columns" + fldName = mkTableByPKeyTy tn args = fromInpValL $ map colInpVal cols - ty = G.toGT $ G.toNT $ mkTableTy tn + ty = G.toGT $ mkTableTy tn colInpVal (PGColInfo n typ _) = InpValInfo Nothing (mkColName n) $ G.toGT $ G.toNT $ mkScalarTy typ diff --git a/server/test/testcases/select_query_author_by_pkey.yaml b/server/test/testcases/select_query_author_by_pkey.yaml index 3a3fb4169f249..3f1e1ef9080dc 100644 --- a/server/test/testcases/select_query_author_by_pkey.yaml +++ b/server/test/testcases/select_query_author_by_pkey.yaml @@ -3,13 +3,13 @@ url: /v1alpha1/graphql status: 200 response: data: - author_by_pkey: + author_by_pk: name: Author 1 id: 1 query: query: | query { - author_by_pkey(id: 1){ + author_by_pk(id: 1){ id name }