2022-04-29 05:13:13 +03:00
|
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
|
|
|
|
module Hasura.SQL.BackendMap
|
|
|
|
( BackendMap,
|
Move, document, and prune action types and custom types types.
### Description
This PR is a first step in a series of cleanups of action relationships. This first step does not contain any behavioral change, and it simply reorganizes / prunes / rearranges / documents the code. Mainly:
- it divides some files in RQL.Types between metadata types, schema cache types, execution types;
- it renames some types for consistency;
- it minimizes exports and prunes unnecessary types;
- it moves some types in places where they make more sense;
- it replaces uses of `DMap BackendTag` with `BackendMap`.
Most of the "movement" within files re-organizes declarations in a "top-down" fashion, by moving all TH splices to the end of the file, which avoids order or declarations mattering.
### Optional list types
One main type change this PR makes is a replacement of variant list types in `CustomTypes.hs`; we had `Maybe [a]`, or sometimes `Maybe (NonEmpty a)`. This PR harmonizes all of them to `[a]`, as most of the code would use them as such, by doing `fromMaybe []` or `maybe [] toList`.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4613
GitOrigin-RevId: bc624e10df587eba862ff27a5e8021b32d0d78a2
2022-06-07 18:43:34 +03:00
|
|
|
singleton,
|
2022-04-29 05:13:13 +03:00
|
|
|
lookup,
|
2022-09-14 15:59:37 +03:00
|
|
|
lookupD,
|
2022-04-29 05:13:13 +03:00
|
|
|
elems,
|
2022-08-18 01:13:32 +03:00
|
|
|
alter,
|
|
|
|
modify,
|
2022-10-20 15:45:31 +03:00
|
|
|
overridesDeeply,
|
2022-04-29 05:13:13 +03:00
|
|
|
)
|
|
|
|
where
|
|
|
|
|
2022-08-18 01:13:32 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
import Data.Aeson (FromJSON, Key, ToJSON)
|
|
|
|
import Data.Aeson qualified as Aeson
|
|
|
|
import Data.Aeson.Key qualified as Key
|
|
|
|
import Data.Aeson.KeyMap qualified as KeyMap
|
2022-09-14 15:59:37 +03:00
|
|
|
import Data.Data
|
2022-04-29 05:13:13 +03:00
|
|
|
import Data.Kind (Type)
|
|
|
|
import Data.Map.Strict (Map)
|
|
|
|
import Data.Map.Strict qualified as Map
|
|
|
|
import Data.Text.Extended (toTxt)
|
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 19:33:56 +03:00
|
|
|
import Hasura.Incremental.Internal.Dependency (Dependency (..), selectD)
|
2022-09-14 15:59:37 +03:00
|
|
|
import Hasura.Incremental.Select
|
2022-08-18 01:13:32 +03:00
|
|
|
import Hasura.Prelude hiding (empty, lookup, modify)
|
2022-10-20 15:45:31 +03:00
|
|
|
import Hasura.SQL.AnyBackend (AnyBackend, SatisfiesForAllBackends, dispatchAnyBackend'', mergeAnyBackend, mkAnyBackend, parseAnyBackendFromJSON, unpackAnyBackend)
|
2022-04-29 05:13:13 +03:00
|
|
|
import Hasura.SQL.Backend (BackendType, parseBackendTypeFromText)
|
2022-09-14 15:59:37 +03:00
|
|
|
import Hasura.SQL.Tag (BackendTag, HasTag, backendTag, reify)
|
2022-04-29 05:13:13 +03:00
|
|
|
|
2022-08-18 01:13:32 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2022-04-29 05:13:13 +03:00
|
|
|
-- | A BackendMap is a data structure that can contain at most one value of an 'i' per 'BackendType'
|
|
|
|
-- The 'i' type must be one that is parameterized by a BackendType-kinded type parameter
|
|
|
|
newtype BackendMap (i :: BackendType -> Type) = BackendMap (Map BackendType (AnyBackend i))
|
|
|
|
deriving stock (Generic)
|
|
|
|
deriving newtype (Semigroup, Monoid)
|
|
|
|
|
|
|
|
deriving newtype instance i `SatisfiesForAllBackends` Show => Show (BackendMap i)
|
|
|
|
|
|
|
|
deriving newtype instance i `SatisfiesForAllBackends` Eq => Eq (BackendMap i)
|
|
|
|
|
2022-08-18 01:13:32 +03:00
|
|
|
instance i `SatisfiesForAllBackends` FromJSON => FromJSON (BackendMap i) where
|
|
|
|
parseJSON =
|
|
|
|
Aeson.withObject "BackendMap" $ \obj -> do
|
|
|
|
BackendMap . Map.fromList
|
|
|
|
<$> traverse
|
|
|
|
( \(backendTypeStr, val) -> do
|
|
|
|
backendType <- parseBackendTypeFromText $ Key.toText backendTypeStr
|
|
|
|
(backendType,) <$> parseAnyBackendFromJSON backendType val
|
|
|
|
)
|
|
|
|
(KeyMap.toList obj)
|
|
|
|
|
|
|
|
instance i `SatisfiesForAllBackends` ToJSON => ToJSON (BackendMap i) where
|
|
|
|
toJSON (BackendMap backendMap) =
|
|
|
|
Aeson.object $ valueToPair <$> Map.elems backendMap
|
|
|
|
where
|
|
|
|
valueToPair :: AnyBackend i -> (Key, Aeson.Value)
|
|
|
|
valueToPair value = dispatchAnyBackend'' @ToJSON @HasTag value $ \(v :: i b) ->
|
|
|
|
let backendTypeText = Key.fromText . toTxt . reify $ backendTag @b
|
|
|
|
in (backendTypeText, Aeson.toJSON v)
|
|
|
|
|
2022-09-14 15:59:37 +03:00
|
|
|
instance Select (BackendMap i) where
|
|
|
|
type Selector (BackendMap i) = BackendMapS i
|
|
|
|
select (BackendMapS (_ :: BackendTag b)) = lookup @b
|
|
|
|
|
|
|
|
data BackendMapS i a where
|
|
|
|
BackendMapS :: forall (b :: BackendType) (i :: BackendType -> Type). HasTag b => BackendTag b -> BackendMapS i (Maybe (i b))
|
|
|
|
|
|
|
|
instance GEq (BackendMapS i) where
|
|
|
|
BackendMapS a `geq` BackendMapS b = case a `geq` b of
|
|
|
|
Just Refl -> Just Refl
|
|
|
|
Nothing -> Nothing
|
|
|
|
|
|
|
|
instance GCompare (BackendMapS i) where
|
|
|
|
BackendMapS a `gcompare` BackendMapS b = case a `gcompare` b of
|
|
|
|
GLT -> GLT
|
|
|
|
GEQ -> GEQ
|
|
|
|
GGT -> GGT
|
|
|
|
|
|
|
|
lookupD ::
|
|
|
|
forall (b :: BackendType) (i :: BackendType -> Type).
|
|
|
|
HasTag b =>
|
|
|
|
Dependency (BackendMap i) ->
|
|
|
|
Dependency (Maybe (i b))
|
|
|
|
lookupD = selectD (BackendMapS (backendTag @b))
|
|
|
|
|
2022-08-18 01:13:32 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
Move, document, and prune action types and custom types types.
### Description
This PR is a first step in a series of cleanups of action relationships. This first step does not contain any behavioral change, and it simply reorganizes / prunes / rearranges / documents the code. Mainly:
- it divides some files in RQL.Types between metadata types, schema cache types, execution types;
- it renames some types for consistency;
- it minimizes exports and prunes unnecessary types;
- it moves some types in places where they make more sense;
- it replaces uses of `DMap BackendTag` with `BackendMap`.
Most of the "movement" within files re-organizes declarations in a "top-down" fashion, by moving all TH splices to the end of the file, which avoids order or declarations mattering.
### Optional list types
One main type change this PR makes is a replacement of variant list types in `CustomTypes.hs`; we had `Maybe [a]`, or sometimes `Maybe (NonEmpty a)`. This PR harmonizes all of them to `[a]`, as most of the code would use them as such, by doing `fromMaybe []` or `maybe [] toList`.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4613
GitOrigin-RevId: bc624e10df587eba862ff27a5e8021b32d0d78a2
2022-06-07 18:43:34 +03:00
|
|
|
singleton :: forall b i. HasTag b => i b -> BackendMap i
|
|
|
|
singleton value = BackendMap $ Map.singleton (reify $ backendTag @b) (mkAnyBackend value)
|
|
|
|
|
2022-04-29 05:13:13 +03:00
|
|
|
-- | Get a value from the map for the particular 'BackendType' 'b'. This function
|
|
|
|
-- is usually used with a type application.
|
|
|
|
-- @
|
|
|
|
-- lookup @('Postgres 'Vanilla) backendMap
|
|
|
|
-- @
|
|
|
|
lookup :: forall (b :: BackendType) i. HasTag b => BackendMap i -> Maybe (i b)
|
|
|
|
lookup (BackendMap backendMap) =
|
|
|
|
Map.lookup (reify $ backendTag @b) backendMap >>= unpackAnyBackend @b
|
|
|
|
|
|
|
|
-- | Get all values in the map
|
|
|
|
elems :: forall i. BackendMap i -> [AnyBackend i]
|
|
|
|
elems (BackendMap backendMap) = Map.elems backendMap
|
|
|
|
|
2022-08-18 01:13:32 +03:00
|
|
|
-- | The expression @modify f bmap@ alters the value @x@ at
|
|
|
|
-- @b@. @modify@ is a restricted version of 'alter' which cannot
|
|
|
|
-- delete entries and if there is no @b@ key present in the map, it
|
|
|
|
-- will apply the modification function to the @i b@ unit value and
|
|
|
|
-- insert the result at @b@.
|
|
|
|
modify :: forall b i. (HasTag b, Monoid (i b)) => (i b -> i b) -> BackendMap i -> BackendMap i
|
|
|
|
modify f = alter \case
|
|
|
|
Nothing -> Just $ f mempty
|
|
|
|
Just ab -> Just $ f ab
|
2022-04-29 05:13:13 +03:00
|
|
|
|
2022-08-18 01:13:32 +03:00
|
|
|
-- | The expression @alter f bmap@ alters the value @x@ at @b@, or
|
|
|
|
-- absence thereof. alter can be used to insert, delete, or update a
|
|
|
|
-- value in a Map.
|
|
|
|
--
|
|
|
|
-- In short : @lookup k (alter f k m) = f (lookup k m)@.
|
|
|
|
alter :: forall b i. HasTag b => (Maybe (i b) -> Maybe (i b)) -> BackendMap i -> BackendMap i
|
|
|
|
alter f (BackendMap bmap) = BackendMap $ Map.alter (wrap . f . unwrap) (reify @b backendTag) bmap
|
|
|
|
where
|
|
|
|
wrap :: Maybe (i b) -> Maybe (AnyBackend i)
|
|
|
|
wrap = fmap mkAnyBackend
|
|
|
|
|
|
|
|
unwrap :: Maybe (AnyBackend i) -> Maybe (i b)
|
|
|
|
unwrap x = x >>= unpackAnyBackend @b
|
2022-10-20 15:45:31 +03:00
|
|
|
|
|
|
|
-- | The expression @a `overridesDeeply b@ applies the values from @a@ on top of the defaults @b@.
|
|
|
|
-- In practice this should union the maps for each backend type.
|
|
|
|
overridesDeeply :: i `SatisfiesForAllBackends` Semigroup => BackendMap i -> BackendMap i -> BackendMap i
|
|
|
|
overridesDeeply (BackendMap a) (BackendMap b) = BackendMap (Map.unionWith override a b)
|
|
|
|
where
|
|
|
|
override a' b' = mergeAnyBackend @Semigroup (<>) a' b' a'
|