diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2017-08-31 16:44:59 +0200 |
---|---|---|
committer | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-09-11 11:28:29 +0200 |
commit | 513356269ca36067d24db28cfda79630130e8bf1 (patch) | |
tree | 195f45813ed029b3c83c3fe2ce5fa6ad1e3027d7 /server | |
parent | c8f7d461d30b6a0bcba6c25c553208f7c39b441d (diff) | |
download | sonarqube-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')
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"); |