PageRenderTime 83ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/rx/DistinctQueryTests.java

http://github.com/WindowsAzure/azure-sdk-for-java
Java | 390 lines | 303 code | 66 blank | 21 comment | 20 complexity | 1c74c3c4627f444b14bbdeb47dccce41 MD5 | raw file
Possible License(s): MIT
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License.
  3. package com.azure.cosmos.rx;
  4. import com.azure.cosmos.CosmosAsyncClient;
  5. import com.azure.cosmos.CosmosAsyncContainer;
  6. import com.azure.cosmos.CosmosClientBuilder;
  7. import com.azure.cosmos.CosmosException;
  8. import com.azure.cosmos.implementation.FailureValidator;
  9. import com.azure.cosmos.implementation.FeedResponseListValidator;
  10. import com.azure.cosmos.implementation.FeedResponseValidator;
  11. import com.azure.cosmos.implementation.InternalObjectNode;
  12. import com.azure.cosmos.implementation.Utils;
  13. import com.azure.cosmos.implementation.guava25.collect.ImmutableMap;
  14. import com.azure.cosmos.implementation.query.UnorderedDistinctMap;
  15. import com.azure.cosmos.implementation.routing.UInt128;
  16. import com.azure.cosmos.models.CosmosQueryRequestOptions;
  17. import com.azure.cosmos.models.FeedResponse;
  18. import com.azure.cosmos.models.ModelBridgeInternal;
  19. import com.azure.cosmos.rx.pojos.City;
  20. import com.azure.cosmos.rx.pojos.Person;
  21. import com.azure.cosmos.rx.pojos.Pet;
  22. import com.azure.cosmos.util.CosmosPagedFlux;
  23. import com.fasterxml.jackson.databind.JsonNode;
  24. import com.fasterxml.jackson.databind.ObjectMapper;
  25. import org.testng.annotations.AfterClass;
  26. import org.testng.annotations.BeforeClass;
  27. import org.testng.annotations.Factory;
  28. import org.testng.annotations.Test;
  29. import java.util.ArrayList;
  30. import java.util.Iterator;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.Random;
  34. import java.util.UUID;
  35. import java.util.stream.Collectors;
  36. import static org.assertj.core.api.Assertions.assertThat;
  37. public class DistinctQueryTests extends TestSuiteBase {
  38. private final int TIMEOUT_LONG = 240000;
  39. private final String FIELD = "name";
  40. private CosmosAsyncContainer createdCollection;
  41. private ArrayList<Person> docs = new ArrayList<>();
  42. private ArrayList<InternalObjectNode> propertiesDocs = new ArrayList<>();
  43. private CosmosAsyncClient client;
  44. @Factory(dataProvider = "clientBuildersWithDirect")
  45. public DistinctQueryTests(CosmosClientBuilder clientBuilder) {
  46. super(clientBuilder);
  47. }
  48. private static String getRandomName(Random rand) {
  49. StringBuilder stringBuilder = new StringBuilder();
  50. stringBuilder.append("name_" + rand.nextInt(100));
  51. return stringBuilder.toString();
  52. }
  53. private static City getRandomCity(Random rand) {
  54. int index = rand.nextInt(3);
  55. switch (index) {
  56. case 0:
  57. return City.LOS_ANGELES;
  58. case 1:
  59. return City.NEW_YORK;
  60. case 2:
  61. return City.SEATTLE;
  62. }
  63. return City.LOS_ANGELES;
  64. }
  65. private static double getRandomIncome(Random rand) {
  66. return rand.nextDouble() * Double.MAX_VALUE;
  67. }
  68. private static int getRandomAge(Random rand) {
  69. return rand.nextInt(100);
  70. }
  71. @Test(groups = {"simple"}, timeOut = TIMEOUT, dataProvider = "queryMetricsArgProvider")
  72. public void queryDocuments(Boolean qmEnabled) {
  73. String query = "SELECT DISTINCT c.name from c";
  74. CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
  75. if (qmEnabled != null) {
  76. options.setQueryMetricsEnabled(qmEnabled);
  77. }
  78. options.setMaxDegreeOfParallelism(2);
  79. CosmosPagedFlux<InternalObjectNode> queryObservable =
  80. createdCollection.queryItems(query,
  81. options,
  82. InternalObjectNode.class);
  83. List<Object> nameList = docs.stream()
  84. .map(d -> d.getName())
  85. .collect(Collectors.toList());
  86. List<Object> collect = propertiesDocs.stream().map(d -> d.get(FIELD)).collect(Collectors.toList());
  87. nameList.add(collect);
  88. List<Object> distinctNameList = nameList.stream().distinct().collect(Collectors.toList());
  89. FeedResponseListValidator<InternalObjectNode> validator =
  90. new FeedResponseListValidator.Builder<InternalObjectNode>()
  91. .totalSize(distinctNameList.size())
  92. .allPagesSatisfy(new FeedResponseValidator.Builder<InternalObjectNode>()
  93. .requestChargeGreaterThanOrEqualTo(1.0)
  94. .build())
  95. .hasValidQueryMetrics(qmEnabled)
  96. .build();
  97. validateQuerySuccess(queryObservable.byPage(5), validator, TIMEOUT);
  98. }
  99. @Test(groups = {"simple"}, timeOut = TIMEOUT_LONG)
  100. public void queryDistinctDocuments() {
  101. Map<String, Boolean> queries = ImmutableMap.<String, Boolean>builder()
  102. // basic distinct queries
  103. .put("SELECT %s VALUE null", true)
  104. .put("SELECT %s VALUE false", false)
  105. .put("SELECT %s VALUE true", false)
  106. .put("SELECT %s VALUE 1", false)
  107. .put("SELECT %s VALUE 'a'", true)
  108. .put("SELECT %s VALUE [null, true, false, 1, 'a']", false)
  109. .put("SELECT %s false AS p", true)
  110. .put("SELECT %s 1 AS p", false)
  111. .put("SELECT %s 'a' AS p", false)
  112. .put("SELECT %s VALUE null FROM c", false)
  113. .put("SELECT %s VALUE false FROM c", false)
  114. .put("SELECT %s VALUE 1 FROM c", false)
  115. .put("SELECT %s VALUE 'a' FROM c", false)
  116. .put("SELECT %s null AS p FROM c", false)
  117. .put("SELECT %s false AS p FROM c", false)
  118. .put("SELECT %s 1 AS p FROM c", false)
  119. .put("SELECT %s 'a' AS p FROM c", false)
  120. // number value distinct queries
  121. .put("SELECT %s VALUE c.income from c", true)
  122. .put("SELECT %s VALUE c.age from c", false)
  123. .put("SELECT %s c.income, c.income AS income2 from c", false)
  124. .put("SELECT %s c.income, c.age from c", false)
  125. // string value distinct queries
  126. .put("SELECT %s c.name from c", true)
  127. .put("SELECT %s VALUE c.city from c", false)
  128. .put("SELECT %s c.name, c.name AS name2 from c", false)
  129. .put("SELECT %s c.name, c.city from c", false)
  130. // array distinct queries
  131. .put("SELECT %s c.children from c", true)
  132. .put("SELECT %s c.children, c.children AS children2 from c", false)
  133. // object value distinct queries
  134. .put("SELECT %s VALUE c.pet from c", true)
  135. .put("SELECT %s c.pet, c.pet AS pet2 from c", false)
  136. // scalar expressions distinct query
  137. .put("SELECT %s VALUE ABS(c.age) FROM c", true)
  138. .put("SELECT %s VALUE LEFT(c.name, 1) FROM c", false)
  139. .put("SELECT %s VALUE c.name || ', ' || (c.city ?? '') FROM c", false)
  140. .put("SELECT %s VALUE ARRAY_LENGTH(c.children) FROM c", false)
  141. .put("SELECT %s VALUE IS_DEFINED(c.city) FROM c", false)
  142. .put("SELECT %s VALUE (c.children[0].age ?? 0) + (c.children[1].age ?? 0) FROM c", false)
  143. // distinct queries with order by
  144. .put("SELECT %s c.name FROM c ORDER BY c.name ASC", false)
  145. .put("SELECT %s c.age FROM c ORDER BY c.age", false)
  146. .put("SELECT %s c.city FROM c ORDER BY c.city", false)
  147. .put("SELECT %s c.city FROM c ORDER BY c.age", false)
  148. .put("SELECT %s LEFT(c.name, 1) FROM c ORDER BY c.name", false)
  149. // distinct queries with top and no matching order by
  150. .put("SELECT %s TOP 2147483647 VALUE c.age FROM c", false)
  151. // distinct queries with top and matching order by
  152. .put("SELECT %s TOP 2147483647 c.age FROM c ORDER BY c.age", false)
  153. // distinct queries with aggregates
  154. .put("SELECT %s VALUE MAX(c.age) FROM c", false)
  155. // distinct queries with joins
  156. .put("SELECT %s VALUE c.age FROM p JOIN c IN p.children", true)
  157. .put("SELECT %s p.age AS ParentAge, c.age ChildAge FROM p JOIN c IN p.children", false)
  158. .put("SELECT %s VALUE c.name FROM p JOIN c IN p.children", false)
  159. .put("SELECT %s p.name AS ParentName, c.name ChildName FROM p JOIN c IN p.children", false)
  160. // distinct queries in subqueries
  161. .put("SELECT %s r.age, s FROM r JOIN (SELECT DISTINCT VALUE c FROM (SELECT 1 a) c) s WHERE r.age > 25", false)
  162. .put("SELECT %s p.name, p.age FROM (SELECT DISTINCT * FROM r) p WHERE p.age > 25", false)
  163. // distinct queries in scalar subqeries
  164. .put("SELECT %s p.name, (SELECT DISTINCT VALUE p.age) AS Age FROM p", true)
  165. .put("SELECT %s p.name, p.age FROM p WHERE (SELECT DISTINCT VALUE LEFT(p.name, 1)) > 'A' AND (SELECT " +
  166. "DISTINCT VALUE p.age) > 21", false)
  167. .put("SELECT %s p.name, (SELECT DISTINCT VALUE p.age) AS Age FROM p WHERE (SELECT DISTINCT VALUE p.name) >" +
  168. " 'A' OR (SELECT DISTINCT VALUE p.age) > 21", false)
  169. // select *
  170. .put("SELECT %s * FROM c", true)
  171. .build();
  172. for (Map.Entry<String, Boolean> entry : queries.entrySet()) {
  173. logger.info("Current distinct query: " + entry.getKey());
  174. CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
  175. options.setMaxDegreeOfParallelism(2);
  176. List<JsonNode> documentsFromWithDistinct = new ArrayList<>();
  177. List<JsonNode> documentsFromWithoutDistinct = new ArrayList<>();
  178. final String queryWithDistinct = String.format(entry.getKey(), "DISTINCT");
  179. final String queryWithoutDistinct = String.format(entry.getKey(), "");
  180. CosmosPagedFlux<JsonNode> queryObservable = createdCollection.queryItems(queryWithoutDistinct,
  181. options,
  182. JsonNode.class);
  183. Iterator<FeedResponse<JsonNode>> iterator = queryObservable.byPage().toIterable().iterator();
  184. Utils.ValueHolder<UInt128> outHash = new Utils.ValueHolder<>();
  185. UnorderedDistinctMap distinctMap = new UnorderedDistinctMap();
  186. while (iterator.hasNext()) {
  187. FeedResponse<JsonNode> next = iterator.next();
  188. for (JsonNode document : next.getResults()) {
  189. if (distinctMap.add(document, outHash)) {
  190. documentsFromWithoutDistinct.add(document);
  191. }
  192. }
  193. }
  194. CosmosPagedFlux<JsonNode> queryObservableWithDistinct = createdCollection
  195. .queryItems(queryWithDistinct, options,
  196. JsonNode.class);
  197. iterator = queryObservableWithDistinct.byPage(5).toIterable().iterator();
  198. while (iterator.hasNext()) {
  199. FeedResponse<JsonNode> next = iterator.next();
  200. documentsFromWithDistinct.addAll(next.getResults());
  201. }
  202. // We want to do Dcount for some queries
  203. if (entry.getValue()) {
  204. // Do a dcount query and validate results
  205. String queryWithDcount = "Select value count(1) from ("
  206. + String.format(entry.getKey(), "DISTINCT")
  207. + ")";
  208. List<Integer> docsWithDCount = new ArrayList<>();
  209. CosmosPagedFlux<Integer> dcountQueryObs = createdCollection.queryItems(queryWithDcount,
  210. options,
  211. Integer.class);
  212. for (FeedResponse<Integer> next : dcountQueryObs.byPage().toIterable()) {
  213. docsWithDCount.addAll(next.getResults());
  214. }
  215. assertThat(docsWithDCount.size()).isEqualTo(1);
  216. int dCount = docsWithDCount.get(0);
  217. assertThat(dCount).isEqualTo(documentsFromWithDistinct.size());
  218. }
  219. assertThat(documentsFromWithDistinct.size()).isGreaterThanOrEqualTo(1);
  220. assertThat(documentsFromWithDistinct.size()).isEqualTo(documentsFromWithoutDistinct.size());
  221. }
  222. }
  223. @Test(groups = {"simple"}, timeOut = TIMEOUT, dataProvider = "queryMetricsArgProvider")
  224. public void queryDocumentsForDistinctIntValues(Boolean qmEnabled) {
  225. String query = "SELECT DISTINCT c.intprop from c";
  226. CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
  227. if (qmEnabled != null) {
  228. options.setQueryMetricsEnabled(qmEnabled);
  229. }
  230. options.setMaxDegreeOfParallelism(2);
  231. CosmosPagedFlux<InternalObjectNode> queryObservable = createdCollection.queryItems(query, options,
  232. InternalObjectNode.class);
  233. Iterator<FeedResponse<InternalObjectNode>> iterator = queryObservable.byPage(5).collectList().single().block()
  234. .iterator();
  235. List<InternalObjectNode> itemPropertiesList = new ArrayList<>();
  236. while (iterator.hasNext()) {
  237. FeedResponse<InternalObjectNode> next = iterator.next();
  238. itemPropertiesList.addAll(next.getResults());
  239. }
  240. assertThat(itemPropertiesList.size()).isEqualTo(2);
  241. List<Object> intpropList = itemPropertiesList
  242. .stream()
  243. .map(internalObjectNode ->
  244. ModelBridgeInternal.getObjectFromJsonSerializable(
  245. internalObjectNode, "intprop"))
  246. .collect(Collectors.toList());
  247. // We insert two documents witn intprop as 5.0 and 5. Distinct should consider them as one
  248. assertThat(intpropList).containsExactlyInAnyOrder(null, 5);
  249. }
  250. @Test(groups = {"simple"}, timeOut = TIMEOUT, dataProvider = "queryWithOrderByProvider")
  251. public void queryDocumentsWithOrderBy(String query, boolean matchedOrderBy) {
  252. CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
  253. options.setMaxBufferedItemCount(1);
  254. CosmosPagedFlux<String> queryObservable = createdCollection.queryItems(query, options,
  255. String.class);
  256. FeedResponse<String> response = queryObservable.byPage(1).blockFirst();
  257. assertThat(response.getResults().size()).isEqualTo(1);
  258. assertThat(response.getContinuationToken()).isNotNull();
  259. assertThat(response.getContinuationToken()).doesNotContain("\"lastHash\":\"\"");
  260. // now using the continuationToken to fetch the next page
  261. if (matchedOrderBy) {
  262. response = queryObservable.byPage(response.getContinuationToken(), 1).blockFirst();
  263. assertThat(response.getResults().size()).isEqualTo(1);
  264. } else {
  265. FailureValidator validator = new FailureValidator.Builder()
  266. .instanceOf(CosmosException.class)
  267. .statusCode(400)
  268. .build();
  269. validateQueryFailure(queryObservable.byPage(response.getContinuationToken(), 1), validator);
  270. }
  271. }
  272. public void bulkInsert() {
  273. generateTestData();
  274. voidBulkInsertBlocking(createdCollection, docs);
  275. voidBulkInsertBlocking(createdCollection, propertiesDocs);
  276. }
  277. public void generateTestData() {
  278. Random rand = new Random();
  279. ObjectMapper mapper = new ObjectMapper();
  280. for (int i = 0; i < 40; i++) {
  281. Person person = getRandomPerson(rand, true);
  282. docs.add(person);
  283. }
  284. String resourceJson = String.format("{ " + "\"id\": \"%s\", \"intprop\": %d }", UUID.randomUUID().toString(),
  285. 5);
  286. String resourceJson2 = String.format("{ " + "\"id\": \"%s\", \"intprop\": %f }", UUID.randomUUID().toString(),
  287. 5.0f);
  288. propertiesDocs.add(new InternalObjectNode(resourceJson));
  289. propertiesDocs.add(new InternalObjectNode(resourceJson2));
  290. }
  291. private Pet getRandomPet(Random rand) {
  292. String name = getRandomName(rand);
  293. int age = getRandomAge(rand);
  294. return new Pet(name, age);
  295. }
  296. public Person getRandomPerson(Random rand, boolean addChildren) {
  297. String name = getRandomName(rand);
  298. City city = getRandomCity(rand);
  299. double income = getRandomIncome(rand);
  300. List<Person> people = new ArrayList<Person>();
  301. if (rand.nextInt(2) == 0 && addChildren) {
  302. // Starting from -1, add at least one children
  303. for (int i = -1; i < rand.nextInt(5); i++) {
  304. people.add(getRandomPerson(rand, false));
  305. }
  306. }
  307. int age = getRandomAge(rand);
  308. Pet pet = getRandomPet(rand);
  309. UUID guid = UUID.randomUUID();
  310. return new Person(name, city, income, people, age, pet, guid);
  311. }
  312. @AfterClass(groups = {"simple"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true)
  313. public void afterClass() {
  314. safeClose(client);
  315. }
  316. @BeforeClass(groups = {"simple"}, timeOut = 3 * SETUP_TIMEOUT)
  317. public void beforeClass() throws Exception {
  318. client = this.getClientBuilder().buildAsyncClient();
  319. createdCollection = getSharedMultiPartitionCosmosContainer(client);
  320. truncateCollection(createdCollection);
  321. bulkInsert();
  322. waitIfNeededForReplicasToCatchUp(this.getClientBuilder());
  323. }
  324. }