/** * Process the request Uri and creates an instance of * RequestDescription from the processed uri. * * @param IService $service Reference to the data service instance. * * @return RequestDescription * * @throws ODataException If any exception occurs while processing the segments * or in case of any version incompatibility. */ public static function process(IService $service) { $host = $service->getHost(); $absoluteRequestUri = $host->getAbsoluteRequestUri(); $absoluteServiceUri = $host->getAbsoluteServiceUri(); $requestUriSegments = array_slice($absoluteRequestUri->getSegments(), $absoluteServiceUri->getSegmentCount()); $segments = SegmentParser::parseRequestUriSegments($requestUriSegments, $service->getProvidersWrapper(), true); $request = new RequestDescription($segments, $absoluteRequestUri, $service->getConfiguration()->getMaxDataServiceVersion(), $host->getRequestVersion(), $host->getRequestMaxVersion()); $kind = $request->getTargetKind(); if ($kind == TargetKind::METADATA() || $kind == TargetKind::BATCH() || $kind == TargetKind::SERVICE_DIRECTORY()) { return $request; } if ($kind == TargetKind::PRIMITIVE_VALUE() || $kind == TargetKind::MEDIA_RESOURCE()) { // http://odata/NW.svc/Orders/$count // http://odata/NW.svc/Orders(123)/Customer/CustomerID/$value // http://odata/NW.svc/Employees(1)/$value // http://odata/NW.svc/Employees(1)/ThumbNail_48X48/$value $request->setContainerName($segments[count($segments) - 2]->getIdentifier()); } else { $request->setContainerName($request->getIdentifier()); } if ($request->getIdentifier() === ODataConstants::URI_COUNT_SEGMENT) { if (!$service->getConfiguration()->getAcceptCountRequests()) { throw ODataException::createBadRequestError(Messages::configurationCountNotAccepted()); } $request->queryType = QueryType::COUNT(); // use of $count requires request DataServiceVersion // and MaxDataServiceVersion greater than or equal to 2.0 $request->raiseResponseVersion(2, 0); $request->raiseMinVersionRequirement(2, 0); } else { if ($request->isNamedStream()) { $request->raiseMinVersionRequirement(3, 0); } else { if ($request->getTargetKind() == TargetKind::RESOURCE()) { if (!$request->isLinkUri()) { $resourceSetWrapper = $request->getTargetResourceSetWrapper(); //assert($resourceSetWrapper != null) $hasNamedStream = $resourceSetWrapper->hasNamedStreams($service->getProvidersWrapper()); $hasBagProperty = $resourceSetWrapper->hasBagProperty($service->getProvidersWrapper()); if ($hasNamedStream || $hasBagProperty) { $request->raiseResponseVersion(3, 0); } } } else { if ($request->getTargetKind() == TargetKind::BAG()) { $request->raiseResponseVersion(3, 0); } } } } return $request; }
/** * Checks whether etag headers are allowed for this request. * * @return boolean True if ETag header (If-Match or If-NoneMatch) * is allowed for the request, False otherwise. */ public function isETagHeaderAllowed() { return $this->lastSegment->isSingleResult() && $this->queryType != QueryType::COUNT() && !$this->isLinkUri() && (is_null($this->_rootProjectionNode) || !$this->_rootProjectionNode->isExpansionSpecified()); }
/** * Write top level url collection. * * @param array $entryObjects Array of entry resources * whose url to be written. * * @return ODataURLCollection */ public function writeUrlElements($entryObjects) { $urls = new ODataURLCollection(); if (!empty($entryObjects)) { $i = 0; foreach ($entryObjects as $entryObject) { $urls->urls[$i] = $this->writeUrlElement($entryObject); $i++; } if ($i > 0 && $this->needNextPageLink(count($entryObjects))) { $urls->nextPageLink = $this->getNextLinkUri($entryObjects[$i - 1], $this->request->getRequestUrl()->getUrlAsString()); } } if ($this->request->queryType == QueryType::ENTITIES_WITH_COUNT()) { $urls->count = $this->request->getCountValue(); } return $urls; }
/** * Execute queries for expansion. * * @param array(mixed)/mixed $result Resource(s) whose navigation properties needs to be expanded. * * * @return void */ private function _executeExpansion($result) { $expandedProjectionNodes = $this->_getExpandedProjectionNodes(); foreach ($expandedProjectionNodes as $expandedProjectionNode) { $isCollection = $expandedProjectionNode->getResourceProperty()->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE; $expandedPropertyName = $expandedProjectionNode->getResourceProperty()->getName(); if (is_array($result)) { foreach ($result as $entry) { // Check for null entry if ($isCollection) { $currentResourceSet = $this->_getCurrentResourceSetWrapper()->getResourceSet(); $resourceSetOfProjectedProperty = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet(); $projectedProperty1 = $expandedProjectionNode->getResourceProperty(); $result1 = $this->providers->getRelatedResourceSet(QueryType::ENTITIES(), $currentResourceSet, $entry, $resourceSetOfProjectedProperty, $projectedProperty1, null, null, null, null)->results; if (!empty($result1)) { $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo(); if (!is_null($internalOrderByInfo)) { $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference(); usort($result1, $orderByFunction); unset($internalOrderByInfo); $takeCount = $expandedProjectionNode->getTakeCount(); if (!is_null($takeCount)) { $result1 = array_slice($result1, 0, $takeCount); } } $entry->{$expandedPropertyName} = $result1; $projectedProperty = $expandedProjectionNode->getResourceProperty(); $needPop = $this->_pushSegmentForNavigationProperty($projectedProperty); $this->_executeExpansion($result1); $this->_popSegment($needPop); } else { $entry->{$expandedPropertyName} = array(); } } else { $currentResourceSet1 = $this->_getCurrentResourceSetWrapper()->getResourceSet(); $resourceSetOfProjectedProperty1 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet(); $projectedProperty2 = $expandedProjectionNode->getResourceProperty(); $result1 = $this->providers->getRelatedResourceReference($currentResourceSet1, $entry, $resourceSetOfProjectedProperty1, $projectedProperty2); $entry->{$expandedPropertyName} = $result1; if (!is_null($result1)) { $projectedProperty3 = $expandedProjectionNode->getResourceProperty(); $needPop = $this->_pushSegmentForNavigationProperty($projectedProperty3); $this->_executeExpansion($result1); $this->_popSegment($needPop); } } } } else { if ($isCollection) { $currentResourceSet2 = $this->_getCurrentResourceSetWrapper()->getResourceSet(); $resourceSetOfProjectedProperty2 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet(); $projectedProperty4 = $expandedProjectionNode->getResourceProperty(); $result1 = $this->providers->getRelatedResourceSet(QueryType::ENTITIES(), $currentResourceSet2, $result, $resourceSetOfProjectedProperty2, $projectedProperty4, null, null, null, null)->results; if (!empty($result1)) { $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo(); if (!is_null($internalOrderByInfo)) { $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference(); usort($result1, $orderByFunction); unset($internalOrderByInfo); $takeCount = $expandedProjectionNode->getTakeCount(); if (!is_null($takeCount)) { $result1 = array_slice($result1, 0, $takeCount); } } $result->{$expandedPropertyName} = $result1; $projectedProperty7 = $expandedProjectionNode->getResourceProperty(); $needPop = $this->_pushSegmentForNavigationProperty($projectedProperty7); $this->_executeExpansion($result1); $this->_popSegment($needPop); } else { $result->{$expandedPropertyName} = array(); } } else { $currentResourceSet3 = $this->_getCurrentResourceSetWrapper()->getResourceSet(); $resourceSetOfProjectedProperty3 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet(); $projectedProperty5 = $expandedProjectionNode->getResourceProperty(); $result1 = $this->providers->getRelatedResourceReference($currentResourceSet3, $result, $resourceSetOfProjectedProperty3, $projectedProperty5); $result->{$expandedPropertyName} = $result1; if (!is_null($result1)) { $projectedProperty6 = $expandedProjectionNode->getResourceProperty(); $needPop = $this->_pushSegmentForNavigationProperty($projectedProperty6); $this->_executeExpansion($result1); $this->_popSegment($needPop); } } } } }
public function testGetRelatedResourceSetReturnsArrayWhenQueryTypeIsEntitiesWithCount() { $orderBy = null; $top = 10; $skip = 10; $fakeQueryResult = new QueryResult(); $fakeQueryResult->count = 4; $fakeQueryResult->results = null; //null is not an array $fakeSourceEntity = new \stdClass(); Phockito::when($this->mockQueryProvider->getRelatedResourceSet(QueryType::ENTITIES_WITH_COUNT(), $this->mockResourceSet, $fakeSourceEntity, $this->mockResourceSet2, $this->mockResourceProperty, $this->mockFilterInfo, $orderBy, $top, $skip))->return($fakeQueryResult); $wrapper = $this->getMockedWrapper(); try { $wrapper->getRelatedResourceSet(QueryType::ENTITIES_WITH_COUNT(), $this->mockResourceSet, $fakeSourceEntity, $this->mockResourceSet2, $this->mockResourceProperty, $this->mockFilterInfo, $orderBy, $top, $skip); $this->fail("expected exception not thrown"); } catch (ODataException $ex) { $this->assertEquals(Messages::queryProviderResultsMissing("IQueryProvider::getRelatedResourceSet", QueryType::ENTITIES_WITH_COUNT()), $ex->getMessage()); $this->assertEquals(500, $ex->getStatusCode()); } }
/** * For queries like http://localhost/NorthWind.svc/Customers */ public function getResourceSet(QueryType $queryType, ResourceSet $resourceSet, $filterInfo = null, $orderBy = null, $top = null, $skip = null) { $result = new QueryResult(); $entityClassName = $resourceSet->getResourceType()->getInstanceType()->name; $entityName = $this->getEntityName($entityClassName); $tableName = $this->getTableName($entityName); $option = null; if ($queryType == QueryType::ENTITIES_WITH_COUNT()) { //tell mysql we want to know the count prior to the LIMIT //$option = 'SQL_CALC_FOUND_ROWS'; } $where = $filterInfo ? ' WHERE ' . $filterInfo->getExpressionAsString() : ''; $order = $orderBy ? ' ORDER BY ' . $this->getOrderByExpressionAsString($orderBy) : ''; $sqlCount = 'SELECT COUNT(*) FROM ' . $tableName . $where; if ($queryType == QueryType::ENTITIES() || $queryType == QueryType::ENTITIES_WITH_COUNT()) { $sql = 'SELECT ' . $option . ' * FROM ' . $tableName . $where . $order . ($top ? ' LIMIT ' . $top : '') . ($skip ? ' OFFSET ' . $skip : ''); $data = $this->queryAll($sql); if ($queryType == QueryType::ENTITIES_WITH_COUNT()) { //get those found rows //$result->count = $this->queryScalar('SELECT FOUND_ROWS()'); $result->count = $this->queryScalar($sqlCount); } $result->results = array_map($entityClassName . '::fromRecord', $data); } elseif ($queryType == QueryType::COUNT()) { $result->count = QueryResult::adjustCountForPaging($this->queryScalar($sqlCount), $top, $skip); } return $result; }
public function testProcessRequestForCollectionWithInlineCountProviderHandlesPaging() { $requestURI = new Url('http://host.com/data.svc/Collection/?$inlinecount=allpages'); Phockito::when($this->mockServiceHost->getAbsoluteRequestUri())->return($requestURI); //mock inline count as all pages Phockito::when($this->mockServiceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_INLINECOUNT))->return("allpages"); $this->fakeServiceConfig->setAcceptCountRequests(true); $this->fakeServiceConfig->setMaxDataServiceVersion(ProtocolVersion::V2()); $uriProcessor = UriProcessor::process($this->mockService); $fakeQueryResult = new QueryResult(); $fakeQueryResult->results = array(1, 2, 3); $fakeQueryResult->count = 10; Phockito::when($this->mockProvidersWrapper->getResourceSet(QueryType::ENTITIES_WITH_COUNT(), $this->mockCollectionResourceSetWrapper, null, null, null, null))->return($fakeQueryResult); //indicate that the Provider performs the paging (thus it will use the count in the QueryResult) Phockito::when($this->mockProvidersWrapper->handlesOrderedPaging())->return(true); $uriProcessor->execute(); $request = $uriProcessor->getRequest(); $actual = $request->getTargetResult(); $this->assertEquals(array(1, 2, 3), $actual); $this->assertEquals(10, $request->getCountValue()); }
/** * Process the $inlinecount option and update the request description. * * @return void * * @throws ODataException Throws bad request error in the following cases * (1) If $inlinecount is disabled by the developer * (2) If both $count and $inlinecount specified * (3) If $inlinecount value is unknown * (4) If capability negotiation over version fails */ private function _processCount() { $inlineCount = $this->service->getHost()->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_INLINECOUNT); //If it's not specified, we're done if (is_null($inlineCount)) { return; } //If the service doesn't allow count requests..then throw an exception if (!$this->service->getConfiguration()->getAcceptCountRequests()) { throw ODataException::createBadRequestError(Messages::configurationCountNotAccepted()); } $inlineCount = trim($inlineCount); //if it's set to none, we don't do inline counts if ($inlineCount === ODataConstants::URI_ROWCOUNT_OFFOPTION) { return; } //You can't specify $count & $inlinecount together //TODO: ensure there's a test for this case see #55 if ($this->request->queryType == QueryType::COUNT()) { throw ODataException::createBadRequestError(Messages::queryProcessorInlineCountWithValueCount()); } $this->_checkSetQueryApplicable(); //TODO: why do we do this check? if ($inlineCount === ODataConstants::URI_ROWCOUNT_ALLOPTION) { $this->request->queryType = QueryType::ENTITIES_WITH_COUNT(); $this->request->raiseMinVersionRequirement(2, 0); $this->request->raiseResponseVersion(2, 0); } else { throw ODataException::createBadRequestError(Messages::queryProcessorInvalidInlineCountOptionError()); } }
private function ValidateQueryResult($queryResult, QueryType $queryType, $methodName) { if (!$queryResult instanceof QueryResult) { throw ODataException::createInternalServerError(Messages::queryProviderReturnsNonQueryResult($methodName)); } if ($queryType == QueryType::COUNT() || $queryType == QueryType::ENTITIES_WITH_COUNT()) { //and the provider is supposed to handle the ordered paging they must return a count! if ($this->queryProvider->handlesOrderedPaging() && !is_numeric($queryResult->count)) { throw ODataException::createInternalServerError(Messages::queryProviderResultCountMissing($methodName, $queryType)); } //If POData is supposed to handle the ordered aging they must return results! (possibly empty) if (!$this->queryProvider->handlesOrderedPaging() && !is_array($queryResult->results)) { throw ODataException::createInternalServerError(Messages::queryProviderResultsMissing($methodName, $queryType)); } } if (($queryType == QueryType::ENTITIES() || $queryType == QueryType::ENTITIES_WITH_COUNT()) && !is_array($queryResult->results)) { throw ODataException::createInternalServerError(Messages::queryProviderResultsMissing($methodName, $queryType)); } }