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
//! What a successful mutation invalidates, decided once and from the schema.
//!
//! # Why this is a sweep and not a classification
//!
//! The invalidation this replaced tried to be precise: a CREATE evicted only
//! entries flagged as "list queries", an UPDATE with an `entity_id` evicted only
//! entries whose rows already contained that UUID. Precision is worth having,
//! but only if the entries it *keeps* are provably unaffected by the write —
//! and neither signal proved that:
//!
//! - **Row count is not query shape (#742).** `is_list_query` was `result.len() > 1`, so a filtered
//! list that currently matches nothing, and one that matches a single row, were not lists. Those
//! are precisely the entries a CREATE is most likely to change.
//! - **`returns_list` would not have been sound either.** A query that returns a single object —
//! `currentUser`, `latestPost`, `userByEmail` — is still affected by a CREATE or by an UPDATE to
//! a row it does not currently return. "Not a list" does not mean "not affected"; the compiled
//! schema has no flag that does.
//! - **Containing the mutated id is not the affected set (#763).** An UPDATE that moves a row
//! *into* a filtered list changes an entry that never mentioned the row. Entity-aware eviction
//! cannot see it by construction.
//! - **The payload's `entity_id` is not the operation kind (#741).** A CREATE that stamps the new
//! row's id took the UPDATE branch and evicted nothing: no entry cached before the row existed
//! can contain its id.
//!
//! So the plan is: **every successful mutation evicts every cached entry for the
//! views it touches.** Over-eviction costs a re-read; under-eviction serves a
//! wrong answer for the entry's whole TTL — forever for the
//! `cache_ttl_seconds = 0` entries documented as "mutation-invalidated only".
//! Entity-aware eviction is kept as a *supplement* for the case where no view
//! resolves, never as a substitute for the sweep.
use ViewName;
use resolve_cascade_views;
use crate::;
/// Everything a single successful mutation must evict.
pub
/// Append `view` unless an equal name is already planned.
use crate;
/// Decide what a mutation outcome invalidates.
///
/// A pure function of `(mutation_def, outcome, schema)`: no cache, no adapter,
/// no I/O — so the decision is testable on its own, which is what the three
/// defects above needed and did not have.
///
/// Views are collected from every declaration that can name one:
///
/// 1. `invalidates_views` — the developer said so explicitly.
/// 2. The mutation's return type, and the entity type it wraps if it is a payload
/// (`CreateUserSuccess` → `User`).
/// 3. The `entity_type` the function stamped on `mutation_response`, which is the only source when
/// the return type is an unbacked payload.
/// 4. Cascade side-effects on other entity types, for a `cascade` mutation.
///
/// A failed mutation wrote nothing, so it invalidates nothing.
pub