@@ -43,21 +43,16 @@ public function resolve(
4343 QueryPlan::CANONICAL , QueryPlan::DIRECT_PK => $ this ->keys ->generation ($ plan ->root ),
4444 default => null ,
4545 } : null ;
46- $ tagKey = str_starts_with ($ namespace , 'g ' )
47- ? $ this ->keys ->tagVersion (substr ($ namespace , 1 ))
48- : null ;
49- $ epochKey = $ this ->runtime ->knownEpoch () === null ? $ this ->keys ->epoch () : null ;
46+ $ tagKey = $ this ->tagKey ($ namespace );
47+ $ epochKey = $ this ->unknownEpochKey ();
5048 $ values = $ this ->store ->mget (array_values (array_unique (array_filter ([
5149 $ epochKey ,
5250 ...array_values ($ versionKeys ),
5351 $ generationKey ,
5452 $ tagKey ,
5553 ...$ alsoFetch ,
5654 ]))));
57-
58- if ($ epochKey !== null ) {
59- $ this ->runtime ->rememberEpoch ($ values [$ epochKey ] ?? '0 ' );
60- }
55+ $ this ->rememberEpochFrom ($ epochKey , $ values );
6156
6257 if (
6358 $ knownVersion !== null
@@ -109,7 +104,7 @@ public function resolve(
109104 return [
110105 new CacheState (
111106 key: $ key ,
112- epoch: $ this ->epoch (),
107+ epoch: $ this ->runtime -> epoch (),
113108 version: $ rootVersion ,
114109 generation: $ generation ,
115110 versions: $ versions ,
@@ -120,112 +115,127 @@ public function resolve(
120115 ];
121116 }
122117
118+ /**
119+ * @param list<string> $rowKeys
120+ * @return array{0: CacheState, 1: array<string, ?string>}
121+ */
122+ public function resolveCanonical (
123+ QueryPlan $ plan ,
124+ string $ namespace ,
125+ string $ queryHash ,
126+ array $ rowKeys ,
127+ ): array {
128+ $ keys = $ this ->stateKeys ($ plan , $ this ->tagKey ($ namespace ), $ this ->unknownEpochKey ());
129+ $ values = $ this ->store ->mget (array_values (array_unique ([
130+ ...$ rowKeys ,
131+ ...$ keys ['all ' ],
132+ ])));
133+ $ this ->rememberEpochFrom ($ keys ['epoch ' ], $ values );
134+
135+ $ versions = [];
136+
137+ foreach ($ keys ['dependencies ' ] as $ hash => $ key ) {
138+ $ versions [$ hash ] = $ values [$ key ] ?? '0 ' ;
139+ }
140+
141+ ksort ($ versions , SORT_STRING );
142+ $ version = $ values [$ keys ['version ' ]] ?? '0 ' ;
143+
144+ return [
145+ new CacheState (
146+ key: $ this ->keys ->membership ($ plan ->root , $ version , $ namespace , $ queryHash ),
147+ epoch: $ this ->runtime ->epoch (),
148+ version: $ version ,
149+ generation: $ values [$ keys ['generation ' ]] ?? '0 ' ,
150+ versions: $ versions ,
151+ tag: $ keys ['tag ' ] !== null ? ($ values [$ keys ['tag ' ]] ?? '0 ' ) : null ,
152+ tagKey: $ keys ['tag ' ],
153+ ),
154+ $ values ,
155+ ];
156+ }
157+
158+ /** @phpstan-impure re-read on each call: callers check either side of a publish */
159+ public function isCurrent (QueryPlan $ plan , CacheState $ expected ): bool
160+ {
161+ $ keys = $ this ->stateKeys ($ plan , $ expected ->tagKey , $ this ->keys ->epoch ());
162+ $ values = $ this ->store ->mget ($ keys ['all ' ]);
163+ $ current = static fn (string $ key ): string => $ values [$ key ] ?? '0 ' ;
164+
165+ if (
166+ $ current ((string ) $ keys ['epoch ' ]) !== $ expected ->epoch
167+ || $ current ($ keys ['version ' ]) !== $ expected ->version
168+ || $ current ($ keys ['generation ' ]) !== $ expected ->generation
169+ ) {
170+ return false ;
171+ }
172+
173+ foreach ($ keys ['dependencies ' ] as $ hash => $ key ) {
174+ if ($ current ($ key ) !== ($ expected ->versions [$ hash ] ?? null )) {
175+ return false ;
176+ }
177+ }
178+
179+ return $ expected ->tag === null
180+ || $ keys ['tag ' ] !== null && $ current ($ keys ['tag ' ]) === $ expected ->tag ;
181+ }
182+
123183 /**
124184 * @return array{
125185 * epoch: ?string,
126186 * version: string,
127187 * generation: string,
128188 * dependencies: array<string, string>,
129189 * tag: ?string,
130- * final : list<string>
190+ * all : list<string>
131191 * }
132192 */
133- public function canonicalKeys (QueryPlan $ plan , string $ namespace ): array
193+ private function stateKeys (QueryPlan $ plan , ? string $ tagKey , ? string $ epochKey ): array
134194 {
135- $ dependencyKeys = [];
195+ $ dependencies = [];
136196
137197 foreach ($ plan ->dependencies as $ dependency ) {
138198 if ($ dependency ->hash !== $ plan ->root ->hash ) {
139- $ dependencyKeys [$ dependency ->hash ] = $ this ->keys ->version ($ dependency );
199+ $ dependencies [$ dependency ->hash ] = $ this ->keys ->version ($ dependency );
140200 }
141201 }
142202
143- $ epochKey = $ this ->runtime ->knownEpoch () === null ? $ this ->keys ->epoch () : null ;
144203 $ versionKey = $ this ->keys ->version ($ plan ->root );
145204 $ generationKey = $ this ->keys ->generation ($ plan ->root );
146- $ tagKey = str_starts_with ($ namespace , 'g ' )
147- ? $ this ->keys ->tagVersion (substr ($ namespace , 1 ))
148- : null ;
149- $ externalKeys = array_values (array_filter ([
150- $ epochKey ,
151- ...array_values ($ dependencyKeys ),
152- $ tagKey ,
153- ]));
154205
155206 return [
156207 'epoch ' => $ epochKey ,
157208 'version ' => $ versionKey ,
158209 'generation ' => $ generationKey ,
159- 'dependencies ' => $ dependencyKeys ,
210+ 'dependencies ' => $ dependencies ,
160211 'tag ' => $ tagKey ,
161- 'final ' => [$ versionKey , $ generationKey , ...$ externalKeys ],
212+ 'all ' => array_values (array_filter ([
213+ $ versionKey ,
214+ $ generationKey ,
215+ $ epochKey ,
216+ ...array_values ($ dependencies ),
217+ $ tagKey ,
218+ ])),
162219 ];
163220 }
164221
165- /**
166- * @param array{
167- * epoch: ?string,
168- * version: string,
169- * generation: string,
170- * dependencies: array<string, string>,
171- * tag: ?string,
172- * final: list<string>
173- * } $keys
174- * @param array<string, ?string> $values
175- */
176- public function canonicalFromFetched (
177- QueryPlan $ plan ,
178- string $ namespace ,
179- string $ queryHash ,
180- array $ keys ,
181- array $ values ,
182- ): CacheState {
183- if ($ keys ['epoch ' ] !== null ) {
184- $ this ->runtime ->rememberEpoch ($ values [$ keys ['epoch ' ]] ?? '0 ' );
185- }
186-
187- $ versions = [];
188-
189- foreach ($ keys ['dependencies ' ] as $ hash => $ key ) {
190- $ versions [$ hash ] = $ values [$ key ] ?? '0 ' ;
191- }
192-
193- ksort ($ versions , SORT_STRING );
194- $ version = $ values [$ keys ['version ' ]] ?? '0 ' ;
195- $ generation = $ values [$ keys ['generation ' ]] ?? '0 ' ;
196- $ tag = $ keys ['tag ' ] !== null ? ($ values [$ keys ['tag ' ]] ?? '0 ' ) : null ;
197-
198- return new CacheState (
199- key: $ this ->keys ->membership (
200- $ plan ->root ,
201- $ version ,
202- $ namespace ,
203- $ queryHash ,
204- ),
205- epoch: $ this ->epoch (),
206- version: $ version ,
207- generation: $ generation ,
208- versions: $ versions ,
209- tag: $ tag ,
210- tagKey: $ keys ['tag ' ],
211- );
222+ private function tagKey (string $ namespace ): ?string
223+ {
224+ return str_starts_with ($ namespace , 'g ' )
225+ ? $ this ->keys ->tagVersion (substr ($ namespace , 1 ))
226+ : null ;
212227 }
213228
214- /**
215- * @param list<string> $keys
216- * @param list<string> $alsoFetch
217- * @return array<string, ?string>
218- */
219- public function fetch (array $ keys , array $ alsoFetch = []): array
229+ private function unknownEpochKey (): ?string
220230 {
221- return $ this ->store ->mget (array_values (array_unique ([
222- ...$ keys ,
223- ...$ alsoFetch ,
224- ])));
231+ return $ this ->runtime ->knownEpoch () === null ? $ this ->keys ->epoch () : null ;
225232 }
226233
227- private function epoch (): string
234+ /** @param array<string, ?string> $values */
235+ private function rememberEpochFrom (?string $ epochKey , array $ values ): void
228236 {
229- return $ this ->runtime ->epoch ();
237+ if ($ epochKey !== null ) {
238+ $ this ->runtime ->rememberEpoch ($ values [$ epochKey ] ?? '0 ' );
239+ }
230240 }
231241}
0 commit comments