diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2015-09-30 16:17:20 +0200 |
---|---|---|
committer | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2015-10-02 09:23:01 +0200 |
commit | 4e337f119df5ee05515f85a6f26075d3667d7707 (patch) | |
tree | aa60d1e5bb9fc150080544a61d697cabb803f5dd /sonar-db | |
parent | 770be1eac18e8a7f99f0596938f38054e2abfb40 (diff) | |
download | sonarqube-4e337f119df5ee05515f85a6f26075d3667d7707.tar.gz sonarqube-4e337f119df5ee05515f85a6f26075d3667d7707.zip |
SONAR-6857 ComponentDao with a generic way to search for components
Diffstat (limited to 'sonar-db')
11 files changed, 406 insertions, 15 deletions
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java index 96199463731..4f4a9cc49ca 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java +++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java @@ -66,6 +66,14 @@ public class ComponentDao implements Dao { return componentDto.get(); } + public List<ComponentDto> selectByQuery(DbSession session, ComponentQuery query, int offset, int limit) { + return mapper(session).selectByQuery(query, new RowBounds(offset, limit)); + } + + public int countByQuery(DbSession session, ComponentQuery query) { + return mapper(session).countByQuery(query); + } + public boolean existsById(Long id, DbSession session) { return mapper(session).countById(id) > 0; } @@ -186,7 +194,7 @@ public class ComponentDao implements Dao { * Does not return component copies */ public List<ComponentDto> selectComponents(DbSession session, Collection<String> qualifiers, int offset, int limit, @Nullable String query) { - Map<String, Object> parameters = newHashMapWithExpectedSize(2); + Map<String, Object> parameters = newHashMapWithExpectedSize(3); addProjectQualifier(parameters); addPartialQueryParameterIfNotNull(parameters, query); addQualifiers(parameters, qualifiers); diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java index dc2ab91b60b..774c14a378d 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java @@ -57,6 +57,10 @@ public interface ComponentMapper { List<String> selectExistingUuids(@Param("uuids") Collection<String> uuids); + List<ComponentDto> selectByQuery(ComponentQuery query, RowBounds rowBounds); + + int countByQuery(ComponentQuery query); + /** * Return all project (PRJ/TRK) uuids */ @@ -127,5 +131,4 @@ public interface ComponentMapper { void update(ComponentDto componentDto); void delete(long componentId); - } diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentQuery.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentQuery.java new file mode 100644 index 00000000000..6c4e361ddfc --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentQuery.java @@ -0,0 +1,61 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.db.component; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.db.Database; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.sonar.db.dialect.WildcardPosition.AFTER; + +public class ComponentQuery { + private final Database database; + private final String nameOrKeyQuery; + private final String[] qualifiers; + + public ComponentQuery(Database database, @Nullable String nameOrKeyQuery, String... qualifiers) { + checkArgument(qualifiers.length > 0, "At least one qualifier must be provided"); + + this.database = database; + this.nameOrKeyQuery = nameOrKeyQuery; + this.qualifiers = qualifiers; + } + + public String[] getQualifiers() { + return qualifiers; + } + + @CheckForNull + public String getNameOrKeyQuery() { + return nameOrKeyQuery; + } + + @CheckForNull + public String getNameOrKeyQueryToSqlForResourceIndex() { + return database.getDialect().buildLikeValue(nameOrKeyQuery, AFTER).toLowerCase(); + } + + @CheckForNull + public String getNameOrKeyQueryToSqlForProjectKey() { + return database.getDialect().buildLikeValue(nameOrKeyQuery, AFTER); + } +} diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/AbstractDialect.java b/sonar-db/src/main/java/org/sonar/db/dialect/AbstractDialect.java index 236c90acd1b..5aeb289f495 100644 --- a/sonar-db/src/main/java/org/sonar/db/dialect/AbstractDialect.java +++ b/sonar-db/src/main/java/org/sonar/db/dialect/AbstractDialect.java @@ -87,4 +87,44 @@ abstract class AbstractDialect implements Dialect { public int getScrollSingleRowFetchSize() { return 1; } + + @Override + public String buildLikeValue(String value, WildcardPosition wildcardPosition) { + String escapedValue = escapePercentAndUnderscore(value); + String wildcard = "%"; + switch (wildcardPosition) { + case BEFORE: + escapedValue = wildcard + escapedValue; + break; + case AFTER: + escapedValue += wildcard; + break; + case BEFORE_AND_AFTER: + escapedValue = wildcard + escapedValue + wildcard; + break; + default: + throw new UnsupportedOperationException("Unhandled WildcardPosition: " + wildcardPosition); + } + + return appendEscapeBackslachForSomeDb(escapedValue); + } + + /** + * Replace escape percent and underscore by adding a slash just before + */ + private static String escapePercentAndUnderscore(String value) { + return value + .replaceAll("%", "\\\\%") + .replaceAll("_", "\\\\_"); + } + + private String appendEscapeBackslachForSomeDb(String value) { + return isOracleOrMsSql() + ? (value + " ESCAPE '\\'") + : value; + } + + private boolean isOracleOrMsSql() { + return getId().equals(Oracle.ID) || getId().equals(MsSql.ID); + } } diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/Dialect.java b/sonar-db/src/main/java/org/sonar/db/dialect/Dialect.java index 3d0a0f6e730..a6fba169d91 100644 --- a/sonar-db/src/main/java/org/sonar/db/dialect/Dialect.java +++ b/sonar-db/src/main/java/org/sonar/db/dialect/Dialect.java @@ -89,4 +89,10 @@ public interface Dialect { * @return a boolean */ boolean supportsMigration(); + + /** + * Returns an escaped value in parameter, with the desired wildcards. + * Suitable to be used in a like sql query + */ + String buildLikeValue(String value, WildcardPosition wildcardPosition); } diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/WildcardPosition.java b/sonar-db/src/main/java/org/sonar/db/dialect/WildcardPosition.java new file mode 100644 index 00000000000..0b8780b58e7 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/dialect/WildcardPosition.java @@ -0,0 +1,25 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.db.dialect; + +public enum WildcardPosition { + BEFORE, AFTER, BEFORE_AND_AFTER +} diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml index da674300494..8d846a61e39 100644 --- a/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -250,6 +250,43 @@ ORDER BY UPPER(p.name), p.name </select> + <select id="selectByQuery" resultType="Component"> + select + <include refid="componentColumns"/> + <include refid="sqlSelectByQuery"/> + ORDER BY LOWER(p.name), p.name + </select> + + <select id="countByQuery" resultType="int"> + select count(p.id) + <include refid="sqlSelectByQuery"/> + </select> + + <sql id="sqlSelectByQuery"> + from projects p + <where> + AND p.enabled=${_true} + AND p.copy_resource_id is null + AND p.qualifier in + <foreach collection="qualifiers" item="qualifier" open="(" close=")" separator=","> + #{qualifier} + </foreach> + <if test="nameOrKeyQuery!=null"> + AND (exists ( + select 1 + from resource_index ri + where + ri.resource_id=p.id + AND ri.qualifier in + <foreach collection="qualifiers" item="qualifier" open="(" close=")" separator=","> + #{qualifier} + </foreach> + AND ri.kee like #{nameOrKeyQueryToSqlForResourceIndex}) + OR p.kee like #{nameOrKeyQueryToSqlForProjectKey}) + </if> + </where> + </sql> + <select id="countRootComponents" resultType="int"> select count(p.id) from projects p diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java index f7125fd1d34..0ca8782056f 100644 --- a/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java @@ -51,6 +51,7 @@ public class ComponentDaoTest { @Rule public DbTester db = DbTester.create(System2.INSTANCE); + ComponentDbTester componentDb = new ComponentDbTester(db); DbSession dbSession = db.getSession(); @@ -59,7 +60,7 @@ public class ComponentDaoTest { @Test public void get_by_uuid() { db.prepareDbUnit(getClass(), "shared.xml"); - + ComponentDto result = underTest.selectByUuid(dbSession, "KLMN").get(); assertThat(result).isNotNull(); assertThat(result.uuid()).isEqualTo("KLMN"); @@ -612,8 +613,8 @@ public class ComponentDaoTest { @Test public void delete() throws Exception { - ComponentDto project1= insertProject(newProjectDto().setKey("PROJECT_1")); - insertProject(newProjectDto().setKey("PROJECT_2")); + ComponentDto project1 = componentDb.insertComponent(newProjectDto().setKey("PROJECT_1")); + componentDb.insertComponent(newProjectDto().setKey("PROJECT_2")); underTest.delete(dbSession, project1.getId()); dbSession.commit(); @@ -624,23 +625,62 @@ public class ComponentDaoTest { @Test public void select_components_with_paging_query_and_qualifiers() { - DbSession session = dbSession; - underTest.insert(session, newProjectDto().setName("aaaa-name")); - underTest.insert(session, newView()); - underTest.insert(session, newDeveloper("project-name")); + underTest.insert(dbSession, newProjectDto().setName("aaaa-name")); + underTest.insert(dbSession, newView()); + underTest.insert(dbSession, newDeveloper("project-name")); for (int i = 9; i >= 1; i--) { - underTest.insert(session, newProjectDto().setName("project-" + i)); + underTest.insert(dbSession, newProjectDto().setName("project-" + i)); } - List<ComponentDto> result = underTest.selectComponents(session, singleton(Qualifiers.PROJECT), 1, 3, "project"); + List<ComponentDto> result = underTest.selectComponents(dbSession, singleton(Qualifiers.PROJECT), 1, 3, "project"); assertThat(result).hasSize(3); assertThat(result).extracting("name").containsExactly("project-2", "project-3", "project-4"); } - private ComponentDto insertProject(ComponentDto project) { - underTest.insert(dbSession, project); - dbSession.commit(); - return project; + @Test + public void select_by_query_with_paging_query_and_qualifiers() { + componentDb.insertProjectAndSnapshot(dbSession, newProjectDto().setName("aaaa-name")); + componentDb.insertProjectAndSnapshot(dbSession, newView()); + componentDb.insertProjectAndSnapshot(dbSession, newDeveloper("project-name")); + for (int i = 9; i >= 1; i--) { + componentDb.insertProjectAndSnapshot(dbSession, newProjectDto().setName("project-" + i)); + } + db.commit(); + componentDb.indexProjects(); + + ComponentQuery query = new ComponentQuery(db.database(), "oJect", Qualifiers.PROJECT); + List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 1, 3); + + assertThat(result).hasSize(3); + assertThat(underTest.countByQuery(dbSession, query)).isEqualTo(9); + assertThat(result).extracting("name").containsExactly("project-2", "project-3", "project-4"); + } + + @Test + public void select_by_query_name_with_special_characters() { + componentDb.insertProjectAndSnapshot(dbSession, newProjectDto().setName("project-_%-name")); + db.commit(); + componentDb.indexProjects(); + + ComponentQuery query = new ComponentQuery(db.database(), "-_%-", Qualifiers.PROJECT); + List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10); + + assertThat(result).hasSize(1); + assertThat(result.get(0).name()).isEqualTo("project-_%-name"); + } + + @Test + public void select_by_query_key_with_special_characters() { + componentDb.insertProjectAndSnapshot(dbSession, newProjectDto() + .setKey("project-_%-key")); + db.commit(); + componentDb.indexProjects(); + + ComponentQuery query = new ComponentQuery(db.database(), "project-_%-", Qualifiers.PROJECT); + List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10); + + assertThat(result).hasSize(1); + assertThat(result.get(0).key()).isEqualTo("project-_%-key"); } } diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java new file mode 100644 index 00000000000..731a05a866a --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java @@ -0,0 +1,54 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.db.component; + +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; + +import static org.sonar.db.component.SnapshotTesting.newSnapshotForProject; + +public class ComponentDbTester { + private final DbTester db; + private final DbClient dbClient; + private final DbSession dbSession; + + public ComponentDbTester(DbTester db) { + this.db = db; + this.dbClient = db.getDbClient(); + this.dbSession = db.getSession(); + } + + public void insertProjectAndSnapshot(DbSession dbSession, ComponentDto component) { + dbClient.componentDao().insert(dbSession, component); + dbClient.snapshotDao().insert(dbSession, newSnapshotForProject(component)); + } + + public ComponentDto insertComponent(ComponentDto component) { + dbClient.componentDao().insert(dbSession, component); + return component; + } + + public void indexProjects() { + dbClient.componentIndexDao().indexProjects(); + db.commit(); + } +} diff --git a/sonar-db/src/test/java/org/sonar/db/component/SnapshotTesting.java b/sonar-db/src/test/java/org/sonar/db/component/SnapshotTesting.java new file mode 100644 index 00000000000..10c3eb0ab43 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/component/SnapshotTesting.java @@ -0,0 +1,64 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.db.component; + +import com.google.common.base.Preconditions; +import org.assertj.core.util.Strings; + +public class SnapshotTesting { + + /** + * Can be used for modules and files + */ + public static SnapshotDto createForComponent(ComponentDto component, SnapshotDto parentSnapshot) { + Preconditions.checkNotNull(parentSnapshot.getId(), "The parent snapshot need to be persisted before creating this snapshot"); + Long parentRootId = parentSnapshot.getRootId(); + return createBasicSnapshot(component, parentSnapshot.getRootProjectId()) + .setRootId(parentRootId != null ? parentRootId : parentSnapshot.getId()) + .setParentId(parentSnapshot.getId()) + .setPath(Strings.isNullOrEmpty(parentSnapshot.getPath()) ? Long.toString(parentSnapshot.getId()) + "." : parentSnapshot.getPath() + Long.toString(parentSnapshot.getId()) + "."); + } + + public static SnapshotDto newSnapshotForProject(ComponentDto project) { + return createBasicSnapshot(project, project.getId()) + .setPath(""); + } + + public static SnapshotDto newSnapshotForView(ComponentDto view) { + return createBasicSnapshot(view, view.getId()) + .setPath(""); + } + + private static SnapshotDto createBasicSnapshot(ComponentDto component, Long rootProjectId) { + Preconditions.checkNotNull(component.getId(), "The project need to be persisted before creating this snapshot"); + Preconditions.checkNotNull(rootProjectId, "Root project id is null"); + return new SnapshotDto() + .setComponentId(component.getId()) + .setRootProjectId(rootProjectId) + .setStatus(SnapshotDto.STATUS_PROCESSED) + .setQualifier(component.qualifier()) + .setScope(component.scope()) + .setCreatedAt(System.currentTimeMillis()) + .setBuildDate(System.currentTimeMillis()) + .setLast(true); + } + +} diff --git a/sonar-db/src/test/java/org/sonar/db/dialect/DialectTest.java b/sonar-db/src/test/java/org/sonar/db/dialect/DialectTest.java new file mode 100644 index 00000000000..c20b8ccd9f4 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/dialect/DialectTest.java @@ -0,0 +1,53 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.db.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.dialect.WildcardPosition.AFTER; +import static org.sonar.db.dialect.WildcardPosition.BEFORE; +import static org.sonar.db.dialect.WildcardPosition.BEFORE_AND_AFTER; + +public class DialectTest { + + Dialect underTest = new H2(); + + @Test + public void buildLikeValue_with_H2() { + String escapedValue = "like-\\_\\%-value"; + String wildcard = "%"; + + assertThat(underTest.buildLikeValue("like-_%-value", BEFORE)).isEqualTo(wildcard + escapedValue); + assertThat(underTest.buildLikeValue("like-_%-value", AFTER)).isEqualTo(escapedValue + wildcard); + assertThat(underTest.buildLikeValue("like-_%-value", BEFORE_AND_AFTER)).isEqualTo(wildcard + escapedValue + wildcard); + } + + @Test + public void buildLikeValue_with_Oracle() { + underTest = new Oracle(); + String escapedValue = "like-\\_\\%-value"; + String wildcard = "%"; + + assertThat(underTest.buildLikeValue("like-_%-value", BEFORE_AND_AFTER)).isEqualTo(wildcard + escapedValue + wildcard + " ESCAPE '\\'"); + + } +} |