*/
package org.sonar.server.component.index;
+import com.google.common.annotations.VisibleForTesting;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
-import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.common.unit.Fuzziness;
+import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.sonar.core.util.stream.Collectors;
import org.sonar.server.es.BaseIndex;
-import org.sonar.server.es.DefaultIndexSettings;
import org.sonar.server.es.EsClient;
import org.sonar.server.user.UserSession;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
-import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_AUTHORIZATION_GROUPS;
import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_AUTHORIZATION_USERS;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_KEY;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_NAME;
import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_QUALIFIER;
import static org.sonar.server.component.index.ComponentIndexDefinition.INDEX_COMPONENTS;
import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_AUTHORIZATION;
import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
-import static org.sonar.server.es.DefaultIndexSettingsElement.FUZZY_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
public class ComponentIndex extends BaseIndex {
}
public List<String> search(ComponentIndexQuery query) {
+ return search(query, ComponentIndexSearchFeature.values());
+ }
+
+ @VisibleForTesting
+ List<String> search(ComponentIndexQuery query, ComponentIndexSearchFeature... features) {
SearchRequestBuilder request = getClient()
.prepareSearch(INDEX_COMPONENTS)
.setTypes(TYPE_COMPONENT)
query.getLimit().ifPresent(request::setSize);
- request.setQuery(createQuery(query));
+ request.setQuery(createQuery(query, features));
- return Arrays.stream(request.get().getHits().hits())
+ SearchResponse searchResponse = request.get();
+
+ return Arrays.stream(searchResponse.getHits().hits())
.map(SearchHit::getId)
.collect(Collectors.toList());
}
- private QueryBuilder createQuery(ComponentIndexQuery query) {
+ private QueryBuilder createQuery(ComponentIndexQuery query, ComponentIndexSearchFeature... features) {
BoolQueryBuilder esQuery = boolQuery();
esQuery.filter(createAuthorizationFilter());
query.getQualifier().ifPresent(q -> esQuery.filter(termQuery(FIELD_QUALIFIER, q)));
- String queryText = query.getQuery();
-
- // 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);
-
- return esQuery.must(boolQuery()
-
- // partial name matches
- .should(matchQuery(SEARCH_GRAMS_ANALYZER.subField(FIELD_NAME), truncatedQuery))
-
- // fuzzy name matches
- .should(matchQuery(FUZZY_ANALYZER.subField(FIELD_NAME), queryText).fuzziness(Fuzziness.AUTO))
+ BoolQueryBuilder featureQuery = boolQuery();
- // prefix matches
- .should(prefixQuery(FUZZY_ANALYZER.subField(FIELD_NAME), queryText))
+ Arrays.stream(features)
+ .map(f -> f.getQuery(query.getQuery()))
+ .forEach(featureQuery::should);
- // exact match on the key
- .should(matchQuery(SORTABLE_ANALYZER.subField(FIELD_KEY), queryText).boost(5f)));
+ return esQuery.must(featureQuery);
}
private QueryBuilder createAuthorizationFilter() {
--- /dev/null
+/*
+ * 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.apache.commons.lang.StringUtils;
+import org.elasticsearch.common.unit.Fuzziness;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.sonar.server.es.DefaultIndexSettings;
+
+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;
+import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_NAME;
+import static org.sonar.server.es.DefaultIndexSettingsElement.FUZZY_ANALYZER;
+import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+
+public enum ComponentIndexSearchFeature {
+
+ PARTIAL {
+ @Override
+ public QueryBuilder getQuery(String queryText) {
+
+ // 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);
+
+ return matchQuery(SEARCH_GRAMS_ANALYZER.subField(FIELD_NAME), truncatedQuery);
+
+ }
+ },
+ FUZZY {
+ @Override
+ public QueryBuilder getQuery(String queryText) {
+ return matchQuery(FUZZY_ANALYZER.subField(FIELD_NAME), queryText).fuzziness(Fuzziness.AUTO);
+ }
+ },
+ FUZZY_PREFIX {
+ @Override
+ public QueryBuilder getQuery(String queryText) {
+ return prefixQuery(FUZZY_ANALYZER.subField(FIELD_NAME), queryText);
+ }
+ },
+ KEY {
+ @Override
+ public QueryBuilder getQuery(String queryText) {
+ return matchQuery(SORTABLE_ANALYZER.subField(FIELD_KEY), queryText).boost(5f);
+ }
+ };
+
+ public abstract QueryBuilder getQuery(String queryText);
+}
public static final String CUSTOM = "custom";
public static final String KEYWORD = "keyword";
public static final String CLASSIC = "classic";
+ public static final String TRUNCATE = "truncate";
public static final String SUB_FIELD_DELIMITER = ".";
public static final String PORTER_STEM = "porter_stem";
public static final String MIN_GRAM = "min_gram";
public static final String MAX_GRAM = "max_gram";
+ public static final String LENGTH = "length";
private DefaultIndexSettings() {
// only static stuff
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;
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;
set("stem_english_possessive", true);
}
},
+ EDGE_NGRAM_FILTER(FILTER) {
+
+ @Override
+ protected void setup() {
+ set(TYPE, "edge_ngram");
+ set(MIN_GRAM, 1);
+ set(MAX_GRAM, 15);
+ }
+ },
// Tokenizers
--- /dev/null
+/*
+ * 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.rules.ExternalResource;
+
+public class ComponentIndexSearchFeatureRule extends ExternalResource {
+
+ private ComponentIndexSearchFeature[] features;
+
+ @Override
+ protected void before() throws Throwable {
+ features = ComponentIndexSearchFeature.values();
+ }
+
+ public ComponentIndexSearchFeature[] get() {
+ return features;
+ }
+
+}
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
- private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es);
+
+ @Rule
+ public ComponentIndexSearchFeatureRule features = new ComponentIndexSearchFeatureRule();
+
+ protected PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es);
private ComponentIndex index;
private ComponentIndexer indexer;
return assertSearch(new ComponentIndexQuery(query));
}
- private AbstractListAssert<?, ? extends List<? extends String>, String> assertSearch(ComponentIndexQuery query) {
- return assertThat(index.search(query));
+ protected AbstractListAssert<?, ? extends List<? extends String>, String> assertSearch(ComponentIndexQuery query) {
+ return assertThat(index.search(query, features.get()));
}
private void assertSearchResults(String query, ComponentDto... expectedComponents) {