PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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

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