/** * Test search InternalSkipToeknInfo::GetIndexOfFirstEntryInNextPage function */ public function testGetIndexOfFirstEntryInNextPage2() { try { $northWindMetadata = CreateNorthWindMetadata1::Create(); $configuration = new DataServiceConfiguration($northWindMetadata); $configuration->setEntitySetAccessRule('*', EntitySetRights::ALL); $metaQueryProverWrapper = new MetadataQueryProviderWrapper($northWindMetadata, null, $configuration, false); $resourceSetWrapper = $metaQueryProverWrapper->resolveResourceSet('Orders'); $resourceType = $resourceSetWrapper->getResourceType(); $orderBy = 'ShipName asc, Freight'; //Note: library will add prim key as last sort key $orderBy .= ', OrderID'; $qp = new NorthWindQueryProvider1(); $orders = $qp->getResourceSet($resourceSetWrapper->getResourceSet()); $internalOrderByInfo = OrderByParser::parseOrderByClause($resourceSetWrapper, $resourceType, $orderBy, $metaQueryProverWrapper); $compFun = $internalOrderByInfo->getSorterFunction(); $fun = $compFun->getReference(); usort($orders, $fun); $numRecords = count($orders); //----------------------------------------------------------------- //Search with a key that exactly matches $skipToken = utf8_decode(urldecode("'Antonio%20Moreno%20Taquer%C3%ADa',22.0000M,10365")); $skipToken = urldecode($skipToken); $internalSkipTokenInfo = SkipTokenParser::parseSkipTokenClause($resourceType, $internalOrderByInfo, $skipToken); $nextIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($orders); $this->assertTrue($nextIndex > 1); $this->assertTrue($nextIndex < $numRecords); //$nextIndex is the index of order record next to the searched record $this->assertEquals($orders[$nextIndex - 1]->OrderID, 10365); $this->assertEquals($orders[$nextIndex - 1]->Freight, 22.0); //----------------------------------------------------------------- //Search with a key that partially matches, in the DB there is no //order with ShipName 'An', but there are records start with //'An', so partial match, since its a parial match other two //key wont be used for comparsion $skipToken = "'An',22.0000M,10365"; $internalSkipTokenInfo = SkipTokenParser::parseSkipTokenClause($resourceType, $internalOrderByInfo, $skipToken); $nextIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($orders); $this->assertTrue($nextIndex > 1); $this->assertTrue($nextIndex < $numRecords); //Make sure this is the most matching record by comparing with previous record $prevOrder = $orders[$nextIndex - 1]; $r = strcmp($prevOrder->ShipName, $orders[$nextIndex]->ShipName); $this->assertTrue($r < 0); //Make sure this is the most matching record by comparing with next record $nextOrder = $orders[$nextIndex + 1]; $r = strcmp($nextOrder->ShipName, $orders[$nextIndex]->ShipName); $this->assertTrue($r >= 0); //----------------------------------------------------------------- //Search with a key that does not exists $skipToken = "'XXX',11,10365"; $internalSkipTokenInfo = SkipTokenParser::parseSkipTokenClause($resourceType, $internalOrderByInfo, $skipToken); $nextIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($orders); $this->assertTrue($nextIndex == -1); //----------------------------------------------------------------- } catch (\Exception $exception) { $this->fail('An unexpected Exception has been raised.' . $exception->getMessage()); } }
/** * Process $orderby option, This function requires _processSkipAndTopOption * function to be already called as this function need to know whether * client has requested for skip, top or paging is enabled for the * requested resource in these cases function generates additional orderby * expression using keys. * * @return void * * @throws ODataException If any error occurs while parsing orderby option. */ private function _processOrderBy() { $orderBy = $this->_dataService->getHost()->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_ORDERBY); if (!is_null($orderBy)) { $this->_checkSetQueryApplicable(); } $targetResourceType = $this->_requestDescription->getTargetResourceType(); //assert($targetResourceType != null) /** * We need to do sorting in the folowing cases, irrespective of * $orderby clause is present or not. * 1. If $top or $skip is specified * skip and take will be applied on sorted list only. If $skip * is specified then RequestDescription::getSkipCount will give * non-null value. If $top is specified then * RequestDescription::getTopCount will give non-null value. * 2. If server side paging is enabled for the requested resource * If server-side paging is enabled for the requested resource then * RequestDescription::getTopCount will give non-null value. * */ if (!is_null($this->_requestDescription->getSkipCount()) || !is_null($this->_requestDescription->getTopCount())) { $orderBy = !is_null($orderBy) ? $orderBy . ', ' : null; $keys = array_keys($targetResourceType->getKeyProperties()); //assert(!empty($keys)) foreach ($keys as $key) { $orderBy = $orderBy . $key . ', '; } $orderBy = rtrim($orderBy, ', '); } if (!is_null($orderBy)) { try { $internalOrderByInfo = OrderByParser::parseOrderByClause($this->_requestDescription->getTargetResourceSetWrapper(), $targetResourceType, $orderBy, $this->_dataService->getMetadataQueryProviderWrapper()); $this->_requestDescription->setInternalOrderByInfo($internalOrderByInfo); } catch (ODataException $odataException) { throw $odataException; } } }
/** * Test whether order by parser identify and remove path duplication */ public function testOrderByWithPathDuplication() { try { $northWindMetadata = CreateNorthWindMetadata3::Create(); $configuration = new DataServiceConfiguration($northWindMetadata); $configuration->setEntitySetAccessRule('*', EntitySetRights::ALL); $metaQueryProverWrapper = new MetadataQueryProviderWrapper($northWindMetadata, null, $configuration, false); $resourceSetWrapper = $metaQueryProverWrapper->resolveResourceSet('Order_Details'); $resourceType = $resourceSetWrapper->getResourceType(); $orderBy = 'Order/Price desc, Product/ProductName asc, Order/Price asc'; $internalOrderInfo = OrderByParser::parseOrderByClause($resourceSetWrapper, $resourceType, $orderBy, $metaQueryProverWrapper); //The orderby path Order/Price appears twice, but parser will consider only first path $orderByInfo = $internalOrderInfo->getOrderByInfo(); //There are navigation (resource reference) properties in the orderby path so getNavigationPropertiesUsed should //not be null $naviUsed = $orderByInfo->getNavigationPropertiesUsed(); $this->assertFalse(is_null($naviUsed)); //3 path segment are there, but last one is duplicate of first one, so parser removes last one $this->assertEquals(count($naviUsed), 2); $this->assertTrue(is_array($naviUsed[0])); $this->assertTrue(is_array($naviUsed[1])); //one navgations used in first orderby 'Order' $this->assertEquals(count($naviUsed[0]), 1); //one navgations used in second orderby 'Prodcut' $this->assertEquals(count($naviUsed[1]), 1); $this->assertEquals($naviUsed[0][0]->getName(), 'Order'); $this->assertEquals($naviUsed[1][0]->getName(), 'Product'); $orderByPathSegments = $orderByInfo->getOrderByPathSegments(); $this->assertEquals(count($orderByPathSegments), 2); } catch (\Exception $exception) { $this->fail('An unexpected Exception has been raised' . $exception->getMessage()); } }
/** * Build 'Projection Tree' from the given expand path segments * * @param array(array(string)) $expandPathSegments Collection of expand * paths. * * @return void * * @throws ODataException If any error occurs while processing the expand * path segments. */ private function _buildProjectionTree($expandPathSegments) { foreach ($expandPathSegments as $expandSubPathSegments) { $currentNode = $this->_rootProjectionNode; foreach ($expandSubPathSegments as $expandSubPathSegment) { $resourceSetWrapper = $currentNode->getResourceSetWrapper(); $resourceType = $currentNode->getResourceType(); $resourceProperty = $resourceType->tryResolvePropertyTypeByName($expandSubPathSegment); if (is_null($resourceProperty)) { ODataException::createSyntaxError(Messages::expandProjectionParserPropertyNotFound($resourceType->getFullName(), $expandSubPathSegment, false)); } else { if ($resourceProperty->getTypeKind() != ResourceTypeKind::ENTITY) { ODataException::createBadRequestError(Messages::expandProjectionParserExpandCanOnlyAppliedToEntity($resourceType->getFullName(), $expandSubPathSegment)); } } $resourceSetWrapper = $this->_providerWrapper->getResourceSetWrapperForNavigationProperty($resourceSetWrapper, $resourceType, $resourceProperty); if (is_null($resourceSetWrapper)) { ODataException::createBadRequestError(Messages::badRequestInvalidPropertyNameSpecified($resourceType->getFullName(), $expandSubPathSegment)); } $singleResult = $resourceProperty->isKindOf(ResourcePropertyKind::RESOURCE_REFERENCE); $resourceSetWrapper->checkResourceSetRightsForRead($singleResult); $pageSize = $resourceSetWrapper->getResourceSetPageSize(); $internalOrderByInfo = null; if ($pageSize != 0 && !$singleResult) { $this->_rootProjectionNode->setPagedExpandedResult(true); $rt = $resourceSetWrapper->getResourceType(); //assert($rt != null) $keys = array_keys($rt->getKeyProperties()); //assert(!empty($keys)) $orderBy = null; foreach ($keys as $key) { $orderBy = $orderBy . $key . ', '; } $orderBy = rtrim($orderBy, ', '); try { $internalOrderByInfo = OrderByParser::parseOrderByClause($resourceSetWrapper, $rt, $orderBy, $this->_providerWrapper); } catch (ODataException $odataException) { throw $odataException; } } $node = $currentNode->findNode($expandSubPathSegment); if (is_null($node)) { $maxResultCount = $this->_providerWrapper->getConfiguration()->getMaxResultsPerCollection(); $node = new ExpandedProjectionNode($expandSubPathSegment, $resourceProperty, $resourceSetWrapper, $internalOrderByInfo, null, $pageSize == 0 ? null : $pageSize, $maxResultCount == PHP_INT_MAX ? null : $maxResultCount); $currentNode->addNode($node); } $currentNode = $node; } } }
/** * test the creation of nextlink from an object. * Test building of link with guid sub-value */ public function testCreationOfNextLink4() { try { $northWindMetadata = CreateNorthWindMetadata3::Create(); $configuration = new DataServiceConfiguration($northWindMetadata); $configuration->setEntitySetAccessRule('*', EntitySetRights::ALL); $metaQueryProverWrapper = new MetadataQueryProviderWrapper($northWindMetadata, null, $configuration, false); $resourceSetWrapper = $metaQueryProverWrapper->resolveResourceSet('Customers'); $resourceType = $resourceSetWrapper->getResourceType(); $orderBy = 'CustomerID, CustomerGuid'; $internalOrderByInfo = OrderByParser::parseOrderByClause($resourceSetWrapper, $resourceType, $orderBy, $metaQueryProverWrapper); $skipToken = "null, guid'05b242e752eb46bd8f0e6568b72cd9a5'"; $internalSkipTokenInfo = SkipTokenParser::parseSkipTokenClause($resourceType, $internalOrderByInfo, $skipToken); $keyObject = $internalSkipTokenInfo->getKeyObject(); $lastObject = new Customer2(); $lastObject->CustomerID = 'ABC'; $lastObject->CustomerGuid = '{05b242e7-52eb-46bd-8f0e-6568b72cd9a5}'; $nextLink = $internalSkipTokenInfo->buildNextPageLink($lastObject); $this->assertEquals($nextLink, "'ABC', guid'%7B05b242e7-52eb-46bd-8f0e-6568b72cd9a5%7D'"); } catch (\Exception $exception) { $this->fail('An unexpected Exception has been raised.' . $exception->getMessage()); } }
/** * This function perform the following tasks with the help of internal helper * functions * (1) Read the orderby clause and perform basic syntax errors * (2) Build 'Order By Tree', creates anonymous sorter function for each leaf * node and check for error * (3) Build 'OrderInfo' structure, holds information about the navigation * properties used in the orderby clause (if any) and orderby path if * IDSQP implementor want to perform sorting * (4) Build top level anonymous sorter function * (4) Release resources hold by the 'Order By Tree' * (5) Create 'InternalOrderInfo' structure, which wraps 'OrderInfo' and top * level sorter function * * @param ResourceSetWrapper $resourceSetWrapper ResourceSetWrapper for * the resource targetted * by resource path. * @param ResourceType $resourceType ResourceType for the * resource targetted * by resource path. * @param string $orderBy The orderby clause. * @param MetadataQueryProviderWrapper $providerWrapper Reference to the * wrapper for IDSQP * and IDSMP impl. * * @return InternalOrderByInfo * * @throws ODataException If any error occur while parsing orderby clause */ public static function parseOrderByClause(ResourceSetWrapper $resourceSetWrapper, ResourceType $resourceType, $orderBy, MetadataQueryProviderWrapper $providerWrapper) { $orderByParser = new OrderByParser($providerWrapper); try { $orderByParser->_dummyObject = $resourceType->getInstanceType()->newInstance(); } catch (\ReflectionException $reflectionException) { throw ODataException::createInternalServerError(Messages::orderByParserFailedToCreateDummyObject()); } $orderByParser->_rootOrderByNode = new OrderByRootNode($resourceSetWrapper, $resourceType); $orderByPathSegments = $orderByParser->_readOrderBy($orderBy); $orderByParser->_buildOrderByTree($orderByPathSegments); $orderByParser->_createOrderInfo($orderByPathSegments); $orderByParser->_generateTopLevelComparisonFunction(); //Recursively release the resources $orderByParser->_rootOrderByNode->free(); //creates internal order info wrapper $internalOrderInfo = new InternalOrderByInfo($orderByParser->_orderByInfo, $orderByParser->_comparisonFunctions, $orderByParser->_topLevelComparisonFunction, $orderByParser->_dummyObject); unset($orderByParser->_orderByInfo); unset($orderByParser->_topLevelComparisonFunction); return $internalOrderInfo; }