PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/solr/core/src/test/org/apache/solr/search/CursorMarkTest.java

https://github.com/apache/solr
Java | 293 lines | 224 code | 30 blank | 39 comment | 38 complexity | d2c999746482b6d005c85aadd8cfb0c0 MD5 | raw file
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package org.apache.solr.search;
  18. import static org.apache.solr.common.params.CursorMarkParams.CURSOR_MARK_START;
  19. import static org.hamcrest.core.StringContains.containsString;
  20. import java.io.IOException;
  21. import java.util.ArrayList;
  22. import java.util.Arrays;
  23. import java.util.Collection;
  24. import java.util.Collections;
  25. import java.util.List;
  26. import java.util.UUID;
  27. import org.apache.lucene.analysis.Analyzer;
  28. import org.apache.lucene.analysis.TokenStream;
  29. import org.apache.lucene.analysis.tokenattributes.TermToBytesRefAttribute;
  30. import org.apache.lucene.tests.util.TestUtil;
  31. import org.apache.lucene.util.BytesRef;
  32. import org.apache.solr.CursorPagingTest;
  33. import org.apache.solr.SolrTestCaseJ4;
  34. import org.apache.solr.common.SolrException;
  35. import org.apache.solr.common.SolrException.ErrorCode;
  36. import org.apache.solr.request.SolrQueryRequest;
  37. import org.apache.solr.schema.IndexSchema;
  38. import org.apache.solr.schema.SchemaField;
  39. import org.junit.BeforeClass;
  40. /**
  41. * Primarily a test of parsing and serialization of the CursorMark values.
  42. *
  43. * <p>NOTE: this class Reuses some utilities from {@link CursorPagingTest} that assume the same
  44. * schema and configs.
  45. *
  46. * @see CursorPagingTest
  47. */
  48. public class CursorMarkTest extends SolrTestCaseJ4 {
  49. @BeforeClass
  50. public static void beforeTests() throws Exception {
  51. System.setProperty(
  52. "solr.test.useFilterForSortedQuery", Boolean.toString(random().nextBoolean()));
  53. initCore(CursorPagingTest.TEST_SOLRCONFIG_NAME, CursorPagingTest.TEST_SCHEMAXML_NAME);
  54. }
  55. public void testNextCursorMark() throws IOException {
  56. final Collection<String> allFieldNames = getAllFieldNames();
  57. final SolrQueryRequest req = req();
  58. final IndexSchema schema = req.getSchema();
  59. final String randomSortString = CursorPagingTest.buildRandomSort(allFieldNames);
  60. final SortSpec ss = SortSpecParsing.parseSortSpec(randomSortString, req);
  61. final CursorMark previous = new CursorMark(schema, ss);
  62. previous.parseSerializedTotem(CURSOR_MARK_START);
  63. List<Object> nextValues = Arrays.<Object>asList(buildRandomSortObjects(ss));
  64. final CursorMark next = previous.createNext(nextValues);
  65. assertEquals("next values not correct", nextValues, next.getSortValues());
  66. assertEquals("next SortSpec not correct", ss, next.getSortSpec());
  67. SolrException e =
  68. expectThrows(
  69. SolrException.class,
  70. "didn't fail on next with incorrect num of sortvalues",
  71. () -> {
  72. // append to our random sort string so we know it has wrong num clauses
  73. final SortSpec otherSort =
  74. SortSpecParsing.parseSortSpec(randomSortString + ",id asc", req);
  75. CursorMark trash =
  76. previous.createNext(Arrays.<Object>asList(buildRandomSortObjects(otherSort)));
  77. });
  78. assertEquals(500, e.code());
  79. assertThat(e.getMessage(), containsString("sort values != sort length"));
  80. }
  81. public void testInvalidUsage() {
  82. final SolrQueryRequest req = req();
  83. final IndexSchema schema = req.getSchema();
  84. try {
  85. final SortSpec ss = SortSpecParsing.parseSortSpec("str desc, score desc", req);
  86. final CursorMark totem = new CursorMark(schema, ss);
  87. fail("no failure from sort that doesn't include uniqueKey field");
  88. } catch (SolrException e) {
  89. assertEquals(ErrorCode.BAD_REQUEST.code, e.code());
  90. assertTrue(0 < e.getMessage().indexOf("uniqueKey"));
  91. }
  92. for (final String dir : Arrays.asList("asc", "desc")) {
  93. try {
  94. final SortSpec ss = SortSpecParsing.parseSortSpec("score " + dir, req);
  95. final CursorMark totem = new CursorMark(schema, ss);
  96. fail("no failure from score only sort: " + dir);
  97. } catch (SolrException e) {
  98. assertEquals(ErrorCode.BAD_REQUEST.code, e.code());
  99. assertTrue(0 < e.getMessage().indexOf("uniqueKey"));
  100. }
  101. try {
  102. final SortSpec ss = SortSpecParsing.parseSortSpec("_docid_ " + dir + ", id desc", req);
  103. final CursorMark totem = new CursorMark(schema, ss);
  104. fail("no failure from sort that includes _docid_: " + dir);
  105. } catch (SolrException e) {
  106. assertEquals(ErrorCode.BAD_REQUEST.code, e.code());
  107. assertTrue(0 < e.getMessage().indexOf("_docid_"));
  108. }
  109. }
  110. }
  111. public void testGarbageParsing() throws IOException {
  112. final SolrQueryRequest req = req();
  113. final IndexSchema schema = req.getSchema();
  114. final SortSpec ss = SortSpecParsing.parseSortSpec("str asc, float desc, id asc", req);
  115. final CursorMark totem = new CursorMark(schema, ss);
  116. // totem string that isn't even valid base64
  117. try {
  118. totem.parseSerializedTotem("all the documents please");
  119. fail("didn't fail on invalid base64 totem");
  120. } catch (SolrException e) {
  121. assertEquals(ErrorCode.BAD_REQUEST.code, e.code());
  122. assertTrue(e.getMessage().contains("Unable to parse 'cursorMark'"));
  123. }
  124. // empty totem string
  125. try {
  126. totem.parseSerializedTotem("");
  127. fail("didn't fail on empty totem");
  128. } catch (SolrException e) {
  129. assertEquals(ErrorCode.BAD_REQUEST.code, e.code());
  130. assertTrue(e.getMessage().contains("Unable to parse 'cursorMark'"));
  131. }
  132. // whitespace-only totem string
  133. try {
  134. totem.parseSerializedTotem(" ");
  135. fail("didn't fail on whitespace-only totem");
  136. } catch (SolrException e) {
  137. assertEquals(ErrorCode.BAD_REQUEST.code, e.code());
  138. assertTrue(e.getMessage().contains("Unable to parse 'cursorMark'"));
  139. }
  140. // totem string from sort with diff num clauses
  141. try {
  142. final SortSpec otherSort = SortSpecParsing.parseSortSpec("double desc, id asc", req);
  143. final CursorMark otherTotem = new CursorMark(schema, otherSort);
  144. otherTotem.setSortValues(Arrays.<Object>asList(buildRandomSortObjects(otherSort)));
  145. totem.parseSerializedTotem(otherTotem.getSerializedTotem());
  146. fail("didn't fail on totem from incorrect sort (num clauses)");
  147. } catch (SolrException e) {
  148. assertEquals(ErrorCode.BAD_REQUEST.code, e.code());
  149. assertTrue(e.getMessage().contains("wrong size"));
  150. }
  151. }
  152. public void testRoundTripParsing() throws IOException {
  153. // for any valid SortSpec, and any legal values, we should be able to round
  154. // trip serialize the totem and get the same values back.
  155. final Collection<String> allFieldNames = getAllFieldNames();
  156. final SolrQueryRequest req = req();
  157. final IndexSchema schema = req.getSchema();
  158. final int numRandomSorts = atLeast(50);
  159. final int numRandomValIters = atLeast(10);
  160. for (int i = 0; i < numRandomSorts; i++) {
  161. final SortSpec ss =
  162. SortSpecParsing.parseSortSpec(CursorPagingTest.buildRandomSort(allFieldNames), req);
  163. final CursorMark totemIn = new CursorMark(schema, ss);
  164. final CursorMark totemOut = new CursorMark(schema, ss);
  165. // trivial case: regardless of sort, "*" should be valid and roundtrippable
  166. totemIn.parseSerializedTotem(CURSOR_MARK_START);
  167. assertEquals(CURSOR_MARK_START, totemIn.getSerializedTotem());
  168. // values should be null (and still roundtrippable)
  169. assertNull(totemIn.getSortValues());
  170. totemOut.setSortValues(null);
  171. assertEquals(CURSOR_MARK_START, totemOut.getSerializedTotem());
  172. for (int j = 0; j < numRandomValIters; j++) {
  173. final Object[] inValues = buildRandomSortObjects(ss);
  174. totemIn.setSortValues(Arrays.<Object>asList(inValues));
  175. totemOut.parseSerializedTotem(totemIn.getSerializedTotem());
  176. final List<Object> out = totemOut.getSortValues();
  177. assertNotNull(out);
  178. final Object[] outValues = out.toArray();
  179. assertArrayEquals(inValues, outValues);
  180. }
  181. }
  182. }
  183. private static Object[] buildRandomSortObjects(SortSpec ss) throws IOException {
  184. List<SchemaField> fields = ss.getSchemaFields();
  185. assertNotNull(fields);
  186. Object[] results = new Object[fields.size()];
  187. for (int i = 0; i < results.length; i++) {
  188. SchemaField sf = fields.get(i);
  189. if (null == sf) {
  190. // score or function
  191. results[i] = (Float) random().nextFloat() * random().nextInt();
  192. break;
  193. } else if (0 == TestUtil.nextInt(random(), 0, 7)) {
  194. // emulate missing value for doc
  195. results[i] = null;
  196. } else {
  197. final String fieldName = sf.getName();
  198. assertNotNull(fieldName);
  199. // Note: In some cases we build a human readable version of the sort value and then
  200. // unmarshall it into the raw, real, sort values that are expected by the FieldTypes.
  201. // In other cases we just build the raw value to begin with because it's easier
  202. Object val = null;
  203. if (fieldName.equals("id")) {
  204. val = sf.getType().unmarshalSortValue(TestUtil.randomSimpleString(random()));
  205. } else if (fieldName.startsWith("str")) {
  206. val = sf.getType().unmarshalSortValue(TestUtil.randomRealisticUnicodeString(random()));
  207. } else if (fieldName.startsWith("bin")) {
  208. byte[] randBytes = new byte[TestUtil.nextInt(random(), 1, 50)];
  209. random().nextBytes(randBytes);
  210. val = new BytesRef(randBytes);
  211. } else if (fieldName.contains("int")) {
  212. val = random().nextInt();
  213. } else if (fieldName.contains("long")) {
  214. val = random().nextLong();
  215. } else if (fieldName.contains("float")) {
  216. val = random().nextFloat() * random().nextInt();
  217. } else if (fieldName.contains("double")) {
  218. val = random().nextDouble() * random().nextInt();
  219. } else if (fieldName.contains("date")) {
  220. val = random().nextLong();
  221. } else if (fieldName.startsWith("currency")) {
  222. val = random().nextDouble();
  223. } else if (fieldName.startsWith("uuid")) {
  224. val = sf.getType().unmarshalSortValue(UUID.randomUUID().toString());
  225. } else if (fieldName.startsWith("bool")) {
  226. val = sf.getType().unmarshalSortValue(random().nextBoolean() ? "t" : "f");
  227. } else if (fieldName.startsWith("enum")) {
  228. val = random().nextInt(CursorPagingTest.SEVERITY_ENUM_VALUES.length);
  229. } else if (fieldName.contains("collation")) {
  230. val = getRandomCollation(sf);
  231. } else {
  232. fail("fell through the rabbit hole, new field in schema? = " + fieldName);
  233. }
  234. results[i] = val;
  235. }
  236. }
  237. return results;
  238. }
  239. private static Object getRandomCollation(SchemaField sf) throws IOException {
  240. Object val;
  241. Analyzer analyzer = sf.getType().getIndexAnalyzer();
  242. String term = TestUtil.randomRealisticUnicodeString(random());
  243. try (TokenStream ts = analyzer.tokenStream("fake", term)) {
  244. TermToBytesRefAttribute termAtt = ts.addAttribute(TermToBytesRefAttribute.class);
  245. ts.reset();
  246. assertTrue(ts.incrementToken());
  247. val = BytesRef.deepCopyOf(termAtt.getBytesRef());
  248. assertFalse(ts.incrementToken());
  249. ts.end();
  250. }
  251. return val;
  252. }
  253. /** a list of the fields in the schema - excluding _version_ */
  254. private Collection<String> getAllFieldNames() {
  255. ArrayList<String> names = new ArrayList<>(37);
  256. for (String f : h.getCore().getLatestSchema().getFields().keySet()) {
  257. if (!f.equals("_version_")) {
  258. names.add(f);
  259. }
  260. }
  261. return Collections.<String>unmodifiableCollection(names);
  262. }
  263. }