]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8678 in global search, all entered terms are mandatory
authorDaniel Schwarz <daniel.schwarz@sonarsource.com>
Mon, 30 Jan 2017 09:20:23 +0000 (10:20 +0100)
committerDaniel Schwarz <bartfastiel@users.noreply.github.com>
Mon, 30 Jan 2017 17:51:39 +0000 (18:51 +0100)
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexSearchFeature.java
server/sonar-server/src/main/java/org/sonar/server/es/DefaultIndexSettingsElement.java
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFuzzyTest.java
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePartialTest.java
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java [new file with mode: 0644]

index fa26c36615056f0fc7b28f0e11218493873f4512..1f1da83826027853ff0d016db9f418796bbd707d 100644 (file)
  */
 package org.sonar.server.component.index;
 
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.stream.Stream;
 import org.apache.commons.lang.StringUtils;
 import org.elasticsearch.common.unit.Fuzziness;
+import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.sonar.server.es.DefaultIndexSettings;
 
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
 import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
 import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_KEY;
@@ -37,19 +42,30 @@ public enum ComponentIndexSearchFeature {
   PARTIAL {
     @Override
     public QueryBuilder getQuery(String queryText) {
+      BoolQueryBuilder query = boolQuery();
 
-      // We will truncate the search to the maximum length of nGrams in the index.
-      // Otherwise the search would for sure not find any results.
-      String truncatedQuery = StringUtils.left(queryText, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH);
+      split(queryText)
+        .map(queryTerm -> {
 
-      return matchQuery(SEARCH_GRAMS_ANALYZER.subField(FIELD_NAME), truncatedQuery);
+          // We will truncate the search to the maximum length of nGrams in the index.
+          // Otherwise the search would for sure not find any results.
+          String truncatedQuery = StringUtils.left(queryTerm, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH);
 
+          return matchQuery(SEARCH_GRAMS_ANALYZER.subField(FIELD_NAME), truncatedQuery);
+        })
+        .forEach(query::must);
+      return query;
     }
   },
   FUZZY {
     @Override
     public QueryBuilder getQuery(String queryText) {
-      return matchQuery(FUZZY_ANALYZER.subField(FIELD_NAME), queryText).fuzziness(Fuzziness.AUTO);
+      BoolQueryBuilder query = boolQuery();
+
+      split(queryText)
+        .map(((Function<String, QueryBuilder>) queryTerm -> matchQuery(FUZZY_ANALYZER.subField(FIELD_NAME), queryTerm).fuzziness(Fuzziness.AUTO))::apply)
+        .forEach(query::must);
+      return query;
     }
   },
   FUZZY_PREFIX {
@@ -65,5 +81,14 @@ public enum ComponentIndexSearchFeature {
     }
   };
 
+  /** Pattern, that splits the user search input **/
+  public static final String SEARCH_TERM_TOKENIZER_PATTERN = "[\\:\\.\\s]+";
+
   public abstract QueryBuilder getQuery(String queryText);
+
+  protected Stream<String> split(String queryText) {
+    return Arrays.stream(
+      queryText.split(SEARCH_TERM_TOKENIZER_PATTERN))
+      .filter(StringUtils::isNotEmpty);
+  }
 }
index 96032a88731e4a64b10cd43c3664f575801acbd0..cac2461a5fb36cb546cb17c1a088f86d21da2693 100644 (file)
@@ -25,17 +25,16 @@ import java.util.Locale;
 import java.util.SortedMap;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings.Builder;
+import org.sonar.server.component.index.ComponentIndexSearchFeature;
 
 import static org.sonar.server.es.DefaultIndexSettings.ANALYSIS;
 import static org.sonar.server.es.DefaultIndexSettings.ANALYZED;
 import static org.sonar.server.es.DefaultIndexSettings.ANALYZER;
 import static org.sonar.server.es.DefaultIndexSettings.ASCIIFOLDING;
-import static org.sonar.server.es.DefaultIndexSettings.CLASSIC;
 import static org.sonar.server.es.DefaultIndexSettings.DELIMITER;
 import static org.sonar.server.es.DefaultIndexSettings.FILTER;
 import static org.sonar.server.es.DefaultIndexSettings.INDEX;
 import static org.sonar.server.es.DefaultIndexSettings.KEYWORD;
-import static org.sonar.server.es.DefaultIndexSettings.LENGTH;
 import static org.sonar.server.es.DefaultIndexSettings.LOWERCASE;
 import static org.sonar.server.es.DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH;
 import static org.sonar.server.es.DefaultIndexSettings.MAX_GRAM;
@@ -49,7 +48,6 @@ import static org.sonar.server.es.DefaultIndexSettings.STRING;
 import static org.sonar.server.es.DefaultIndexSettings.SUB_FIELD_DELIMITER;
 import static org.sonar.server.es.DefaultIndexSettings.TOKENIZER;
 import static org.sonar.server.es.DefaultIndexSettings.TRIM;
