/** * 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()); }
/** * Applies the query options to the resource(s) retrieved from the data source. * * @param SegmentDescriptor $segment The descriptor which holds resource(s) on which query options to be applied. * */ private function applyQueryOptions(SegmentDescriptor $segment) { //TODO: I'm not really happy with this..i think i'd rather keep the result the QueryResult //not even bother with the setCountValue stuff (shouldn't counts be on segments?) //and just work with the QueryResult in the object model serializer $result = $segment->getResult(); if (!$result instanceof QueryResult) { //If the segment isn't a query result, then there's no paging or counting to be done return; } // Note $inlinecount=allpages means include the total count regardless of paging..so we set the counts first // regardless if POData does the paging or not. if ($this->request->queryType == QueryType::ENTITIES_WITH_COUNT()) { if ($this->providers->handlesOrderedPaging()) { $this->request->setCountValue($result->count); } else { $this->request->setCountValue(count($result->results)); } } //Have POData perform paging if necessary if (!$this->providers->handlesOrderedPaging() && !empty($result->results)) { $result->results = $this->performPaging($result->results); } //a bit surprising, but $skip and $top affects $count so update it here, not above //IE data.svc/Collection/$count?$top=10 returns 10 even if Collection has 11+ entries if ($this->request->queryType == QueryType::COUNT()) { if ($this->providers->handlesOrderedPaging()) { $this->request->setCountValue($result->count); } else { $this->request->setCountValue(count($result->results)); } } $segment->setResult($result->results); }
public function testGetRelatedResourceSetReturnsCountWhenQueryTypeIsCountProviderHandlesPaging() { $orderBy = null; $top = 10; $skip = 10; $fakeQueryResult = new QueryResult(); $fakeQueryResult->count = null; //null is not numeric //Because the provider handles paging and this request needs a count, the count must be numeric Phockito::when($this->mockQueryProvider->handlesOrderedPaging())->return(true); $fakeSourceEntity = new \stdClass(); Phockito::when($this->mockQueryProvider->getRelatedResourceSet(QueryType::COUNT(), $this->mockResourceSet, $fakeSourceEntity, $this->mockResourceSet2, $this->mockResourceProperty, $this->mockFilterInfo, $orderBy, $top, $skip))->return($fakeQueryResult); $wrapper = $this->getMockedWrapper(); try { $wrapper->getRelatedResourceSet(QueryType::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::queryProviderResultCountMissing("IQueryProvider::getRelatedResourceSet", QueryType::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 testProcessRequestForCollectionCountProviderHandlesPaging() { $requestURI = new Url('http://host.com/data.svc/Collection/$count'); Phockito::when($this->mockServiceHost->getAbsoluteRequestUri())->return($requestURI); $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; //note this differs from the size of the results array Phockito::when($this->mockProvidersWrapper->getResourceSet(QueryType::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(10, $actual); }
/** * 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)); } }