graphql-engine/server/src-lib/Hasura/RQL/IR/BoolExp/AggregationPredicates.hs
Auke Booij cdac24c79f server: delete the Cacheable type class in favor of Eq
What is the `Cacheable` type class about?
```haskell
class Eq a => Cacheable a where
  unchanged :: Accesses -> a -> a -> Bool
  default unchanged :: (Generic a, GCacheable (Rep a)) => Accesses -> a -> a -> Bool
  unchanged accesses a b = gunchanged (from a) (from b) accesses
```
Its only method is an alternative to `(==)`. The added value of `unchanged` (and the additional `Accesses` argument) arises _only_ for one type, namely `Dependency`. Indeed, the `Cacheable (Dependency a)` instance is non-trivial, whereas every other `Cacheable` instance is completely boilerplate (and indeed either generated from `Generic`, or simply `unchanged _ = (==)`). The `Cacheable (Dependency a)` instance is the only one where the `Accesses` argument is not just passed onwards.

The only callsite of the `unchanged` method is in the `ArrowCache (Rule m)` method. That is to say that the `Cacheable` type class is used to decide when we can re-use parts of the schema cache between Metadata operations.

So what is the `Cacheable (Dependency a)` instance about? Normally, the output of a `Rule m a b` is re-used when the new input (of type `a`) is equal to the old one. But sometimes, that's too coarse: it might be that a certain `Rule m a b` only depends on a small part of its input of type `a`. A `Dependency` allows us to spell out what parts of `a` are being depended on, and these parts are recorded as values of types `Access a` in the state `Accesses`.

If the input `a` changes, but not in a way that touches the recorded `Accesses`, then the output `b` of that rule can be re-used without recomputing.

So now you understand _why_ we're passing `Accesses` to the `unchanged` method: `unchanged` is an equality check in disguise that just needs some additional context.

But we don't need to pass `Accesses` as a function argument. We can use the `reflection` package to pass it as type-level context. So the core of this PR is that we change the instance declaration from
```haskell
instance (Cacheable a) => Cacheable (Dependency a) where
```
to
```haskell
 instance (Given Accesses, Eq a) => Eq (Dependency a) where
```
and use `(==)` instead of `unchanged`.

If you haven't seen `reflection` before: it's like a `MonadReader`, but it doesn't require a `Monad`.

In order to pass the current `Accesses` value, instead of simply passing the `Accesses` as a function argument, we need to instantiate the `Given Accesses` context. We use the `give` method from the `reflection` package for that.
```haskell
give :: forall r. Accesses -> (Given Accesses => r) -> r

unchanged :: (Given Accesses => Eq a) => Accesses -> a -> a -> Bool
unchanged accesses a b = give accesses (a == b)
```
With these three components in place, we can delete the `Cacheable` type class entirely.

The remainder of this PR is just to remove the `Cacheable` type class and its instances.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6877
GitOrigin-RevId: 7125f5e11d856e7672ab810a23d5bf5ad176e77f
2022-11-21 16:35:37 +00:00

172 lines
4.9 KiB
Haskell