-import static org.sonar.server.es.DefaultIndexSettings.TRUNCATE;
 import static org.sonar.server.es.DefaultIndexSettings.TYPE;
 import static org.sonar.server.es.DefaultIndexSettings.WHITESPACE;
 
@@ -102,6 +100,14 @@ public enum DefaultIndexSettingsElement {
       set(PATTERN, "\\.");
     }
   },
+  FUZZY_TOKENIZER(TOKENIZER) {
+
+    @Override
+    protected void setup() {
+      set(TYPE, PATTERN);
+      set(PATTERN, ComponentIndexSearchFeature.SEARCH_TERM_TOKENIZER_PATTERN);
+    }
+  },
 
   // Analyzers
 
@@ -199,7 +205,7 @@ public enum DefaultIndexSettingsElement {
 
     @Override
     protected void setup() {
-      set(TOKENIZER, CLASSIC);
+      set(TOKENIZER, FUZZY_TOKENIZER);
       setArray(FILTER, LOWERCASE);
     }
 
index abb0cbfb77d9746777ffc99233132141e0e56ee8..96ac23331dda1288b39054a923b2df346bf009ff 100644 (file)
@@ -58,4 +58,12 @@ public class ComponentIndexFeatureFuzzyTest extends ComponentIndexTest {
       "sonaqube.java",
       "sonqube.java");
   }
+
+  @Test
+  public void fuzziness_for_filename_without_suffix() {
+    assertFileMatches("StringUtils",
+      "StringUtils.java",
+      "StringUils.java",
+      "StringUls.java");
+  }
 }
index c49001cde028c388e78bdd9d0cb3ac52ad3db180..6c57f12da3569dba5acf5d1804dd781824f6b905 100644 (file)
@@ -115,11 +115,6 @@ public class ComponentIndexFeaturePartialTest extends ComponentIndexTest {
     assertFileMatches("factory abstract", "AbstractPluginFactory.java");
   }
 
-  @Test
-  public void should_find_item_with_at_least_one_matching_word() {
-    assertFileMatches("abstract object", "AbstractPluginFactory.java");
-  }
-
   @Test
   public void should_require_at_least_one_matching_word() {
     assertNoFileMatches("monitor object", "AbstractPluginFactory.java");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java
new file mode 100644 (file)
index 0000000..f971c3d
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import org.junit.Test;
+
+public class ComponentIndexMultipleWordsTest extends ComponentIndexTest {
+
+  @Test
+  public void should_find_perfect_match() {
+    assertResultOrder("struts java",
+      "Struts.java");
+  }
+
+  @Test
+  public void should_find_fuzzy_match() {
+    features.set(ComponentIndexSearchFeature.FUZZY);
+    assertResultOrder("StrutX ProjecX",
+      "Struts Project");
+  }
+
+  @Test
+  public void should_find_partial_match() {
+    features.set(ComponentIndexSearchFeature.PARTIAL);
+    assertResultOrder("struts java",
+      "Xstrutsx.Xjavax");
+  }
+
+  @Test
+  public void should_find_partial_match_prefix_word1() {
+    assertResultOrder("struts java",
+      "MyStruts.java");
+  }
+
+  @Test
+  public void should_find_partial_match_suffix_word1() {
+    assertResultOrder("struts java",
+      "StrutsObject.java");
+  }
+
+  @Test
+  public void should_find_partial_match_prefix_word2() {
+    assertResultOrder("struts java",
+      "MyStruts.xjava");
+  }
+
+  @Test
+  public void should_find_partial_match_suffix_word2() {
+    assertResultOrder("struts java",
+      "MyStruts.javax");
+  }
+
+  @Test
+  public void should_find_partial_match_prefix_and_suffix_everywhere() {
+    assertResultOrder("struts java",
+      "MyStrutsObject.xjavax");
+  }
+
+  @Test
+  public void should_find_subset_of_document_terms() {
+    assertResultOrder("struts java",
+      "Some.Struts.Class.java.old");
+  }
+
+  @Test
+  public void should_require_all_words_to_match() {
+    assertNoFileMatches("struts java",
+      "Struts");
+  }
+
+  @Test
+  public void should_ignore_empty_words() {
+    assertFileMatches("     :     .  struts   \n .    :\n\n",
+      "Struts");
+  }
+
+  @Test
+  public void should_require_all_words_to_match_for_fuzziness() {
+    features.set(ComponentIndexSearchFeature.FUZZY);
+    assertNoFileMatches("struts java",
+      "Struts");
+  }
+
+  @Test
+  public void should_require_all_words_to_match_for_partial() {
+    features.set(ComponentIndexSearchFeature.PARTIAL);
+    assertNoFileMatches("struts java",
+      "Struts");
+  }
+
+}