forked from hasura/graphql-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDependency.hs
268 lines (232 loc) · 11.3 KB
/
Dependency.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
{-# OPTIONS_HADDOCK not-home #-}
{-# LANGUAGE GADTs #-}
-- | Supporting functionality for fine-grained dependency tracking.
module Hasura.Incremental.Internal.Dependency where
import Hasura.Prelude
import qualified Data.Dependent.Map as DM
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Network.URI.Extended as N
import qualified Data.URL.Template as UT
import Control.Applicative
import Data.Aeson (Value)
import Data.Functor.Classes (Eq1 (..), Eq2 (..))
import Data.GADT.Compare
import Data.Int
import Data.Scientific (Scientific)
import Data.Time.Clock
import Data.Vector (Vector)
import GHC.Generics ((:*:) (..), (:+:) (..), Generic (..), K1 (..),
M1 (..), U1 (..), V1)
import System.Cron.Types
import Data.Time.LocalTime (TimeZone(..))
import Hasura.Incremental.Select
-- | A 'Dependency' represents a value that a 'Rule' can /conditionally/ depend on. A 'Dependency'
-- is created using 'newDependency', and it can be “opened” again using 'dependOn'. What makes a
-- 'Dependency' useful is the way it cooperates with 'cache'---if a 'Dependency' is passed to a
-- cached rule, but that rule (or any of its sub-rules) never “opens” it using 'dependOn', then
-- subsequent executions of the rule will ignore the 'Dependency' when computing whether or not it
-- is necessary to re-execute the rule.
--
-- The above functionality is useful on its own to express conditional dependencies, but even more
-- useful is the ability to express /partial/ dependencies. For example, if a 'Dependency' contains
-- a 'HashMap', a rule can choose to only depend on the value associated with a particular key by
-- using 'selectKeyD' (or the more general 'selectD'). Only the parts that are actually used will be
-- counted when computing whether a rule needs to be re-executed.
data Dependency a = Dependency !(DependencyKey a) !a
instance (Eq a) => Eq (Dependency a) where
Dependency _ a == Dependency _ b = a == b
-- | Applies a 'Selector' to select part of a 'Dependency'.
selectD :: (Select a) => Selector a b -> Dependency a -> Dependency b
selectD k (Dependency dk a) = Dependency (DependencyChild k dk) (select k a)
-- | Selects a single key from a dependency containing a map-like data structure.
selectKeyD :: (Select a, Selector a ~ ConstS k v) => k -> Dependency a -> Dependency v
selectKeyD = selectD . ConstS
-- | Tracks whether a 'Dependency' is a “root” dependency created by 'newDependency' or a “child”
-- dependency created from an existing dependency using 'selectD'.
data DependencyKey a where
DependencyRoot :: !(UniqueS a) -> DependencyKey a
DependencyChild :: (Select a) => !(Selector a b) -> !(DependencyKey a) -> DependencyKey b
instance GEq DependencyKey where
DependencyRoot a `geq` DependencyRoot b
| Just Refl <- a `geq` b
= Just Refl
DependencyChild a1 a2 `geq` DependencyChild b1 b2
| Just Refl <- a2 `geq` b2
, Just Refl <- a1 `geq` b1
= Just Refl
_ `geq` _ = Nothing
instance GCompare DependencyKey where
DependencyRoot a `gcompare` DependencyRoot b = case gcompare a b of
GLT -> GLT
GEQ -> GEQ
GGT -> GGT
DependencyChild a1 a2 `gcompare` DependencyChild b1 b2 = case gcompare a2 b2 of
GLT -> GLT
GEQ -> case gcompare a1 b1 of
GLT -> GLT
GEQ -> GEQ
GGT -> GGT
GGT -> GGT
DependencyRoot _ `gcompare` DependencyChild _ _ = GLT
DependencyChild _ _ `gcompare` DependencyRoot _ = GGT
-- | A typeclass that implements the dependency-checking machinery used by 'cache'. Morally, this
-- class is like 'Eq', but it only checks the parts of a 'Dependency' that were actually accessed on
-- the previous execution. It is highly unlikely you will need to implement any 'Cacheable'
-- instances yourself; the default implementation uses 'Generic' to derive an instance
-- automatically.
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
{-# INLINABLE unchanged #-}
-- | A mapping from root 'Dependency' keys to the accesses made against those dependencies.
newtype Accesses = Accesses { unAccesses :: DM.DMap UniqueS Access }
instance Semigroup Accesses where
Accesses a <> Accesses b = Accesses $ DM.unionWithKey (const (<>)) a b
instance Monoid Accesses where
mempty = Accesses DM.empty
recordAccess :: DependencyKey a -> Access a -> Accesses -> Accesses
recordAccess depKey !access (Accesses accesses) = case depKey of
DependencyRoot rootKey -> Accesses $ DM.insertWith' (<>) rootKey access accesses
DependencyChild selector parentKey ->
recordAccess parentKey (AccessedParts $ DM.singleton selector access) (Accesses accesses)
-- | Records the accesses made within a single 'Dependency' and its children. The 'Semigroup'
-- instance for 'Access' computes a least upper bound:
--
-- * 'AccessedAll' serves as the top of the lattice and records the dependency’s entire value was
-- accessed.
-- * 'AccessedParts' records a set of accesses for individual parts of a dependency.
data Access a where
AccessedAll :: (Cacheable a) => Access a
AccessedParts :: (Select a) => !(DM.DMap (Selector a) Access) -> Access a
instance Semigroup (Access a) where
AccessedAll <> _ = AccessedAll
_ <> AccessedAll = AccessedAll
AccessedParts a <> AccessedParts b = AccessedParts $ DM.unionWithKey (const (<>)) a b
instance (Cacheable a) => Cacheable (Dependency a) where
unchanged accesses (Dependency key1 v1) (Dependency _ v2) =
-- look up which parts of this dependency were previously accessed
case lookupAccess key1 of
-- looking up the access was enough to determine the result
Left result -> result
-- otherwise, look through the accessed children
Right access -> unchangedBy v1 v2 access
where
-- Looks up the Access associated with the given DependencyKey, if it exists.
lookupAccess :: DependencyKey b -> Either Bool (Access b)
lookupAccess = \case
DependencyRoot key -> handleNoAccess $ DM.lookup key (unAccesses accesses)
DependencyChild selector key -> lookupAccess key >>= \case
AccessedAll -> Left (unchanged accesses v1 v2)
AccessedParts parts -> handleNoAccess $ DM.lookup selector parts
where
-- if this dependency was never accessed, then it’s certainly unchanged
handleNoAccess = maybe (Left True) Right
-- Walks the given values guided by the given Access, checking that all the subparts
-- identified by the AccessedAll leaves are unchanged.
unchangedBy :: forall b. b -> b -> Access b -> Bool
unchangedBy a b = \case
AccessedAll -> unchanged accesses a b
AccessedParts parts -> DM.foldrWithKey reduce True parts
where
reduce :: (Select b) => Selector b c -> Access c -> Bool -> Bool
reduce selector = (&&) . unchangedBy (select selector a) (select selector b)
-- -------------------------------------------------------------------------------------------------
-- boilerplate Cacheable instances
instance Cacheable Char where unchanged _ = (==)
instance Cacheable Double where unchanged _ = (==)
instance Cacheable Int where unchanged _ = (==)
instance Cacheable Int32 where unchanged _ = (==)
instance Cacheable Integer where unchanged _ = (==)
instance Cacheable Scientific where unchanged _ = (==)
instance Cacheable Text where unchanged _ = (==)
instance Cacheable N.URIAuth where unchanged _ = (==)
instance Cacheable DiffTime where unchanged _ = (==)
instance Cacheable NominalDiffTime where unchanged _ = (==)
instance Cacheable UTCTime where unchanged _ = (==)
instance Cacheable TimeZone where unchanged _ = (==)
-- instances for CronSchedule from package `cron`
instance Cacheable StepField
instance Cacheable RangeField
instance Cacheable SpecificField
instance Cacheable BaseField
instance Cacheable CronField
instance Cacheable MonthSpec
instance Cacheable DayOfMonthSpec
instance Cacheable DayOfWeekSpec
instance Cacheable HourSpec
instance Cacheable MinuteSpec
instance Cacheable CronSchedule
instance (Cacheable a) => Cacheable (Seq a) where
unchanged = liftEq . unchanged
instance (Cacheable a) => Cacheable (Vector a) where
unchanged = liftEq . unchanged
instance (Cacheable k, Cacheable v) => Cacheable (HashMap k v) where
unchanged accesses = liftEq2 (unchanged accesses) (unchanged accesses)
instance (Cacheable a) => Cacheable (HashSet a) where
unchanged = liftEq . unchanged
instance Cacheable ()
instance (Cacheable a, Cacheable b) => Cacheable (a, b)
instance (Cacheable a, Cacheable b, Cacheable c) => Cacheable (a, b, c)
instance (Cacheable a, Cacheable b, Cacheable c, Cacheable d) => Cacheable (a, b, c, d)
instance (Cacheable a, Cacheable b, Cacheable c, Cacheable d, Cacheable e) => Cacheable (a, b, c, d, e)
instance Cacheable Bool
instance Cacheable Value
instance Cacheable G.Argument
instance Cacheable G.Directive
instance Cacheable G.ExecutableDefinition
instance Cacheable G.Field
instance Cacheable G.FragmentDefinition
instance Cacheable G.FragmentSpread
instance Cacheable G.GType
instance Cacheable G.InlineFragment
instance Cacheable G.Nullability
instance Cacheable G.OperationDefinition
instance Cacheable G.OperationType
instance Cacheable G.Selection
instance Cacheable G.TypedOperationDefinition
instance Cacheable G.Value
instance Cacheable G.ValueConst
instance Cacheable G.VariableDefinition
instance Cacheable N.URI
instance Cacheable UT.Variable
instance Cacheable UT.TemplateItem
instance Cacheable UT.URLTemplate
instance (Cacheable a) => Cacheable (Maybe a)
instance (Cacheable a, Cacheable b) => Cacheable (Either a b)
instance (Cacheable a) => Cacheable [a]
instance (Cacheable a) => Cacheable (NonEmpty a)
instance (Cacheable a) => Cacheable (G.ObjectFieldG a)
deriving instance Cacheable G.Alias
deriving instance Cacheable G.EnumValue
deriving instance Cacheable G.ExecutableDocument
deriving instance Cacheable G.ListType
deriving instance Cacheable G.Name
deriving instance Cacheable G.NamedType
deriving instance Cacheable G.StringValue
deriving instance Cacheable G.Variable
deriving instance Cacheable G.Description
deriving instance (Cacheable a) => Cacheable (G.ListValueG a)
deriving instance (Cacheable a) => Cacheable (G.ObjectValueG a)
class GCacheable f where
gunchanged :: f p -> f p -> Accesses -> Bool
instance GCacheable V1 where
gunchanged a = case a of {}
{-# INLINE gunchanged #-}
instance GCacheable U1 where
gunchanged U1 U1 _ = True
{-# INLINE gunchanged #-}
instance (Cacheable a) => GCacheable (K1 t a) where
gunchanged (K1 a) (K1 b) accesses = unchanged accesses a b
{-# INLINE gunchanged #-}
instance (GCacheable f) => GCacheable (M1 t m f) where
gunchanged (M1 a) (M1 b) = gunchanged a b
{-# INLINE gunchanged #-}
instance (GCacheable f, GCacheable g) => GCacheable (f :*: g) where
gunchanged (a1 :*: a2) (b1 :*: b2) = liftA2 (&&) (gunchanged a1 b1) (gunchanged a2 b2)
{-# INLINE gunchanged #-}
instance (GCacheable f, GCacheable g) => GCacheable (f :+: g) where
gunchanged (L1 a) (L1 b) = gunchanged a b
gunchanged (R1 a) (R1 b) = gunchanged a b
gunchanged _ _ = const False
{-# INLINE gunchanged #-}