Add schema implementation for Aggregation Predicates

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5259
GitOrigin-RevId: f53e310951fa4eb7570006d8c616398a98078632
This commit is contained in:
Philip Lykke Carlsen 2022-08-22 15:57:46 +00:00 committed by hasura-bot
parent 967bdcc5d5
commit 4431fb5ea9
15 changed files with 307 additions and 27 deletions

View File

@ -815,6 +815,7 @@ library
, Hasura.GraphQL.Schema.Action
, Hasura.GraphQL.Schema.Backend
, Hasura.GraphQL.Schema.BoolExp
, Hasura.GraphQL.Schema.BoolExp.AggregationPredicates
, Hasura.GraphQL.Schema.Build
, Hasura.GraphQL.Schema.Common
, Hasura.GraphQL.Schema.Instances

View File

@ -55,7 +55,9 @@ import Language.GraphQL.Draft.Syntax qualified as G
-- @tablename@ defined /and/ these grant non-empty column permissions.
ifMatchedFieldParser ::
forall r m n.
MonadBuildSchema 'MSSQL r m n =>
( MonadBuildSchema 'MSSQL r m n,
AggregationPredicatesSchema 'MSSQL
) =>
SourceInfo 'MSSQL ->
TableInfo 'MSSQL ->
m (InputFieldsParser n (Maybe (IfMatched (UnpreparedValue 'MSSQL))))
@ -66,7 +68,9 @@ ifMatchedFieldParser sourceInfo tableInfo = do
-- | Parse a @tablename_if_matched@ object.
ifMatchedObjectParser ::
forall r m n.
MonadBuildSchema 'MSSQL r m n =>
( MonadBuildSchema 'MSSQL r m n,
AggregationPredicatesSchema 'MSSQL
) =>
SourceInfo 'MSSQL ->
TableInfo 'MSSQL ->
m (Maybe (Parser 'Input n (IfMatched (UnpreparedValue 'MSSQL))))

View File

@ -56,7 +56,9 @@ import Language.GraphQL.Draft.Syntax qualified as G
-- enum. See <https://github.com/hasura/graphql-engine/issues/6804>.
onConflictFieldParser ::
forall pgKind r m n.
MonadBuildSchema ('Postgres pgKind) r m n =>
( MonadBuildSchema ('Postgres pgKind) r m n,
AggregationPredicatesSchema ('Postgres pgKind)
) =>
SourceInfo ('Postgres pgKind) ->
TableInfo ('Postgres pgKind) ->
m (InputFieldsParser n (Maybe (IR.OnConflictClause ('Postgres pgKind) (IR.UnpreparedValue ('Postgres pgKind)))))
@ -73,7 +75,9 @@ onConflictFieldParser sourceInfo tableInfo = do
-- | Create a parser for the @_on_conflict@ object of the given table.
conflictObjectParser ::
forall pgKind r m n.
MonadBuildSchema ('Postgres pgKind) r m n =>
( MonadBuildSchema ('Postgres pgKind) r m n,
AggregationPredicatesSchema ('Postgres pgKind)
) =>
SourceInfo ('Postgres pgKind) ->
TableInfo ('Postgres pgKind) ->
Maybe (UpdPermInfo ('Postgres pgKind)) ->

View File

@ -23,6 +23,7 @@ import Hasura.Backends.Postgres.Types.ComputedField qualified as PG
import Hasura.Backends.Postgres.Types.Function qualified as PG
import Hasura.Base.Error
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.BoolExp
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.Options qualified as Options
import Hasura.GraphQL.Schema.Parser
@ -146,6 +147,7 @@ selectFunctionAggregate mkRootFieldName sourceInfo fi@FunctionInfo {..} descript
selectFunctionConnection ::
forall pgKind r m n.
( MonadBuildSchema ('Postgres pgKind) r m n,
AggregationPredicatesSchema ('Postgres pgKind),
BackendTableSelectSchema ('Postgres pgKind)
) =>
MkRootFieldName ->
@ -194,8 +196,9 @@ selectFunctionConnection mkRootFieldName sourceInfo fi@FunctionInfo {..} descrip
-- | Computed field parser
computedFieldPG ::
forall pgKind r m n.
MonadBuildSchema ('Postgres pgKind) r m n =>
BackendTableSelectSchema ('Postgres pgKind) =>
( MonadBuildSchema ('Postgres pgKind) r m n,
BackendTableSelectSchema ('Postgres pgKind)
) =>
SourceInfo ('Postgres pgKind) ->
ComputedFieldInfo ('Postgres pgKind) ->
TableName ('Postgres pgKind) ->

View File

@ -340,7 +340,7 @@ type ComparisonExp b = OpExpG b (UnpreparedValue b)
--
-- * Pro: You can specify both shared and diverging behavior.
-- * Pro: You can specify a lot of behavior implicitly, i.e. it's easy to write.
-- * Con: You can specify a lot of behavior implicitly, i.e. it's hard do
-- * Con: You can specify a lot of behavior implicitly, i.e. it's hard to
-- understand without tracing through implementations.
-- * Con: You get a proliferation of type class methods and it's difficult to
-- understand how they fit together.
@ -355,7 +355,7 @@ type ComparisonExp b = OpExpG b (UnpreparedValue b)
-- instead of other type class methods.
--
-- When we do this, the function call sites (which will often be in @instance
-- BackendSchema ...@) becomes the centralised place where we decide which behavior
-- BackendSchema ...@) become the centralised places where we decide which behavior
-- variation to follow.
--
-- When faced with answering the question of "what does this method do, and how does

View File

@ -2,7 +2,8 @@
{-# LANGUAGE TemplateHaskell #-}
module Hasura.GraphQL.Schema.BoolExp
( boolExp,
( AggregationPredicatesSchema (..),
boolExp,
mkBoolOperator,
equalityOperators,
comparisonOperators,
@ -15,7 +16,7 @@ import Data.Text.Casing qualified as C
import Data.Text.Extended
import Hasura.GraphQL.Parser.Class
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.Common (SchemaContext (..), askTableInfo, partialSQLExpToUnpreparedValue, retrieve)
import Hasura.GraphQL.Schema.Common (MonadBuildSchemaBase, SchemaContext (..), askTableInfo, partialSQLExpToUnpreparedValue, retrieve)
import Hasura.GraphQL.Schema.NamingCase
import Hasura.GraphQL.Schema.Options qualified as Options
import Hasura.GraphQL.Schema.Parser
@ -39,8 +40,35 @@ import Hasura.RQL.Types.SchemaCache hiding (askTableInfo)
import Hasura.RQL.Types.Source
import Hasura.RQL.Types.SourceCustomization
import Hasura.RQL.Types.Table
import Hasura.SQL.Backend (BackendType)
import Language.GraphQL.Draft.Syntax qualified as G
-- | Backends implement this type class to specify the schema of
-- aggregation predicates.
--
-- The default implementation results in a parser that does not parse anything.
--
-- The scope of this class is local to the function 'boolExp'. In particular,
-- methods in `class BackendSchema` and `type MonadBuildSchema` should *NOT*
-- include this class as a constraint.
class AggregationPredicatesSchema (b :: BackendType) where
aggregationPredicatesParser ::
forall r m n.
MonadBuildSchemaBase r m n =>
SourceInfo b ->
TableInfo b ->
m (Maybe (InputFieldsParser n [AggregationPredicates b (UnpreparedValue b)]))
-- Overlapping instance for backends that do not implement Aggregation Predicates.
instance {-# OVERLAPPABLE #-} (AggregationPredicates b ~ Const Void) => AggregationPredicatesSchema (b :: BackendType) where
aggregationPredicatesParser ::
forall r m n.
(MonadBuildSchemaBase r m n) =>
SourceInfo b ->
TableInfo b ->
m (Maybe (InputFieldsParser n [AggregationPredicates b (UnpreparedValue b)]))
aggregationPredicatesParser _ _ = return Nothing
-- |
-- > input type_bool_exp {
-- > _or: [type_bool_exp!]
@ -51,7 +79,7 @@ import Language.GraphQL.Draft.Syntax qualified as G
-- > }
boolExp ::
forall b r m n.
MonadBuildSchema b r m n =>
(MonadBuildSchema b r m n, AggregationPredicatesSchema b) =>
SourceInfo b ->
TableInfo b ->
m (Parser 'Input n (AnnBoolExp b (UnpreparedValue b)))
@ -66,10 +94,12 @@ boolExp sourceInfo tableInfo = P.memoizeOn 'boolExp (_siName sourceInfo, tableNa
fieldInfos <- tableSelectFields sourceInfo tableInfo
tableFieldParsers <- catMaybes <$> traverse mkField fieldInfos
-- TODO: This naming is somewhat unsatifactory..
aggregationPredicatesParser' <- fromMaybe (pure []) <$> aggregationPredicatesParser sourceInfo tableInfo
recur <- boolExp sourceInfo tableInfo
-- Bafflingly, ApplicativeDo doesnt work if we inline this definition (I
-- think the TH splices throw it off), so we have to define it separately.
let specialFieldParsers =
let connectiveFieldParsers =
[ P.fieldOptional Name.__or Nothing (BoolOr <$> P.list recur),
P.fieldOptional Name.__and Nothing (BoolAnd <$> P.list recur),
P.fieldOptional Name.__not Nothing (BoolNot <$> recur)
@ -78,8 +108,9 @@ boolExp sourceInfo tableInfo = P.memoizeOn 'boolExp (_siName sourceInfo, tableNa
pure $
BoolAnd <$> P.object name (Just description) do
tableFields <- map BoolField . catMaybes <$> sequenceA tableFieldParsers
specialFields <- catMaybes <$> sequenceA specialFieldParsers
pure (tableFields ++ specialFields)
specialFields <- catMaybes <$> sequenceA connectiveFieldParsers
aggregationPredicateFields <- map (BoolField . AVAggregationPredicates) <$> aggregationPredicatesParser'
pure (tableFields ++ specialFields ++ aggregationPredicateFields)
where
tableName = tableInfoName tableInfo

View File

@ -0,0 +1,167 @@
{-# LANGUAGE ApplicativeDo #-}
-- | This module defines the schema aspect of the default implementation of
-- aggregation predicates.
module Hasura.GraphQL.Schema.BoolExp.AggregationPredicates
( defaultAggregationPredicatesParser,
-- * Data types describing aggregation functions supported by a backend
FunctionSignature (..),
ArgumentsSignature (..),
ArgumentSignature (..),
)
where
import Data.Functor.Compose
import Data.List.NonEmpty qualified as NE
import Hasura.GraphQL.Parser qualified as P
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.BoolExp
import Hasura.GraphQL.Schema.Common (MonadBuildSchemaBase, askTableInfo, textToName)
import Hasura.GraphQL.Schema.Parser
( InputFieldsParser,
Kind (..),
Parser,
)
import Hasura.GraphQL.Schema.Table
import Hasura.Name qualified as Name
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp.AggregationPredicates
import Hasura.RQL.IR.Value
import Hasura.RQL.Types.Backend qualified as B
import Hasura.RQL.Types.Column
import Hasura.RQL.Types.Common (relNameToTxt)
import Hasura.RQL.Types.Relationships.Local
import Hasura.RQL.Types.SchemaCache hiding (askTableInfo)
import Hasura.RQL.Types.Source
import Hasura.RQL.Types.Table
import Hasura.SQL.Backend (BackendType)
import Language.GraphQL.Draft.Syntax qualified as G
-- | This function is meant to serve as the default schema for Aggregation
-- Predicates represented in the IR by the type
-- 'Hasura.RQL.IR.BoolExp.AggregationPredicates.AggregationPredicates'.
defaultAggregationPredicatesParser ::
forall b r m n.
( MonadBuildSchemaBase r m n,
BackendSchema b,
AggregationPredicatesSchema b
) =>
[FunctionSignature b] ->
SourceInfo b ->
TableInfo b ->
m (Maybe (InputFieldsParser n [AggregationPredicatesImplementation b (UnpreparedValue b)]))
defaultAggregationPredicatesParser aggFns si ti = runMaybeT do
arrayRelationships <- fails $ return $ nonEmpty $ tableArrayRelationships ti
aggregationFunctions <- fails $ return $ nonEmpty aggFns
buildAnyOptionalFields $
arrayRelationships <&> \rel -> do
relTable <- askTableInfo si (riRTable rel)
relGqlName <- textToName $ relNameToTxt $ riName rel
typeGqlName <- (<> Name.__ <> relGqlName) <$> getTableGQLName relTable
-- We only make a field for aggregations over a relation if at least
-- some aggregation predicates are callable.
relAggregateField rel relGqlName typeGqlName
-- We only return an InputFieldsParser for aggregation predicates,
-- if we parse at least one aggregation predicate
<$> buildAnyOptionalFields
( aggregationFunctions <&> \FunctionSignature {..} -> do
aggPredicateField fnGQLName typeGqlName <$> unfuse do
aggPredArguments <-
-- We only include an aggregation predicate if we are able to
-- access columns all its arguments. This might fail due to
-- permissions or due to no columns of suitable types
-- existing on the table.
case fnArguments of
ArgumentsStar ->
maybe AggregationPredicateArgumentsStar AggregationPredicateArguments . nonEmpty
<$> fuse (fieldOptionalDefault Name._arguments Nothing [] . P.list <$> fails (tableSelectColumnsEnum si relTable))
Arguments args ->
AggregationPredicateArguments
<$> fuse
( P.field Name._arguments Nothing
. P.object (typeGqlName <> Name.__ <> fnGQLName <> Name.__ <> Name._arguments) Nothing
<$> buildAllFieldsNE
( args `for` \ArgumentSignature {..} ->
P.field argName Nothing <$> fails (tableSelectColumnsPredEnum (== (ColumnScalar argType)) relGqlName si relTable)
)
)
aggPredDistinct <- fuse $ return $ fieldOptionalDefault Name._distinct Nothing False P.boolean
let aggPredFunctionName = fnName
aggPredPredicate <- fuse $ P.field Name._predicate Nothing <$> lift (comparisonExps @b (ColumnScalar fnReturnType))
aggPredFilter <- fuse $ P.fieldOptional Name._filter Nothing <$> lift (boolExp si relTable)
pure $ AggregationPredicate {..}
)
where
-- Input field of the aggregation predicates for one array relation.
relAggregateField ::
RelInfo b ->
G.Name ->
G.Name ->
(InputFieldsParser n [AggregationPredicate b (UnpreparedValue b)]) ->
(InputFieldsParser n (Maybe (AggregationPredicatesImplementation b (UnpreparedValue b))))
relAggregateField rel typeGqlName relGqlName =
P.fieldOptional (relGqlName <> Name.__ <> Name._aggregate) Nothing
. P.object typeGqlName Nothing
. fmap (AggregationPredicatesImplementation rel)
-- Input field for a single aggregation predicate.
aggPredicateField ::
G.Name ->
G.Name ->
InputFieldsParser n (AggregationPredicate b (UnpreparedValue b)) ->
InputFieldsParser n (Maybe (AggregationPredicate b (UnpreparedValue b)))
aggPredicateField fnGQLName typeGqlName =
P.fieldOptional fnGQLName Nothing . P.object (typeGqlName <> Name.__ <> fnGQLName) Nothing
buildAnyOptionalFields :: NonEmpty (MaybeT m (InputFieldsParser n (Maybe c))) -> MaybeT m (InputFieldsParser n [c])
buildAnyOptionalFields = fmap collectOptionalFields . collectBranchesNE
where
-- Collect a non-empty list of optional input field parsers into one input field
-- parser parsing a list of the specified values.
collectOptionalFields :: NonEmpty (InputFieldsParser n (Maybe a)) -> InputFieldsParser n [a]
collectOptionalFields = fmap (catMaybes . NE.toList) . sequenceA
buildAllFieldsNE :: MaybeT m (NonEmpty (InputFieldsParser n c)) -> MaybeT m (InputFieldsParser n (NonEmpty c))
buildAllFieldsNE = fmap sequenceA
-- Collect all the non-failed branches, failing if all branches failed.
collectBranchesNE :: forall f a. Applicative f => NonEmpty (MaybeT f a) -> MaybeT f (NonEmpty a)
collectBranchesNE xs = MaybeT $ NE.nonEmpty . catMaybes . NE.toList <$> sequenceA (xs <&> runMaybeT)
-- Mark a computation as potentially failing.
fails :: m (Maybe a) -> MaybeT m a
fails = MaybeT
-- Compose our monad with InputFieldsParser into one fused Applicative that
-- acts on the parsed values directly.
fuse :: MaybeT m (InputFieldsParser n a) -> Compose (MaybeT m) (InputFieldsParser n) a
fuse = Compose
-- The inverse of 'fuse'.
unfuse :: Compose (MaybeT m) (InputFieldsParser n) a -> MaybeT m (InputFieldsParser n a)
unfuse = getCompose
-- Optional input field with a default value when the field is elided or null.
fieldOptionalDefault ::
forall k a. ('Input P.<: k) => G.Name -> Maybe G.Description -> a -> Parser k n a -> InputFieldsParser n a
fieldOptionalDefault n d a p = fromMaybe a <$> P.fieldOptional n d p
data FunctionSignature (b :: BackendType) = FunctionSignature
{ fnName :: Text,
fnGQLName :: G.Name,
fnArguments :: ArgumentsSignature b,
fnReturnType :: B.ScalarType b
}
data ArgumentsSignature (b :: BackendType)
= ArgumentsStar
| Arguments (NonEmpty (ArgumentSignature b))
data ArgumentSignature (b :: BackendType) = ArgumentSignature
{ argType :: B.ScalarType b,
argName :: G.Name
}

View File

@ -59,6 +59,7 @@ import Data.Text.Casing qualified as C
import Data.Text.Extended
import Hasura.GraphQL.ApolloFederation
import Hasura.GraphQL.Schema.Backend (BackendTableSelectSchema (..), MonadBuildSchema)
import Hasura.GraphQL.Schema.BoolExp (AggregationPredicatesSchema)
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.Mutation
import Hasura.GraphQL.Schema.NamingCase
@ -104,6 +105,7 @@ setFieldNameCase tCase tInfo crf getFieldName tableName =
buildTableQueryAndSubscriptionFields ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b,
BackendTableSelectSchema b
) =>
MkRootFieldName ->
@ -200,6 +202,7 @@ buildTableQueryAndSubscriptionFields mkRootFieldName sourceInfo tableName tableI
buildTableStreamingSubscriptionFields ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b,
BackendTableSelectSchema b
) =>
MkRootFieldName ->
@ -276,6 +279,7 @@ buildTableInsertMutationFields backendInsertAction mkRootFieldName scenario sour
buildTableUpdateMutationFields ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b,
BackendTableSelectSchema b
) =>
-- | an action that builds @BackendUpdate@ with the
@ -319,6 +323,7 @@ buildTableUpdateMutationFields mkBackendUpdate mkRootFieldName scenario sourceIn
buildTableDeleteMutationFields ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b,
BackendTableSelectSchema b
) =>
MkRootFieldName ->

View File

@ -353,6 +353,7 @@ mkInsertObject objects tableInfo backendInsert insertPerms updatePerms =
deleteFromTable ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b,
BackendTableSelectSchema b
) =>
Scenario ->

View File

@ -149,7 +149,8 @@ defaultSelectTable sourceInfo tableInfo fieldName description = runMaybeT do
selectTableConnection ::
forall b r m n.
( MonadBuildSchema b r m n,
BackendTableSelectSchema b
BackendTableSelectSchema b,
AggregationPredicatesSchema b
) =>
SourceInfo b ->
-- | table info
@ -368,9 +369,10 @@ cause errors on the client side, for the following reasons:
-- > }
defaultTableSelectionSet ::
forall b r m n.
( MonadBuildSchema b r m n,
( AggregationPredicatesSchema b,
BackendTableSelectSchema b,
Eq (AnnBoolExp b (IR.UnpreparedValue b))
Eq (AnnBoolExp b (IR.UnpreparedValue b)),
MonadBuildSchema b r m n
) =>
SourceInfo b ->
TableInfo b ->
@ -582,7 +584,7 @@ tableConnectionSelectionSet sourceInfo tableInfo = runMaybeT do
-- > where: table_bool_exp
defaultTableArgs ::
forall b r m n.
MonadBuildSchema b r m n =>
(MonadBuildSchema b r m n, AggregationPredicatesSchema b) =>
SourceInfo b ->
TableInfo b ->
m (InputFieldsParser n (SelectArgs b))
@ -630,7 +632,9 @@ defaultTableArgs sourceInfo tableInfo = do
-- > where: table_bool_exp
tableWhereArg ::
forall b r m n.
MonadBuildSchema b r m n =>
( AggregationPredicatesSchema b,
MonadBuildSchema b r m n
) =>
SourceInfo b ->
TableInfo b ->
m (InputFieldsParser n (Maybe (IR.AnnBoolExp b (IR.UnpreparedValue b))))
@ -723,7 +727,7 @@ tableOffsetArg =
-- > after: String
tableConnectionArgs ::
forall b r m n.
MonadBuildSchema b r m n =>
(MonadBuildSchema b r m n, AggregationPredicatesSchema b) =>
PrimaryKeyColumns b ->
SourceInfo b ->
TableInfo b ->
@ -971,9 +975,10 @@ tableAggregationFields sourceInfo tableInfo =
-- > field_name(arg_name: arg_type, ...): field_type
fieldSelection ::
forall b r m n.
( MonadBuildSchema b r m n,
( AggregationPredicatesSchema b,
BackendTableSelectSchema b,
Eq (AnnBoolExp b (IR.UnpreparedValue b))
Eq (AnnBoolExp b (IR.UnpreparedValue b)),
MonadBuildSchema b r m n
) =>
SourceInfo b ->
TableName b ->
@ -1124,9 +1129,10 @@ join) satisfies `p(T)`.
-- | Field parsers for a table relationship
relationshipField ::
forall b r m n.
( MonadBuildSchema b r m n,
( AggregationPredicatesSchema b,
BackendTableSelectSchema b,
Eq (AnnBoolExp b (IR.UnpreparedValue b))
Eq (AnnBoolExp b (IR.UnpreparedValue b)),
MonadBuildSchema b r m n
) =>
SourceInfo b ->
TableName b ->

View File

@ -14,6 +14,7 @@ import Data.Text.Extended ((<>>))
import Hasura.Base.Error (QErr)
import Hasura.GraphQL.Parser.Class
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.BoolExp (AggregationPredicatesSchema)
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.NamingCase
import Hasura.GraphQL.Schema.Options qualified as Options
@ -218,7 +219,9 @@ tableStreamCursorArg sourceInfo tableInfo = do
-- > table_stream (cursor: [table_stream_cursor_input]!, batch_size: Int!, where: table_bool_exp)
tableStreamArguments ::
forall b r m n.
MonadBuildSchema b r m n =>
( AggregationPredicatesSchema b,
MonadBuildSchema b r m n
) =>
SourceInfo b ->
TableInfo b ->
m (InputFieldsParser n (SelectStreamArgs b))
@ -241,6 +244,7 @@ tableStreamArguments sourceInfo tableInfo = do
selectStreamTable ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b,
BackendTableSelectSchema b
) =>
SourceInfo b ->

View File

@ -2,6 +2,7 @@
module Hasura.GraphQL.Schema.Table
( getTableGQLName,
tableSelectColumnsEnum,
tableSelectColumnsPredEnum,
tableUpdateColumnsEnum,
updateColumnsPlaceholderParser,
tableSelectPermissions,
@ -16,6 +17,7 @@ where
import Data.Has
import Data.HashMap.Strict qualified as Map
import Data.HashSet qualified as Set
import Data.Text (pack)
import Data.Text.Casing qualified as C
import Data.Text.Extended
import Hasura.Base.Error (QErr)
@ -83,7 +85,7 @@ getTableIdentifierName tableInfo =
-- permissions for.
tableSelectColumnsEnum ::
forall b r m n.
MonadBuildSchema b r m n =>
(Backend b, MonadBuildSchemaBase r m n) =>
SourceInfo b ->
TableInfo b ->
m (Maybe (Parser 'Both n (Column b)))
@ -108,6 +110,42 @@ tableSelectColumnsEnum sourceInfo tableInfo = do
define name =
P.Definition name (Just $ G.Description "column name") Nothing [] P.EnumValueInfo
-- | Table select columns enum of a certain type.
--
-- Parser for an enum type that matches, of a given table, certain columns which
-- satisfy a predicate. Used as a parameter for aggregation predicate
-- arguments, among others. Maps to the table_select_column object.
--
-- Return Nothing if there's no column the current user has "select"
-- permissions for.
tableSelectColumnsPredEnum ::
forall b r m n.
MonadBuildSchema b r m n =>
(ColumnType b -> Bool) ->
G.Name ->
SourceInfo b ->
TableInfo b ->
m (Maybe (Parser 'Both n (Column b)))
tableSelectColumnsPredEnum columnPredicate predName sourceInfo tableInfo = do
tableGQLName <- getTableGQLName @b tableInfo
columns <- filter (columnPredicate . ciType) <$> tableSelectColumns sourceInfo tableInfo
enumName <- mkTypename $ tableGQLName <> Name.__select_column <> Name.__ <> predName
let description =
Just $
G.Description $
pack ("select " ++ show predName ++ "columns of table ") <>> tableInfoName tableInfo
pure $
P.enum enumName description
<$> nonEmpty
[ ( define $ ciName column,
ciColumn column
)
| column <- columns
]
where
define name =
P.Definition name (Just $ G.Description "column name") Nothing [] P.EnumValueInfo
-- | Table update columns enum
--
-- Parser for an enum type that matches the columns of the given

View File

@ -25,7 +25,7 @@ import Data.Text.Extended (toTxt, (<>>))
import Hasura.Base.Error (QErr)
import Hasura.Base.ToErrorValue
import Hasura.GraphQL.Schema.Backend (BackendSchema (..), BackendTableSelectSchema (..), MonadBuildSchema, columnParser)
import Hasura.GraphQL.Schema.BoolExp (boolExp)
import Hasura.GraphQL.Schema.BoolExp (AggregationPredicatesSchema, boolExp)
import Hasura.GraphQL.Schema.Common (Scenario (..), SchemaContext (..), mapField, partialSQLExpToUnpreparedValue, retrieve)
import Hasura.GraphQL.Schema.Mutation (mutationSelectionSet, primaryKeysArguments)
import Hasura.GraphQL.Schema.NamingCase
@ -256,6 +256,7 @@ incOp = UpdateOperator {..}
updateTable ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b,
BackendTableSelectSchema b
) =>
-- | backend-specific data needed to perform an update mutation

View File

@ -606,3 +606,14 @@ __Entity = [G.name|_Entity|]
__entities :: G.Name
__entities = [G.name|_entities|]
-- * Aggregation Predicates
_arguments :: G.Name
_arguments = [G.name|arguments|]
_predicate :: G.Name
_predicate = [G.name|predicate|]
_filter :: G.Name
_filter = [G.name|filter|]

View File

@ -60,6 +60,7 @@ module Hasura.RQL.Types.Table
sortCols,
tableInfoName,
getRolePermInfo,
tableArrayRelationships,
tcCustomName,
tcCustomRootFields,
tcComment,
@ -929,6 +930,9 @@ tiName = tiCoreInfo . tciName
tableInfoName :: TableInfo b -> TableName b
tableInfoName = view tiName
tableArrayRelationships :: TableInfo b -> [RelInfo b]
tableArrayRelationships ti = [rel | rel <- getRels . _tciFieldInfoMap . _tiCoreInfo $ ti, riType rel == ArrRel]
getRolePermInfo :: RoleName -> TableInfo b -> RolePermInfo b
getRolePermInfo role tableInfo
| role == adminRoleName = _tiAdminRolePermInfo tableInfo