storeTestVariables($deliveryResultIdentifier, $test, [$testVariable], $callIdTest); } /** * @inheritdoc * Stores the test variables in table and their values in key/value storage. */ public function storeTestVariables($deliveryResultIdentifier, $test, array $testVariables, $callIdTest) { $dataToInsert = []; foreach ($testVariables as $testVariable) { $dataToInsert[] = $this->prepareTestVariableData( $deliveryResultIdentifier, $test, $testVariable, $callIdTest ); } $this->insertMultiple($dataToInsert); } /** * @inheritdoc * Stores the item in table and its value in key/value storage. */ public function storeItemVariable($deliveryResultIdentifier, $test, $item, Variable $itemVariable, $callIdItem) { $this->storeItemVariables($deliveryResultIdentifier, $test, $item, [$itemVariable], $callIdItem); } /** * @inheritdoc * Stores the item variables in table and their values in key/value storage. */ public function storeItemVariables($deliveryResultIdentifier, $test, $item, array $itemVariables, $callIdItem) { $dataToInsert = []; foreach ($itemVariables as $itemVariable) { $dataToInsert[] = $this->prepareItemVariableData( $deliveryResultIdentifier, $test, $item, $itemVariable, $callIdItem ); } $this->insertMultiple($dataToInsert); } public function storeRelatedTestTaker($deliveryResultIdentifier, $testTakerIdentifier) { $this->storeRelatedData($deliveryResultIdentifier, self::TEST_TAKER_COLUMN, $testTakerIdentifier); } public function storeRelatedDelivery($deliveryResultIdentifier, $deliveryIdentifier) { $this->storeRelatedData($deliveryResultIdentifier, self::DELIVERY_COLUMN, $deliveryIdentifier); } /** * Store Delivery corresponding to the current test * * @param string $deliveryResultIdentifier * @param string $relatedField * @param string $relatedIdentifier */ public function storeRelatedData($deliveryResultIdentifier, $relatedField, $relatedIdentifier) { $qb = $this->getQueryBuilder() ->select('COUNT(*)') ->from(self::RESULTS_TABLENAME) ->andWhere(self::RESULTS_TABLE_ID . ' = :id') ->setParameter('id', $deliveryResultIdentifier); if ((int)$qb->execute()->fetchColumn() === 0) { $this->getPersistence()->insert( self::RESULTS_TABLENAME, [ self::RESULTS_TABLE_ID => $deliveryResultIdentifier, $relatedField => $relatedIdentifier, ] ); } else { $sqlUpdate = 'UPDATE ' . self::RESULTS_TABLENAME . ' SET ' . $relatedField . ' = ? WHERE ' . self::RESULTS_TABLE_ID . ' = ?'; $paramsUpdate = [$relatedIdentifier, $deliveryResultIdentifier]; $this->getPersistence()->exec($sqlUpdate, $paramsUpdate); } } public function getVariables($callId) { if (!is_array($callId)) { $callId = [$callId]; } $qb = $this->getQueryBuilder() ->select('*') ->from(self::VARIABLES_TABLENAME) ->andWhere(self::CALL_ID_ITEM_COLUMN . ' IN (:ids) OR ' . self::CALL_ID_TEST_COLUMN . ' IN (:ids)') ->orderBy($this->getVariablesSortingField()) ->setParameter('ids', $callId, Connection::PARAM_STR_ARRAY); $returnValue = []; foreach ($qb->execute()->fetchAll() as $variable) { $returnValue[$variable[self::VARIABLES_TABLE_ID]][] = $this->getResultRow($variable); } return $returnValue; } public function getDeliveryVariables($deliveryResultIdentifier) { if (!is_array($deliveryResultIdentifier)) { $deliveryResultIdentifier = [$deliveryResultIdentifier]; } $qb = $this->getQueryBuilder() ->select('*') ->from(self::VARIABLES_TABLENAME) ->andWhere(self::VARIABLES_FK_COLUMN . ' IN (:ids)') ->orderBy($this->getVariablesSortingField()) ->setParameter('ids', $deliveryResultIdentifier, Connection::PARAM_STR_ARRAY); $returnValue = []; foreach ($qb->execute()->fetchAll() as $variable) { $returnValue[$variable[self::VARIABLES_TABLE_ID]][] = $this->getResultRow($variable); } return $returnValue; } public function getVariable($callId, $variableIdentifier) { $qb = $this->getQueryBuilder() ->select('*') ->from(self::VARIABLES_TABLENAME) ->andWhere(self::CALL_ID_ITEM_COLUMN . ' = :callId OR ' . self::CALL_ID_TEST_COLUMN . ' = :callId') ->andWhere(self::VARIABLE_IDENTIFIER . ' = :variableId') ->setParameter('callId', $callId) ->setParameter('variableId', $variableIdentifier); $returnValue = []; foreach ($qb->execute()->fetchAll() as $variable) { $returnValue[$variable[self::VARIABLES_TABLE_ID]] = $this->getResultRow($variable); } return $returnValue; } public function getVariableProperty($variableId, $property) { $qb = $this->getQueryBuilder() ->select(self::VARIABLE_VALUE) ->from(self::VARIABLES_TABLENAME) ->andWhere(self::VARIABLES_TABLE_ID . ' = :variableId') ->setParameter('variableId', $variableId); $variableValue = $qb->execute()->fetchColumn(); $variableValue = $this->unserializeVariableValue($variableValue); $getter = 'get' . ucfirst($property); if (is_callable([$variableValue, $getter])) { return $variableValue->$getter(); } return null; } /** * Returns the field to sort item and test variables. * * @return string */ abstract protected function getVariablesSortingField(); public function getTestTaker($deliveryResultIdentifier) { return $this->getRelatedData($deliveryResultIdentifier, self::TEST_TAKER_COLUMN); } public function getDelivery($deliveryResultIdentifier) { return $this->getRelatedData($deliveryResultIdentifier, self::DELIVERY_COLUMN); } /** * Retrieves data related to a result. * * @param string $deliveryResultIdentifier * @param string $field * * @return mixed */ public function getRelatedData($deliveryResultIdentifier, $field) { $qb = $this->getQueryBuilder() ->select($field) ->from(self::RESULTS_TABLENAME) ->andWhere(self::RESULTS_TABLE_ID . ' = :id') ->setParameter('id', $deliveryResultIdentifier); return $qb->execute()->fetchColumn(); } /** * @inheritdoc * o(n) do not use real time (postprocessing) */ public function getAllCallIds() { $qb = $this->getQueryBuilder() ->select('DISTINCT(' . self::CALL_ID_ITEM_COLUMN . '), ' . self::CALL_ID_TEST_COLUMN . ', ' . self::VARIABLES_FK_COLUMN) ->from(self::VARIABLES_TABLENAME); $returnValue = []; foreach ($qb->execute()->fetchAll() as $value) { $returnValue[] = ($value[self::CALL_ID_ITEM_COLUMN] != '') ? $value[self::CALL_ID_ITEM_COLUMN] : $value[self::CALL_ID_TEST_COLUMN]; } return $returnValue; } public function getRelatedItemCallIds($deliveryResultIdentifier) { return $this->getRelatedCallIds($deliveryResultIdentifier, self::CALL_ID_ITEM_COLUMN); } public function getRelatedTestCallIds($deliveryResultIdentifier) { return $this->getRelatedCallIds($deliveryResultIdentifier, self::CALL_ID_TEST_COLUMN); } public function getRelatedCallIds($deliveryResultIdentifier, $field) { $qb = $this->getQueryBuilder() ->select('DISTINCT(' . $field . ')') ->from(self::VARIABLES_TABLENAME) ->andWhere(self::VARIABLES_FK_COLUMN . ' = :id AND ' . $field . ' <> :field') ->setParameter('id', $deliveryResultIdentifier) ->setParameter('field', ''); $returnValue = []; foreach ($qb->execute()->fetchAll() as $value) { if (isset($value[$field])) { $returnValue[] = $value[$field]; } } return $returnValue; } public function getAllTestTakerIds() { return $this->getAllIds(self::FIELD_TEST_TAKER, self::TEST_TAKER_COLUMN); } public function getAllDeliveryIds() { return $this->getAllIds(self::FIELD_DELIVERY, self::DELIVERY_COLUMN); } public function getAllIds($fieldName, $field) { $qb = $this->getQueryBuilder() ->select(self::RESULTS_TABLE_ID . ', ' . $field) ->from(self::RESULTS_TABLENAME); $returnValue = []; foreach ($qb->execute()->fetchAll() as $value) { $returnValue[] = [ self::FIELD_DELIVERY_RESULT => $value[self::RESULTS_TABLE_ID], $fieldName => $value[$field], ]; } return $returnValue; } public function getResultByDelivery($delivery, $options = []) { if (!is_array($delivery)) { $delivery = [$delivery]; } $qb = $this->getQueryBuilder() ->select('*') ->from(self::RESULTS_TABLENAME) ->orderBy($this->getOrderField($options), $this->getOrderDirection($options)); if (isset($options['offset'])) { $qb->setFirstResult($options['offset']); } if (isset($options['limit'])) { $qb->setMaxResults($options['limit']); } if (count($delivery) > 0) { $qb ->andWhere(self::DELIVERY_COLUMN . ' IN (:delivery)') ->setParameter(':delivery', $delivery, Connection::PARAM_STR_ARRAY); } $returnValue = []; foreach ($qb->execute()->fetchAll() as $value) { $returnValue[] = [ self::FIELD_DELIVERY_RESULT => $value[self::RESULTS_TABLE_ID], self::FIELD_TEST_TAKER => $value[self::TEST_TAKER_COLUMN], self::FIELD_DELIVERY => $value[self::DELIVERY_COLUMN], ]; } return $returnValue; } /** * Generates and sanitize ORDER BY field. * * @param array $options * * @return string */ protected function getOrderField(array $options) { $allowedOrderFields = [self::DELIVERY_COLUMN, self::TEST_TAKER_COLUMN, self::RESULTS_TABLE_ID]; if (isset($options['order']) && in_array($options['order'], $allowedOrderFields)) { return $options['order']; } return self::RESULTS_TABLE_ID; } /** * Generates and sanitize ORDER BY direction. * * @param array $options * * @return string */ protected function getOrderDirection(array $options) { $allowedOrderDirections = ['ASC', 'DESC']; if (isset($options['orderdir']) && in_array(strtoupper($options['orderdir']), $allowedOrderDirections)) { return $options['orderdir']; } return 'ASC'; } public function countResultByDelivery($delivery) { if (!is_array($delivery)) { $delivery = [$delivery]; } $qb = $this->getQueryBuilder() ->select('COUNT(*)') ->from(self::RESULTS_TABLENAME); if (count($delivery) > 0) { $qb ->andWhere(self::DELIVERY_COLUMN . ' IN (:delivery)') ->setParameter('delivery', $delivery, Connection::PARAM_STR_ARRAY); } return $qb->execute()->fetchColumn(); } public function deleteResult($deliveryResultIdentifier) { // remove variables $sql = 'DELETE FROM ' . self::VARIABLES_TABLENAME . ' WHERE ' . self::VARIABLES_FK_COLUMN . ' = ?'; if ($this->getPersistence()->exec($sql, [$deliveryResultIdentifier]) === false) { return false; } // remove results $sql = 'DELETE FROM ' . self::RESULTS_TABLENAME . ' WHERE ' . self::RESULTS_TABLE_ID . ' = ?'; if ($this->getPersistence()->exec($sql, [$deliveryResultIdentifier]) === false) { return false; } return true; } /** * Prepares data to be inserted in database. * * @param string $deliveryResultIdentifier * @param string $test * @param string $item * @param Variable $variable * @param string $callId * * @return array */ protected function prepareItemVariableData($deliveryResultIdentifier, $test, $item, Variable $variable, $callId) { $variableData = $this->prepareVariableData($deliveryResultIdentifier, $test, $variable, $callId); $variableData[self::ITEM_COLUMN] = $item; $variableData[self::CALL_ID_ITEM_COLUMN] = $callId; return $variableData; } /** * Prepares data to be inserted in database. * * @param string $deliveryResultIdentifier * @param string $test * @param Variable $variable * @param string $callId * * @return array */ protected function prepareTestVariableData($deliveryResultIdentifier, $test, Variable $variable, $callId) { $variableData = $this->prepareVariableData($deliveryResultIdentifier, $test, $variable, $callId); $variableData[self::CALL_ID_TEST_COLUMN] = $callId; return $variableData; } /** * Prepares data to be inserted in database. * * @param string $deliveryResultIdentifier * @param string $test * @param Variable $variable * @param string $callId * * @return array */ protected function prepareVariableData($deliveryResultIdentifier, $test, Variable $variable, $callId) { // Ensures that variable has epoch. if (!$variable->isSetEpoch()) { $variable->setEpoch(microtime()); } return $this->prepareVariableDataForSchema($deliveryResultIdentifier, $test, $variable, $callId); } /** * Prepares data to be inserted in database according to a given schema. * * @param string $deliveryResultIdentifier * @param string $test * @param Variable $variable * @param string $callId * * @return array */ abstract protected function prepareVariableDataForSchema($deliveryResultIdentifier, $test, Variable $variable, $callId); /** * Builds a variable from database row. * * @param array $variable * * @return \stdClass */ protected function getResultRow($variable) { $resultVariable = $this->unserializeVariableValue($variable[self::VARIABLE_VALUE]); $object = new \stdClass(); $object->uri = $variable[self::VARIABLES_TABLE_ID]; $object->class = get_class($resultVariable); $object->deliveryResultIdentifier = $variable[self::VARIABLES_FK_COLUMN]; $object->callIdItem = $variable[self::CALL_ID_ITEM_COLUMN]; $object->callIdTest = $variable[self::CALL_ID_TEST_COLUMN]; $object->test = $variable[self::TEST_COLUMN]; $object->item = $variable[self::ITEM_COLUMN]; $object->variable = clone $resultVariable; return $object; } /** * @return QueryBuilder */ protected function getQueryBuilder() { return $this->getPersistence()->getPlatform()->getQueryBuilder(); } /** * @return \common_persistence_SqlPersistence */ public function getPersistence() { if ($this->persistence === null) { $persistenceId = $this->hasOption(self::OPTION_PERSISTENCE) ? $this->getOption(self::OPTION_PERSISTENCE) : 'default'; $this->persistence = $this->getServiceLocator()->get(PersistenceManager::SERVICE_ID)->getPersistenceById($persistenceId); } return $this->persistence; } /** * @param $value * * @return mixed */ protected function unserializeVariableValue($value) { $unserializedValue = json_decode($value, true); if (json_last_error() === JSON_ERROR_NONE) { return Variable::fromData($unserializedValue); } return unserialize( $value, [ 'allowed_classes' => [ \taoResultServer_models_classes_ResponseVariable::class, \taoResultServer_models_classes_OutcomeVariable::class, \taoResultServer_models_classes_TraceVariable::class, ], ] ); } /** * @param $value * * @return string */ protected function serializeVariableValue($value) { if (!$value instanceof \taoResultServer_models_classes_Variable) { throw new \LogicException( sprintf( "Value cannot be serialized. Expected instance of '%s', '%s' received.", \taoResultServer_models_classes_Variable::class, gettype($value) ) ); } return json_encode($value); } /** * @param array $data * * @param array $types * @throws DuplicateVariableException */ private function insertMultiple(array $data, array $types = []) { if (empty($types)) { $types = $this->getTypes($data); } $duplicatedData = false; try { $this->getPersistence()->insertMultiple(self::VARIABLES_TABLENAME, $data, $types); } catch (UniqueConstraintViolationException $e) { $duplicatedData = true; foreach ($data as $row) { try { $this->getPersistence()->insert(self::VARIABLES_TABLENAME, $row, $types); } catch (UniqueConstraintViolationException $e) { //do nothing, just skip it } } } if ($duplicatedData) { throw new DuplicateVariableException(sprintf('An identical result variable already exists.')); } } public function spawnResult() { $this->getLogger()->error('Unsupported function'); } /* * retrieve specific parameters from the resultserver to configure the storage */ public function configure($callOptions = []) { $this->getLogger()->info('configure RdsResultStorage with options : ' . implode(' ', $callOptions)); } /** * * @param mixed $a * @param mixed $b * * @return number */ public static function sortTimeStamps($a, $b) { [$usec, $sec] = explode(' ', $a); $floata = ((float)$usec + (float)$sec); [$usec, $sec] = explode(' ', $b); $floatb = ((float)$usec + (float)$sec); //the callback is expecting an int returned, for the case where the difference is of less than a second if ((floatval($floata) - floatval($floatb)) > 0) { return 1; } elseif ((floatval($floata) - floatval($floatb)) < 0) { return -1; } else { return 0; } } /** * Creates the table for results storage. * * @param Schema $schema * * @return Table * @throws SchemaException */ public function createResultsTable(Schema $schema) { $table = $schema->createtable(self::RESULTS_TABLENAME); $table->addOption('engine', 'MyISAM'); $table->addColumn(self::RESULTS_TABLE_ID, 'string', ['length' => 255]); $table->addColumn(self::TEST_TAKER_COLUMN, 'string', ['notnull' => false, 'length' => 255]); $table->addColumn(self::DELIVERY_COLUMN, 'string', ['notnull' => false, 'length' => 255]); $table->setPrimaryKey([self::RESULTS_TABLE_ID]); return $table; } /** * Creates the table for variables storage. * * @param Schema $schema * * @return Table * @throws SchemaException */ abstract public function createVariablesTable(Schema $schema); /** * Adds constraints for the tables. * * @param Table $variablesTable * @param Table $resultsTable * * @throws SchemaException */ public function createTableConstraints(Table $variablesTable, Table $resultsTable) { $variablesTable->addForeignKeyConstraint( $resultsTable, [self::VARIABLES_FK_COLUMN], [self::RESULTS_TABLE_ID], [], self::VARIABLES_FK_NAME ); } protected function getTypes(array $data = []): array { return [ ParameterType::STRING, ParameterType::STRING, ParameterType::STRING, ParameterType::STRING, ParameterType::STRING, ParameterType::STRING, null, ParameterType::STRING, ]; } }