Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.23% |
277 / 307 |
|
86.05% |
37 / 43 |
CRAP | |
0.00% |
0 / 1 |
AbstractRdsResultStorage | |
90.23% |
277 / 307 |
|
86.05% |
37 / 43 |
90.58 | |
0.00% |
0 / 1 |
storeTestVariable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
storeTestVariables | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
storeItemVariable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
storeItemVariables | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
replaceItemVariables | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
2 | |||
replaceTestVariables | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
storeRelatedTestTaker | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
storeRelatedDelivery | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
storeRelatedData | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
2 | |||
getVariables | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
getDeliveryVariables | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
getVariable | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
getVariableProperty | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
getVariablesSortingField | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getTestTaker | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDelivery | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRelatedData | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getAllCallIds | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
getRelatedItemCallIds | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRelatedTestCallIds | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRelatedCallIds | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
getAllTestTakerIds | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAllDeliveryIds | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAllIds | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
getResultByDelivery | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
6 | |||
getOrderField | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getOrderDirection | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
countResultByDelivery | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
deleteResult | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
3.10 | |||
prepareItemVariableData | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
prepareTestVariableData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
prepareVariableData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
prepareVariableDataForSchema | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getResultRow | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
getQueryBuilder | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPersistence | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
unserializeVariableValue | |
23.08% |
3 / 13 |
|
0.00% |
0 / 1 |
3.82 | |||
serializeVariableValue | |
22.22% |
2 / 9 |
|
0.00% |
0 / 1 |
3.88 | |||
insertMultiple | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
spawnResult | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
configure | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
sortTimeStamps | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
createResultsTable | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
createVariablesTable | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
createTableConstraints | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
getTypes | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2014-2023 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT); |
19 | * |
20 | */ |
21 | |
22 | namespace oat\taoOutcomeRds\model; |
23 | |
24 | use Doctrine\DBAL\Connection; |
25 | use Doctrine\DBAL\Exception\UniqueConstraintViolationException; |
26 | use Doctrine\DBAL\ParameterType; |
27 | use Doctrine\DBAL\Query\QueryBuilder; |
28 | use Doctrine\DBAL\Schema\Schema; |
29 | use Doctrine\DBAL\Schema\SchemaException; |
30 | use Doctrine\DBAL\Schema\Table; |
31 | use Exception; |
32 | use oat\generis\persistence\PersistenceManager; |
33 | use oat\oatbox\log\LoggerAwareTrait; |
34 | use oat\oatbox\service\ConfigurableService; |
35 | use oat\taoResultServer\models\classes\ResultDeliveryExecutionDelete; |
36 | use oat\taoResultServer\models\classes\ResultManagement; |
37 | use oat\taoResultServer\models\Exceptions\DuplicateVariableException; |
38 | use Psr\Log\LoggerAwareInterface; |
39 | use taoResultServer_models_classes_ReadableResultStorage as ReadableResultStorage; |
40 | use taoResultServer_models_classes_Variable as Variable; |
41 | use taoResultServer_models_classes_WritableResultStorage as WritableResultStorage; |
42 | |
43 | /** |
44 | * Implements tao results storage using the configured persistence "taoOutcomeRds" |
45 | */ |
46 | abstract class AbstractRdsResultStorage extends ConfigurableService implements |
47 | WritableResultStorage, |
48 | ReadableResultStorage, |
49 | ResultManagement, |
50 | LoggerAwareInterface |
51 | { |
52 | use LoggerAwareTrait; |
53 | use ResultDeliveryExecutionDelete; |
54 | |
55 | public const SERVICE_ID = 'taoOutcomeRds/RdsResultStorage'; |
56 | /** |
57 | * Constants for the database creation and data access |
58 | */ |
59 | public const RESULTS_TABLENAME = 'results_storage'; |
60 | public const RESULTS_TABLE_ID = 'result_id'; |
61 | public const TEST_TAKER_COLUMN = 'test_taker'; |
62 | public const DELIVERY_COLUMN = 'delivery'; |
63 | public const VARIABLES_TABLENAME = 'variables_storage'; |
64 | public const VARIABLES_TABLE_ID = 'variable_id'; |
65 | public const CALL_ID_ITEM_COLUMN = 'call_id_item'; |
66 | public const CALL_ID_TEST_COLUMN = 'call_id_test'; |
67 | public const TEST_COLUMN = 'test'; |
68 | public const ITEM_COLUMN = 'item'; |
69 | public const VARIABLE_VALUE = 'value'; |
70 | public const VARIABLE_IDENTIFIER = 'identifier'; |
71 | public const VARIABLE_HASH = 'variable_hash'; |
72 | public const CALL_ID_ITEM_INDEX = 'idx_variables_storage_call_id_item'; |
73 | public const CALL_ID_TEST_INDEX = 'idx_variables_storage_call_id_test'; |
74 | public const UNIQUE_VARIABLE_INDEX = 'idx_unique_variables_storage'; |
75 | /** @deprecated */ |
76 | public const VARIABLE_CLASS = 'class'; |
77 | public const VARIABLES_FK_COLUMN = 'results_result_id'; |
78 | public const VARIABLES_FK_NAME = 'fk_variables_results'; |
79 | /** @deprecated */ |
80 | public const RESULT_KEY_VALUE_TABLE_NAME = 'results_kv_storage'; |
81 | /** @deprecated */ |
82 | public const KEY_COLUMN = 'result_key'; |
83 | /** @deprecated */ |
84 | public const VALUE_COLUMN = 'result_value'; |
85 | /** @deprecated */ |
86 | public const RESULTSKV_FK_COLUMN = 'variables_variable_id'; |
87 | /** @deprecated */ |
88 | public const RESULTSKV_FK_NAME = 'fk_resultsKv_variables'; |
89 | /** result storage persistence identifier */ |
90 | public const OPTION_PERSISTENCE = 'persistence'; |
91 | // Fields for results retrieval. |
92 | public const FIELD_DELIVERY_RESULT = 'deliveryResultIdentifier'; |
93 | public const FIELD_TEST_TAKER = 'testTakerIdentifier'; |
94 | public const FIELD_DELIVERY = 'deliveryIdentifier'; |
95 | |
96 | /** @var */ |
97 | protected $persistence; |
98 | |
99 | public function storeTestVariable($deliveryResultIdentifier, $test, Variable $testVariable, $callIdTest) |
100 | { |
101 | $this->storeTestVariables($deliveryResultIdentifier, $test, [$testVariable], $callIdTest); |
102 | } |
103 | |
104 | /** |
105 | * @inheritdoc |
106 | * Stores the test variables in table and their values in key/value storage. |
107 | */ |
108 | public function storeTestVariables($deliveryResultIdentifier, $test, array $testVariables, $callIdTest) |
109 | { |
110 | $dataToInsert = []; |
111 | |
112 | foreach ($testVariables as $testVariable) { |
113 | $dataToInsert[] = $this->prepareTestVariableData( |
114 | $deliveryResultIdentifier, |
115 | $test, |
116 | $testVariable, |
117 | $callIdTest |
118 | ); |
119 | } |
120 | |
121 | $this->insertMultiple($dataToInsert); |
122 | } |
123 | |
124 | /** |
125 | * @inheritdoc |
126 | * Stores the item in table and its value in key/value storage. |
127 | */ |
128 | public function storeItemVariable($deliveryResultIdentifier, $test, $item, Variable $itemVariable, $callIdItem) |
129 | { |
130 | $this->storeItemVariables($deliveryResultIdentifier, $test, $item, [$itemVariable], $callIdItem); |
131 | } |
132 | |
133 | /** |
134 | * @inheritdoc |
135 | * Stores the item variables in table and their values in key/value storage. |
136 | */ |
137 | public function storeItemVariables($deliveryResultIdentifier, $test, $item, array $itemVariables, $callIdItem) |
138 | { |
139 | $dataToInsert = []; |
140 | |
141 | foreach ($itemVariables as $itemVariable) { |
142 | $dataToInsert[] = $this->prepareItemVariableData( |
143 | $deliveryResultIdentifier, |
144 | $test, |
145 | $item, |
146 | $itemVariable, |
147 | $callIdItem |
148 | ); |
149 | } |
150 | |
151 | $this->insertMultiple($dataToInsert); |
152 | } |
153 | |
154 | /** |
155 | * Force update existing variable |
156 | * |
157 | * @param array $itemVariables - [ |
158 | * '{{variable_id}}' => (Object of taoResultServer_models_classes_Variable) |
159 | * ] |
160 | * @return void |
161 | */ |
162 | public function replaceItemVariables( |
163 | string $deliveryResultIdentifier, |
164 | string $testUri, |
165 | string $itemUri, |
166 | string $callIdItem, |
167 | array $itemVariables |
168 | ): void { |
169 | $update = []; |
170 | |
171 | foreach ($itemVariables as $itemVariableId => $itemVariable) { |
172 | $update[] = [ |
173 | 'conditions' => [ |
174 | self::VARIABLES_TABLE_ID => $itemVariableId, |
175 | ], |
176 | 'updateValues' => $this->prepareItemVariableData( |
177 | $deliveryResultIdentifier, |
178 | $testUri, |
179 | $itemUri, |
180 | $itemVariable, |
181 | $callIdItem |
182 | ) |
183 | ]; |
184 | } |
185 | |
186 | $this->getPersistence()->updateMultiple(self::VARIABLES_TABLENAME, $update); |
187 | } |
188 | |
189 | /** |
190 | * Force update existing variable |
191 | * |
192 | * @param array $testVariables - [ |
193 | * '{{variable_id}}' => (Object of taoResultServer_models_classes_Variable) |
194 | * ] |
195 | * @return void |
196 | */ |
197 | public function replaceTestVariables( |
198 | string $deliveryResultIdentifier, |
199 | string $testUri, |
200 | string $callIdTest, |
201 | array $testVariables |
202 | ): void { |
203 | $update = []; |
204 | |
205 | foreach ($testVariables as $testVariableId => $testVariable) { |
206 | $update[] = [ |
207 | 'conditions' => [ |
208 | self::VARIABLES_TABLE_ID => $testVariableId, |
209 | ], |
210 | 'updateValues' => $this->prepareTestVariableData( |
211 | $deliveryResultIdentifier, |
212 | $testUri, |
213 | $testVariable, |
214 | $callIdTest |
215 | ) |
216 | ]; |
217 | } |
218 | |
219 | $this->getPersistence()->updateMultiple(self::VARIABLES_TABLENAME, $update); |
220 | } |
221 | |
222 | public function storeRelatedTestTaker($deliveryResultIdentifier, $testTakerIdentifier) |
223 | { |
224 | $this->storeRelatedData($deliveryResultIdentifier, self::TEST_TAKER_COLUMN, $testTakerIdentifier); |
225 | } |
226 | |
227 | public function storeRelatedDelivery($deliveryResultIdentifier, $deliveryIdentifier) |
228 | { |
229 | $this->storeRelatedData($deliveryResultIdentifier, self::DELIVERY_COLUMN, $deliveryIdentifier); |
230 | } |
231 | |
232 | /** |
233 | * Store Delivery corresponding to the current test |
234 | * |
235 | * @param string $deliveryResultIdentifier |
236 | * @param string $relatedField |
237 | * @param string $relatedIdentifier |
238 | */ |
239 | public function storeRelatedData($deliveryResultIdentifier, $relatedField, $relatedIdentifier) |
240 | { |
241 | $qb = $this->getQueryBuilder() |
242 | ->select('COUNT(*)') |
243 | ->from(self::RESULTS_TABLENAME) |
244 | ->andWhere(self::RESULTS_TABLE_ID . ' = :id') |
245 | ->setParameter('id', $deliveryResultIdentifier); |
246 | if ((int)$qb->execute()->fetchColumn() === 0) { |
247 | $this->getPersistence()->insert( |
248 | self::RESULTS_TABLENAME, |
249 | [ |
250 | self::RESULTS_TABLE_ID => $deliveryResultIdentifier, |
251 | $relatedField => $relatedIdentifier, |
252 | ] |
253 | ); |
254 | } else { |
255 | $sqlUpdate = 'UPDATE ' . self::RESULTS_TABLENAME . ' SET ' . $relatedField . ' = ? WHERE ' . |
256 | self::RESULTS_TABLE_ID . ' = ?'; |
257 | $paramsUpdate = [$relatedIdentifier, $deliveryResultIdentifier]; |
258 | $this->getPersistence()->exec($sqlUpdate, $paramsUpdate); |
259 | } |
260 | } |
261 | |
262 | public function getVariables($callId) |
263 | { |
264 | if (!is_array($callId)) { |
265 | $callId = [$callId]; |
266 | } |
267 | |
268 | $qb = $this->getQueryBuilder() |
269 | ->select('*') |
270 | ->from(self::VARIABLES_TABLENAME) |
271 | ->andWhere(self::CALL_ID_ITEM_COLUMN . ' IN (:ids) OR ' . self::CALL_ID_TEST_COLUMN . ' IN (:ids)') |
272 | ->orderBy($this->getVariablesSortingField()) |
273 | ->setParameter('ids', $callId, Connection::PARAM_STR_ARRAY); |
274 | |
275 | $returnValue = []; |
276 | foreach ($qb->execute()->fetchAll() as $variable) { |
277 | $returnValue[$variable[self::VARIABLES_TABLE_ID]][] = $this->getResultRow($variable); |
278 | } |
279 | |
280 | return $returnValue; |
281 | } |
282 | |
283 | public function getDeliveryVariables($deliveryResultIdentifier) |
284 | { |
285 | if (!is_array($deliveryResultIdentifier)) { |
286 | $deliveryResultIdentifier = [$deliveryResultIdentifier]; |
287 | } |
288 | |
289 | $qb = $this->getQueryBuilder() |
290 | ->select('*') |
291 | ->from(self::VARIABLES_TABLENAME) |
292 | ->andWhere(self::VARIABLES_FK_COLUMN . ' IN (:ids)') |
293 | ->orderBy($this->getVariablesSortingField()) |
294 | ->setParameter('ids', $deliveryResultIdentifier, Connection::PARAM_STR_ARRAY); |
295 | |
296 | $returnValue = []; |
297 | foreach ($qb->execute()->fetchAll() as $variable) { |
298 | $returnValue[$variable[self::VARIABLES_TABLE_ID]][] = $this->getResultRow($variable); |
299 | } |
300 | |
301 | return $returnValue; |
302 | } |
303 | |
304 | public function getVariable($callId, $variableIdentifier) |
305 | { |
306 | $qb = $this->getQueryBuilder() |
307 | ->select('*') |
308 | ->from(self::VARIABLES_TABLENAME) |
309 | ->andWhere(self::CALL_ID_ITEM_COLUMN . ' = :callId OR ' . self::CALL_ID_TEST_COLUMN . ' = :callId') |
310 | ->andWhere(self::VARIABLE_IDENTIFIER . ' = :variableId') |
311 | ->setParameter('callId', $callId) |
312 | ->setParameter('variableId', $variableIdentifier); |
313 | |
314 | $returnValue = []; |
315 | foreach ($qb->execute()->fetchAll() as $variable) { |
316 | $returnValue[$variable[self::VARIABLES_TABLE_ID]] = $this->getResultRow($variable); |
317 | } |
318 | |
319 | return $returnValue; |
320 | } |
321 | |
322 | public function getVariableProperty($variableId, $property) |
323 | { |
324 | $qb = $this->getQueryBuilder() |
325 | ->select(self::VARIABLE_VALUE) |
326 | ->from(self::VARIABLES_TABLENAME) |
327 | ->andWhere(self::VARIABLES_TABLE_ID . ' = :variableId') |
328 | ->setParameter('variableId', $variableId); |
329 | |
330 | $variableValue = $qb->execute()->fetchColumn(); |
331 | $variableValue = $this->unserializeVariableValue($variableValue); |
332 | $getter = 'get' . ucfirst($property); |
333 | if (is_callable([$variableValue, $getter])) { |
334 | return $variableValue->$getter(); |
335 | } |
336 | |
337 | return null; |
338 | } |
339 | |
340 | /** |
341 | * Returns the field to sort item and test variables. |
342 | * |
343 | * @return string |
344 | */ |
345 | abstract protected function getVariablesSortingField(); |
346 | |
347 | public function getTestTaker($deliveryResultIdentifier) |
348 | { |
349 | return $this->getRelatedData($deliveryResultIdentifier, self::TEST_TAKER_COLUMN); |
350 | } |
351 | |
352 | public function getDelivery($deliveryResultIdentifier) |
353 | { |
354 | return $this->getRelatedData($deliveryResultIdentifier, self::DELIVERY_COLUMN); |
355 | } |
356 | |
357 | /** |
358 | * Retrieves data related to a result. |
359 | * |
360 | * @param string $deliveryResultIdentifier |
361 | * @param string $field |
362 | * |
363 | * @return mixed |
364 | */ |
365 | public function getRelatedData($deliveryResultIdentifier, $field) |
366 | { |
367 | $qb = $this->getQueryBuilder() |
368 | ->select($field) |
369 | ->from(self::RESULTS_TABLENAME) |
370 | ->andWhere(self::RESULTS_TABLE_ID . ' = :id') |
371 | ->setParameter('id', $deliveryResultIdentifier); |
372 | |
373 | return $qb->execute()->fetchColumn(); |
374 | } |
375 | |
376 | /** |
377 | * @inheritdoc |
378 | * o(n) do not use real time (postprocessing) |
379 | */ |
380 | public function getAllCallIds() |
381 | { |
382 | $qb = $this->getQueryBuilder() |
383 | ->select( |
384 | 'DISTINCT(' . self::CALL_ID_ITEM_COLUMN . '), ' . |
385 | self::CALL_ID_TEST_COLUMN . ', ' . self::VARIABLES_FK_COLUMN |
386 | ) |
387 | ->from(self::VARIABLES_TABLENAME); |
388 | |
389 | $returnValue = []; |
390 | foreach ($qb->execute()->fetchAll() as $value) { |
391 | $returnValue[] = ($value[self::CALL_ID_ITEM_COLUMN] != '') |
392 | ? $value[self::CALL_ID_ITEM_COLUMN] |
393 | : $value[self::CALL_ID_TEST_COLUMN]; |
394 | } |
395 | |
396 | return $returnValue; |
397 | } |
398 | |
399 | public function getRelatedItemCallIds($deliveryResultIdentifier) |
400 | { |
401 | return $this->getRelatedCallIds($deliveryResultIdentifier, self::CALL_ID_ITEM_COLUMN); |
402 | } |
403 | |
404 | public function getRelatedTestCallIds($deliveryResultIdentifier) |
405 | { |
406 | return $this->getRelatedCallIds($deliveryResultIdentifier, self::CALL_ID_TEST_COLUMN); |
407 | } |
408 | |
409 | public function getRelatedCallIds($deliveryResultIdentifier, $field) |
410 | { |
411 | $qb = $this->getQueryBuilder() |
412 | ->select('DISTINCT(' . $field . ')') |
413 | ->from(self::VARIABLES_TABLENAME) |
414 | ->andWhere(self::VARIABLES_FK_COLUMN . ' = :id AND ' . $field . ' <> :field') |
415 | ->setParameter('id', $deliveryResultIdentifier) |
416 | ->setParameter('field', ''); |
417 | |
418 | $returnValue = []; |
419 | foreach ($qb->execute()->fetchAll() as $value) { |
420 | if (isset($value[$field])) { |
421 | $returnValue[] = $value[$field]; |
422 | } |
423 | } |
424 | |
425 | return $returnValue; |
426 | } |
427 | |
428 | public function getAllTestTakerIds() |
429 | { |
430 | return $this->getAllIds(self::FIELD_TEST_TAKER, self::TEST_TAKER_COLUMN); |
431 | } |
432 | |
433 | public function getAllDeliveryIds() |
434 | { |
435 | return $this->getAllIds(self::FIELD_DELIVERY, self::DELIVERY_COLUMN); |
436 | } |
437 | |
438 | public function getAllIds($fieldName, $field) |
439 | { |
440 | $qb = $this->getQueryBuilder() |
441 | ->select(self::RESULTS_TABLE_ID . ', ' . $field) |
442 | ->from(self::RESULTS_TABLENAME); |
443 | |
444 | $returnValue = []; |
445 | foreach ($qb->execute()->fetchAll() as $value) { |
446 | $returnValue[] = [ |
447 | self::FIELD_DELIVERY_RESULT => $value[self::RESULTS_TABLE_ID], |
448 | $fieldName => $value[$field], |
449 | ]; |
450 | } |
451 | |
452 | return $returnValue; |
453 | } |
454 | |
455 | public function getResultByDelivery($delivery, $options = []) |
456 | { |
457 | if (!is_array($delivery)) { |
458 | $delivery = [$delivery]; |
459 | } |
460 | $qb = $this->getQueryBuilder() |
461 | ->select('*') |
462 | ->from(self::RESULTS_TABLENAME) |
463 | ->orderBy($this->getOrderField($options), $this->getOrderDirection($options)); |
464 | |
465 | if (isset($options['offset'])) { |
466 | $qb->setFirstResult($options['offset']); |
467 | } |
468 | if (isset($options['limit'])) { |
469 | $qb->setMaxResults($options['limit']); |
470 | } |
471 | |
472 | if (count($delivery) > 0) { |
473 | $qb |
474 | ->andWhere(self::DELIVERY_COLUMN . ' IN (:delivery)') |
475 | ->setParameter(':delivery', $delivery, Connection::PARAM_STR_ARRAY); |
476 | } |
477 | |
478 | $returnValue = []; |
479 | foreach ($qb->execute()->fetchAll() as $value) { |
480 | $returnValue[] = [ |
481 | self::FIELD_DELIVERY_RESULT => $value[self::RESULTS_TABLE_ID], |
482 | self::FIELD_TEST_TAKER => $value[self::TEST_TAKER_COLUMN], |
483 | self::FIELD_DELIVERY => $value[self::DELIVERY_COLUMN], |
484 | ]; |
485 | } |
486 | |
487 | return $returnValue; |
488 | } |
489 | |
490 | /** |
491 | * Generates and sanitize ORDER BY field. |
492 | * |
493 | * @param array $options |
494 | * |
495 | * @return string |
496 | */ |
497 | protected function getOrderField(array $options) |
498 | { |
499 | $allowedOrderFields = [self::DELIVERY_COLUMN, self::TEST_TAKER_COLUMN, self::RESULTS_TABLE_ID]; |
500 | |
501 | if (isset($options['order']) && in_array($options['order'], $allowedOrderFields)) { |
502 | return $options['order']; |
503 | } |
504 | |
505 | return self::RESULTS_TABLE_ID; |
506 | } |
507 | |
508 | /** |
509 | * Generates and sanitize ORDER BY direction. |
510 | * |
511 | * @param array $options |
512 | * |
513 | * @return string |
514 | */ |
515 | protected function getOrderDirection(array $options) |
516 | { |
517 | $allowedOrderDirections = ['ASC', 'DESC']; |
518 | |
519 | if (isset($options['orderdir']) && in_array(strtoupper($options['orderdir']), $allowedOrderDirections)) { |
520 | return $options['orderdir']; |
521 | } |
522 | |
523 | return 'ASC'; |
524 | } |
525 | |
526 | public function countResultByDelivery($delivery) |
527 | { |
528 | if (!is_array($delivery)) { |
529 | $delivery = [$delivery]; |
530 | } |
531 | $qb = $this->getQueryBuilder() |
532 | ->select('COUNT(*)') |
533 | ->from(self::RESULTS_TABLENAME); |
534 | |
535 | if (count($delivery) > 0) { |
536 | $qb |
537 | ->andWhere(self::DELIVERY_COLUMN . ' IN (:delivery)') |
538 | ->setParameter('delivery', $delivery, Connection::PARAM_STR_ARRAY); |
539 | } |
540 | |
541 | return $qb->execute()->fetchColumn(); |
542 | } |
543 | |
544 | public function deleteResult($deliveryResultIdentifier) |
545 | { |
546 | // remove variables |
547 | $sql = 'DELETE FROM ' . self::VARIABLES_TABLENAME . ' |
548 | WHERE ' . self::VARIABLES_FK_COLUMN . ' = ?'; |
549 | |
550 | if ($this->getPersistence()->exec($sql, [$deliveryResultIdentifier]) === false) { |
551 | return false; |
552 | } |
553 | |
554 | // remove results |
555 | $sql = 'DELETE FROM ' . self::RESULTS_TABLENAME . ' |
556 | WHERE ' . self::RESULTS_TABLE_ID . ' = ?'; |
557 | |
558 | if ($this->getPersistence()->exec($sql, [$deliveryResultIdentifier]) === false) { |
559 | return false; |
560 | } |
561 | |
562 | return true; |
563 | } |
564 | |
565 | /** |
566 | * Prepares data to be inserted in database. |
567 | * |
568 | * @param string $deliveryResultIdentifier |
569 | * @param string $test |
570 | * @param string $item |
571 | * @param Variable $variable |
572 | * @param string $callId |
573 | * |
574 | * @return array |
575 | */ |
576 | protected function prepareItemVariableData($deliveryResultIdentifier, $test, $item, Variable $variable, $callId) |
577 | { |
578 | $variableData = $this->prepareVariableData($deliveryResultIdentifier, $test, $variable, $callId); |
579 | $variableData[self::ITEM_COLUMN] = $item; |
580 | $variableData[self::CALL_ID_ITEM_COLUMN] = $callId; |
581 | |
582 | return $variableData; |
583 | } |
584 | |
585 | /** |
586 | * Prepares data to be inserted in database. |
587 | * |
588 | * @param string $deliveryResultIdentifier |
589 | * @param string $test |
590 | * @param Variable $variable |
591 | * @param string $callId |
592 | * |
593 | * @return array |
594 | */ |
595 | protected function prepareTestVariableData($deliveryResultIdentifier, $test, Variable $variable, $callId) |
596 | { |
597 | $variableData = $this->prepareVariableData($deliveryResultIdentifier, $test, $variable, $callId); |
598 | $variableData[self::CALL_ID_TEST_COLUMN] = $callId; |
599 | |
600 | return $variableData; |
601 | } |
602 | |
603 | /** |
604 | * Prepares data to be inserted in database. |
605 | * |
606 | * @param string $deliveryResultIdentifier |
607 | * @param string $test |
608 | * @param Variable $variable |
609 | * @param string $callId |
610 | * |
611 | * @return array |
612 | */ |
613 | protected function prepareVariableData($deliveryResultIdentifier, $test, Variable $variable, $callId) |
614 | { |
615 | // Ensures that variable has epoch. |
616 | if (!$variable->isSetEpoch()) { |
617 | $variable->setEpoch(microtime()); |
618 | } |
619 | |
620 | return $this->prepareVariableDataForSchema($deliveryResultIdentifier, $test, $variable, $callId); |
621 | } |
622 | |
623 | /** |
624 | * Prepares data to be inserted in database according to a given schema. |
625 | * |
626 | * @param string $deliveryResultIdentifier |
627 | * @param string $test |
628 | * @param Variable $variable |
629 | * @param string $callId |
630 | * |
631 | * @return array |
632 | */ |
633 | abstract protected function prepareVariableDataForSchema( |
634 | $deliveryResultIdentifier, |
635 | $test, |
636 | Variable $variable, |
637 | $callId |
638 | ); |
639 | |
640 | /** |
641 | * Builds a variable from database row. |
642 | * |
643 | * @param array $variable |
644 | * |
645 | * @return \stdClass |
646 | */ |
647 | protected function getResultRow($variable) |
648 | { |
649 | $resultVariable = $this->unserializeVariableValue($variable[self::VARIABLE_VALUE]); |
650 | $object = new \stdClass(); |
651 | $object->uri = $variable[self::VARIABLES_TABLE_ID]; |
652 | $object->class = get_class($resultVariable); |
653 | $object->deliveryResultIdentifier = $variable[self::VARIABLES_FK_COLUMN]; |
654 | $object->callIdItem = $variable[self::CALL_ID_ITEM_COLUMN]; |
655 | $object->callIdTest = $variable[self::CALL_ID_TEST_COLUMN]; |
656 | $object->test = $variable[self::TEST_COLUMN]; |
657 | $object->item = $variable[self::ITEM_COLUMN]; |
658 | $object->variable = clone $resultVariable; |
659 | |
660 | return $object; |
661 | } |
662 | |
663 | /** |
664 | * @return QueryBuilder |
665 | */ |
666 | protected function getQueryBuilder() |
667 | { |
668 | return $this->getPersistence()->getPlatform()->getQueryBuilder(); |
669 | } |
670 | |
671 | /** |
672 | * @return \common_persistence_SqlPersistence |
673 | */ |
674 | public function getPersistence() |
675 | { |
676 | if ($this->persistence === null) { |
677 | $persistenceId = $this->hasOption(self::OPTION_PERSISTENCE) ? |
678 | $this->getOption(self::OPTION_PERSISTENCE) |
679 | : 'default'; |
680 | $this->persistence = $this->getServiceLocator() |
681 | ->get(PersistenceManager::SERVICE_ID) |
682 | ->getPersistenceById($persistenceId); |
683 | } |
684 | |
685 | return $this->persistence; |
686 | } |
687 | |
688 | /** |
689 | * @param $value |
690 | * |
691 | * @return mixed |
692 | */ |
693 | protected function unserializeVariableValue($value) |
694 | { |
695 | $unserializedValue = json_decode($value, true); |
696 | |
697 | if (json_last_error() === JSON_ERROR_NONE) { |
698 | return Variable::fromData($unserializedValue); |
699 | } |
700 | |
701 | return unserialize( |
702 | $value, |
703 | [ |
704 | 'allowed_classes' => [ |
705 | \taoResultServer_models_classes_ResponseVariable::class, |
706 | \taoResultServer_models_classes_OutcomeVariable::class, |
707 | \taoResultServer_models_classes_TraceVariable::class, |
708 | ], |
709 | ] |
710 | ); |
711 | } |
712 | |
713 | /** |
714 | * @param $value |
715 | * |
716 | * @return string |
717 | */ |
718 | protected function serializeVariableValue($value) |
719 | { |
720 | if (!$value instanceof \taoResultServer_models_classes_Variable) { |
721 | throw new \LogicException( |
722 | sprintf( |
723 | "Value cannot be serialized. Expected instance of '%s', '%s' received.", |
724 | \taoResultServer_models_classes_Variable::class, |
725 | gettype($value) |
726 | ) |
727 | ); |
728 | } |
729 | |
730 | return json_encode($value); |
731 | } |
732 | |
733 | /** |
734 | * @param array $data |
735 | * |
736 | * @param array $types |
737 | * @throws DuplicateVariableException |
738 | */ |
739 | private function insertMultiple(array $data, array $types = []) |
740 | { |
741 | if (empty($types)) { |
742 | $types = $this->getTypes($data); |
743 | } |
744 | $duplicatedData = false; |
745 | try { |
746 | $this->getPersistence()->insertMultiple(self::VARIABLES_TABLENAME, $data, $types); |
747 | } catch (UniqueConstraintViolationException $e) { |
748 | $duplicatedData = true; |
749 | foreach ($data as $row) { |
750 | try { |
751 | $this->getPersistence()->insert(self::VARIABLES_TABLENAME, $row, $types); |
752 | } catch (UniqueConstraintViolationException $e) { |
753 | //do nothing, just skip it |
754 | } |
755 | } |
756 | } |
757 | |
758 | if ($duplicatedData) { |
759 | throw new DuplicateVariableException(sprintf('An identical result variable already exists.')); |
760 | } |
761 | } |
762 | |
763 | public function spawnResult() |
764 | { |
765 | $this->getLogger()->error('Unsupported function'); |
766 | } |
767 | |
768 | /* |
769 | * retrieve specific parameters from the resultserver to configure the storage |
770 | */ |
771 | public function configure($callOptions = []) |
772 | { |
773 | $this->getLogger()->info('configure RdsResultStorage with options : ' . implode(' ', $callOptions)); |
774 | } |
775 | |
776 | /** |
777 | * |
778 | * @param mixed $a |
779 | * @param mixed $b |
780 | * |
781 | * @return number |
782 | */ |
783 | public static function sortTimeStamps($a, $b) |
784 | { |
785 | [$usec, $sec] = explode(' ', $a); |
786 | $floata = ((float)$usec + (float)$sec); |
787 | [$usec, $sec] = explode(' ', $b); |
788 | $floatb = ((float)$usec + (float)$sec); |
789 | //the callback is expecting an int returned, for the case where the difference is of less than a second |
790 | if ((floatval($floata) - floatval($floatb)) > 0) { |
791 | return 1; |
792 | } elseif ((floatval($floata) - floatval($floatb)) < 0) { |
793 | return -1; |
794 | } else { |
795 | return 0; |
796 | } |
797 | } |
798 | |
799 | /** |
800 | * Creates the table for results storage. |
801 | * |
802 | * @param Schema $schema |
803 | * |
804 | * @return Table |
805 | * @throws SchemaException |
806 | */ |
807 | public function createResultsTable(Schema $schema) |
808 | { |
809 | $table = $schema->createtable(self::RESULTS_TABLENAME); |
810 | $table->addOption('engine', 'MyISAM'); |
811 | |
812 | $table->addColumn(self::RESULTS_TABLE_ID, 'string', ['length' => 255]); |
813 | $table->addColumn(self::TEST_TAKER_COLUMN, 'string', ['notnull' => false, 'length' => 255]); |
814 | $table->addColumn(self::DELIVERY_COLUMN, 'string', ['notnull' => false, 'length' => 255]); |
815 | |
816 | $table->setPrimaryKey([self::RESULTS_TABLE_ID]); |
817 | |
818 | return $table; |
819 | } |
820 | |
821 | /** |
822 | * Creates the table for variables storage. |
823 | * |
824 | * @param Schema $schema |
825 | * |
826 | * @return Table |
827 | * @throws SchemaException |
828 | */ |
829 | abstract public function createVariablesTable(Schema $schema); |
830 | |
831 | /** |
832 | * Adds constraints for the tables. |
833 | * |
834 | * @param Table $variablesTable |
835 | * @param Table $resultsTable |
836 | * |
837 | * @throws SchemaException |
838 | */ |
839 | public function createTableConstraints(Table $variablesTable, Table $resultsTable) |
840 | { |
841 | $variablesTable->addForeignKeyConstraint( |
842 | $resultsTable, |
843 | [self::VARIABLES_FK_COLUMN], |
844 | [self::RESULTS_TABLE_ID], |
845 | [], |
846 | self::VARIABLES_FK_NAME |
847 | ); |
848 | } |
849 | protected function getTypes(array $data = []): array |
850 | { |
851 | return [ |
852 | ParameterType::STRING, |
853 | ParameterType::STRING, |
854 | ParameterType::STRING, |
855 | ParameterType::STRING, |
856 | ParameterType::STRING, |
857 | ParameterType::STRING, |
858 | null, |
859 | ParameterType::STRING, |
860 | ]; |
861 | } |
862 | } |