aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2017-08-31 16:44:59 +0200
committerStas Vilchik <stas.vilchik@sonarsource.com>2017-09-11 11:28:29 +0200
commit513356269ca36067d24db28cfda79630130e8bf1 (patch)
tree195f45813ed029b3c83c3fe2ce5fa6ad1e3027d7 /server
parentc8f7d461d30b6a0bcba6c25c553208f7c39b441d (diff)
downloadsonarqube-513356269ca36067d24db28cfda79630130e8bf1.tar.gz
sonarqube-513356269ca36067d24db28cfda79630130e8bf1.zip
SONAR-8640 WS api/projects/search query on key does partial match and is case insensitive
Diffstat (limited to 'server')
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java41
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml11
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java63
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java20
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java9
6 files changed, 93 insertions, 59 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java
index 58858246033..2568f8ba22b 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java
@@ -30,30 +30,16 @@ import static org.sonar.db.DaoDatabaseUtils.buildLikeValue;
public class ComponentQuery {
private final String nameOrKeyQuery;
+ private final boolean partialMatchOnKey;
private final String[] qualifiers;
private final String language;
private final Boolean isPrivate;
private final Set<Long> componentIds;
private final Long analyzedBefore;
- /**
- * Used by Dev Cockpit 1.9.
- * Could be removed when Developer Cockpit doesn't use it anymore.
- *
- * @deprecated since 5.4, use {@link Builder} instead
- */
- @Deprecated
- public ComponentQuery(@Nullable String nameOrKeyQuery, String... qualifiers) {
- this.nameOrKeyQuery = nameOrKeyQuery;
- this.qualifiers = Builder.validateQualifiers(qualifiers);
- this.language = null;
- this.componentIds = null;
- this.isPrivate = null;
- this.analyzedBefore = null;
- }
-
private ComponentQuery(Builder builder) {
this.nameOrKeyQuery = builder.nameOrKeyQuery;
+ this.partialMatchOnKey = builder.partialMatchOnKey == null ? false : builder.partialMatchOnKey;
this.qualifiers = builder.qualifiers;
this.language = builder.language;
this.componentIds = builder.componentIds;
@@ -78,6 +64,13 @@ public class ComponentQuery {
return buildLikeValue(nameOrKeyQuery, WildcardPosition.BEFORE_AND_AFTER).toUpperCase(Locale.ENGLISH);
}
+ /**
+ * Used by MyBatis mapper
+ */
+ public boolean isPartialMatchOnKey() {
+ return partialMatchOnKey;
+ }
+
@CheckForNull
public String getLanguage() {
return language;
@@ -104,6 +97,7 @@ public class ComponentQuery {
public static class Builder {
private String nameOrKeyQuery;
+ private Boolean partialMatchOnKey;
private String[] qualifiers;
private String language;
private Boolean isPrivate;
@@ -115,6 +109,14 @@ public class ComponentQuery {
return this;
}
+ /**
+ * Beware, can be resource intensive! Should be used with precautions.
+ */
+ public Builder setPartialMatchOnKey(@Nullable Boolean partialMatchOnKey) {
+ this.partialMatchOnKey = partialMatchOnKey;
+ return this;
+ }
+
public Builder setQualifiers(String... qualifiers) {
this.qualifiers = qualifiers;
return this;
@@ -140,13 +142,10 @@ public class ComponentQuery {
return this;
}
- protected static String[] validateQualifiers(@Nullable String... qualifiers) {
+ public ComponentQuery build() {
checkArgument(qualifiers != null && qualifiers.length > 0, "At least one qualifier must be provided");
- return qualifiers;
- }
+ checkArgument(nameOrKeyQuery != null || partialMatchOnKey == null, "A query must be provided if a partial match on key is specified.");
- public ComponentQuery build() {
- validateQualifiers(this.qualifiers);
return new ComponentQuery(this);
}
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
index 905d1ef8717..d9e90aa8110 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
@@ -276,9 +276,16 @@
</if>
<if test="query.nameOrKeyQuery!=null">
and (
- p.kee = #{query.nameOrKeyQuery,jdbcType=VARCHAR}
- or
upper(p.name) like #{query.nameOrKeyUpperLikeQuery,jdbcType=VARCHAR} escape '/'
+ or
+ <choose>
+ <when test="query.isPartialMatchOnKey()">
+ upper(p.kee) like #{query.nameOrKeyUpperLikeQuery,jdbcType=VARCHAR} escape '/'
+ </when>
+ <otherwise>
+ p.kee = #{query.nameOrKeyQuery,jdbcType=VARCHAR}
+ </otherwise>
+ </choose>
)
</if>
<if test="query.private!=null">
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
index aae487bf389..484612e7e28 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
@@ -608,8 +608,8 @@ public class ComponentDaoTest {
ComponentDto provisionedProject = db.components().insertPrivateProject();
ComponentDto provisionedView = db.components().insertView(organization, (dto) -> {
});
- String projectUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization)).getComponentUuid();
- String disabledProjectUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization).setEnabled(false)).getComponentUuid();
+ String projectUuid = db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization)).getComponentUuid();
+ String disabledProjectUuid = db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization).setEnabled(false)).getComponentUuid();
String viewUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newView(organization)).getComponentUuid();
assertThat(underTest.selectProjects(dbSession))
@@ -621,10 +621,10 @@ public class ComponentDaoTest {
public void select_provisioned() {
OrganizationDto organization = db.organizations().insert();
ComponentDto provisionedProject = db.components()
- .insertComponent(ComponentTesting.newPrivateProjectDto(organization).setDbKey("provisioned.project").setName("Provisioned Project"));
+ .insertComponent(newPrivateProjectDto(organization).setDbKey("provisioned.project").setName("Provisioned Project"));
ComponentDto provisionedView = db.components().insertView(organization);
- String projectUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization)).getComponentUuid();
- String disabledProjectUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization).setEnabled(false)).getComponentUuid();
+ String projectUuid = db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization)).getComponentUuid();
+ String disabledProjectUuid = db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization).setEnabled(false)).getComponentUuid();
String viewUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newView(organization)).getComponentUuid();
Set<String> projectQualifiers = newHashSet(Qualifiers.PROJECT);
@@ -668,7 +668,7 @@ public class ComponentDaoTest {
public void count_provisioned() {
OrganizationDto organization = db.organizations().insert();
db.components().insertPrivateProject(organization);
- db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization));
+ db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization));
db.components().insertProjectAndSnapshot(ComponentTesting.newView(organization));
assertThat(underTest.countProvisioned(dbSession, organization.getUuid(), null, newHashSet(Qualifiers.PROJECT))).isEqualTo(1);
@@ -898,10 +898,10 @@ public class ComponentDaoTest {
@Test
public void selectByQuery_with_paging_query_and_qualifiers() {
OrganizationDto organizationDto = db.organizations().insert();
- db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organizationDto).setName("aaaa-name"));
+ db.components().insertProjectAndSnapshot(newPrivateProjectDto(organizationDto).setName("aaaa-name"));
db.components().insertProjectAndSnapshot(newView(organizationDto));
for (int i = 9; i >= 1; i--) {
- db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organizationDto).setName("project-" + i));
+ db.components().insertProjectAndSnapshot(newPrivateProjectDto(organizationDto).setName("project-" + i));
}
ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("oJect").setQualifiers(Qualifiers.PROJECT).build();
@@ -953,7 +953,7 @@ public class ComponentDaoTest {
@Test
public void selectByQuery_name_with_special_characters() {
- db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setName("project-\\_%/-name"));
+ db.components().insertProjectAndSnapshot(newPrivateProjectDto(db.getDefaultOrganization()).setName("project-\\_%/-name"));
ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("-\\_%/-").setQualifiers(Qualifiers.PROJECT).build();
List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
@@ -964,7 +964,8 @@ public class ComponentDaoTest {
@Test
public void selectByQuery_key_with_special_characters() {
- db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(db.organizations().insert()).setDbKey("project-_%-key"));
+ db.components().insertProjectAndSnapshot(newPrivateProjectDto(db.organizations().insert()).setDbKey("project-_%-key"));
+ db.components().insertProjectAndSnapshot(newPrivateProjectDto(db.organizations().insert()).setDbKey("project-key-that-does-not-match"));
ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("project-_%-key").setQualifiers(Qualifiers.PROJECT).build();
List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
@@ -974,9 +975,23 @@ public class ComponentDaoTest {
}
@Test
+ public void selectByQuery_on_key_partial_match_case_insensitive() {
+ db.components().insertProjectAndSnapshot(newPrivateProjectDto(db.organizations().insert()).setDbKey("project-key"));
+
+ ComponentQuery query = ComponentQuery.builder()
+ .setNameOrKeyQuery("JECT-K")
+ .setPartialMatchOnKey(true)
+ .setQualifiers(Qualifiers.PROJECT).build();
+ List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getDbKey()).isEqualTo("project-key");
+ }
+
+ @Test
public void selectByQuery_filter_on_language() {
- db.components().insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("java-project-key").setLanguage("java"));
- db.components().insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("cpp-project-key").setLanguage("cpp"));
+ db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("java-project-key").setLanguage("java"));
+ db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("cpp-project-key").setLanguage("cpp"));
ComponentQuery query = ComponentQuery.builder().setLanguage("java").setQualifiers(Qualifiers.PROJECT).build();
List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
@@ -1006,7 +1021,7 @@ public class ComponentDaoTest {
@Test
public void selectByQuery_filter_on_visibility() {
- db.components().insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("private-key"));
+ db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("private-key"));
db.components().insertComponent(ComponentTesting.newPublicProjectDto(db.getDefaultOrganization()).setDbKey("public-key"));
ComponentQuery privateProjectsQuery = ComponentQuery.builder().setPrivate(true).setQualifiers(Qualifiers.PROJECT).build();
@@ -1031,9 +1046,9 @@ public class ComponentDaoTest {
@Test
public void selectByQuery_on_component_ids() {
OrganizationDto organizationDto = db.organizations().insert();
- ComponentDto sonarqube = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto));
- ComponentDto jdk8 = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto));
- ComponentDto cLang = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto));
+ ComponentDto sonarqube = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+ ComponentDto jdk8 = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+ ComponentDto cLang = db.components().insertComponent(newPrivateProjectDto(organizationDto));
ComponentQuery query = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT)
.setComponentIds(newHashSet(sonarqube.getId(), jdk8.getId())).build();
@@ -1181,12 +1196,12 @@ public class ComponentDaoTest {
@Test
public void select_projects_by_name_query() {
OrganizationDto organizationDto = db.organizations().insert();
- ComponentDto project1 = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setName("project1"));
+ ComponentDto project1 = db.components().insertComponent(newPrivateProjectDto(organizationDto).setName("project1"));
ComponentDto module1 = db.components().insertComponent(newModuleDto(project1).setName("module1"));
ComponentDto subModule1 = db.components().insertComponent(newModuleDto(module1).setName("subModule1"));
db.components().insertComponent(newFileDto(subModule1).setName("file"));
- ComponentDto project2 = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setName("project2"));
- ComponentDto project3 = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setName("project3"));
+ ComponentDto project2 = db.components().insertComponent(newPrivateProjectDto(organizationDto).setName("project2"));
+ ComponentDto project3 = db.components().insertComponent(newPrivateProjectDto(organizationDto).setName("project3"));
assertThat(underTest.selectProjectsByNameQuery(dbSession, null, false)).extracting(ComponentDto::uuid)
.containsOnly(project1.uuid(), project2.uuid(), project3.uuid());
@@ -1207,11 +1222,11 @@ public class ComponentDaoTest {
OrganizationDto organizationDto = db.organizations().insert();
String[] uuids = {
- db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setProjectUuid(uuid1).setPrivate(true)).uuid(),
- db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setProjectUuid(uuid1).setPrivate(false)).uuid(),
- db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setProjectUuid(uuid2).setPrivate(true)).uuid(),
- db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setProjectUuid(uuid2).setPrivate(false)).uuid(),
- db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setRootUuid(uuid1).setProjectUuid("foo").setPrivate(false)).uuid(),
+ db.components().insertComponent(newPrivateProjectDto(organizationDto).setProjectUuid(uuid1).setPrivate(true)).uuid(),
+ db.components().insertComponent(newPrivateProjectDto(organizationDto).setProjectUuid(uuid1).setPrivate(false)).uuid(),
+ db.components().insertComponent(newPrivateProjectDto(organizationDto).setProjectUuid(uuid2).setPrivate(true)).uuid(),
+ db.components().insertComponent(newPrivateProjectDto(organizationDto).setProjectUuid(uuid2).setPrivate(false)).uuid(),
+ db.components().insertComponent(newPrivateProjectDto(organizationDto).setRootUuid(uuid1).setProjectUuid("foo").setPrivate(false)).uuid(),
};
underTest.setPrivateForRootComponentUuid(db.getSession(), uuid1, true);
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java
index 1b6971c6fc7..d69f98c277f 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java
@@ -58,6 +58,16 @@ public class ComponentQueryTest {
}
@Test
+ public void test_getNameOrKeyUpperLikeQuery() throws Exception {
+ ComponentQuery underTest = ComponentQuery.builder()
+ .setNameOrKeyQuery("NAME/key")
+ .setQualifiers(PROJECT)
+ .build();
+
+ assertThat(underTest.getNameOrKeyUpperLikeQuery()).isEqualTo("%NAME//KEY%");
+ }
+
+ @Test
public void fail_if_no_qualifier_provided() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("At least one qualifier must be provided");
@@ -66,12 +76,10 @@ public class ComponentQueryTest {
}
@Test
- public void test_getNameOrKeyUpperLikeQuery() throws Exception {
- ComponentQuery underTest = ComponentQuery.builder()
- .setNameOrKeyQuery("NAME/key")
- .setQualifiers(PROJECT)
- .build();
+ public void fail_if_partial_match_on_key_without_a_query() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("A query must be provided if a partial match on key is specified.");
- assertThat(underTest.getNameOrKeyUpperLikeQuery()).isEqualTo("%NAME//KEY%");
+ ComponentQuery.builder().setQualifiers(PROJECT).setPartialMatchOnKey(false).build();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java
index 015bf3740b7..562a136c070 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java
@@ -92,7 +92,7 @@ public class SearchAction implements ProjectsWsAction {
action.createParam(Param.TEXT_QUERY)
.setDescription("Limit search to: <ul>" +
"<li>component names that contain the supplied string</li>" +
- "<li>component keys that are exactly the same as the supplied string</li>" +
+ "<li>component keys that contain the supplied string</li>" +
"</ul>")
.setExampleValue("sonar");
@@ -153,9 +153,13 @@ public class SearchAction implements ProjectsWsAction {
private static ComponentQuery buildQuery(SearchWsRequest request) {
List<String> qualifiers = request.getQualifiers();
ComponentQuery.Builder query = ComponentQuery.builder()
- .setNameOrKeyQuery(request.getQuery())
.setQualifiers(qualifiers.toArray(new String[qualifiers.size()]));
+ setNullable(request.getQuery(), q -> {
+ query.setNameOrKeyQuery(q);
+ query.setPartialMatchOnKey(true);
+ return query;
+ });
setNullable(request.getVisibility(), v -> query.setPrivate(Visibility.isPrivate(v)));
setNullable(request.getAnalyzedBefore(), d -> query.setAnalyzedBefore(parseDateOrDateTime(d).getTime()));
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java
index 439d4d10f2f..c4fbc6a0bf6 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java
@@ -92,15 +92,16 @@ public class SearchActionTest {
new SearchAction(db.getDbClient(), userSession, defaultOrganizationProvider, new ProjectsWsSupport(db.getDbClient(), mock(BillingValidationsProxy.class))));
@Test
- public void search_by_key_query() throws IOException {
+ public void search_by_key_query_with_partial_match_case_insensitive() throws IOException {
userSession.addPermission(ADMINISTER, db.getDefaultOrganization());
db.components().insertComponents(
ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("project-_%-key"),
+ ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("PROJECT-_%-KEY"),
ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("project-key-without-escaped-characters"));
- SearchWsResponse response = call(SearchWsRequest.builder().setQuery("project-_%-key").build());
+ SearchWsResponse response = call(SearchWsRequest.builder().setQuery("JeCt-_%-k").build());
- assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("project-_%-key");
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("project-_%-key", "PROJECT-_%-KEY");
}
@Test
@@ -288,7 +289,7 @@ public class SearchActionTest {
assertThat(qParam.description()).isEqualTo("Limit search to: " +
"<ul>" +
"<li>component names that contain the supplied string</li>" +
- "<li>component keys that are exactly the same as the supplied string</li>" +
+ "<li>component keys that contain the supplied string</li>" +
"</ul>");
WebService.Param qualifierParam = action.param("qualifiers");