/** * @param SegmentDescriptor[] $segmentDescriptors Description of segments in the resource path. * @param Url $requestUri * @param Version $serviceMaxVersion * @param $requestVersion * @param $maxRequestVersion */ public function __construct($segmentDescriptors, Url $requestUri, Version $serviceMaxVersion, $requestVersion, $maxRequestVersion) { $this->segments = $segmentDescriptors; $this->_segmentCount = count($this->segments); $this->requestUrl = $requestUri; $this->lastSegment = $segmentDescriptors[$this->_segmentCount - 1]; $this->queryType = QueryType::ENTITIES(); //we use this for validation checks down in validateVersions...but maybe we should check that outside of this object... $this->maxServiceVersion = $serviceMaxVersion; //Per OData 1 & 2 spec we must return the smallest size //We start at 1.0 and move it up as features are requested $this->requiredMinResponseVersion = clone Version::v1(); $this->requiredMinRequestVersion = clone Version::v1(); //see http://www.odata.org/documentation/odata-v2-documentation/overview/#ProtocolVersioning //if requestVersion isn't there, use Service Max Version $this->requestVersion = is_null($requestVersion) ? $serviceMaxVersion : self::parseVersionHeader($requestVersion, ODataConstants::ODATAVERSIONHEADER); //if max version isn't there, use the request version $this->requestMaxVersion = is_null($maxRequestVersion) ? $this->requestVersion : self::parseVersionHeader($maxRequestVersion, ODataConstants::ODATAMAXVERSIONHEADER); //if it's OData v3..things change a bit if ($this->maxServiceVersion == Version::v3()) { if (is_null($maxRequestVersion)) { //if max request version isn't specified we use the service max version instead of the request version //thus we favour newer versions $this->requestMaxVersion = $this->maxServiceVersion; } //also we change min response version to be the max version, again favoring later things //note that if the request max version is specified, it is still respected $this->requiredMinResponseVersion = clone $this->requestMaxVersion; } $this->_containerName = null; $this->_skipCount = null; $this->_topCount = null; $this->_topOptionCount = null; $this->internalOrderByInfo = null; $this->_internalSkipTokenInfo = null; $this->_filterInfo = null; $this->_countValue = null; $this->_isExecuted = false; }
/** * 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 testGetRelatedResourceSetReturnsArrayWhenQueryTypeIsEntities() { $orderBy = null; $top = 10; $skip = 10; $fakeQueryResult = new QueryResult(); $fakeQueryResult->count = 2; $fakeQueryResult->results = null; //null is not an array $fakeSourceEntity = new \stdClass(); Phockito::when($this->mockQueryProvider->getRelatedResourceSet(QueryType::ENTITIES(), $this->mockResourceSet, $fakeSourceEntity, $this->mockResourceSet2, $this->mockResourceProperty, $this->mockFilterInfo, $orderBy, $top, $skip))->return($fakeQueryResult); $wrapper = $this->getMockedWrapper(); try { $wrapper->getRelatedResourceSet(QueryType::ENTITIES(), $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()), $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 testProcessRequestForCollectionWithNoInlineCountWhenVersionIsTooLow() { //I'm not so sure about this test...basically $inlinecount is ignored if it's none, but maybe we should //be throwing an exception? $requestURI = new Url('http://host.com/data.svc/Collection/?$inlinecount=none'); Phockito::when($this->mockServiceHost->getAbsoluteRequestUri())->return($requestURI); //mock inline count as all pages Phockito::when($this->mockServiceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_INLINECOUNT))->return("none"); $this->fakeServiceConfig->setAcceptCountRequests(true); $this->fakeServiceConfig->setMaxDataServiceVersion(ProtocolVersion::V1()); $uriProcessor = UriProcessor::process($this->mockService); $fakeQueryResult = new QueryResult(); $fakeQueryResult->results = array(1, 2, 3); $fakeQueryResult->count = 10; //note this is different than the size of the array Phockito::when($this->mockProvidersWrapper->getResourceSet(QueryType::ENTITIES(), $this->mockCollectionResourceSetWrapper, null, null, null, null))->return($fakeQueryResult); //indicate that POData must perform the paging (thus it will use the count of the results in QueryResult) Phockito::when($this->mockProvidersWrapper->handlesOrderedPaging())->return(false); $uriProcessor->execute(); $request = $uriProcessor->getRequest(); $actual = $request->getTargetResult(); $this->assertEquals(array(1, 2, 3), $actual); $this->assertNull($request->getCountValue(), 'Since $inlinecount is specified as none, there should be no count set'); }
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)); } }