{-# LANGUAGE UndecidableInstances #-}
-- | This module contains the default types and function that model aggregation
-- predicates.
module Hasura.RQL.IR.BoolExp.AggregationPredicates
( AggregationPredicatesImplementation (..),
AggregationPredicate (..),
AggregationPredicateArguments (..),
)
where
import Data.Aeson
import Data.Aeson.Extended (ToJSONKeyValue (..))
import Data.Aeson.Key (fromText)
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp (AnnBoolExp, OpExpG)
import Hasura.RQL.Types.Backend (Backend (Column))
import Hasura.RQL.Types.Backend qualified as B
import Hasura.RQL.Types.Relationships.Local (RelInfo)
import Hasura.SQL.Backend (BackendType)
-- | This type the default non-empty implementation of the
-- 'AggregationPredicates' type family of 'class Backend'.
--
-- This represents an _applied_ aggregation predicate, i.e. _not_ an aggegation
-- function in isolation.
--
-- In the default schema implementation, this type results from parsing graphql such as:
--
-- table(_where(
-- <relation>_aggregate:
-- {<functionname>: {
-- arguments: <arguments>,
-- predicate: <predicate>,
-- distinct: bool
-- }
-- }
-- ))
-- { ... }
--
-- Note that we make no attempt at modelling window functions or so-called
-- 'analytical' functions such as 'percentile_cont'.
data AggregationPredicatesImplementation (b :: BackendType) field = AggregationPredicatesImplementation
{ aggRelation :: RelInfo b,
aggRowPermission :: AnnBoolExp b field,
aggPredicate :: AggregationPredicate b field
}
deriving stock (Foldable, Traversable, Functor, Generic)
deriving instance
( B.Backend b,
Eq (AggregationPredicate b field),
Eq (AnnBoolExp b field)
) =>
Eq (AggregationPredicatesImplementation b field)
deriving instance
( B.Backend b,
Show (AggregationPredicate b field),
Show (AnnBoolExp b field)
) =>
Show (AggregationPredicatesImplementation b field)
instance
( B.Backend b,
Hashable (AggregationPredicate b field),
Hashable (AnnBoolExp b field)
) =>
Hashable (AggregationPredicatesImplementation b field)
instance
( B.Backend b,
NFData (AggregationPredicate b field),
NFData (AnnBoolExp b field)
) =>
NFData (AggregationPredicatesImplementation b field)
instance
( ToJSON (AnnBoolExp b field),
ToJSON (AggregationPredicate b field),
ToJSON (OpExpG b field),
B.Backend b
) =>
ToJSON (AggregationPredicatesImplementation b field)
instance
( Backend b,
ToJSONKeyValue (AggregationPredicate b field)
) =>
ToJSONKeyValue (AggregationPredicatesImplementation b field)
where
toJSONKeyValue AggregationPredicatesImplementation {..} =
( fromText "aggregation_predicates",
toJSON
[ ("predicate" :: Text, toJSON $ toJSONKeyValue aggPredicate),
("relation", toJSON aggRelation)
]
)
data AggregationPredicate (b :: BackendType) field = AggregationPredicate
{ aggPredFunctionName :: Text,
aggPredDistinct :: Bool,
aggPredFilter :: Maybe (AnnBoolExp b field),
aggPredArguments :: AggregationPredicateArguments b,
aggPredPredicate :: [OpExpG b field]
}
deriving stock (Foldable, Traversable, Functor, Generic)
deriving instance
( B.Backend b,
Eq (AnnBoolExp b field),
Eq (OpExpG b field),
Eq (AggregationPredicateArguments b)
) =>
Eq (AggregationPredicate b field)
deriving instance
( B.Backend b,
Show (AnnBoolExp b field),
Show (OpExpG b field),
Show (AggregationPredicateArguments b)
) =>
Show (AggregationPredicate b field)
instance
( B.Backend b,
Hashable (B.BooleanOperators b field),
Hashable (AnnBoolExp b field),
Hashable field
) =>
Hashable (AggregationPredicate b field)
instance
( B.Backend b,
NFData (AggregationPredicateArguments b),
NFData (AnnBoolExp b field),
NFData (OpExpG b field)
) =>
NFData (AggregationPredicate b field)
instance
( ToJSON (AggregationPredicateArguments b),
ToJSON (AnnBoolExp b field),
ToJSONKeyValue (OpExpG b field)
) =>
ToJSONKeyValue (AggregationPredicate b field)
where
toJSONKeyValue AggregationPredicate {..} =
( fromText aggPredFunctionName,
toJSON
[ ("aggPredDistinct" :: Text, toJSON aggPredDistinct),
("aggPredFilter", toJSON aggPredFilter),
("aggPredArguments", toJSON aggPredArguments),
("aggPredPredicate", toJSON (map toJSONKeyValue aggPredPredicate))
]
)
data AggregationPredicateArguments (b :: BackendType)
= AggregationPredicateArgumentsStar
| AggregationPredicateArguments (NonEmpty (Column b))
deriving stock (Generic)
deriving instance B.Backend b => Eq (AggregationPredicateArguments b)
deriving instance B.Backend b => Show (AggregationPredicateArguments b)
instance Backend b => Hashable (AggregationPredicateArguments b)
instance Backend b => NFData (AggregationPredicateArguments b)
instance (Backend b) => ToJSON (AggregationPredicateArguments b)