Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
92.25% |
512 / 555 |
|
77.50% |
31 / 40 |
CRAP | |
0.00% |
0 / 1 |
ZObjectStore | |
92.25% |
512 / 555 |
|
77.50% |
31 / 40 |
103.56 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getNextAvailableZid | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
2 | |||
fetchZObjectByTitle | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
fetchBatchZObjects | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
3 | |||
createNewZObject | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
updateZObject | |
72.41% |
42 / 58 |
|
0.00% |
0 / 1 |
15.02 | |||
updateZObjectAsSystemUser | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
deleteZObjectLabelsByZid | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
deleteZObjectLabelConflictsByZid | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
findZObjectLabelConflicts | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
4 | |||
insertZObjectLabels | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
3 | |||
insertZLanguageToLanguagesCache | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
insertZObjectLabelConflicts | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
3 | |||
insertZObjectAliases | |
94.74% |
18 / 19 |
|
0.00% |
0 / 1 |
4.00 | |||
fetchZidsOfType | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
fetchAllZids | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
3 | |||
fetchAllZLanguageObjects | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
fetchAllZLanguageCodes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
searchZObjectLabels | |
88.10% |
37 / 42 |
|
0.00% |
0 / 1 |
10.17 | |||
fetchZObjectLabel | |
97.30% |
36 / 37 |
|
0.00% |
0 / 1 |
8 | |||
fetchZFunctionReturnType | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
findFirstZImplementationFunction | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
findReferencedZObjectsByZFunctionIdAsList | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
findReferencedZObjectsByZFunctionId | |
93.75% |
15 / 16 |
|
0.00% |
0 / 1 |
2.00 | |||
insertZFunctionReference | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
deleteZFunctionReference | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
findZTesterResult | |
84.00% |
42 / 50 |
|
0.00% |
0 / 1 |
10.41 | |||
insertZTesterResult | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
1 | |||
deleteZFunctionFromZTesterResultsCache | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
deleteZImplementationFromZTesterResultsCache | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
deleteZTesterFromZTesterResultsCache | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
deleteZLanguageFromLanguagesCache | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
deleteFromLabelsSecondaryTables | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
deleteFromFunctionsSecondaryTables | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
deleteFromLanguageCacheSecondaryTables | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
deleteFromTesterResultsSecondaryTables | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
clearLabelsSecondaryTables | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
clearFunctionsSecondaryTables | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
clearTesterResultsSecondaryTables | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
clearLanguageCacheSecondaryTables | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * WikiLambda Data Access Object service |
4 | * |
5 | * @file |
6 | * @ingroup Extensions |
7 | * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt |
8 | * @license MIT |
9 | */ |
10 | |
11 | namespace MediaWiki\Extension\WikiLambda; |
12 | |
13 | use MediaWiki\Extension\WikiLambda\Registry\ZErrorTypeRegistry; |
14 | use MediaWiki\Extension\WikiLambda\Registry\ZLangRegistry; |
15 | use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry; |
16 | use MediaWiki\Extension\WikiLambda\ZObjects\ZPersistentObject; |
17 | use MediaWiki\Extension\WikiLambda\ZObjects\ZResponseEnvelope; |
18 | use MediaWiki\Languages\LanguageFallback; |
19 | use MediaWiki\MediaWikiServices; |
20 | use MediaWiki\Page\WikiPageFactory; |
21 | use MediaWiki\Revision\RevisionRecord; |
22 | use MediaWiki\Revision\RevisionStore; |
23 | use MediaWiki\Revision\SlotRecord; |
24 | use MediaWiki\Title\Title; |
25 | use MediaWiki\Title\TitleArrayFromResult; |
26 | use MediaWiki\Title\TitleFactory; |
27 | use MediaWiki\User\User; |
28 | use MediaWiki\User\UserGroupManager; |
29 | use Psr\Log\LoggerInterface; |
30 | use Wikimedia\Rdbms\IConnectionProvider; |
31 | use Wikimedia\Rdbms\IDatabase; |
32 | use Wikimedia\Rdbms\IResultWrapper; |
33 | use Wikimedia\Rdbms\SelectQueryBuilder; |
34 | use WikiPage; |
35 | |
36 | class ZObjectStore { |
37 | |
38 | private IConnectionProvider $dbProvider; |
39 | private TitleFactory $titleFactory; |
40 | private WikiPageFactory $wikiPageFactory; |
41 | private RevisionStore $revisionStore; |
42 | private UserGroupManager $userGroupManager; |
43 | private LoggerInterface $logger; |
44 | |
45 | /** |
46 | * @param IConnectionProvider $dbProvider |
47 | * @param TitleFactory $titleFactory |
48 | * @param WikiPageFactory $wikiPageFactory |
49 | * @param RevisionStore $revisionStore |
50 | * @param UserGroupManager $userGroupManager |
51 | * @param LoggerInterface $logger |
52 | */ |
53 | public function __construct( |
54 | IConnectionProvider $dbProvider, |
55 | TitleFactory $titleFactory, |
56 | WikiPageFactory $wikiPageFactory, |
57 | RevisionStore $revisionStore, |
58 | UserGroupManager $userGroupManager, |
59 | LoggerInterface $logger |
60 | ) { |
61 | $this->dbProvider = $dbProvider; |
62 | $this->titleFactory = $titleFactory; |
63 | $this->wikiPageFactory = $wikiPageFactory; |
64 | $this->revisionStore = $revisionStore; |
65 | $this->userGroupManager = $userGroupManager; |
66 | $this->logger = $logger; |
67 | } |
68 | |
69 | /** |
70 | * Find next available ZID in the database to create a new ZObject |
71 | * |
72 | * @return string Next available ZID |
73 | */ |
74 | public function getNextAvailableZid(): string { |
75 | // Intentionally use DB_PRIMARY as we need the latest data here. |
76 | $dbr = $this->dbProvider->getPrimaryDatabase(); |
77 | $res = $dbr->newSelectQueryBuilder() |
78 | ->select( [ 'page_title' ] ) |
79 | ->from( 'page' ) |
80 | ->where( [ |
81 | 'page_namespace' => NS_MAIN, |
82 | 'LENGTH( page_title ) > 5' |
83 | ] ) |
84 | ->orderBy( 'page_id', SelectQueryBuilder::SORT_DESC ) |
85 | ->groupBy( [ 'page_id', 'page_title' ] ) |
86 | ->limit( 1 ) |
87 | ->caller( __METHOD__ ) |
88 | ->fetchResultSet(); |
89 | |
90 | // NOTE: This hard-codes user-provided content as starting from 'Z10000'. Now that the |
91 | // wiki has launched, change it will have unpredicatable effects. |
92 | $highestZid = $res->numRows() > 0 ? $res->fetchRow()[ 0 ] : 'Z9999'; |
93 | $targetZid = 'Z' . ( max( intval( substr( $highestZid, 1 ) ) + 1, 10000 ) ); |
94 | |
95 | return $targetZid; |
96 | } |
97 | |
98 | /** |
99 | * Fetch the ZObject given its title and return it wrapped in a ZObjectContent object |
100 | * |
101 | * @param Title $title The ZObject to fetch |
102 | * @param int|null $requestedRevision The revision ID of the page to fetch. If unset, the latest is returned. |
103 | * @return ZObjectContent|bool Found ZObject |
104 | */ |
105 | public function fetchZObjectByTitle( Title $title, ?int $requestedRevision = null ) { |
106 | if ( $requestedRevision ) { |
107 | $revision = $this->revisionStore->getRevisionByTitle( $title, $requestedRevision, 0 ); |
108 | } else { |
109 | $revision = $this->revisionStore->getKnownCurrentRevision( $title ); |
110 | } |
111 | |
112 | if ( !$revision ) { |
113 | // TODO (T300521): Handle errors by creating and returning Z5 |
114 | return false; |
115 | } |
116 | |
117 | // NOTE: Hard-coding use of MAIN slot; if we're going the MCR route, we may wish to change this (or not). |
118 | $slot = $revision->getSlot( SlotRecord::MAIN, RevisionRecord::RAW ); |
119 | // @phan-suppress-next-line PhanTypeMismatchReturnSuperType |
120 | return $slot->getContent(); |
121 | } |
122 | |
123 | /** |
124 | * Returns an array of ZPersistentObjects fetched from the DB given an array of their Zids |
125 | * |
126 | * Note that this will only fetch the latest revision of a ZObject; if you want a specific |
127 | * revision, you need to use ZObjectStore::fetchZObjectByTitle() instead. |
128 | * |
129 | * @param string[] $zids |
130 | * @return ZPersistentObject[] |
131 | */ |
132 | public function fetchBatchZObjects( $zids ) { |
133 | $dbr = $this->dbProvider->getReplicaDatabase(); |
134 | $query = WikiPage::getQueryInfo(); |
135 | |
136 | $res = $dbr->newSelectQueryBuilder() |
137 | ->select( $query['fields'] ) |
138 | ->from( 'page' ) |
139 | ->where( [ |
140 | 'page_namespace' => NS_MAIN, |
141 | 'page_title' => $zids |
142 | ] ) |
143 | ->caller( __METHOD__ ) |
144 | ->fetchResultSet(); |
145 | |
146 | $titleArray = new TitleArrayFromResult( $res ); |
147 | |
148 | $dataArray = []; |
149 | foreach ( $titleArray as $title ) { |
150 | // TODO (T300521): Handle error from fetchZObjectByTitle |
151 | $content = $this->fetchZObjectByTitle( $title ); |
152 | if ( $content->isValid() ) { |
153 | $dataArray[ $title->getBaseText() ] = $content->getZObject(); |
154 | } |
155 | } |
156 | return $dataArray; |
157 | } |
158 | |
159 | /** |
160 | * Create a new ZObject, with a newly assigned ZID, and store it in the Database |
161 | * |
162 | * @param string $data |
163 | * @param string $summary |
164 | * @param User $user |
165 | * @return ZObjectPage |
166 | */ |
167 | public function createNewZObject( string $data, string $summary, User $user ) { |
168 | // Find all placeholder ZIDs and ZKeys and replace those with the next available ZID |
169 | $zid = $this->getNextAvailableZid(); |
170 | $zPlaceholderRegex = '/\"' . ZTypeRegistry::Z_NULL_REFERENCE . '(K[1-9]\d*)?\"/'; |
171 | $zObjectString = preg_replace( $zPlaceholderRegex, "\"$zid$1\"", $data ); |
172 | return $this->updateZObject( $zid, $zObjectString, $summary, $user, EDIT_NEW ); |
173 | } |
174 | |
175 | /** |
176 | * Create or update a ZObject it in the Database |
177 | * |
178 | * @param string $zid The ZID of the page to create/update, e.g. 'Z12345' |
179 | * @param string $data The ZObject's JSON to store, in string form, i.e. "{ Z1K1: "Z2", Z2K1: … }" |
180 | * @param string $summary An edit summary to display in the page's history, Recent Changes, watchlists, etc. |
181 | * @param User $user The user making the edit. |
182 | * @param int $flags Either EDIT_UPDATE (default) if editing or EDIT_NEW if creating a page |
183 | * @return ZObjectPage |
184 | */ |
185 | public function updateZObject( string $zid, string $data, string $summary, User $user, int $flags = EDIT_UPDATE ) { |
186 | $title = $this->titleFactory->newFromText( $zid, NS_MAIN ); |
187 | |
188 | // ERROR: Title is empty or invalid |
189 | if ( !( $title instanceof Title ) ) { |
190 | $error = ZErrorFactory::createZErrorInstance( |
191 | ZErrorTypeRegistry::Z_ERROR_INVALID_TITLE, |
192 | [ 'title' => $zid ] |
193 | ); |
194 | return ZObjectPage::newFatal( $error ); |
195 | } |
196 | |
197 | // If edit flag or title did not exist, we are creating a new object |
198 | $creating = ( $flags === EDIT_NEW ) || !( $title->exists() ); |
199 | |
200 | try { |
201 | $content = ZObjectContentHandler::makeContent( $data, $title ); |
202 | } catch ( ZErrorException $e ) { |
203 | return ZObjectPage::newFatal( $e->getZError() ); |
204 | } |
205 | |
206 | // Error: ZObject validation errors. |
207 | if ( !( $content->isValid() ) ) { |
208 | return ZObjectPage::newFatal( $content->getErrors() ); |
209 | } |
210 | |
211 | // Validate that $zid and zObject[Z2K1] are the same |
212 | $zObjectId = $content->getZid(); |
213 | if ( $zObjectId !== $zid ) { |
214 | $error = ZErrorFactory::createZErrorInstance( |
215 | ZErrorTypeRegistry::Z_ERROR_UNMATCHING_ZID, |
216 | [ |
217 | 'zid' => $zObjectId, |
218 | 'title' => $zid |
219 | ] |
220 | ); |
221 | return ZObjectPage::newFatal( $error ); |
222 | } |
223 | |
224 | $ztype = $content->getZType(); |
225 | |
226 | // Stop from creating and editing any types form DISALLOWED_ROOT_ZOBJECT |
227 | if ( in_array( $ztype, ZTypeRegistry::DISALLOWED_ROOT_ZOBJECTS ) ) { |
228 | $error = ZErrorFactory::createZErrorInstance( |
229 | ZErrorTypeRegistry::Z_ERROR_DISALLOWED_ROOT_ZOBJECT, |
230 | [ 'data' => $content->getZType() ] |
231 | ); |
232 | return ZObjectPage::newFatal( $error ); |
233 | } |
234 | |
235 | // Find the label conflicts. |
236 | $labels = $content->getLabels()->getValueAsList(); |
237 | $clashes = $this->findZObjectLabelConflicts( $zid, $ztype, $labels ); |
238 | |
239 | if ( count( $clashes ) > 0 ) { |
240 | $error = ZErrorFactory::createLabelClashZErrors( $clashes ); |
241 | return ZObjectPage::newFatal( $error ); |
242 | } |
243 | |
244 | // Use ZObjectAuthorization service to check that the user has the required permissions |
245 | // while creating or editing an object |
246 | $fromContent = null; |
247 | if ( !$creating ) { |
248 | $currentRevision = $this->revisionStore->getKnownCurrentRevision( $title ); |
249 | $fromContent = $currentRevision->getSlots()->getContent( SlotRecord::MAIN ); |
250 | '@phan-var ZObjectContent $fromContent'; |
251 | } |
252 | $authorizationService = WikiLambdaServices::getZObjectAuthorization(); |
253 | $status = $authorizationService->authorize( $fromContent, $content, $user, $title ); |
254 | |
255 | // Return AuthorizationStatus->error if authorization service failed |
256 | if ( !$status->isValid() ) { |
257 | return ZObjectPage::newFatal( $status->getErrors() ); |
258 | } |
259 | |
260 | // We prepare the content to be saved |
261 | $page = $this->wikiPageFactory->newFromTitle( $title ); |
262 | try { |
263 | $status = $page->doUserEditContent( $content, $user, $summary, $flags ); |
264 | } catch ( \Exception $e ) { |
265 | // Error: Database or a deeper MediaWiki error, e.g. a general editing rate limit |
266 | $error = ZErrorFactory::createZErrorInstance( |
267 | ZErrorTypeRegistry::Z_ERROR_UNKNOWN, |
268 | // TODO (T362236): Add the rendering language as a parameter, don't default to English |
269 | [ 'message' => $e->getMessage() ] |
270 | ); |
271 | return ZObjectPage::newFatal( $error ); |
272 | } |
273 | |
274 | if ( !$status->isOK() ) { |
275 | // Error: Other doUserEditContent related errors |
276 | $error = ZErrorFactory::createZErrorInstance( |
277 | ZErrorTypeRegistry::Z_ERROR_UNKNOWN, |
278 | [ 'message' => $status->getMessage() ] |
279 | ); |
280 | return ZObjectPage::newFatal( $error ); |
281 | } |
282 | |
283 | // Success: return WikiPage |
284 | return ZObjectPage::newSuccess( $page ); |
285 | } |
286 | |
287 | /** |
288 | * Create or update a ZObject it in the Database as a System User |
289 | * |
290 | * @param string $zid |
291 | * @param string $data |
292 | * @param string $summary |
293 | * @param int $flags |
294 | * @return ZObjectPage |
295 | */ |
296 | public function updateZObjectAsSystemUser( string $zid, string $data, string $summary, int $flags = EDIT_UPDATE ) { |
297 | $creatingUserName = wfMessage( 'wikilambda-systemuser' )->inLanguage( 'en' )->text(); |
298 | // System user must belong to all privileged groups in order to |
299 | // perform all zobject creation and editing actions: |
300 | $user = User::newSystemUser( $creatingUserName, [ 'steal' => true ] ); |
301 | $this->userGroupManager->addUserToGroup( $user, 'sysop' ); |
302 | $this->userGroupManager->addUserToGroup( $user, 'functionmaintainer' ); |
303 | $this->userGroupManager->addUserToGroup( $user, 'functioneer' ); |
304 | $this->userGroupManager->addUserToGroup( $user, 'wikifunctions-staff' ); |
305 | return $this->updateZObject( $zid, $data, $summary, $user, $flags ); |
306 | } |
307 | |
308 | /** |
309 | * Delete the labels from the wikilambda_zobject_labels database that correspond |
310 | * to the given ZID. |
311 | * |
312 | * @param string $zid |
313 | */ |
314 | public function deleteZObjectLabelsByZid( string $zid ) { |
315 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
316 | |
317 | $dbw->newDeleteQueryBuilder() |
318 | ->deleteFrom( 'wikilambda_zobject_labels' ) |
319 | ->where( [ 'wlzl_zobject_zid' => $zid ] ) |
320 | ->caller( __METHOD__ )->execute(); |
321 | } |
322 | |
323 | /** |
324 | * Delete the label conflicts from the wikilambda_zobject_label_conflicts database |
325 | * that correspond to the given ZID. |
326 | * |
327 | * @param string $zid |
328 | */ |
329 | public function deleteZObjectLabelConflictsByZid( string $zid ) { |
330 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
331 | |
332 | $dbw->newDeleteQueryBuilder() |
333 | ->deleteFrom( 'wikilambda_zobject_label_conflicts' ) |
334 | ->where( $dbw->makeList( |
335 | [ |
336 | 'wlzlc_existing_zid' => $zid, |
337 | 'wlzlc_conflicting_zid' => $zid |
338 | ], |
339 | $dbw::LIST_OR |
340 | ) |
341 | ) |
342 | ->caller( __METHOD__ )->execute(); |
343 | } |
344 | |
345 | /** |
346 | * Query the wikilambda_zobject_labels database for primary labels that have |
347 | * the same combination of language code and value for a different ZID |
348 | * than the given in the parameters. These will be considered conflicting labels. |
349 | * |
350 | * @param string $zid |
351 | * @param string $ztype |
352 | * @param array $labels Array of labels, where the key is the language code and the value |
353 | * is the string representation of the label in that language |
354 | * @return array Conflicts found in the wikilambda_zobject_labels database |
355 | */ |
356 | public function findZObjectLabelConflicts( $zid, $ztype, $labels ): array { |
357 | $dbr = $this->dbProvider->getReplicaDatabase(); |
358 | |
359 | // remove labels with an undefined value |
360 | $labels = array_filter( |
361 | $labels, static function ( $value ) { |
362 | return $value !== ""; |
363 | } ); |
364 | |
365 | if ( $labels === [] ) { |
366 | return []; |
367 | } |
368 | |
369 | $labelConflictConditions = []; |
370 | foreach ( $labels as $language => $value ) { |
371 | $labelConflictConditions[] = $dbr->makeList( [ |
372 | 'wlzl_language' => $language, |
373 | 'wlzl_label' => $value, |
374 | 'wlzl_label_primary' => true |
375 | ], $dbr::LIST_AND ); |
376 | } |
377 | |
378 | $res = $dbr->newSelectQueryBuilder() |
379 | ->select( [ 'wlzl_zobject_zid', 'wlzl_language' ] ) |
380 | ->from( 'wikilambda_zobject_labels' ) |
381 | ->where( [ |
382 | 'wlzl_zobject_zid != ' . $dbr->addQuotes( $zid ), |
383 | // TODO (T357552): Check against type, once we properly implement that. |
384 | // 'wlzl_type' => $dbr->addQuotes( $ztype ), |
385 | $dbr->makeList( $labelConflictConditions, $dbr::LIST_OR ) |
386 | ] ) |
387 | ->caller( __METHOD__ ) |
388 | ->fetchResultSet(); |
389 | |
390 | $conflicts = []; |
391 | foreach ( $res as $row ) { |
392 | // TODO (T362247): What if more than one conflicts with us on each language? |
393 | $conflicts[ $row->wlzl_language ] = $row->wlzl_zobject_zid; |
394 | } |
395 | |
396 | return $conflicts; |
397 | } |
398 | |
399 | /** |
400 | * Insert labels into the wikilambda_zobject_labels database for a given ZID and Type |
401 | * |
402 | * @param string $zid |
403 | * @param string $ztype |
404 | * @param array $labels Array of labels, where the key is the language code and the value |
405 | * is the string representation of the label in that language |
406 | * @param string|null $returnType |
407 | * @return void|bool |
408 | */ |
409 | public function insertZObjectLabels( $zid, $ztype, $labels, $returnType = null ) { |
410 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
411 | |
412 | $updates = []; |
413 | foreach ( $labels as $language => $value ) { |
414 | $updates[] = [ |
415 | 'wlzl_zobject_zid' => $zid, |
416 | 'wlzl_language' => $language, |
417 | 'wlzl_type' => $ztype, |
418 | 'wlzl_label' => $value, |
419 | 'wlzl_label_normalised' => ZObjectUtils::comparableString( $value ), |
420 | 'wlzl_label_primary' => true, |
421 | 'wlzl_return_type' => $returnType |
422 | ]; |
423 | } |
424 | |
425 | // Exit early if there are no updates to make. |
426 | if ( count( $updates ) === 0 ) { |
427 | return; |
428 | } |
429 | |
430 | $dbw->newInsertQueryBuilder() |
431 | ->insertInto( 'wikilambda_zobject_labels' ) |
432 | ->rows( $updates ) |
433 | ->caller( __METHOD__ )->execute(); |
434 | } |
435 | |
436 | /** |
437 | * Insert language code into the wikilambda_zlanguages database for a given ZID |
438 | * |
439 | * @param string $zid |
440 | * @param string $languageCode |
441 | * @return void|bool |
442 | */ |
443 | public function insertZLanguageToLanguagesCache( string $zid, string $languageCode ) { |
444 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
445 | |
446 | $dbw->newInsertQueryBuilder() |
447 | ->insertInto( 'wikilambda_zlanguages' ) |
448 | ->row( [ 'wlzlangs_zid' => $zid, 'wlzlangs_language' => $languageCode ] ) |
449 | ->caller( __METHOD__ )->execute(); |
450 | } |
451 | |
452 | /** |
453 | * Insert label conflicts into the wikilambda_zobject_label_conflicts database for a given ZID |
454 | * |
455 | * @param string $zid |
456 | * @param array $conflicts Array of labels, where the key is the language code and the value |
457 | * is the other ZID for which this label is repeated |
458 | * @return void|bool |
459 | */ |
460 | public function insertZObjectLabelConflicts( $zid, $conflicts ) { |
461 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
462 | |
463 | $updates = []; |
464 | foreach ( $conflicts as $language => $existingZid ) { |
465 | $updates[] = [ |
466 | 'wlzlc_existing_zid' => $existingZid, |
467 | 'wlzlc_conflicting_zid' => $zid, |
468 | 'wlzlc_language' => $language, |
469 | ]; |
470 | } |
471 | |
472 | // Exit early if there are no updates to make. |
473 | if ( count( $updates ) === 0 ) { |
474 | return; |
475 | } |
476 | |
477 | $dbw->newInsertQueryBuilder() |
478 | ->insertInto( 'wikilambda_zobject_label_conflicts' ) |
479 | ->rows( $updates ) |
480 | ->caller( __METHOD__ )->execute(); |
481 | } |
482 | |
483 | /** |
484 | * Insert alias (secondary labels) into the wikilambda_zobject_labels database for a given ZID and Type |
485 | * |
486 | * @param string $zid |
487 | * @param string $ztype |
488 | * @param array $aliases Set of labels, where the key is the language code |
489 | * and the value is an array of strings |
490 | * @param string|null $returnType |
491 | * @return void|bool |
492 | */ |
493 | public function insertZObjectAliases( $zid, $ztype, $aliases, $returnType = null ) { |
494 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
495 | |
496 | $updates = []; |
497 | foreach ( $aliases as $language => $stringset ) { |
498 | foreach ( $stringset as $value ) { |
499 | $updates[] = [ |
500 | 'wlzl_zobject_zid' => $zid, |
501 | 'wlzl_language' => $language, |
502 | 'wlzl_type' => $ztype, |
503 | 'wlzl_label' => $value, |
504 | 'wlzl_label_normalised' => ZObjectUtils::comparableString( $value ), |
505 | 'wlzl_label_primary' => false, |
506 | 'wlzl_return_type' => $returnType |
507 | ]; |
508 | } |
509 | } |
510 | |
511 | if ( count( $updates ) === 0 ) { |
512 | return; |
513 | } |
514 | |
515 | $dbw->newInsertQueryBuilder() |
516 | ->insertInto( 'wikilambda_zobject_labels' ) |
517 | ->rows( $updates ) |
518 | ->caller( __METHOD__ )->execute(); |
519 | } |
520 | |
521 | /** |
522 | * Gets from the secondary database a list of all Zids belonging to a given type |
523 | * |
524 | * @param string $ztype |
525 | * @return string[] |
526 | */ |
527 | public function fetchZidsOfType( $ztype ) { |
528 | $dbr = $this->dbProvider->getReplicaDatabase(); |
529 | $res = $dbr->newSelectQueryBuilder() |
530 | ->select( [ 'wlzl_zobject_zid' ] ) |
531 | ->from( 'wikilambda_zobject_labels' ) |
532 | ->where( [ |
533 | 'wlzl_type' => $ztype |
534 | ] ) |
535 | ->orderBy( 'wlzl_zobject_zid', SelectQueryBuilder::SORT_ASC ) |
536 | ->caller( __METHOD__ ) |
537 | ->fetchResultSet(); |
538 | |
539 | $zids = []; |
540 | foreach ( $res as $row ) { |
541 | $zids[] = $row->wlzl_zobject_zid; |
542 | } |
543 | return $zids; |
544 | } |
545 | |
546 | /** |
547 | * Get a list of all Zids persisted in the database |
548 | * |
549 | * @return string[] All persisted Zids |
550 | */ |
551 | public function fetchAllZids() { |
552 | $dbr = $this->dbProvider->getReplicaDatabase(); |
553 | $res = $dbr->newSelectQueryBuilder() |
554 | ->select( [ 'page_title' ] ) |
555 | ->from( 'page' ) |
556 | ->where( [ |
557 | 'page_namespace' => NS_MAIN |
558 | ] ) |
559 | ->caller( __METHOD__ ) |
560 | ->fetchResultSet(); |
561 | |
562 | $zids = []; |
563 | foreach ( $res as $row ) { |
564 | $zid = $row->page_title; |
565 | if ( ZObjectUtils::isValidZObjectReference( $zid ) ) { |
566 | $zids[] = $zid; |
567 | } |
568 | } |
569 | return $zids; |
570 | } |
571 | |
572 | /** |
573 | * Gets from the secondary database a list of all natural language ZIDs, |
574 | * mapping from ZID to BCP47 (or MediaWiki) language code |
575 | * |
576 | * @return array<string,string> |
577 | */ |
578 | public function fetchAllZLanguageObjects() { |
579 | $dbr = $this->dbProvider->getReplicaDatabase(); |
580 | $res = $dbr->newSelectQueryBuilder() |
581 | ->select( [ 'wlzlangs_zid', 'wlzlangs_language' ] ) |
582 | ->from( 'wikilambda_zlanguages' ) |
583 | ->orderBy( 'wlzlangs_zid', SelectQueryBuilder::SORT_ASC ) |
584 | ->caller( __METHOD__ ) |
585 | ->fetchResultSet(); |
586 | |
587 | $languages = []; |
588 | foreach ( $res as $row ) { |
589 | $languages[ $row->wlzlangs_zid ] = $row->wlzlangs_language; |
590 | } |
591 | return $languages; |
592 | } |
593 | |
594 | /** |
595 | * Gets from the secondary database a list of all supported natural languages, |
596 | * mapping from ZID to BCP47 (or MediaWiki) language code |
597 | * |
598 | * @return array<string> |
599 | */ |
600 | public function fetchAllZLanguageCodes() { |
601 | return array_values( $this->fetchAllZLanguageObjects() ); |
602 | } |
603 | |
604 | /** |
605 | * Search labels in the secondary database, filtering by language Zids, type or label string. |
606 | * |
607 | * @param string $label Term to search in the label database |
608 | * @param bool $exact Whether to search by exact match |
609 | * @param string[] $languages List of language Zids to filter by |
610 | * @param string|null $type Zid of the type to filter by. If null, don't filter by type. |
611 | * @param string|null $returnType Zid of the return type to filter by. If null, don't filter by return type. |
612 | * @param bool $strictReturnType Whether to exclude Z1s as return type. |
613 | * @param string|null $continue Id to start. If null, start from the first result. |
614 | * @param int|null $limit Maximum number of results to return. |
615 | * @return IResultWrapper |
616 | */ |
617 | public function searchZObjectLabels( |
618 | $label, |
619 | $exact, |
620 | $languages, |
621 | $type, |
622 | $returnType, |
623 | $strictReturnType, |
624 | $continue, |
625 | $limit |
626 | ) { |
627 | $dbr = $this->dbProvider->getReplicaDatabase(); |
628 | |
629 | // Set language filter if any |
630 | if ( count( $languages ) > 0 ) { |
631 | $conditions = [ 'wlzl_language' => $languages ]; |
632 | } |
633 | |
634 | // Set type filter |
635 | $typeConditions = []; |
636 | if ( $type != null ) { |
637 | $typeConditions[] = 'wlzl_type = ' . $dbr->addQuotes( $type ); |
638 | } |
639 | |
640 | // Set returntype filter |
641 | if ( $returnType != null ) { |
642 | $typeConditions[] = 'wlzl_return_type = ' . $dbr->addQuotes( $returnType ); |
643 | if ( !$strictReturnType ) { |
644 | $typeConditions[] = 'wlzl_return_type = ' . $dbr->addQuotes( ZTypeRegistry::Z_OBJECT ); |
645 | } |
646 | } |
647 | |
648 | // Set type conditions |
649 | if ( count( $typeConditions ) > 0 ) { |
650 | $conditions[] = $dbr->makeList( $typeConditions, $dbr::LIST_OR ); |
651 | } |
652 | |
653 | // Set minimum id bound if we are continuing a paged result |
654 | if ( $continue != null ) { |
655 | $conditions[] = "wlzl_id >= $continue"; |
656 | } |
657 | |
658 | // Set search Term and column |
659 | if ( ZObjectUtils::isValidZObjectReference( $label ) ) { |
660 | $searchedColumn = 'wlzl_zobject_zid'; |
661 | $searchTerm = $label; |
662 | } elseif ( $exact ) { |
663 | $searchedColumn = 'wlzl_label'; |
664 | $searchTerm = $label; |
665 | } else { |
666 | $searchedColumn = 'wlzl_label_normalised'; |
667 | $searchTerm = ZObjectUtils::comparableString( $label ); |
668 | } |
669 | |
670 | $conditions[] = $searchedColumn . $dbr->buildLike( $dbr->anyString(), $searchTerm, $dbr->anyString() ); |
671 | |
672 | // Create query builder |
673 | $queryBuilder = $dbr->newSelectQueryBuilder() |
674 | ->select( [ |
675 | 'wlzl_zobject_zid', |
676 | 'wlzl_type', |
677 | 'wlzl_return_type', |
678 | 'wlzl_language', |
679 | 'wlzl_label', |
680 | 'wlzl_label_primary', |
681 | 'wlzl_id' |
682 | ] ) |
683 | ->from( 'wikilambda_zobject_labels' ) |
684 | ->where( $conditions ) |
685 | ->orderBy( 'wlzl_id', SelectQueryBuilder::SORT_ASC ) |
686 | ->orderBy( 'wlzl_label_primary', SelectQueryBuilder::SORT_DESC ); |
687 | |
688 | // Set limit if not null |
689 | if ( $limit ) { |
690 | $queryBuilder->limit( $limit ); |
691 | } |
692 | |
693 | return $queryBuilder |
694 | ->caller( __METHOD__ ) |
695 | ->fetchResultSet(); |
696 | } |
697 | |
698 | /** |
699 | * Fetch the label in the secondary database for a given object by Zid, in a given language. |
700 | * Returns null if no labels are found in the given language or any other language in that |
701 | * language's fallback chain, including English (Z1002). If the language code given is not |
702 | * recognised, this will fall back to returning the English label, if available. |
703 | * |
704 | * @param string $zid Term to search in the label database |
705 | * @param string $languageCode Code of the language in which to fetch the label |
706 | * @param bool $fallback Whether to only match in the given language, or use |
707 | * the language fallback change (default behaviour). |
708 | * @return string|null |
709 | */ |
710 | public function fetchZObjectLabel( $zid, $languageCode, $fallback = true ) { |
711 | $dbr = $this->dbProvider->getReplicaDatabase(); |
712 | |
713 | $conditions = [ 'wlzl_zobject_zid' => $zid ]; |
714 | |
715 | $zLangRegistry = ZLangRegistry::singleton(); |
716 | |
717 | // Provided language code is not known, so fall back to English. |
718 | $languageCode = $zLangRegistry->isLanguageKnownGivenCode( $languageCode ) ? $languageCode : 'en'; |
719 | $languageZid = $zLangRegistry->getLanguageZidFromCode( $languageCode ); |
720 | |
721 | // Set language filter |
722 | $languages = [ $languageZid ]; |
723 | if ( $fallback ) { |
724 | // TODO (T362246): Dependency-inject |
725 | $fallbackLanguages = MediaWikiServices::getInstance()->getLanguageFallback()->getAll( |
726 | $languageCode, |
727 | // Note: We intentionally fall all the way back to English as this will likely be the most |
728 | // common entry language, and so doing this will result in a marginally better UX behaviour |
729 | // for the circumstance where no label exists. |
730 | LanguageFallback::MESSAGES |
731 | ); |
732 | |
733 | foreach ( $fallbackLanguages as $index => $languageCode ) { |
734 | $languages[] = $zLangRegistry->getLanguageZidFromCode( $languageCode ); |
735 | } |
736 | } |
737 | $conditions[ 'wlzl_language' ] = $languages; |
738 | |
739 | // We only want primary labels, not aliases |
740 | $conditions[ 'wlzl_label_primary' ] = '1'; |
741 | |
742 | $res = $dbr->newSelectQueryBuilder() |
743 | ->select( [ |
744 | 'wlzl_zobject_zid', |
745 | 'wlzl_language', |
746 | 'wlzl_label' |
747 | ] ) |
748 | ->from( 'wikilambda_zobject_labels' ) |
749 | ->where( $conditions ) |
750 | ->orderBy( 'wlzl_id', SelectQueryBuilder::SORT_ASC ) |
751 | // Hard-coded performance limit just in case there's a very long / circular language fallback chain. |
752 | ->limit( 5 ) |
753 | ->caller( __METHOD__ ) |
754 | ->fetchResultSet(); |
755 | |
756 | // No hits at all; allow callers to give a fallback message or trigger a DB fetch if they want. |
757 | if ( $res->numRows() === 0 ) { |
758 | return null; |
759 | } |
760 | |
761 | // Collapse labels into a simple array |
762 | $labels = []; |
763 | foreach ( $res as $row ) { |
764 | $labels[$row->wlzl_language] = $row->wlzl_label; |
765 | } |
766 | |
767 | // Walk the labels in order of the language chain, so that language preference is preserved |
768 | foreach ( $languages as $index => $languageZid ) { |
769 | if ( array_key_exists( $languageZid, $labels ) ) { |
770 | return $labels[ $languageZid ]; |
771 | } |
772 | } |
773 | |
774 | // Somehow we've reached this point without a hit? Oh well. |
775 | return null; |
776 | } |
777 | |
778 | /** |
779 | * Get the return type of a given Function Zid or null if not available |
780 | * |
781 | * @param string $zid |
782 | * @return string|null |
783 | */ |
784 | public function fetchZFunctionReturnType( $zid ) { |
785 | $dbr = $this->dbProvider->getReplicaDatabase(); |
786 | $res = $dbr->newSelectQueryBuilder() |
787 | ->select( [ 'wlzl_return_type' ] ) |
788 | ->from( 'wikilambda_zobject_labels' ) |
789 | ->where( [ |
790 | 'wlzl_zobject_zid' => $zid, |
791 | 'wlzl_type' => ZTypeRegistry::Z_FUNCTION, |
792 | ] ) |
793 | ->limit( 1 ) |
794 | ->caller( __METHOD__ ) |
795 | ->fetchField(); |
796 | |
797 | return $res ? (string)$res : null; |
798 | } |
799 | |
800 | /** |
801 | * Search implementations in the secondary database, return the first one |
802 | * This function is primarily used for the example API request |
803 | * |
804 | * @return string |
805 | */ |
806 | public function findFirstZImplementationFunction(): string { |
807 | $dbr = $this->dbProvider->getReplicaDatabase(); |
808 | $res = $dbr->newSelectQueryBuilder() |
809 | ->select( [ 'wlzf_zfunction_zid' ] ) |
810 | ->from( 'wikilambda_zobject_function_join' ) |
811 | ->where( [ |
812 | 'wlzf_type' => ZTypeRegistry::Z_IMPLEMENTATION, |
813 | ] ) |
814 | ->limit( 1 ) |
815 | ->caller( __METHOD__ ) |
816 | ->fetchField(); |
817 | |
818 | return $res ? (string)$res : ''; |
819 | } |
820 | |
821 | /** |
822 | * Converts findReferencedZObjectsByZFunctionId into a list of zids |
823 | * |
824 | * @param string $zid the ZID of the ZFunction |
825 | * @param string $type the type of the ZFunction reference |
826 | * @return string[] All ZIDs of referenced ZObjects associated to the ZFunction |
827 | */ |
828 | public function findReferencedZObjectsByZFunctionIdAsList( |
829 | $zid, |
830 | $type |
831 | ) { |
832 | $res = $this->findReferencedZObjectsByZFunctionId( $zid, $type ); |
833 | $zids = []; |
834 | foreach ( $res as $row ) { |
835 | $zids[] = $row->wlzf_ref_zid; |
836 | } |
837 | |
838 | return $zids; |
839 | } |
840 | |
841 | /** |
842 | * Search implementations in the secondary database and return all matching a given ZID |
843 | * |
844 | * @param string $zid the ZID of the ZFunction |
845 | * @param string $type the type of the ZFunction reference |
846 | * @param string|null $continue Id to start. If null (the default), start from the first result. |
847 | * @param int|null $limit Maximum number of results to return. Defaults to 10 |
848 | * @return IResultWrapper |
849 | */ |
850 | public function findReferencedZObjectsByZFunctionId( |
851 | $zid, |
852 | $type, |
853 | $continue = null, |
854 | $limit = 10 |
855 | ) { |
856 | $dbr = $this->dbProvider->getReplicaDatabase(); |
857 | |
858 | $conditions = [ |
859 | 'wlzf_zfunction_zid' => $zid, |
860 | 'wlzf_type' => $type |
861 | ]; |
862 | |
863 | // Set minimum id bound if we are continuing a paged result |
864 | if ( $continue != null ) { |
865 | $conditions[] = "wlzf_id >= $continue"; |
866 | } |
867 | $res = $dbr->newSelectQueryBuilder() |
868 | ->select( [ 'wlzf_ref_zid', 'wlzf_id' ] ) |
869 | ->from( 'wikilambda_zobject_function_join' ) |
870 | ->where( $conditions ) |
871 | ->orderBy( 'wlzf_id', SelectQueryBuilder::SORT_ASC ) |
872 | ->limit( $limit ) |
873 | ->caller( __METHOD__ ) |
874 | ->fetchResultSet(); |
875 | |
876 | return $res; |
877 | } |
878 | |
879 | /** |
880 | * Add a record to the database for a given ZObject ID and ZFunction ID |
881 | * |
882 | * @param string $refId the ZObject ref ID |
883 | * @param string $zFunctionId the ZFunction ID |
884 | * @param string $type the type of the ZFunction reference |
885 | * @return void|bool |
886 | */ |
887 | public function insertZFunctionReference( $refId, $zFunctionId, $type ) { |
888 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
889 | |
890 | $dbw->newInsertQueryBuilder() |
891 | ->insertInto( 'wikilambda_zobject_function_join' ) |
892 | ->rows( [ |
893 | [ |
894 | 'wlzf_ref_zid' => $refId, |
895 | 'wlzf_zfunction_zid' => $zFunctionId, |
896 | 'wlzf_type' => $type |
897 | ] |
898 | ] ) |
899 | ->caller( __METHOD__ )->execute(); |
900 | } |
901 | |
902 | /** |
903 | * Remove a given ZObject ref from the secondary database |
904 | * |
905 | * @param string $refId the ZObject ID |
906 | * @return void |
907 | */ |
908 | public function deleteZFunctionReference( $refId ): void { |
909 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
910 | |
911 | $dbw->newDeleteQueryBuilder() |
912 | ->deleteFrom( 'wikilambda_zobject_function_join' ) |
913 | ->where( [ 'wlzf_ref_zid' => $refId ] ) |
914 | ->caller( __METHOD__ )->execute(); |
915 | } |
916 | |
917 | /** |
918 | * Search test run results in the secondary tester results table; the latest result (highest |
919 | * database ID) will be used. |
920 | * |
921 | * @param ?string $functionZID The ZID of the ZFunction |
922 | * @param ?int $functionRevision The revision ID of the ZFunction |
923 | * @param ?string $implementationZID The ZID of the ZImplementation |
924 | * @param ?int $implementationRevision The revision ID of the ZImplementation |
925 | * @param ?string $testerZID The ZID of the ZTester |
926 | * @param ?int $testerRevision The revision ID of the ZTester |
927 | * @return ?ZResponseEnvelope |
928 | */ |
929 | public function findZTesterResult( |
930 | ?string $functionZID, |
931 | ?int $functionRevision, |
932 | ?string $implementationZID, |
933 | ?int $implementationRevision, |
934 | ?string $testerZID, |
935 | ?int $testerRevision |
936 | ) { |
937 | $dbr = $this->dbProvider->getReplicaDatabase(); |
938 | |
939 | $purgeableResults = []; |
940 | $conditions = []; |
941 | if ( $functionZID !== null ) { |
942 | $conditions[] = "wlztr_zfunction_zid = " . $dbr->addQuotes( $functionZID ); |
943 | $purgeableResults[] = $functionZID; |
944 | } |
945 | if ( $functionRevision !== null ) { |
946 | $conditions[] = "wlztr_zfunction_revision = " . $dbr->addQuotes( $functionRevision ); |
947 | } |
948 | if ( $implementationZID !== null ) { |
949 | $conditions[] = "wlztr_zimplementation_zid = " . $dbr->addQuotes( $implementationZID ); |
950 | $purgeableResults[] = $implementationZID; |
951 | } |
952 | if ( $implementationRevision !== null ) { |
953 | $conditions[] = "wlztr_zimplementation_revision = " . $dbr->addQuotes( $implementationRevision ); |
954 | } |
955 | if ( $testerZID !== null ) { |
956 | $conditions[] = "wlztr_ztester_zid = " . $dbr->addQuotes( $testerZID ); |
957 | $purgeableResults[] = $testerZID; |
958 | } |
959 | if ( $testerRevision !== null ) { |
960 | $conditions[] = "wlztr_ztester_revision = " . $dbr->addQuotes( $testerRevision ); |
961 | } |
962 | |
963 | $res = $dbr->newSelectQueryBuilder() |
964 | ->select( [ |
965 | 'wlztr_id', |
966 | 'wlztr_zfunction_zid', 'wlztr_zfunction_revision', |
967 | 'wlztr_zimplementation_zid', 'wlztr_zimplementation_revision', |
968 | 'wlztr_ztester_zid', 'wlztr_ztester_revision', |
969 | 'wlztr_pass', |
970 | 'wlztr_returnobject' |
971 | ] ) |
972 | ->from( 'wikilambda_ztester_results' ) |
973 | ->where( $conditions ) |
974 | ->orderBy( 'wlztr_id', SelectQueryBuilder::SORT_DESC ) |
975 | ->limit( 1 ) |
976 | ->caller( __METHOD__ ) |
977 | ->fetchResultSet(); |
978 | |
979 | if ( $res->numRows() == 0 ) { |
980 | return null; |
981 | } |
982 | |
983 | $result = $res->fetchRow(); |
984 | try { |
985 | $responseObjectString = $result['wlztr_returnobject']; |
986 | $responseObject = ZObjectFactory::create( json_decode( $responseObjectString ) ); |
987 | if ( $responseObject instanceof ZResponseEnvelope ) { |
988 | return $responseObject; |
989 | } |
990 | |
991 | // Something's gone wrong, somehow |
992 | $this->logger->error( |
993 | __METHOD__ . ' retrieved a non-ZResponseEnvelope: ' . $responseObjectString, |
994 | [ 'responseObject' => $responseObjectString ] |
995 | ); |
996 | } catch ( \Throwable $th ) { |
997 | // Something's gone differently wrong, somehow |
998 | $this->logger->error( |
999 | __METHOD__ . ' threw from ZObjectFactory', |
1000 | [ 'throwable' => $th ] |
1001 | ); |
1002 | } |
1003 | |
1004 | return null; |
1005 | } |
1006 | |
1007 | /** |
1008 | * Cache a test run result in the secondary tester results table |
1009 | * |
1010 | * @param string $functionZID The ZID of the ZFunction |
1011 | * @param int $functionRevision The revision ID of the ZFunction |
1012 | * @param string $implementationZID The ZID of the ZImplementation |
1013 | * @param int $implementationRevision The revision ID of the ZImplementation |
1014 | * @param string $testerZID The ZID of the ZTester |
1015 | * @param int $testerRevision The revision ID of the ZTester |
1016 | * @param bool $testerResult Whether the test run passed (true) or failed (false) |
1017 | * @param string $testerResponse The test run response JSON object |
1018 | * @return bool |
1019 | */ |
1020 | public function insertZTesterResult( |
1021 | string $functionZID, |
1022 | int $functionRevision, |
1023 | string $implementationZID, |
1024 | int $implementationRevision, |
1025 | string $testerZID, |
1026 | int $testerRevision, |
1027 | bool $testerResult, |
1028 | string $testerResponse |
1029 | ): bool { |
1030 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1031 | |
1032 | $dbw->newInsertQueryBuilder() |
1033 | ->insertInto( 'wikilambda_ztester_results' ) |
1034 | ->row( [ |
1035 | 'wlztr_zfunction_zid' => $functionZID, |
1036 | 'wlztr_zfunction_revision' => $functionRevision, |
1037 | 'wlztr_zimplementation_zid' => $implementationZID, |
1038 | 'wlztr_zimplementation_revision' => $implementationRevision, |
1039 | 'wlztr_ztester_zid' => $testerZID, |
1040 | 'wlztr_ztester_revision' => $testerRevision, |
1041 | 'wlztr_pass' => $testerResult, |
1042 | 'wlztr_returnobject' => $testerResponse, |
1043 | ] ) |
1044 | ->onDuplicateKeyUpdate() |
1045 | ->uniqueIndexFields( [ |
1046 | 'wlztr_zfunction_revision', |
1047 | 'wlztr_zimplementation_revision', |
1048 | 'wlztr_ztester_revision' |
1049 | ] ) |
1050 | ->set( [ |
1051 | 'wlztr_pass' => $testerResult, |
1052 | 'wlztr_returnobject' => $testerResponse, |
1053 | ] ) |
1054 | ->caller( __METHOD__ )->execute(); |
1055 | |
1056 | return (bool)$dbw->affectedRows(); |
1057 | } |
1058 | |
1059 | /** |
1060 | * Remove a given ZFunction's results from the secondary tester results table. |
1061 | * |
1062 | * @param string $refId the ZID of the ZFunction whose results you wish to delete |
1063 | * @return void |
1064 | */ |
1065 | public function deleteZFunctionFromZTesterResultsCache( string $refId ): void { |
1066 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1067 | |
1068 | $dbw->newDeleteQueryBuilder() |
1069 | ->deleteFrom( 'wikilambda_ztester_results' ) |
1070 | ->where( [ 'wlztr_zfunction_zid' => $refId ] ) |
1071 | ->caller( __METHOD__ )->execute(); |
1072 | } |
1073 | |
1074 | /** |
1075 | * Remove a given ZImplementation's results from the secondary tester results table. |
1076 | * |
1077 | * @param string $refId the ZID of the ZImplementation whose results you wish to delete |
1078 | * @return void |
1079 | */ |
1080 | public function deleteZImplementationFromZTesterResultsCache( string $refId ): void { |
1081 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1082 | |
1083 | $dbw->newDeleteQueryBuilder() |
1084 | ->deleteFrom( 'wikilambda_ztester_results' ) |
1085 | ->where( [ 'wlztr_zimplementation_zid' => $refId ] ) |
1086 | ->caller( __METHOD__ )->execute(); |
1087 | } |
1088 | |
1089 | /** |
1090 | * Remove a given ZTester's results from the secondary tester results table. |
1091 | * |
1092 | * @param string $refId the ZID of the ZTester whose results you wish to delete |
1093 | * @return void |
1094 | */ |
1095 | public function deleteZTesterFromZTesterResultsCache( string $refId ): void { |
1096 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1097 | |
1098 | $dbw->newDeleteQueryBuilder() |
1099 | ->deleteFrom( 'wikilambda_ztester_results' ) |
1100 | ->where( [ 'wlztr_ztester_zid' => $refId ] ) |
1101 | ->caller( __METHOD__ )->execute(); |
1102 | } |
1103 | |
1104 | /** |
1105 | * Remove a given ZNaturalLanguage's entry from the secondary languages table. |
1106 | * |
1107 | * @param string $zid the ZID of the ZNaturalLanguage you wish to delete |
1108 | * @return void |
1109 | */ |
1110 | public function deleteZLanguageFromLanguagesCache( string $zid ): void { |
1111 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1112 | |
1113 | $dbw->newDeleteQueryBuilder() |
1114 | ->deleteFrom( 'wikilambda_zlanguages' ) |
1115 | ->where( [ 'wlzlangs_id' => $zid ] ) |
1116 | ->caller( __METHOD__ )->execute(); |
1117 | } |
1118 | |
1119 | /** |
1120 | * Delete data related to the given zids from the secondary labels table. |
1121 | * This method is only for its use by reloadBuiltinData with the --force flag. |
1122 | * |
1123 | * @param string[] $zids list of zids to clear |
1124 | * @return void |
1125 | */ |
1126 | public function deleteFromLabelsSecondaryTables( array $zids ): void { |
1127 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1128 | |
1129 | $dbw->newDeleteQueryBuilder() |
1130 | ->deleteFrom( 'wikilambda_zobject_labels' ) |
1131 | ->where( [ 'wlzl_zobject_zid' => $zids ] ) |
1132 | ->caller( __METHOD__ )->execute(); |
1133 | |
1134 | $dbw->newDeleteQueryBuilder() |
1135 | ->deleteFrom( 'wikilambda_zobject_label_conflicts' ) |
1136 | ->where( [ 'wlzlc_existing_zid' => $zids ] ) |
1137 | ->caller( __METHOD__ )->execute(); |
1138 | } |
1139 | |
1140 | /** |
1141 | * Delete data related to the given zids from the secondary function joins table. |
1142 | * This method is only for its use by reloadBuiltinData with the --force flag. |
1143 | * |
1144 | * @param string[] $zids list of zids to clear |
1145 | * @return void |
1146 | */ |
1147 | public function deleteFromFunctionsSecondaryTables( array $zids ): void { |
1148 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1149 | |
1150 | $dbw->newDeleteQueryBuilder() |
1151 | ->deleteFrom( 'wikilambda_zobject_function_join' ) |
1152 | ->where( $dbw->makeList( |
1153 | [ |
1154 | 'wlzf_zfunction_zid' => $zids, |
1155 | 'wlzf_ref_zid' => $zids |
1156 | ], |
1157 | $dbw::LIST_OR |
1158 | ) |
1159 | ) |
1160 | ->caller( __METHOD__ )->execute(); |
1161 | } |
1162 | |
1163 | /** |
1164 | * Delete data related to the given zids from the secondary languages table. |
1165 | * This method is only for its use by reloadBuiltinData with the --force flag. |
1166 | * |
1167 | * @param string[] $zids list of zids to clear |
1168 | * @return void |
1169 | */ |
1170 | public function deleteFromLanguageCacheSecondaryTables( array $zids ): void { |
1171 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1172 | |
1173 | $dbw->newDeleteQueryBuilder() |
1174 | ->deleteFrom( 'wikilambda_zlanguages' ) |
1175 | ->where( [ 'wlzlangs_zid' => $zids ] ) |
1176 | ->caller( __METHOD__ )->execute(); |
1177 | } |
1178 | |
1179 | /** |
1180 | * Delete data related to the given zids from the secondary tester results table. |
1181 | * This method is only for its use by reloadBuiltinData with the --force flag. |
1182 | * |
1183 | * @param string[] $zids list of zids to clear |
1184 | * @return void |
1185 | */ |
1186 | public function deleteFromTesterResultsSecondaryTables( array $zids ): void { |
1187 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1188 | |
1189 | $dbw->newDeleteQueryBuilder() |
1190 | ->deleteFrom( 'wikilambda_ztester_results' ) |
1191 | ->where( |
1192 | $dbw->makeList( |
1193 | [ |
1194 | 'wlztr_zfunction_zid' => $zids, |
1195 | 'wlztr_zimplementation_zid' => $zids, |
1196 | 'wlztr_ztester_zid' => $zids |
1197 | ], |
1198 | $dbw::LIST_OR |
1199 | ) |
1200 | ) |
1201 | ->caller( __METHOD__ )->execute(); |
1202 | } |
1203 | |
1204 | /** |
1205 | * Clear all data from the secondary labels table. This method |
1206 | * is only for its use by reloadBuiltinData with the --force flag. |
1207 | * |
1208 | * @return void |
1209 | */ |
1210 | public function clearLabelsSecondaryTables(): void { |
1211 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1212 | |
1213 | $dbw->newDeleteQueryBuilder() |
1214 | ->deleteFrom( 'wikilambda_zobject_labels' ) |
1215 | ->where( IDatabase::ALL_ROWS ) |
1216 | ->caller( __METHOD__ )->execute(); |
1217 | |
1218 | $dbw->newDeleteQueryBuilder() |
1219 | ->deleteFrom( 'wikilambda_zobject_label_conflicts' ) |
1220 | ->where( IDatabase::ALL_ROWS ) |
1221 | ->caller( __METHOD__ )->execute(); |
1222 | } |
1223 | |
1224 | /** |
1225 | * Clear all data from the secondary function joins table. This method |
1226 | * is only for its use by reloadBuiltinData with the --force flag. |
1227 | * |
1228 | * @return void |
1229 | */ |
1230 | public function clearFunctionsSecondaryTables(): void { |
1231 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1232 | |
1233 | $dbw->newDeleteQueryBuilder() |
1234 | ->deleteFrom( 'wikilambda_zobject_function_join' ) |
1235 | ->where( IDatabase::ALL_ROWS ) |
1236 | ->caller( __METHOD__ )->execute(); |
1237 | } |
1238 | |
1239 | /** |
1240 | * Clear all data from the secondary tester results table. This method |
1241 | * is only for its use by reloadBuiltinData with the --force flag. |
1242 | * |
1243 | * @return void |
1244 | */ |
1245 | public function clearTesterResultsSecondaryTables(): void { |
1246 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1247 | |
1248 | $dbw->newDeleteQueryBuilder() |
1249 | ->deleteFrom( 'wikilambda_ztester_results' ) |
1250 | ->where( IDatabase::ALL_ROWS ) |
1251 | ->caller( __METHOD__ )->execute(); |
1252 | } |
1253 | |
1254 | /** |
1255 | * Clear all data from the secondary languages table. This method |
1256 | * is only for its use by reloadBuiltinData with the --force flag. |
1257 | * |
1258 | * @return void |
1259 | */ |
1260 | public function clearLanguageCacheSecondaryTables(): void { |
1261 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
1262 | |
1263 | $dbw->newDeleteQueryBuilder() |
1264 | ->deleteFrom( 'wikilambda_zlanguages' ) |
1265 | ->where( IDatabase::ALL_ROWS ) |
1266 | ->caller( __METHOD__ )->execute(); |
1267 | } |
1268 | |
1269 | } |