99import com .azure .cosmos .CosmosBridgeInternal ;
1010import com .azure .cosmos .CosmosClientBuilder ;
1111import com .azure .cosmos .CosmosException ;
12- import com .azure .cosmos .models .ModelBridgeInternal ;
13- import com .azure .cosmos .util .CosmosPagedFlux ;
14- import com .azure .cosmos .implementation .InternalObjectNode ;
15- import com .azure .cosmos .models .CosmosItemRequestOptions ;
16- import com .azure .cosmos .models .CosmosQueryRequestOptions ;
17- import com .azure .cosmos .models .FeedResponse ;
18- import com .azure .cosmos .models .PartitionKey ;
19- import com .azure .cosmos .implementation .Resource ;
12+ import com .azure .cosmos .implementation .AsyncDocumentClient ;
2013import com .azure .cosmos .implementation .FeedResponseListValidator ;
2114import com .azure .cosmos .implementation .FeedResponseValidator ;
15+ import com .azure .cosmos .implementation .InternalObjectNode ;
16+ import com .azure .cosmos .implementation .PartitionKeyRange ;
17+ import com .azure .cosmos .implementation .Resource ;
2218import com .azure .cosmos .implementation .ResourceValidator ;
2319import com .azure .cosmos .implementation .RetryAnalyzer ;
2420import com .azure .cosmos .implementation .Utils ;
2723import com .azure .cosmos .implementation .query .OrderByContinuationToken ;
2824import com .azure .cosmos .implementation .query .QueryItem ;
2925import com .azure .cosmos .implementation .routing .Range ;
26+ import com .azure .cosmos .models .CosmosContainerProperties ;
27+ import com .azure .cosmos .models .CosmosItemRequestOptions ;
28+ import com .azure .cosmos .models .CosmosQueryRequestOptions ;
29+ import com .azure .cosmos .models .FeedResponse ;
30+ import com .azure .cosmos .models .IncludedPath ;
31+ import com .azure .cosmos .models .IndexingPolicy ;
32+ import com .azure .cosmos .models .ModelBridgeInternal ;
33+ import com .azure .cosmos .models .PartitionKey ;
34+ import com .azure .cosmos .util .CosmosPagedFlux ;
3035import com .fasterxml .jackson .core .JsonProcessingException ;
3136import io .reactivex .subscribers .TestSubscriber ;
3237import org .apache .commons .lang3 .StringUtils ;
3944import reactor .core .publisher .Flux ;
4045
4146import java .util .ArrayList ;
47+ import java .util .Arrays ;
4248import java .util .Collections ;
4349import java .util .Comparator ;
4450import java .util .HashMap ;
5258import static org .assertj .core .api .Assertions .assertThat ;
5359
5460public class OrderbyDocumentQueryTest extends TestSuiteBase {
61+ public static final String PROPTYPE = "proptype" ;
62+ public static final String PROP_MIXED = "propMixed" ;
5563 private final double minQueryRequestChargePerPartition = 2.0 ;
5664
5765 private CosmosAsyncClient client ;
@@ -230,6 +238,117 @@ public void queryOrderByString() throws Exception {
230238 validateQuerySuccess (queryObservable .byPage (pageSize ), validator );
231239 }
232240
241+ @ Test (groups = {"simple" }, timeOut = TIMEOUT , dataProvider = "sortOrder" )
242+ public void queryOrderByMixedTypes (String sortOrder ) throws Exception {
243+ List <PartitionKeyRange > partitionKeyRanges = getPartitionKeyRanges (createdCollection .getId (),
244+ BridgeInternal
245+ .getContextClient (this .client ));
246+ // Ensure its a cross partition query
247+ assertThat (partitionKeyRanges .size ()).isGreaterThan (1 );
248+ // We are inserting documents with int, float, string, array, object and missing propMixed.
249+ String query = String .format ("SELECT * FROM r ORDER BY r.propMixed " , sortOrder );
250+ CosmosQueryRequestOptions options = new CosmosQueryRequestOptions ();
251+ List <String > sourceIds = createdDocuments .stream ()
252+ .map (Resource ::getId )
253+ .collect (Collectors .toList ());
254+
255+ int pageSize = 20 ;
256+ CosmosPagedFlux <InternalObjectNode > queryFlux = createdCollection
257+ .queryItems (query , options , InternalObjectNode .class );
258+ TestSubscriber <FeedResponse <InternalObjectNode >> subscriber = new TestSubscriber <>();
259+ queryFlux .byPage (pageSize ).subscribe (subscriber );
260+ subscriber .awaitTerminalEvent ();
261+ subscriber .assertComplete ();
262+ subscriber .assertNoErrors ();
263+ List <InternalObjectNode > results = new ArrayList <>();
264+ subscriber .values ().forEach (feedResponse -> results .addAll (feedResponse .getResults ()));
265+ // Make sure all elements inserted are returned
266+ assertThat (results .size ()).isEqualTo (createdDocuments .size ());
267+
268+ // Make sure all ids are present
269+ List <String > resultIds = results .stream ().map (Resource ::getId ).collect (Collectors .toList ());
270+ assertThat (resultIds ).containsExactlyInAnyOrderElementsOf (sourceIds );
271+
272+ // Make sure the below defined group order for mixed types match with the result grouping
273+ final List <String > typeList = Arrays .asList ("undefined" , "null" , "boolean" , "number" , "string" , "array" ,
274+ "object" );
275+ List <String > observedTypes = new ArrayList <>();
276+
277+ results .forEach (item -> {
278+ String propType = "undefined" ;
279+ if (item .has (PROPTYPE )) {
280+ propType = item .getString (PROPTYPE );
281+ }
282+ System .out .println ("item.get(PROPTYPE) = " + item .get (PROPTYPE ));
283+ System .out .println ("propType = " + propType );
284+ if (!observedTypes .contains (propType )) {
285+ observedTypes .add (propType );
286+ } else {
287+ boolean equals = observedTypes .get (observedTypes .size () - 1 ).equals (propType );
288+ assertThat (equals ).isTrue ().as ("Items of same type should be contiguous" );
289+ }
290+ });
291+
292+ // Esuring that the returned results are grouped in the expected order
293+ assertThat (observedTypes ).containsExactlyElementsOf (typeList );
294+
295+ // Now check ordering inside each type group
296+ for (String type : typeList ) {
297+ List <InternalObjectNode > items = results .stream ().filter (r -> {
298+ if ("undefined" .equals (type )) {
299+ return !r .has (PROPTYPE );
300+ }
301+ return type .equals (r .getString (PROPTYPE ));
302+ }).collect (Collectors .toList ());
303+
304+ // Skip comparing undefined types
305+ // Skip comparing null types
306+ // Skip comparing Array and object types
307+ // Compare booleans, number and String types among their own groups to check if they are sorted
308+ if ("boolean" .equals (type )) {
309+ List <Boolean > sourceList =
310+ items .stream ().map (n -> n .getBoolean (PROP_MIXED )).collect (Collectors .toList ());
311+ List <Boolean > toBeSortedList = new ArrayList <>(sourceList );
312+ toBeSortedList .sort (Comparator .comparing (Boolean ::booleanValue ));
313+ // making sure the list before and after sorting should be the same as we get already should have got
314+ // properly sorted results from the query
315+ assertThat (toBeSortedList ).containsExactlyElementsOf (sourceList );
316+ }
317+ if ("number" .equals (type )) {
318+ List <Number > numberList =
319+ items .stream ().map (n -> (Number ) n .get (PROP_MIXED )).collect (Collectors .toList ());
320+ List <Number > toBeSortedList = new ArrayList <>(numberList );
321+ Collections .copy (toBeSortedList , numberList );
322+ toBeSortedList .sort (Comparator .comparingDouble (Number ::doubleValue ));
323+ // making sure the list before and after sorting should be the same as we get already should have got
324+ // properly sorted results from the query
325+ assertThat (toBeSortedList ).containsExactlyElementsOf (numberList );
326+ }
327+ if ("string" .equals (type )) {
328+ List <String > sourceList =
329+ items .stream ().map (n -> n .getString (PROP_MIXED )).collect (Collectors .toList ());
330+ List <String > toBeSortedList = new ArrayList <>(sourceList );
331+ Collections .copy (toBeSortedList , sourceList );
332+ toBeSortedList .sort (Comparator .comparing (String ::valueOf ));
333+ // making sure the list before and after sorting should be the same as we get already should have got
334+ // properly sorted results from the query
335+ assertThat (toBeSortedList ).containsExactlyElementsOf (sourceList );
336+ }
337+ }
338+ }
339+
340+ private List <PartitionKeyRange > getPartitionKeyRanges (
341+ String containerId , AsyncDocumentClient asyncDocumentClient ) {
342+ List <PartitionKeyRange > partitionKeyRanges = new ArrayList <>();
343+ List <FeedResponse <PartitionKeyRange >> partitionFeedResponseList = asyncDocumentClient
344+ .readPartitionKeyRanges ("/dbs/" + createdDatabase .getId ()
345+ + "/colls/" + containerId ,
346+ new CosmosQueryRequestOptions ())
347+ .collectList ().block ();
348+ partitionFeedResponseList .forEach (f -> partitionKeyRanges .addAll (f .getResults ()));
349+ return partitionKeyRanges ;
350+ }
351+
233352 @ DataProvider (name = "topValue" )
234353 public Object [][] topValueParameter () {
235354 return new Object [][] { { 0 }, { 1 }, { 5 }, { createdDocuments .size () - 1 }, { createdDocuments .size () },
@@ -472,6 +591,7 @@ public void before_OrderbyDocumentQueryTest() throws Exception {
472591
473592 List <Map <String , Object >> keyValuePropsList = new ArrayList <>();
474593 Map <String , Object > props ;
594+ boolean flag = false ;
475595
476596 for (int i = 0 ; i < 30 ; i ++) {
477597 props = new HashMap <>();
@@ -487,6 +607,40 @@ public void before_OrderbyDocumentQueryTest() throws Exception {
487607 }
488608 props .put ("propArray" , orderByArray );
489609 props .put ("propObject" , orderByObject );
610+ switch (i % 8 ) {
611+ case 0 :
612+ props .put (PROP_MIXED , i );
613+ props .put (PROPTYPE , "number" );
614+ break ;
615+ case 1 :
616+ props .put (PROP_MIXED , String .valueOf (i ));
617+ props .put (PROPTYPE , "string" );
618+ break ;
619+ case 2 :
620+ props .put (PROP_MIXED , orderByArray );
621+ props .put (PROPTYPE , "array" );
622+ break ;
623+ case 3 :
624+ props .put (PROP_MIXED , orderByObject );
625+ props .put (PROPTYPE , "object" );
626+ break ;
627+ case 4 :
628+ props .put (PROP_MIXED , (float )i *3.17 );
629+ props .put (PROPTYPE , "number" );
630+ break ;
631+ case 5 :
632+ props .put (PROP_MIXED , null );
633+ props .put (PROPTYPE , "null" );
634+ break ;
635+ case 6 :
636+ flag = !flag ;
637+ props .put (PROP_MIXED , flag );
638+ props .put (PROPTYPE , "boolean" );
639+ break ;
640+ default :
641+ // skips the propMixed
642+ break ;
643+ }
490644 keyValuePropsList .add (props );
491645 }
492646
@@ -510,6 +664,20 @@ public void before_OrderbyDocumentQueryTest() throws Exception {
510664 .flatMap (p -> Flux .fromIterable (p .getResults ())).collectList ().single ().block ().size ();
511665
512666 waitIfNeededForReplicasToCatchUp (getClientBuilder ());
667+ updateCollectionIndex ();
668+ }
669+
670+ private void updateCollectionIndex () {
671+ CosmosContainerProperties containerProperties = createdCollection .read ().block ().getProperties ();
672+ IndexingPolicy indexingPolicy = containerProperties .getIndexingPolicy ();
673+ List <IncludedPath > includedPaths = indexingPolicy .getIncludedPaths ();
674+ IncludedPath includedPath = new IncludedPath ("/propMixed/?" );
675+ if (!includedPaths .contains (includedPath )) {
676+ includedPaths .add (includedPath );
677+ indexingPolicy .setIncludedPaths (includedPaths );
678+ containerProperties .setIndexingPolicy (indexingPolicy );
679+ createdCollection .replace (containerProperties ).block ();
680+ }
513681 }
514682
515683 @ AfterClass (groups = { "simple" }, timeOut = SHUTDOWN_TIMEOUT , alwaysRun = true )
0 commit comments