graphql-engine/server/src-lib/Hasura/GraphQL/Schema.hs
Rakesh Emmadi 5f274b5527 fix mutation returning when relationships are present (fix #1576) (#1703)
If returning field contains nested selections then mutation is performed in two steps
1. Mutation is performed with returning columns of any primary key and unique constraints
2. returning fields are queried on rows returned by selecting from table by filtering with column values returned in Step 1.

Since mutation takes two courses based on selecting relations in returning field, it is hard to maintain sequence of prepared arguments (PrepArg) generated while resolving returning field. So, we're using txtConverter instead of prepare to resolve mutation fields.
2019-03-07 15:54:07 +05:30

1852 lines
57 KiB
Haskell

module Hasura.GraphQL.Schema
( mkGCtxMap
, GCtxMap
, getGCtx
, GCtx(..)
, OpCtx(..)
, InsCtx(..)
, InsCtxMap
, RelationInfoMap
, isAggFld
, qualObjectToName
-- Schema stitching related
, RemoteGCtx (..)
, checkSchemaConflicts
, checkConflictingNode
, emptyGCtx
, mergeMaybeMaps
, ppGCtx
) where
import Data.Has
import Data.Maybe (maybeToList)
import qualified Data.HashMap.Strict as Map
import qualified Data.HashSet as Set
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Language.GraphQL.Draft.Syntax as G
import Hasura.GraphQL.Context
import Hasura.GraphQL.Resolve.Context
import Hasura.GraphQL.Validate.Types
import Hasura.Prelude
import Hasura.RQL.DML.Internal (mkAdminRolePermInfo)
import Hasura.RQL.Types
import Hasura.SQL.Types
getInsPerm :: TableInfo -> RoleName -> Maybe InsPermInfo
getInsPerm tabInfo role
| role == adminRole = _permIns $ mkAdminRolePermInfo tabInfo
| otherwise = Map.lookup role rolePermInfoMap >>= _permIns
where
rolePermInfoMap = tiRolePermInfoMap tabInfo
getTabInfo
:: MonadError QErr m
=> TableCache -> QualifiedTable -> m TableInfo
getTabInfo tc t =
onNothing (Map.lookup t tc) $
throw500 $ "table not found: " <>> t
data RemoteGCtx
= RemoteGCtx
{ _rgTypes :: !TypeMap
, _rgQueryRoot :: !ObjTyInfo
, _rgMutationRoot :: !(Maybe ObjTyInfo)
, _rgSubscriptionRoot :: !(Maybe ObjTyInfo)
} deriving (Show, Eq)
instance Has TypeMap RemoteGCtx where
getter = _rgTypes
modifier f ctx = ctx { _rgTypes = f $ _rgTypes ctx }
type SelField = Either PGColInfo (RelInfo, Bool, AnnBoolExpSQL, Maybe Int, Bool)
qualObjectToName :: (ToTxt a) => QualifiedObject a -> G.Name
qualObjectToName = G.Name . snakeCaseQualObject
isValidObjectName :: (ToTxt a) => QualifiedObject a -> Bool
isValidObjectName = isValidName . qualObjectToName
isValidField :: FieldInfo -> Bool
isValidField = \case
FIColumn (PGColInfo col _ _) -> isColEligible col
FIRelationship (RelInfo rn _ _ remTab _) -> isRelEligible rn remTab
where
isColEligible = isValidName . G.Name . getPGColTxt
isRelEligible rn rt = isValidName (G.Name $ getRelTxt rn)
&& isValidObjectName rt
upsertable :: [ConstraintName] -> Bool -> Bool -> Bool
upsertable uniqueOrPrimaryCons isUpsertAllowed view =
not (null uniqueOrPrimaryCons) && isUpsertAllowed && not view
toValidFieldInfos :: FieldInfoMap -> [FieldInfo]
toValidFieldInfos = filter isValidField . Map.elems
validPartitionFieldInfoMap :: FieldInfoMap -> ([PGColInfo], [RelInfo])
validPartitionFieldInfoMap = partitionFieldInfos . toValidFieldInfos
getValidCols :: FieldInfoMap -> [PGColInfo]
getValidCols = fst . validPartitionFieldInfoMap
getValidRels :: FieldInfoMap -> [RelInfo]
getValidRels = snd . validPartitionFieldInfoMap
mkValidConstraints :: [TableConstraint] -> [TableConstraint]
mkValidConstraints =
filter (isValidName . G.Name . getConstraintTxt . tcName)
isRelNullable :: FieldInfoMap -> RelInfo -> Bool
isRelNullable fim ri = isNullable
where
lCols = map fst $ riMapping ri
allCols = getValidCols fim
lColInfos = getColInfos lCols allCols
isNullable = any pgiIsNullable lColInfos
numAggOps :: [G.Name]
numAggOps = [ "sum", "avg", "stddev", "stddev_samp", "stddev_pop"
, "variance", "var_samp", "var_pop"
]
compAggOps :: [G.Name]
compAggOps = ["max", "min"]
isAggFld :: G.Name -> Bool
isAggFld = flip elem (numAggOps <> compAggOps)
mkColName :: PGCol -> G.Name
mkColName (PGCol n) = G.Name n
mkRelName :: RelName -> G.Name
mkRelName (RelName r) = G.Name r
mkAggRelName :: RelName -> G.Name
mkAggRelName (RelName r) = G.Name $ r <> "_aggregate"
mkBoolExpName :: QualifiedTable -> G.Name
mkBoolExpName tn =
qualObjectToName tn <> "_bool_exp"
mkBoolExpTy :: QualifiedTable -> G.NamedType
mkBoolExpTy =
G.NamedType . mkBoolExpName
mkFuncArgsName :: QualifiedFunction -> G.Name
mkFuncArgsName fn =
qualObjectToName fn <> "_args"
mkFuncArgsTy :: QualifiedFunction -> G.NamedType
mkFuncArgsTy =
G.NamedType . mkFuncArgsName
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"
mkTableAggFldsTy :: QualifiedTable -> G.NamedType
mkTableAggFldsTy tn =
G.NamedType $ qualObjectToName tn <> "_aggregate_fields"
mkTableColAggFldsTy :: G.Name -> QualifiedTable -> G.NamedType
mkTableColAggFldsTy op tn =
G.NamedType $ qualObjectToName tn <> "_" <> op <> "_fields"
mkTableByPkName :: QualifiedTable -> G.Name
mkTableByPkName tn = qualObjectToName tn <> "_by_pk"
mkPGColFld :: PGColInfo -> ObjFldInfo
mkPGColFld (PGColInfo colName colTy isNullable) =
mkHsraObjFldInfo Nothing n Map.empty ty
where
n = G.Name $ getPGColTxt colName
ty = bool notNullTy nullTy isNullable
scalarTy = mkScalarTy colTy
notNullTy = G.toGT $ G.toNT scalarTy
nullTy = G.toGT scalarTy
-- where: table_bool_exp
-- limit: Int
-- offset: Int
-- distinct_on: [table_select_column!]
mkSelArgs :: QualifiedTable -> [InpValInfo]
mkSelArgs tn =
[ InpValInfo (Just whereDesc) "where" Nothing $ G.toGT $ mkBoolExpTy tn
, InpValInfo (Just limitDesc) "limit" Nothing $ G.toGT $ mkScalarTy PGInteger
, InpValInfo (Just offsetDesc) "offset" Nothing $ G.toGT $ mkScalarTy PGInteger
, InpValInfo (Just orderByDesc) "order_by" Nothing $ G.toGT $ G.toLT $ G.toNT $
mkOrdByTy tn
, InpValInfo (Just distinctDesc) "distinct_on" Nothing $ G.toGT $ G.toLT $
G.toNT $ mkSelColumnInpTy tn
]
where
whereDesc = "filter the rows returned"
limitDesc = "limit the nuber of rows returned"
offsetDesc = "skip the first n rows. Use only with order_by"
orderByDesc = "sort the rows by one or more columns"
distinctDesc = "distinct select on columns"
-- fromInpValL :: [InpValInfo] -> Map.HashMap G.Name InpValInfo
-- fromInpValL = mapFromL _iviName
{-
array_relationship(
where: remote_table_bool_exp
limit: Int
offset: Int
): [remote_table!]!
array_relationship_aggregate(
where: remote_table_bool_exp
limit: Int
offset: Int
): remote_table_aggregate!
object_relationship: remote_table
-}
mkRelFld
:: Bool
-> RelInfo
-> Bool
-> [ObjFldInfo]
mkRelFld allowAgg (RelInfo rn rTy _ remTab isManual) isNullable = case rTy of
ArrRel -> bool [arrRelFld] [arrRelFld, aggArrRelFld] allowAgg
ObjRel -> [objRelFld]
where
objRelFld = mkHsraObjFldInfo (Just "An object relationship")
(G.Name $ getRelTxt rn) Map.empty objRelTy
objRelTy = bool (G.toGT $ G.toNT relTabTy) (G.toGT relTabTy) isObjRelNullable
isObjRelNullable = isManual || isNullable
relTabTy = mkTableTy remTab
arrRelFld =
mkHsraObjFldInfo (Just "An array relationship") (G.Name $ getRelTxt rn)
(fromInpValL $ mkSelArgs remTab) arrRelTy
arrRelTy = G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkTableTy remTab
aggArrRelFld = mkHsraObjFldInfo (Just "An aggregated array relationship")
(mkAggRelName rn) (fromInpValL $ mkSelArgs remTab) $
G.toGT $ G.toNT $ mkTableAggTy remTab
{-
type table {
col1: colty1
.
.
rel1: relty1
}
-}
mkTableObj
:: QualifiedTable
-> [SelField]
-> ObjTyInfo
mkTableObj tn allowedFlds =
mkObjTyInfo (Just desc) (mkTableTy tn) Set.empty (mapFromL _fiName flds) HasuraType
where
flds = concatMap (either (pure . mkPGColFld) mkRelFld') allowedFlds
mkRelFld' (relInfo, allowAgg, _, _, isNullable) =
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
nodes: [table!]!
}
-}
mkTableAggObj
:: QualifiedTable -> ObjTyInfo
mkTableAggObj tn =
mkHsraObjTyInfo (Just desc) (mkTableAggTy tn) Set.empty $ mapFromL _fiName
[aggFld, nodesFld]
where
desc = G.Description $
"aggregated selection of " <>> tn
aggFld = mkHsraObjFldInfo Nothing "aggregate" Map.empty $ G.toGT $
mkTableAggFldsTy tn
nodesFld = mkHsraObjFldInfo Nothing "nodes" Map.empty $ G.toGT $
G.toNT $ G.toLT $ G.toNT $ mkTableTy tn
{-
type table_aggregate_fields{
count: Int
sum: table_sum_fields
avg: table_avg_fields
stddev: table_stddev_fields
stddev_pop: table_stddev_pop_fields
variance: table_variance_fields
var_pop: table_var_pop_fields
max: table_max_fields
min: table_min_fields
}
-}
mkTableAggFldsObj
:: QualifiedTable -> [PGCol] -> [PGCol] -> ObjTyInfo
mkTableAggFldsObj tn numCols compCols =
mkHsraObjTyInfo (Just desc) (mkTableAggFldsTy tn) Set.empty $ mapFromL _fiName $
countFld : (numFlds <> compFlds)
where
desc = G.Description $
"aggregate fields of " <>> tn
countFld = mkHsraObjFldInfo Nothing "count" countParams $ G.toGT $
mkScalarTy PGInteger
countParams = fromInpValL [countColInpVal, distinctInpVal]
countColInpVal = InpValInfo Nothing "columns" Nothing $ G.toGT $
G.toLT $ G.toNT $ mkSelColumnInpTy tn
distinctInpVal = InpValInfo Nothing "distinct" Nothing $ G.toGT $
mkScalarTy PGBoolean
numFlds = bool (map mkColOpFld numAggOps) [] $ null numCols
compFlds = bool (map mkColOpFld compAggOps) [] $ null compCols
mkColOpFld op = mkHsraObjFldInfo Nothing op Map.empty $ G.toGT $
mkTableColAggFldsTy op tn
{-
type table_<agg-op>_fields{
num_col: Int
. .
. .
}
-}
mkTableColAggFldsObj
:: QualifiedTable
-> G.Name
-> (PGColType -> G.NamedType)
-> [PGColInfo]
-> ObjTyInfo
mkTableColAggFldsObj tn op f cols =
mkHsraObjTyInfo (Just desc) (mkTableColAggFldsTy op tn) Set.empty $ mapFromL _fiName $
map mkColObjFld cols
where
desc = G.Description $ "aggregate " <> G.unName op <> " on columns"
mkColObjFld c = mkHsraObjFldInfo Nothing (G.Name $ getPGColTxt $ pgiName c)
Map.empty $ G.toGT $ f $ pgiType c
{-
table(
where: table_bool_exp
limit: Int
offset: Int
): [table!]!
-}
mkSelFld
:: QualifiedTable
-> ObjFldInfo
mkSelFld tn =
mkHsraObjFldInfo (Just desc) fldName args ty
where
desc = G.Description $ "fetch data from the table: " <>> tn
fldName = qualObjectToName tn
args = fromInpValL $ mkSelArgs tn
ty = G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkTableTy tn
{-
table_by_pk(
col1: value1!,
. .
. .
coln: valuen!
): table
-}
mkSelFldPKey
:: QualifiedTable -> [PGColInfo]
-> ObjFldInfo
mkSelFldPKey tn cols =
mkHsraObjFldInfo (Just desc) fldName args ty
where
desc = G.Description $ "fetch data from the table: " <> tn
<<> " using primary key columns"
fldName = mkTableByPkName tn
args = fromInpValL $ map colInpVal cols
ty = G.toGT $ mkTableTy tn
colInpVal (PGColInfo n typ _) =
InpValInfo Nothing (mkColName n) Nothing $ G.toGT $ G.toNT $ mkScalarTy typ
{-
table_aggregate(
where: table_bool_exp
limit: Int
offset: Int
): table_aggregate!
-}
mkAggSelFld
:: QualifiedTable
-> ObjFldInfo
mkAggSelFld tn =
mkHsraObjFldInfo (Just desc) fldName args ty
where
desc = G.Description $ "fetch aggregated fields from the table: "
<>> tn
fldName = qualObjectToName tn <> "_aggregate"
args = fromInpValL $ mkSelArgs tn
ty = G.toGT $ G.toNT $ mkTableAggTy tn
{-
function(
args: function_args
where: table_bool_exp
limit: Int
offset: Int
): [table!]!
-}
mkFuncArgs :: FunctionInfo -> ParamMap
mkFuncArgs funInfo =
fromInpValL $ funcInpArgs <> mkSelArgs retTable
where
funcName = fiName funInfo
funcArgs = fiInputArgs funInfo
retTable = fiReturnType funInfo
funcArgDesc = G.Description $ "input parameters for function " <>> funcName
funcInpArg = InpValInfo (Just funcArgDesc) "args" Nothing $ G.toGT $ G.toNT $
mkFuncArgsTy funcName
funcInpArgs = bool [funcInpArg] [] $ null funcArgs
mkFuncQueryFld
:: FunctionInfo -> ObjFldInfo
mkFuncQueryFld funInfo =
mkHsraObjFldInfo (Just desc) fldName (mkFuncArgs funInfo) ty
where
retTable = fiReturnType funInfo
funcName = fiName funInfo
desc = G.Description $ "execute function " <> funcName
<<> " which returns " <>> retTable
fldName = qualObjectToName funcName
ty = G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkTableTy retTable
{-
function_aggregate(
args: function_args
where: table_bool_exp
limit: Int
offset: Int
): table_aggregate!
-}
mkFuncAggQueryFld
:: FunctionInfo -> ObjFldInfo
mkFuncAggQueryFld funInfo =
mkHsraObjFldInfo (Just desc) fldName (mkFuncArgs funInfo) ty
where
funcName = fiName funInfo
retTable = fiReturnType funInfo
desc = G.Description $ "execute function " <> funcName
<<> " and query aggregates on result of table type "
<>> retTable
fldName = qualObjectToName funcName <> "_aggregate"
ty = G.toGT $ G.toNT $ mkTableAggTy retTable
-- table_mutation_response
mkMutRespTy :: QualifiedTable -> G.NamedType
mkMutRespTy tn =
G.NamedType $ qualObjectToName tn <> "_mutation_response"
{-
type table_mutation_response {
affected_rows: Int!
returning: [table!]!
}
-}
mkMutRespObj
:: QualifiedTable
-> Bool -- is sel perm defined
-> Bool -- is nested allowed
-> ObjTyInfo
mkMutRespObj tn sel nestAlwd =
mkHsraObjTyInfo (Just objDesc) (mkMutRespTy tn) Set.empty $ mapFromL _fiName
$ affectedRowsFld : bool [] [returningFld] sel
where
objDesc = G.Description $
"response of any mutation on the table " <>> tn
affectedRowsFld =
mkHsraObjFldInfo (Just desc) "affected_rows" Map.empty $
G.toGT $ G.toNT $ mkScalarTy PGInteger
where
desc = "number of affected rows by the mutation"
returningFld =
mkHsraObjFldInfo (Just desc) "returning" Map.empty $
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
:: QualifiedTable
-- the fields that are allowed
-> [SelField]
-> InpObjTyInfo
mkBoolExpInp tn fields =
mkHsraInpTyInfo (Just desc) boolExpTy $ Map.fromList
[(_iviName inpVal, inpVal) | inpVal <- inpValues]
where
desc = G.Description $
"Boolean expression to filter rows from the table " <> tn <<>
". All fields are combined with a logical 'AND'."
-- the type of this boolean expression
boolExpTy = mkBoolExpTy tn
-- all the fields of this input object
inpValues = combinators <> map mkFldExpInp fields
mk n ty = InpValInfo Nothing n Nothing $ G.toGT ty
boolExpListTy = G.toLT boolExpTy
combinators =
[ mk "_not" boolExpTy
, mk "_and" boolExpListTy
, mk "_or" boolExpListTy
]
mkFldExpInp = \case
Left (PGColInfo colName colTy _) ->
mk (mkColName colName) (mkCompExpTy colTy)
Right (RelInfo relName _ _ remTab _, _, _, _, _) ->
mk (G.Name $ getRelTxt relName) (mkBoolExpTy remTab)
mkPGColInp :: PGColInfo -> InpValInfo
mkPGColInp (PGColInfo colName colTy _) =
InpValInfo Nothing (G.Name $ getPGColTxt colName) Nothing $
G.toGT $ mkScalarTy colTy
{-
input function_args {
arg1: arg-type1!
. .
. .
argn: arg-typen!
}
-}
procFuncArgs
:: Seq.Seq FunctionArg
-> (PGColType -> Text -> a) -> [a]
procFuncArgs argSeq f =
fst $ foldl mkItem ([], 1::Int) argSeq
where
mkItem (items, argNo) (FunctionArg nameM ty) =
case nameM of
Just argName ->
let argT = getFuncArgNameTxt argName
in (items <> pure (f ty argT), argNo)
Nothing ->
let argT = "arg_" <> T.pack (show argNo)
in (items <> pure (f ty argT), argNo + 1)
mkFuncArgsInp :: FunctionInfo -> Maybe InpObjTyInfo
mkFuncArgsInp funcInfo =
bool (Just inpObj) Nothing $ null funcArgs
where
funcName = fiName funcInfo
funcArgs = fiInputArgs funcInfo
funcArgsTy = mkFuncArgsTy funcName
inpObj = mkHsraInpTyInfo Nothing funcArgsTy $
fromInpValL argInps
argInps = procFuncArgs funcArgs mkInpVal
mkInpVal ty t =
InpValInfo Nothing (G.Name t) Nothing $ G.toGT $
G.toNT $ mkScalarTy ty
-- table_set_input
mkUpdSetTy :: QualifiedTable -> G.NamedType
mkUpdSetTy tn =
G.NamedType $ qualObjectToName tn <> "_set_input"
{-
input table_set_input {
col1: colty1
.
.
coln: coltyn
}
-}
mkUpdSetInp
:: QualifiedTable -> [PGColInfo] -> InpObjTyInfo
mkUpdSetInp tn cols =
mkHsraInpTyInfo (Just desc) (mkUpdSetTy tn) $
fromInpValL $ map mkPGColInp cols
where
desc = G.Description $
"input type for updating data in table " <>> tn
-- table_inc_input
mkUpdIncTy :: QualifiedTable -> G.NamedType
mkUpdIncTy tn =
G.NamedType $ qualObjectToName tn <> "_inc_input"
{-
input table_inc_input {
integer-col1: int
.
.
integer-coln: int
}
-}
mkUpdIncInp
:: QualifiedTable -> Maybe [PGColInfo] -> Maybe InpObjTyInfo
mkUpdIncInp tn = maybe Nothing mkType
where
mkType cols = let intCols = onlyIntCols cols
incObjTy =
mkHsraInpTyInfo (Just desc) (mkUpdIncTy tn) $
fromInpValL $ map mkPGColInp intCols
in bool (Just incObjTy) Nothing $ null intCols
desc = G.Description $
"input type for incrementing integer columne in table " <>> tn
-- table_<json-op>_input
mkJSONOpTy :: QualifiedTable -> G.Name -> G.NamedType
mkJSONOpTy tn op =
G.NamedType $ qualObjectToName tn <> op <> "_input"
-- json ops are _concat, _delete_key, _delete_elem, _delete_at_path
{-
input table_concat_input {
jsonb-col1: json
.
.
jsonb-coln: json
}
-}
{-
input table_delete_key_input {
jsonb-col1: string
.
.
jsonb-coln: string
}
-}
{-
input table_delete_elem_input {
jsonb-col1: int
.
.
jsonb-coln: int
}
-}
{-
input table_delete_at_path_input {
jsonb-col1: [string]
.
.
jsonb-coln: [string]
}
-}
-- jsonb operators and descriptions
prependOp :: G.Name
prependOp = "_prepend"
prependDesc :: G.Description
prependDesc = "prepend existing jsonb value of filtered columns with new jsonb value"
appendOp :: G.Name
appendOp = "_append"
appendDesc :: G.Description
appendDesc = "append existing jsonb value of filtered columns with new jsonb value"
deleteKeyOp :: G.Name
deleteKeyOp = "_delete_key"
deleteKeyDesc :: G.Description
deleteKeyDesc = "delete key/value pair or string element."
<> " key/value pairs are matched based on their key value"
deleteElemOp :: G.Name
deleteElemOp = "_delete_elem"
deleteElemDesc :: G.Description
deleteElemDesc = "delete the array element with specified index (negative integers count from the end)."
<> " throws an error if top level container is not an array"
deleteAtPathOp :: G.Name
deleteAtPathOp = "_delete_at_path"
deleteAtPathDesc :: G.Description
deleteAtPathDesc = "delete the field or element with specified path"
<> " (for JSON arrays, negative integers count from the end)"
mkUpdJSONOpInp
:: QualifiedTable -> [PGColInfo] -> [InpObjTyInfo]
mkUpdJSONOpInp tn cols = bool inpObjs [] $ null jsonbCols
where
jsonbCols = onlyJSONBCols cols
jsonbColNames = map pgiName jsonbCols
inpObjs = [ prependInpObj, appendInpObj, deleteKeyInpObj
, deleteElemInpObj, deleteAtPathInpObj
]
appendInpObj =
mkHsraInpTyInfo (Just appendDesc) (mkJSONOpTy tn appendOp) $
fromInpValL $ map mkPGColInp jsonbCols
prependInpObj =
mkHsraInpTyInfo (Just prependDesc) (mkJSONOpTy tn prependOp) $
fromInpValL $ map mkPGColInp jsonbCols
deleteKeyInpObj =
mkHsraInpTyInfo (Just deleteKeyDesc) (mkJSONOpTy tn deleteKeyOp) $
fromInpValL $ map deleteKeyInpVal jsonbColNames
deleteKeyInpVal c = InpValInfo Nothing (G.Name $ getPGColTxt c) Nothing $
G.toGT $ G.NamedType "String"
deleteElemInpObj =
mkHsraInpTyInfo (Just deleteElemDesc) (mkJSONOpTy tn deleteElemOp) $
fromInpValL $ map deleteElemInpVal jsonbColNames
deleteElemInpVal c = InpValInfo Nothing (G.Name $ getPGColTxt c) Nothing $
G.toGT $ G.NamedType "Int"
deleteAtPathInpObj =
mkHsraInpTyInfo (Just deleteAtPathDesc) (mkJSONOpTy tn deleteAtPathOp) $
fromInpValL $ map deleteAtPathInpVal jsonbColNames
deleteAtPathInpVal c = InpValInfo Nothing (G.Name $ getPGColTxt c) Nothing $
G.toGT $ G.toLT $ G.NamedType "String"
{-
update_table(
where : table_bool_exp!
_set : table_set_input
_inc : table_inc_input
_concat: table_concat_input
_delete_key: table_delete_key_input
_delete_elem: table_delete_elem_input
_delete_path_at: table_delete_path_at_input
): table_mutation_response
-}
mkIncInpVal :: QualifiedTable -> [PGColInfo] -> Maybe InpValInfo
mkIncInpVal tn cols = bool (Just incArg) Nothing $ null intCols
where
intCols = onlyIntCols cols
incArgDesc = "increments the integer columns with given value of the filtered values"
incArg =
InpValInfo (Just incArgDesc) "_inc" Nothing $ G.toGT $ mkUpdIncTy tn
mkJSONOpInpVals :: QualifiedTable -> [PGColInfo] -> [InpValInfo]
mkJSONOpInpVals tn cols = bool jsonbOpArgs [] $ null jsonbCols
where
jsonbCols = onlyJSONBCols cols
jsonbOpArgs = [appendArg, prependArg, deleteKeyArg, deleteElemArg, deleteAtPathArg]
appendArg =
InpValInfo (Just appendDesc) appendOp Nothing $ G.toGT $ mkJSONOpTy tn appendOp
prependArg =
InpValInfo (Just prependDesc) prependOp Nothing $ G.toGT $ mkJSONOpTy tn prependOp
deleteKeyArg =
InpValInfo (Just deleteKeyDesc) deleteKeyOp Nothing $
G.toGT $ mkJSONOpTy tn deleteKeyOp
deleteElemArg =
InpValInfo (Just deleteElemDesc) deleteElemOp Nothing $
G.toGT $ mkJSONOpTy tn deleteElemOp
deleteAtPathArg =
InpValInfo (Just deleteAtPathDesc) deleteAtPathOp Nothing $
G.toGT $ mkJSONOpTy tn deleteAtPathOp
mkUpdMutFld
:: QualifiedTable -> [PGColInfo] -> ObjFldInfo
mkUpdMutFld tn cols =
mkHsraObjFldInfo (Just desc) fldName (fromInpValL inputValues) $
G.toGT $ mkMutRespTy tn
where
inputValues = [filterArg, setArg] <> incArg
<> mkJSONOpInpVals tn cols
desc = G.Description $ "update data of the table: " <>> tn
fldName = "update_" <> qualObjectToName tn
filterArgDesc = "filter the rows which have to be updated"
filterArg =
InpValInfo (Just filterArgDesc) "where" Nothing $ G.toGT $
G.toNT $ mkBoolExpTy tn
setArgDesc = "sets the columns of the filtered rows to the given values"
setArg =
InpValInfo (Just setArgDesc) "_set" Nothing $ G.toGT $ mkUpdSetTy tn
incArg = maybeToList $ mkIncInpVal tn cols
{-
delete_table(
where : table_bool_exp!
): table_mutation_response
-}
mkDelMutFld
:: QualifiedTable -> ObjFldInfo
mkDelMutFld tn =
mkHsraObjFldInfo (Just desc) fldName (fromInpValL [filterArg]) $
G.toGT $ mkMutRespTy tn
where
desc = G.Description $ "delete data from the table: " <>> tn
fldName = "delete_" <> qualObjectToName tn
filterArgDesc = "filter the rows which have to be deleted"
filterArg =
InpValInfo (Just filterArgDesc) "where" Nothing $ G.toGT $
G.toNT $ mkBoolExpTy tn
-- table_insert_input
mkInsInpTy :: QualifiedTable -> G.NamedType
mkInsInpTy tn =
G.NamedType $ qualObjectToName tn <> "_insert_input"
-- table_obj_rel_insert_input
mkObjInsInpTy :: QualifiedTable -> G.NamedType
mkObjInsInpTy tn =
G.NamedType $ qualObjectToName tn <> "_obj_rel_insert_input"
-- table_arr_rel_insert_input
mkArrInsInpTy :: QualifiedTable -> G.NamedType
mkArrInsInpTy tn =
G.NamedType $ qualObjectToName tn <> "_arr_rel_insert_input"
-- table_on_conflict
mkOnConflictInpTy :: QualifiedTable -> G.NamedType
mkOnConflictInpTy tn =
G.NamedType $ qualObjectToName tn <> "_on_conflict"
-- table_constraint
mkConstraintInpTy :: QualifiedTable -> G.NamedType
mkConstraintInpTy tn =
G.NamedType $ qualObjectToName tn <> "_constraint"
-- conflict_action
conflictActionTy :: G.NamedType
conflictActionTy = G.NamedType "conflict_action"
-- table_update_column
mkUpdColumnInpTy :: QualifiedTable -> G.NamedType
mkUpdColumnInpTy tn =
G.NamedType $ qualObjectToName tn <> "_update_column"
--table_select_column
mkSelColumnInpTy :: QualifiedTable -> G.NamedType
mkSelColumnInpTy tn =
G.NamedType $ qualObjectToName tn <> "_select_column"
{-
input table_obj_rel_insert_input {
data: table_insert_input!
on_conflict: table_on_conflict
}
-}
{-
input table_arr_rel_insert_input {
data: [table_insert_input!]!
on_conflict: table_on_conflict
}
-}
mkRelInsInps
:: QualifiedTable -> Bool -> [InpObjTyInfo]
mkRelInsInps tn upsertAllowed = [objRelInsInp, arrRelInsInp]
where
onConflictInpVal =
InpValInfo Nothing "on_conflict" Nothing $ G.toGT $ mkOnConflictInpTy tn
onConflictInp = bool [] [onConflictInpVal] upsertAllowed
objRelDesc = G.Description $
"input type for inserting object relation for remote table " <>> tn
objRelDataInp = InpValInfo Nothing "data" Nothing $ G.toGT $
G.toNT $ mkInsInpTy tn
objRelInsInp = mkHsraInpTyInfo (Just objRelDesc) (mkObjInsInpTy tn)
$ fromInpValL $ objRelDataInp : onConflictInp
arrRelDesc = G.Description $
"input type for inserting array relation for remote table " <>> tn
arrRelDataInp = InpValInfo Nothing "data" Nothing $ G.toGT $
G.toNT $ G.toLT $ G.toNT $ mkInsInpTy tn
arrRelInsInp = mkHsraInpTyInfo (Just arrRelDesc) (mkArrInsInpTy tn)
$ fromInpValL $ arrRelDataInp : onConflictInp
{-
input table_insert_input {
col1: colty1
.
.
coln: coltyn
}
-}
mkInsInp
:: QualifiedTable -> InsCtx -> InpObjTyInfo
mkInsInp tn insCtx =
mkHsraInpTyInfo (Just desc) (mkInsInpTy tn) $ fromInpValL $
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
relInfoMap = icRelations insCtx
relInps = flip map (Map.toList relInfoMap) $
\(relName, relInfo) ->
let rty = riType relInfo
remoteQT = riRTable relInfo
in case rty of
ObjRel -> InpValInfo Nothing (G.Name $ getRelTxt relName) Nothing $
G.toGT $ mkObjInsInpTy remoteQT
ArrRel -> InpValInfo Nothing (G.Name $ getRelTxt relName) Nothing $
G.toGT $ mkArrInsInpTy remoteQT
{-
input table_on_conflict {
action: conflict_action
constraint: table_constraint!
update_columns: [table_column!]
}
-}
mkOnConflictInp :: QualifiedTable -> InpObjTyInfo
mkOnConflictInp tn =
mkHsraInpTyInfo (Just desc) (mkOnConflictInpTy tn) $ fromInpValL
[constraintInpVal, updateColumnsInpVal]
where
desc = G.Description $
"on conflict condition type for table " <>> tn
constraintInpVal = InpValInfo Nothing (G.Name "constraint") Nothing $
G.toGT $ G.toNT $ mkConstraintInpTy tn
updateColumnsInpVal = InpValInfo Nothing (G.Name "update_columns") Nothing $
G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkUpdColumnInpTy tn
{-
insert_table(
objects: [table_insert_input!]!
on_conflict: table_on_conflict
): table_mutation_response!
-}
mkInsMutFld
:: QualifiedTable -> Bool -> ObjFldInfo
mkInsMutFld tn isUpsertable =
mkHsraObjFldInfo (Just desc) fldName (fromInpValL inputVals) $
G.toGT $ mkMutRespTy tn
where
inputVals = catMaybes [Just objectsArg , onConflictInpVal]
desc = G.Description $
"insert data into the table: " <>> tn
fldName = "insert_" <> qualObjectToName tn
objsArgDesc = "the rows to be inserted"
objectsArg =
InpValInfo (Just objsArgDesc) "objects" Nothing $ G.toGT $
G.toNT $ G.toLT $ G.toNT $ mkInsInpTy tn
onConflictInpVal = bool Nothing (Just onConflictArg) isUpsertable
onConflictDesc = "on conflict condition"
onConflictArg =
InpValInfo (Just onConflictDesc) "on_conflict" Nothing $ G.toGT $ mkOnConflictInpTy tn
mkConstriantTy :: QualifiedTable -> [ConstraintName] -> EnumTyInfo
mkConstriantTy tn cons = enumTyInfo
where
enumTyInfo = mkHsraEnumTyInfo (Just desc) (mkConstraintInpTy tn) $
mapFromL _eviVal $ map mkConstraintEnumVal cons
desc = G.Description $
"unique or primary key constraints on table " <>> tn
mkConstraintEnumVal (ConstraintName n) =
EnumValInfo (Just "unique or primary key constraint")
(G.EnumValue $ G.Name n) False
mkColumnEnumVal :: PGCol -> EnumValInfo
mkColumnEnumVal (PGCol col) =
EnumValInfo (Just "column name") (G.EnumValue $ G.Name col) False
mkUpdColumnTy :: QualifiedTable -> [PGCol] -> EnumTyInfo
mkUpdColumnTy tn cols = enumTyInfo
where
enumTyInfo = mkHsraEnumTyInfo (Just desc) (mkUpdColumnInpTy tn) $
mapFromL _eviVal $ map mkColumnEnumVal cols
desc = G.Description $
"update columns of table " <>> tn
mkSelColumnTy :: QualifiedTable -> [PGCol] -> EnumTyInfo
mkSelColumnTy tn cols = enumTyInfo
where
enumTyInfo = mkHsraEnumTyInfo (Just desc) (mkSelColumnInpTy tn) $
mapFromL _eviVal $ map mkColumnEnumVal cols
desc = G.Description $
"select columns of table " <>> tn
mkConflictActionTy :: Bool -> EnumTyInfo
mkConflictActionTy updAllowed =
mkHsraEnumTyInfo (Just desc) conflictActionTy $ mapFromL _eviVal $
[enumValIgnore] <> bool [] [enumValUpdate] updAllowed
where
desc = G.Description "conflict action"
enumValIgnore = EnumValInfo (Just "ignore the insert on this row")
(G.EnumValue "ignore") False
enumValUpdate = EnumValInfo (Just "update the row with the given values")
(G.EnumValue "update") False
-- ordByTy :: G.NamedType
-- ordByTy = G.NamedType "order_by"
-- ordByEnumTy :: EnumTyInfo
-- ordByEnumTy =
-- mkHsraEnumTyInfo (Just desc) ordByTy $ mapFromL _eviVal $
-- map mkEnumVal enumVals
-- where
-- desc = G.Description "column ordering options"
-- mkEnumVal (n, d) =
-- EnumValInfo (Just d) (G.EnumValue n) False
-- enumVals =
-- [ ( "asc"
-- , "in the ascending order, nulls last"
-- ),
-- ( "desc"
-- , "in the descending order, nulls last"
-- ),
-- ( "asc_nulls_first"
-- , "in the ascending order, nulls first"
-- ),
-- ( "desc_nulls_first"
-- , "in the ascending order, nulls first"
-- )
-- ]
mkTabAggOpOrdByTy :: QualifiedTable -> G.Name -> G.NamedType
mkTabAggOpOrdByTy tn op =
G.NamedType $ qualObjectToName tn <> "_" <> op <> "_order_by"
{-
input table_<op>_order_by {
col1: order_by
. .
. .
}
-}
mkTabAggOpOrdByInpObjs
:: QualifiedTable -> [PGCol] -> [PGCol] -> [InpObjTyInfo]
mkTabAggOpOrdByInpObjs tn numCols compCols =
mapMaybe (mkInpObjTyM numCols) numAggOps
<> mapMaybe (mkInpObjTyM compCols) compAggOps
where
mkDesc (G.Name op) = G.Description $ "order by " <> op <> "() on columns of table " <>> tn
mkInpObjTyM cols op = bool (Just $ mkInpObjTy cols op) Nothing $ null cols
mkInpObjTy cols op = mkHsraInpTyInfo (Just $ mkDesc op) (mkTabAggOpOrdByTy tn op) $
fromInpValL $ map mkColInpVal cols
mkColInpVal c = InpValInfo Nothing (mkColName c) Nothing $ G.toGT
ordByTy
mkTabAggOrdByTy :: QualifiedTable -> G.NamedType
mkTabAggOrdByTy tn =
G.NamedType $ qualObjectToName tn <> "_aggregate_order_by"
{-
input table_aggregate_order_by {
count: order_by
<op-name>: table_<op-name>_order_by
}
-}
mkTabAggOrdByInpObj
:: QualifiedTable -> [PGCol] -> [PGCol] -> InpObjTyInfo
mkTabAggOrdByInpObj tn numCols compCols =
mkHsraInpTyInfo (Just desc) (mkTabAggOrdByTy tn) $ fromInpValL $
numOpOrdBys <> compOpOrdBys <> [countInpVal]
where
desc = G.Description $
"order by aggregate values of table " <>> tn
numOpOrdBys = bool (map mkInpValInfo numAggOps) [] $ null numCols
compOpOrdBys = bool (map mkInpValInfo compAggOps) [] $ null compCols
mkInpValInfo op = InpValInfo Nothing op Nothing $ G.toGT $
mkTabAggOpOrdByTy tn op
countInpVal = InpValInfo Nothing "count" Nothing $ G.toGT ordByTy
mkOrdByTy :: QualifiedTable -> G.NamedType
mkOrdByTy tn =
G.NamedType $ qualObjectToName tn <> "_order_by"
{-
input table_order_by {
col1: order_by
col2: order_by
. .
. .
coln: order_by
obj-rel: <remote-table>_order_by
}
-}
mkOrdByInpObj
:: QualifiedTable -> [SelField] -> (InpObjTyInfo, OrdByCtx)
mkOrdByInpObj tn selFlds = (inpObjTy, ordByCtx)
where
inpObjTy =
mkHsraInpTyInfo (Just desc) namedTy $ fromInpValL $
map mkColOrdBy pgCols <> map mkObjRelOrdBy objRels
<> mapMaybe mkArrRelAggOrdBy arrRels
namedTy = mkOrdByTy tn
desc = G.Description $
"ordering options when selecting data from " <>> tn
pgCols = lefts selFlds
relFltr ty = flip filter (rights selFlds) $ \(ri, _, _, _, _) ->
riType ri == ty
objRels = relFltr ObjRel
arrRels = relFltr ArrRel
mkColOrdBy ci = InpValInfo Nothing (mkColName $ pgiName ci) Nothing $
G.toGT ordByTy
mkObjRelOrdBy (ri, _, _, _, _) =
InpValInfo Nothing (mkRelName $ riName ri) Nothing $
G.toGT $ mkOrdByTy $ riRTable ri
mkArrRelAggOrdBy (ri, isAggAllowed, _, _, _) =
let ivi = InpValInfo Nothing (mkAggRelName $ riName ri) Nothing $
G.toGT $ mkTabAggOrdByTy $ riRTable ri
in bool Nothing (Just ivi) isAggAllowed
ordByCtx = Map.singleton namedTy $ Map.fromList $
colOrdBys <> relOrdBys <> arrRelOrdBys
colOrdBys = flip map pgCols $ \ci ->
( mkColName $ pgiName ci
, OBIPGCol ci
)
relOrdBys = flip map objRels $ \(ri, _, fltr, _, _) ->
( mkRelName $ riName ri
, OBIRel ri fltr
)
arrRelOrdBys = flip mapMaybe arrRels $ \(ri, isAggAllowed, fltr, _, _) ->
let obItem = ( mkAggRelName $ riName ri
, OBIAgg ri fltr
)
in bool Nothing (Just obItem) isAggAllowed
-- newtype RootFlds
-- = RootFlds
-- { _taMutation :: Map.HashMap G.Name (OpCtx, Either ObjFldInfo ObjFldInfo)
-- } deriving (Show, Eq)
-- instance Semigroup RootFlds where
-- (RootFlds m1) <> (RootFlds m2)
-- = RootFlds (Map.union m1 m2)
-- instance Monoid RootFlds where
-- mempty = RootFlds Map.empty
-- mappend = (<>)
mkOnConflictTypes
:: QualifiedTable -> [ConstraintName] -> [PGCol] -> Bool -> [TypeInfo]
mkOnConflictTypes tn uniqueOrPrimaryCons cols =
bool [] tyInfos
where
tyInfos = [ TIEnum $ mkConflictActionTy isUpdAllowed
, TIEnum $ mkConstriantTy tn uniqueOrPrimaryCons
, TIEnum $ mkUpdColumnTy tn cols
, TIInpObj $ mkOnConflictInp tn
]
isUpdAllowed = not $ null cols
mkGCtxRole'
:: QualifiedTable
-- all columns
-> [PGColInfo]
-- insert perm
-> Maybe (InsCtx, Bool)
-- select permission
-> Maybe (Bool, [SelField])
-- update cols
-> Maybe [PGColInfo]
-- delete cols
-> Maybe ()
-- primary key columns
-> [PGColInfo]
-- constraints
-> [TableConstraint]
-> Maybe ViewInfo
-- all functions
-> [FunctionInfo]
-> TyAgg
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
alwNestMutFld = isJust $ getUniqCols allCols constraints
constNames = map tcName constraints
isUpsertable = upsertable constNames upsertPerm $ isJust viM
updatableCols = maybe [] (map pgiName) updColsM
onConflictTypes = mkOnConflictTypes tn constNames updatableCols isUpsertable
jsonOpTys = fromMaybe [] updJSONOpInpObjTysM
relInsInpObjTys = maybe [] (map TIInpObj) $
mutHelper viIsInsertable relInsInpObjsM
funcInpArgTys = bool [] (map TIInpObj funcArgInpObjs) $ isJust selFldsM
allTypes = relInsInpObjTys <> onConflictTypes <> jsonOpTys
<> queryTypes <> aggQueryTypes <> mutationTypes
<> funcInpArgTys
queryTypes = catMaybes
[ TIInpObj <$> boolExpInpObjM
, TIInpObj <$> ordByInpObjM
, TIObj <$> selObjM
, TIObj <$> selColObjM
]
aggQueryTypes = map TIObj aggObjs <> map TIInpObj aggOrdByInps
mutationTypes = catMaybes
[ TIInpObj <$> mutHelper viIsInsertable insInpObjM
, TIInpObj <$> mutHelper viIsUpdatable updSetInpObjM
, TIInpObj <$> mutHelper viIsUpdatable updIncInpObjM
, TIObj <$> mutRespObjM
, TIEnum <$> selColInpTyM
]
mutHelper :: (ViewInfo -> Bool) -> Maybe a -> Maybe a
mutHelper f objM = bool Nothing objM $ isMutable f viM
fieldMap = Map.unions $ catMaybes
[ insInpObjFldsM, updSetInpObjFldsM
, boolExpInpObjFldsM , selObjFldsM
, selColObjFldsM
]
scalars = Set.unions [selByPkScalarSet, funcArgScalarSet]
-- helper
mkColFldMap ty cols = Map.fromList $ flip map cols $
\c -> ((ty, mkColName $ pgiName c), Left c)
insCtxM = fst <$> insPermM
insColsM = icColumns <$> insCtxM
-- insert input type
insInpObjM = mkInsInp tn <$> insCtxM
-- column fields used in insert input object
insInpObjFldsM = mkColFldMap (mkInsInpTy tn) <$> insColsM
-- relationship input objects
relInsInpObjsM = const (mkRelInsInps tn isUpsertable) <$> insCtxM
-- update set input type
updSetInpObjM = mkUpdSetInp tn <$> updColsM
-- update increment input type
updIncInpObjM = mkUpdIncInp tn updColsM
-- update json operator input type
updJSONOpInpObjsM = mkUpdJSONOpInp tn <$> updColsM
updJSONOpInpObjTysM = map TIInpObj <$> updJSONOpInpObjsM
-- fields used in set input object
updSetInpObjFldsM = mkColFldMap (mkUpdSetTy tn) <$> updColsM
selFldsM = snd <$> selPermM
selColsM = (map pgiName . lefts) <$> selFldsM
selColInpTyM = mkSelColumnTy tn <$> selColsM
-- boolexp input type
boolExpInpObjM = case selFldsM of
Just selFlds -> Just $ mkBoolExpInp tn selFlds
-- no select permission
Nothing ->
-- but update/delete is defined
if isJust updColsM || isJust delPermM
then Just $ mkBoolExpInp tn []
else Nothing
-- funcargs input type
funcArgInpObjs = mapMaybe mkFuncArgsInp funcs
-- funcArgCtx = Map.unions funcArgCtxs
funcArgScalarSet = Set.fromList $
concatMap (map faType . toList . fiInputArgs) funcs
-- helper
mkFldMap ty = Map.fromList . concatMap (mkFld ty)
mkFld ty = \case
Left ci -> [((ty, mkColName $ pgiName ci), Left ci)]
Right (ri, allowAgg, perm, lim, _) ->
let relFld = ( (ty, G.Name $ getRelTxt $ riName ri)
, Right (ri, False, perm, lim)
)
aggRelFld = ( (ty, mkAggRelName $ riName ri)
, Right (ri, True, perm, lim)
)
in case riType ri of
ObjRel -> [relFld]
ArrRel -> bool [relFld] [relFld, aggRelFld] allowAgg
-- the fields used in bool exp
boolExpInpObjFldsM = mkFldMap (mkBoolExpTy tn) <$> selFldsM
-- mut resp obj
mutRespObjM =
if isMut
then Just $ mkMutRespObj tn (isJust selFldsM) alwNestMutFld
else Nothing
isMut = (isJust insColsM || isJust updColsM || isJust delPermM)
&& any (`isMutable` viM) [viIsInsertable, viIsUpdatable, viIsDeletable]
-- 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) ->
let numCols = (map pgiName . getNumCols) selFlds
compCols = (map pgiName . getCompCols) selFlds
objs = [ mkTableAggObj tn
, mkTableAggFldsObj tn numCols compCols
] <> mkColAggFldsObjs selFlds
ordByInps = mkTabAggOrdByInpObj tn numCols compCols
: mkTabAggOpOrdByInpObjs tn numCols compCols
in (objs, ordByInps)
_ -> ([], [])
getNumCols = onlyNumCols . lefts
getCompCols = onlyComparableCols . lefts
onlyFloat = const $ mkScalarTy PGFloat
mkTypeMaker "sum" = mkScalarTy
mkTypeMaker _ = onlyFloat
mkColAggFldsObjs flds =
let numCols = getNumCols flds
compCols = getCompCols flds
mkNumObjFld n = mkTableColAggFldsObj tn n (mkTypeMaker n) numCols
mkCompObjFld n = mkTableColAggFldsObj tn n mkScalarTy compCols
numFldsObjs = bool (map mkNumObjFld numAggOps) [] $ null numCols
compFldsObjs = bool (map mkCompObjFld compAggOps) [] $ null compCols
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
ordByInpCtxM = mkOrdByInpObj tn <$> selFldsM
(ordByInpObjM, ordByCtxM) = case ordByInpCtxM of
Just (a, b) -> (Just a, Just b)
Nothing -> (Nothing, Nothing)
getRootFldsRole'
:: QualifiedTable
-> [PGCol]
-> [TableConstraint]
-> FieldInfoMap
-> [FunctionInfo]
-> Maybe ([T.Text], Bool) -- insert perm
-> Maybe (AnnBoolExpSQL, Maybe Int, [T.Text], Bool) -- select filter
-> Maybe ([PGCol], PreSetCols, AnnBoolExpSQL, [T.Text]) -- update filter
-> Maybe (AnnBoolExpSQL, [T.Text]) -- delete filter
-> Maybe ViewInfo
-> RootFlds
getRootFldsRole' tn primCols constraints fields funcs insM selM updM delM viM =
RootFlds mFlds
where
mFlds = mapFromL (either _fiName _fiName . snd) $
funcQueries <>
funcAggQueries <>
catMaybes
[ mutHelper viIsInsertable getInsDet insM
, mutHelper viIsUpdatable getUpdDet updM
, mutHelper viIsDeletable getDelDet delM
, getSelDet <$> selM, getSelAggDet selM
, getPKeySelDet selM $ getColInfos primCols colInfos
]
funcQueries = maybe [] getFuncQueryFlds selM
funcAggQueries = maybe [] getFuncAggQueryFlds selM
mutHelper :: (ViewInfo -> Bool) -> (a -> b) -> Maybe a -> Maybe b
mutHelper f getDet mutM =
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 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 uniqCols
, Right $ mkUpdMutFld tn $ getColInfos updCols colInfos
)
getDelDet (delFltr, hdrs) =
( OCDelete $ DelOpCtx tn hdrs delFltr uniqCols
, Right $ mkDelMutFld tn
)
getSelDet (selFltr, pLimit, hdrs, _) =
selFldHelper OCSelect mkSelFld selFltr pLimit hdrs
getSelAggDet (Just (selFltr, pLimit, hdrs, True)) =
Just $ selFldHelper OCSelectAgg mkAggSelFld selFltr pLimit hdrs
getSelAggDet _ = Nothing
selFldHelper f g pFltr pLimit hdrs =
( f $ SelOpCtx tn hdrs pFltr pLimit
, Left $ g tn
)
getPKeySelDet Nothing _ = Nothing
getPKeySelDet _ [] = Nothing
getPKeySelDet (Just (selFltr, _, hdrs, _)) pCols = Just
( OCSelectPkey $ SelPkOpCtx tn hdrs selFltr $
mapFromL (mkColName . pgiName) pCols
, Left $ mkSelFldPKey tn pCols
)
getFuncQueryFlds (selFltr, pLimit, hdrs, _) =
funcFldHelper OCFuncQuery mkFuncQueryFld selFltr pLimit hdrs
getFuncAggQueryFlds (selFltr, pLimit, hdrs, True) =
funcFldHelper OCFuncAggQuery mkFuncAggQueryFld selFltr pLimit hdrs
getFuncAggQueryFlds _ = []
funcFldHelper f g pFltr pLimit hdrs =
flip map funcs $ \fi ->
( f $ FuncQOpCtx tn hdrs pFltr pLimit (fiName fi) $ mkFuncArgItemSeq fi
, Left $ g fi
)
mkFuncArgItemSeq fi = Seq.fromList $
procFuncArgs (fiInputArgs fi) $ \_ t -> FuncArgItem $ G.Name t
getSelPermission :: TableInfo -> RoleName -> Maybe SelPermInfo
getSelPermission tabInfo role =
Map.lookup role (tiRolePermInfoMap tabInfo) >>= _permSel
getSelPerm
:: (MonadError QErr m)
=> TableCache
-- all the fields of a table
-> FieldInfoMap
-- role and its permission
-> RoleName -> SelPermInfo
-> m (Bool, [SelField])
getSelPerm tableCache fields role selPermInfo = do
selFlds <- fmap catMaybes $ forM (toValidFieldInfos fields) $ \case
FIColumn pgColInfo ->
return $ fmap Left $ bool Nothing (Just pgColInfo) $
Set.member (pgiName pgColInfo) allowedCols
FIRelationship relInfo -> do
remTableInfo <- getTabInfo tableCache $ riRTable relInfo
let remTableSelPermM = getSelPermission remTableInfo role
return $ flip fmap remTableSelPermM $
\rmSelPermM -> Right ( relInfo
, spiAllowAgg rmSelPermM
, spiFilter rmSelPermM
, spiLimit rmSelPermM
, isRelNullable fields relInfo
)
return (spiAllowAgg selPermInfo, selFlds)
where
allowedCols = spiCols selPermInfo
mkInsCtx
:: MonadError QErr m
=> RoleName
-> 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
remoteTableInfo <- getTabInfo tableCache remoteTable
let insPermM = getInsPerm remoteTableInfo role
viewInfoM = tiViewInfo remoteTableInfo
return $ bool Nothing (Just (relName, relInfo)) $
isInsertable insPermM viewInfoM
let relInfoMap = Map.fromList $ catMaybes relTupsM
return $ InsCtx iView cols setCols relInfoMap updPermForIns uniqCols
where
cols = getValidCols fields
rels = getValidRels fields
iView = ipiView insPermInfo
setCols = ipiSet insPermInfo
updPermForIns = mkUpdPermForIns <$> updPermM
mkUpdPermForIns upi = UpdPermForIns (toList $ upiCols upi)
(upiFilter upi) (upiSet upi)
isInsertable Nothing _ = False
isInsertable (Just _) viewInfoM = isMutable viIsInsertable viewInfoM
mkAdminInsCtx
:: MonadError QErr m
=> 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
remoteTableInfo <- getTabInfo tc remoteTable
let viewInfoM = tiViewInfo remoteTableInfo
return $ bool Nothing (Just (relName, relInfo)) $
isMutable viIsInsertable viewInfoM
let relInfoMap = Map.fromList $ catMaybes relTupsM
updPerm = UpdPermForIns (map pgiName cols) noFilter Map.empty
return $ InsCtx tn cols Map.empty relInfoMap (Just updPerm) uniqCols
where
cols = getValidCols fields
rels = getValidRels fields
mkGCtxRole
:: (MonadError QErr m)
=> TableCache
-> QualifiedTable
-> FieldInfoMap
-> [PGCol]
-> [TableConstraint]
-> [FunctionInfo]
-> Maybe ViewInfo
-> RoleName
-> RolePermInfo
-> m (TyAgg, RootFlds, InsCtxMap)
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 uniqCols $ _permUpd permInfo
return (tic, isJust $ _permUpd permInfo)
let updColsM = filterColInfos . upiCols <$> _permUpd permInfo
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 allCols
filterColInfos allowedSet =
filter ((`Set.member` allowedSet) . pgiName) colInfos
getRootFldsRole
:: QualifiedTable
-> [PGCol]
-> [TableConstraint]
-> FieldInfoMap
-> [FunctionInfo]
-> Maybe ViewInfo
-> RolePermInfo
-> RootFlds
getRootFldsRole tn pCols constraints fields funcs viM (RolePermInfo insM selM updM delM) =
getRootFldsRole' tn pCols constraints fields funcs
(mkIns <$> insM) (mkSel <$> selM)
(mkUpd <$> updM) (mkDel <$> delM)
viM
where
mkIns i = (ipiRequiredHeaders i, isJust updM)
mkSel s = ( spiFilter s, spiLimit s
, spiRequiredHeaders s, spiAllowAgg s
)
mkUpd u = ( Set.toList $ upiCols u
, upiSet u
, upiFilter u
, upiRequiredHeaders u
)
mkDel d = (dpiFilter d, dpiRequiredHeaders d)
mkGCtxMapTable
:: (MonadError QErr m)
=> TableCache
-> FunctionCache
-> TableInfo
-> m (Map.HashMap RoleName (TyAgg, RootFlds, InsCtxMap))
mkGCtxMapTable tableCache funcCache tabInfo = do
m <- Map.traverseWithKey
(mkGCtxRole tableCache tn fields pkeyCols validConstraints tabFuncs viewInfo) rolePerms
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
validColNames = map pgiName colInfos
pkeyColInfos = getColInfos pkeyCols colInfos
tabFuncs = filter (isValidObjectName . fiName) $
getFuncsOfTable tn funcCache
selFlds = flip map (toValidFieldInfos fields) $ \case
FIColumn pgColInfo -> Left pgColInfo
FIRelationship relInfo -> Right (relInfo, True, noFilter, Nothing, isRelNullable fields relInfo)
adminRootFlds =
getRootFldsRole' tn pkeyCols validConstraints fields tabFuncs
(Just ([], True)) (Just (noFilter, Nothing, [], True))
(Just (validColNames, mempty, noFilter, [])) (Just (noFilter, []))
viewInfo
noFilter :: AnnBoolExpSQL
noFilter = annBoolExpTrue
checkSchemaConflicts
:: (MonadError QErr m)
=> GCtx -> GCtx -> m ()
checkSchemaConflicts gCtx remoteCtx = do
let typeMap = _gTypes gCtx -- hasura typemap
-- check type conflicts
let hTypes = Map.elems typeMap
hTyNames = map G.unNamedType $ Map.keys typeMap
-- get the root names from the remote schema
rmQRootName = _otiName $ _gQueryRoot remoteCtx
rmMRootName = maybeToList $ _otiName <$> _gMutRoot remoteCtx
rmSRootName = maybeToList $ _otiName <$> _gSubRoot remoteCtx
rmRootNames = map G.unNamedType (rmQRootName:(rmMRootName ++ rmSRootName))
let rmTypes = Map.filterWithKey
(\k _ -> G.unNamedType k `notElem` builtinTy ++ rmRootNames)
$ _gTypes remoteCtx
isTyInfoSame ty = any (`tyinfoEq` ty) hTypes
-- name is same and structure is not same
isSame n ty = G.unNamedType n `elem` hTyNames &&
not (isTyInfoSame ty)
conflictedTypes = Map.filterWithKey isSame rmTypes
conflictedTyNames = map G.unNamedType $ Map.keys conflictedTypes
unless (Map.null conflictedTypes) $
throw400 RemoteSchemaConflicts $ tyMsg conflictedTyNames
-- check node conflicts
let rmQRoot = _otiFields $ _gQueryRoot remoteCtx
rmMRoot = _otiFields <$> _gMutRoot remoteCtx
rmRoots = filter (`notElem` builtinNodes ++ rmRootNames) . Map.keys <$>
mergeMaybeMaps (Just rmQRoot) rmMRoot
hQR = _otiFields <$>
join (getObjTyM <$> Map.lookup hQRName typeMap)
hMR = _otiFields <$>
join (getObjTyM <$> Map.lookup hMRName typeMap)
hRoots = Map.keys <$> mergeMaybeMaps hQR hMR
case (rmRoots, hRoots) of
(Just rmR, Just hR) -> do
let conflictedNodes = filter (`elem` hR) rmR
unless (null conflictedNodes) $
throw400 RemoteSchemaConflicts $ nodesMsg conflictedNodes
_ -> return ()
where
tyinfoEq a b = case (a, b) of
(TIScalar t1, TIScalar t2) -> typeEq t1 t2
(TIObj t1, TIObj t2) -> typeEq t1 t2
(TIEnum t1, TIEnum t2) -> typeEq t1 t2
(TIInpObj t1, TIInpObj t2) -> typeEq t1 t2
_ -> False
hQRName = G.NamedType "query_root"
hMRName = G.NamedType "mutation_root"
tyMsg ty = "types: [" <> namesToTxt ty <>
"] already exist in current graphql schema"
nodesMsg n = "nodes : [" <> namesToTxt n <>
"] already exist in current graphql schema"
namesToTxt = T.intercalate ", " . map G.unName
builtinNodes = ["__type", "__schema", "__typename"]
builtinTy = [ "__Directive"
, "__DirectiveLocation"
, "__EnumValue"
, "__Field"
, "__InputValue"
, "__Schema"
, "__Type"
, "__TypeKind"
, "Int"
, "Float"
, "String"
, "Boolean"
, "ID"
]
checkConflictingNode
:: (MonadError QErr m)
=> GCtx
-> G.Name
-> m ()
checkConflictingNode gCtx node = do
let typeMap = _gTypes gCtx
hQR = _otiFields <$>
join (getObjTyM <$> Map.lookup hQRName typeMap)
hMR = _otiFields <$>
join (getObjTyM <$> Map.lookup hMRName typeMap)
hRoots = Map.keys <$> mergeMaybeMaps hQR hMR
case hRoots of
Just hR ->
when (node `elem` hR) $
throw400 RemoteSchemaConflicts msg
_ -> return ()
where
hQRName = G.NamedType "query_root"
hMRName = G.NamedType "mutation_root"
msg = "node " <> G.unName node <>
" already exists in current graphql schema"
mkGCtxMap
:: (MonadError QErr m)
=> TableCache -> FunctionCache -> m GCtxMap
mkGCtxMap tableCache functionCache = do
typesMapL <- mapM (mkGCtxMapTable tableCache functionCache) $
filter tableFltr $ Map.elems tableCache
let typesMap = foldr (Map.unionWith mappend) Map.empty typesMapL
return $ flip Map.map typesMap $ \(ty, flds, insCtxMap) ->
mkGCtx ty flds insCtxMap
where
tableFltr ti = not (tiSystemDefined ti)
&& isValidObjectName (tiName ti)
getGCtx :: (CacheRM m) => RoleName -> GCtxMap -> m GCtx
getGCtx rn ctxMap = do
sc <- askSchemaCache
return $ fromMaybe (scDefaultRemoteGCtx sc) $ Map.lookup rn ctxMap
mergeMaybeMaps
:: (Eq k, Hashable k)
=> Maybe (Map.HashMap k v)
-> Maybe (Map.HashMap k v)
-> Maybe (Map.HashMap k v)
mergeMaybeMaps m1 m2 = case (m1, m2) of
(Nothing, Nothing) -> Nothing
(Just m1', Nothing) -> Just m1'
(Nothing, Just m2') -> Just m2'
(Just m1', Just m2') -> Just $ Map.union m1' m2'
-- pretty print GCtx
ppGCtx :: GCtx -> String
ppGCtx gCtx =
"GCtx ["
<> "\n types = " <> show types
<> "\n query root = " <> show qRoot
<> "\n mutation root = " <> show mRoot
<> "\n subscription root = " <> show sRoot
<> "\n]"
where
types = map (G.unName . G.unNamedType) $ Map.keys $ _gTypes gCtx
qRoot = (,) (_otiName qRootO) $
map G.unName $ Map.keys $ _otiFields qRootO
mRoot = (,) (_otiName <$> mRootO) $
maybe [] (map G.unName . Map.keys . _otiFields) mRootO
sRoot = (,) (_otiName <$> sRootO) $
maybe [] (map G.unName . Map.keys . _otiFields) sRootO
qRootO = _gQueryRoot gCtx
mRootO = _gMutRoot gCtx
sRootO = _gSubRoot gCtx