SONAR-6332 Prevent provisioning projects with same key on MySQL

This commit is contained in:
Julien Lancelot 2015-09-25 17:19:28 +02:00
parent cdcc70c8fe
commit 99374f79d0
10 changed files with 347 additions and 169 deletions

View File

@ -146,7 +146,12 @@ public class ComponentService {
public ComponentDto create(NewComponent newComponent) {
userSession.checkGlobalPermission(GlobalPermissions.PROVISIONING);
ComponentDto project = createProject(newComponent);
removeDuplicatedProjects(project.getKey());
return project;
}
private ComponentDto createProject(NewComponent newComponent) {
DbSession session = dbClient.openSession(false);
try {
checkKeyFormat(newComponent.qualifier(), newComponent.key());
@ -174,19 +179,37 @@ public class ComponentService {
dbClient.componentDao().insert(session, component);
dbClient.componentIndexDao().indexResource(session, component.getId());
session.commit();
return component;
} finally {
dbClient.closeSession(session);
}
}
/**
* On MySQL, as PROJECTS.KEE is not unique, if the same project is provisioned multiple times, then it will be duplicated in the database.
* So, after creating a project, we commit, and we search in the db if their are some duplications and we remove them.
*
* SONAR-6332
*/
private void removeDuplicatedProjects(String projectKey) {
DbSession session = dbClient.openSession(false);
try {
List<ComponentDto> duplicated = dbClient.componentDao().selectComponentsHavingSameKeyOrderedById(session, projectKey);
for (int i = 1; i < duplicated.size(); i++) {
dbClient.componentDao().delete(session, duplicated.get(i).getId());
}
session.commit();
} finally {
dbClient.closeSession(session);
}
}
public Collection<String> componentUuids(@Nullable Collection<String> componentKeys) {
DbSession session = dbClient.openSession(false);
try {
return componentUuids(session, componentKeys, false);
} finally {
session.close();
dbClient.closeSession(session);
}
}

View File

@ -20,6 +20,7 @@
package org.sonar.server.component;
import com.google.common.base.Optional;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
@ -28,27 +29,36 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.api.i18n.I18n;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDao;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.component.ResourceIndexDao;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.test.DbTests;
import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.guava.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
@Category(DbTests.class)
public class ComponentServiceTest {
@ -268,7 +278,7 @@ public class ComponentServiceTest {
@Test
public void create_project() {
userSessionRule.login("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
userSessionRule.login("john").setGlobalPermissions(PROVISIONING);
String key = service.create(NewComponent.create("struts", "Struts project")).getKey();
@ -288,7 +298,7 @@ public class ComponentServiceTest {
@Test
public void create_new_project_with_branch() {
userSessionRule.login("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
userSessionRule.login("john").setGlobalPermissions(PROVISIONING);
String key = service.create(NewComponent.create("struts", "Struts project").setBranch("origin/branch")).getKey();
@ -299,7 +309,7 @@ public class ComponentServiceTest {
@Test
public void create_view() {
userSessionRule.login("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
userSessionRule.login("john").setGlobalPermissions(PROVISIONING);
String key = service.create(NewComponent.create("all-project", "All Projects").setQualifier(Qualifiers.VIEW)).getKey();
@ -319,7 +329,7 @@ public class ComponentServiceTest {
@Test
public void fail_to_create_new_component_on_invalid_key() {
userSessionRule.login("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
userSessionRule.login("john").setGlobalPermissions(PROVISIONING);
try {
service.create(NewComponent.create("struts?parent", "Struts project"));
@ -332,7 +342,7 @@ public class ComponentServiceTest {
@Test
public void fail_to_create_new_component_on_invalid_branch() {
userSessionRule.login("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
userSessionRule.login("john").setGlobalPermissions(PROVISIONING);
try {
service.create(NewComponent.create("struts", "Struts project").setBranch("origin?branch"));
@ -345,7 +355,7 @@ public class ComponentServiceTest {
@Test
public void fail_to_create_new_component_if_key_already_exists() {
userSessionRule.login("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
userSessionRule.login("john").setGlobalPermissions(PROVISIONING);
ComponentDto project = ComponentTesting.newProjectDto().setKey("struts");
dbClient.componentDao().insert(session, project);
@ -359,6 +369,42 @@ public class ComponentServiceTest {
}
}
@Test
public void remove_duplicated_components_when_creating_project() throws Exception {
String projectKey = "PROJECT_KEY";
userSessionRule.login("john").setGlobalPermissions(PROVISIONING);
DbSession session = mock(DbSession.class);
ComponentDao componentDao = mock(ComponentDao.class);
when(componentDao.selectByKey(session, projectKey)).thenReturn(Optional.<ComponentDto>absent());
DbClient dbClient = mock(DbClient.class);
when(dbClient.openSession(false)).thenReturn(session);
when(dbClient.componentDao()).thenReturn(componentDao);
when(dbClient.componentIndexDao()).thenReturn(mock(ResourceIndexDao.class));
doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock invocation) {
((ComponentDto) invocation.getArguments()[1]).setId(1L);
return null;
}
}).when(componentDao).insert(eq(session), any(ComponentDto.class));
when(componentDao.selectComponentsHavingSameKeyOrderedById(session, projectKey)).thenReturn(newArrayList(
ComponentTesting.newProjectDto().setId(1L).setKey(projectKey),
ComponentTesting.newProjectDto().setId(2L).setKey(projectKey),
ComponentTesting.newProjectDto().setId(3L).setKey(projectKey)
));
service = new ComponentService(dbClient, i18n, userSessionRule, System2.INSTANCE, new ComponentFinder(dbClient));
service.create(NewComponent.create(projectKey, projectKey));
verify(componentDao).delete(session, 2L);
verify(componentDao).delete(session, 3L);
}
@Test
public void should_return_project_uuids() {
ComponentDto project = createProject("sample:root");

View File

@ -135,6 +135,10 @@ public class ComponentDao implements Dao {
return executeLargeInputs(keys, new KeyToDto(mapper(session)));
}
public List<ComponentDto> selectComponentsHavingSameKeyOrderedById(DbSession session, String key) {
return mapper(session).selectComponentsHavingSameKeyOrderedById(key);
}
private static class KeyToDto implements Function<List<String>, List<ComponentDto>> {
private final ComponentMapper mapper;
@ -271,6 +275,10 @@ public class ComponentDao implements Dao {
mapper(session).update(item);
}
public void delete(DbSession session, long componentId) {
mapper(session).delete(componentId);
}
private ComponentMapper mapper(DbSession session) {
return session.getMapper(ComponentMapper.class);
}

View File

@ -118,10 +118,14 @@ public interface ComponentMapper {
List<ComponentDto> selectGhostProjects(Map<String, Object> parameters, RowBounds rowBounds);
List<ComponentDto> selectComponentsHavingSameKeyOrderedById(String key);
long countGhostProjects(Map<String, Object> parameters);
void insert(ComponentDto componentDto);
void update(ComponentDto componentDto);
void delete(long componentId);
}

View File

@ -52,6 +52,15 @@
</where>
</select>
<select id="selectComponentsHavingSameKeyOrderedById" parameterType="String" resultType="Component">
SELECT id
FROM projects p
<where>
AND p.kee=#{key}
</where>
ORDER BY p.id ASC
</select>
<select id="selectById" parameterType="long" resultType="Component">
SELECT
<include refid="componentColumns"/>
@ -362,7 +371,7 @@
#{createdAt,jdbcType=TIMESTAMP}, #{authorizationUpdatedAt,jdbcType=BIGINT})
</insert>
<insert id="update" parameterType="Component" useGeneratedKeys="false">
<update id="update" parameterType="Component" useGeneratedKeys="false">
UPDATE projects SET
kee=#{kee,jdbcType=VARCHAR},
deprecated_kee=#{deprecatedKey,jdbcType=VARCHAR},
@ -381,6 +390,10 @@
enabled=#{enabled,jdbcType=BOOLEAN},
authorization_updated_at=#{authorizationUpdatedAt,jdbcType=BIGINT}
WHERE uuid=#{uuid}
</insert>
</update>
<delete id="delete" parameterType="long">
DELETE FROM projects WHERE id=#{id}
</delete>
</mapper>

View File

@ -52,13 +52,15 @@ public class ComponentDaoTest {
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
DbSession dbSession = db.getSession();
ComponentDao underTest = new ComponentDao();
@Test
public void get_by_uuid() {
db.prepareDbUnit(getClass(), "shared.xml");
ComponentDto result = underTest.selectByUuid(db.getSession(), "KLMN").get();
ComponentDto result = underTest.selectByUuid(dbSession, "KLMN").get();
assertThat(result).isNotNull();
assertThat(result.uuid()).isEqualTo("KLMN");
assertThat(result.moduleUuid()).isEqualTo("EFGH");
@ -74,14 +76,14 @@ public class ComponentDaoTest {
assertThat(result.language()).isEqualTo("java");
assertThat(result.getCopyResourceId()).isNull();
assertThat(underTest.selectByUuid(db.getSession(), "UNKNOWN")).isAbsent();
assertThat(underTest.selectByUuid(dbSession, "UNKNOWN")).isAbsent();
}
@Test
public void get_by_uuid_on_technical_project_copy() {
db.prepareDbUnit(getClass(), "shared.xml");
ComponentDto result = underTest.selectByUuid(db.getSession(), "STUV").get();
ComponentDto result = underTest.selectByUuid(dbSession, "STUV").get();
assertThat(result).isNotNull();
assertThat(result.uuid()).isEqualTo("STUV");
assertThat(result.moduleUuid()).isEqualTo("OPQR");
@ -102,7 +104,7 @@ public class ComponentDaoTest {
public void get_by_uuid_on_disabled_component() {
db.prepareDbUnit(getClass(), "shared.xml");
ComponentDto result = underTest.selectByUuid(db.getSession(), "DCBA").get();
ComponentDto result = underTest.selectByUuid(dbSession, "DCBA").get();
assertThat(result).isNotNull();
assertThat(result.isEnabled()).isFalse();
}
@ -113,14 +115,14 @@ public class ComponentDaoTest {
db.prepareDbUnit(getClass(), "shared.xml");
underTest.selectOrFailByUuid(db.getSession(), "unknown");
underTest.selectOrFailByUuid(dbSession, "unknown");
}
@Test
public void get_by_key() {
db.prepareDbUnit(getClass(), "shared.xml");
Optional<ComponentDto> optional = underTest.selectByKey(db.getSession(), "org.struts:struts-core:src/org/struts/RequestContext.java");
Optional<ComponentDto> optional = underTest.selectByKey(dbSession, "org.struts:struts-core:src/org/struts/RequestContext.java");
assertThat(optional).isPresent();
ComponentDto result = optional.get();
@ -133,7 +135,7 @@ public class ComponentDaoTest {
assertThat(result.language()).isEqualTo("java");
assertThat(result.parentProjectId()).isEqualTo(2);
assertThat(underTest.selectByKey(db.getSession(), "unknown")).isAbsent();
assertThat(underTest.selectByKey(dbSession, "unknown")).isAbsent();
}
@Test
@ -142,14 +144,14 @@ public class ComponentDaoTest {
db.prepareDbUnit(getClass(), "shared.xml");
underTest.selectOrFailByKey(db.getSession(), "unknown");
underTest.selectOrFailByKey(dbSession, "unknown");
}
@Test
public void get_by_key_on_disabled_component() {
db.prepareDbUnit(getClass(), "shared.xml");
ComponentDto result = underTest.selectOrFailByKey(db.getSession(), "org.disabled.project");
ComponentDto result = underTest.selectOrFailByKey(dbSession, "org.disabled.project");
assertThat(result.isEnabled()).isFalse();
}
@ -157,7 +159,7 @@ public class ComponentDaoTest {
public void get_by_key_on_a_root_project() {
db.prepareDbUnit(getClass(), "shared.xml");
ComponentDto result = underTest.selectOrFailByKey(db.getSession(), "org.struts:struts");
ComponentDto result = underTest.selectOrFailByKey(dbSession, "org.struts:struts");
assertThat(result.key()).isEqualTo("org.struts:struts");
assertThat(result.deprecatedKey()).isEqualTo("org.struts:struts");
assertThat(result.path()).isNull();
@ -175,7 +177,7 @@ public class ComponentDaoTest {
public void get_by_keys() {
db.prepareDbUnit(getClass(), "shared.xml");
List<ComponentDto> results = underTest.selectByKeys(db.getSession(), Collections.singletonList("org.struts:struts-core:src/org/struts/RequestContext.java"));
List<ComponentDto> results = underTest.selectByKeys(dbSession, Collections.singletonList("org.struts:struts-core:src/org/struts/RequestContext.java"));
assertThat(results).hasSize(1);
ComponentDto result = results.get(0);
@ -189,14 +191,14 @@ public class ComponentDaoTest {
assertThat(result.language()).isEqualTo("java");
assertThat(result.parentProjectId()).isEqualTo(2);
assertThat(underTest.selectByKeys(db.getSession(), Collections.singletonList("unknown"))).isEmpty();
assertThat(underTest.selectByKeys(dbSession, Collections.singletonList("unknown"))).isEmpty();
}
@Test
public void get_by_ids() {
db.prepareDbUnit(getClass(), "shared.xml");
List<ComponentDto> results = underTest.selectByIds(db.getSession(), newArrayList(4L));
List<ComponentDto> results = underTest.selectByIds(dbSession, newArrayList(4L));
assertThat(results).hasSize(1);
ComponentDto result = results.get(0);
@ -210,14 +212,14 @@ public class ComponentDaoTest {
assertThat(result.language()).isEqualTo("java");
assertThat(result.parentProjectId()).isEqualTo(2);
assertThat(underTest.selectByIds(db.getSession(), newArrayList(555L))).isEmpty();
assertThat(underTest.selectByIds(dbSession, newArrayList(555L))).isEmpty();
}
@Test
public void get_by_uuids() {
db.prepareDbUnit(getClass(), "shared.xml");
List<ComponentDto> results = underTest.selectByUuids(db.getSession(), newArrayList("KLMN"));
List<ComponentDto> results = underTest.selectByUuids(dbSession, newArrayList("KLMN"));
assertThat(results).hasSize(1);
ComponentDto result = results.get(0);
@ -235,14 +237,14 @@ public class ComponentDaoTest {
assertThat(result.scope()).isEqualTo("FIL");
assertThat(result.language()).isEqualTo("java");
assertThat(underTest.selectByUuids(db.getSession(), newArrayList("unknown"))).isEmpty();
assertThat(underTest.selectByUuids(dbSession, newArrayList("unknown"))).isEmpty();
}
@Test
public void get_by_uuids_on_removed_components() {
db.prepareDbUnit(getClass(), "shared.xml");
List<ComponentDto> results = underTest.selectByUuids(db.getSession(), newArrayList("DCBA"));
List<ComponentDto> results = underTest.selectByUuids(dbSession, newArrayList("DCBA"));
assertThat(results).hasSize(1);
ComponentDto result = results.get(0);
@ -254,25 +256,25 @@ public class ComponentDaoTest {
public void select_existing_uuids() {
db.prepareDbUnit(getClass(), "shared.xml");
List<String> results = underTest.selectExistingUuids(db.getSession(), newArrayList("KLMN"));
List<String> results = underTest.selectExistingUuids(dbSession, newArrayList("KLMN"));
assertThat(results).containsOnly("KLMN");
assertThat(underTest.selectExistingUuids(db.getSession(), newArrayList("KLMN", "unknown"))).hasSize(1);
assertThat(underTest.selectExistingUuids(db.getSession(), newArrayList("unknown"))).isEmpty();
assertThat(underTest.selectExistingUuids(dbSession, newArrayList("KLMN", "unknown"))).hasSize(1);
assertThat(underTest.selectExistingUuids(dbSession, newArrayList("unknown"))).isEmpty();
}
@Test
public void get_by_id() {
db.prepareDbUnit(getClass(), "shared.xml");
assertThat(underTest.selectOrFailById(db.getSession(), 4L)).isNotNull();
assertThat(underTest.selectOrFailById(dbSession, 4L)).isNotNull();
}
@Test
public void get_by_id_on_disabled_component() {
db.prepareDbUnit(getClass(), "shared.xml");
Optional<ComponentDto> result = underTest.selectById(db.getSession(), 10L);
Optional<ComponentDto> result = underTest.selectById(dbSession, 10L);
assertThat(result).isPresent();
assertThat(result.get().isEnabled()).isFalse();
}
@ -281,23 +283,23 @@ public class ComponentDaoTest {
public void fail_to_get_by_id_when_project_not_found() {
db.prepareDbUnit(getClass(), "shared.xml");
underTest.selectOrFailById(db.getSession(), 111L);
underTest.selectOrFailById(dbSession, 111L);
}
@Test
public void get_nullable_by_id() {
db.prepareDbUnit(getClass(), "shared.xml");
assertThat(underTest.selectById(db.getSession(), 4L)).isPresent();
assertThat(underTest.selectById(db.getSession(), 111L)).isAbsent();
assertThat(underTest.selectById(dbSession, 4L)).isPresent();
assertThat(underTest.selectById(dbSession, 111L)).isAbsent();
}
@Test
public void count_by_id() {
db.prepareDbUnit(getClass(), "shared.xml");
assertThat(underTest.existsById(4L, db.getSession())).isTrue();
assertThat(underTest.existsById(111L, db.getSession())).isFalse();
assertThat(underTest.existsById(4L, dbSession)).isTrue();
assertThat(underTest.existsById(111L, dbSession)).isFalse();
}
@Test
@ -305,34 +307,34 @@ public class ComponentDaoTest {
db.prepareDbUnit(getClass(), "multi-modules.xml");
// Sub project of a file
List<ComponentDto> results = underTest.selectSubProjectsByComponentUuids(db.getSession(), newArrayList("HIJK"));
List<ComponentDto> results = underTest.selectSubProjectsByComponentUuids(dbSession, newArrayList("HIJK"));
assertThat(results).hasSize(1);
assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-data");
// Sub project of a directory
results = underTest.selectSubProjectsByComponentUuids(db.getSession(), newArrayList("GHIJ"));
results = underTest.selectSubProjectsByComponentUuids(dbSession, newArrayList("GHIJ"));
assertThat(results).hasSize(1);
assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-data");
// Sub project of a sub module
results = underTest.selectSubProjectsByComponentUuids(db.getSession(), newArrayList("FGHI"));
results = underTest.selectSubProjectsByComponentUuids(dbSession, newArrayList("FGHI"));
assertThat(results).hasSize(1);
assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts");
// Sub project of a module
results = underTest.selectSubProjectsByComponentUuids(db.getSession(), newArrayList("EFGH"));
results = underTest.selectSubProjectsByComponentUuids(dbSession, newArrayList("EFGH"));
assertThat(results).hasSize(1);
assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts");
// Sub project of a project
assertThat(underTest.selectSubProjectsByComponentUuids(db.getSession(), newArrayList("ABCD"))).isEmpty();
assertThat(underTest.selectSubProjectsByComponentUuids(dbSession, newArrayList("ABCD"))).isEmpty();
// SUb projects of a component and a sub module
assertThat(underTest.selectSubProjectsByComponentUuids(db.getSession(), newArrayList("HIJK", "FGHI"))).hasSize(2);
assertThat(underTest.selectSubProjectsByComponentUuids(dbSession, newArrayList("HIJK", "FGHI"))).hasSize(2);
assertThat(underTest.selectSubProjectsByComponentUuids(db.getSession(), newArrayList("unknown"))).isEmpty();
assertThat(underTest.selectSubProjectsByComponentUuids(dbSession, newArrayList("unknown"))).isEmpty();
assertThat(underTest.selectSubProjectsByComponentUuids(db.getSession(), Collections.<String>emptyList())).isEmpty();
assertThat(underTest.selectSubProjectsByComponentUuids(dbSession, Collections.<String>emptyList())).isEmpty();
}
@Test
@ -340,20 +342,20 @@ public class ComponentDaoTest {
db.prepareDbUnit(getClass(), "multi-modules.xml");
// From root project
List<ComponentDto> modules = underTest.selectEnabledDescendantModules(db.getSession(), "ABCD");
List<ComponentDto> modules = underTest.selectEnabledDescendantModules(dbSession, "ABCD");
assertThat(modules).extracting("uuid").containsOnly("ABCD", "EFGH", "FGHI");
// From module
modules = underTest.selectEnabledDescendantModules(db.getSession(), "EFGH");
modules = underTest.selectEnabledDescendantModules(dbSession, "EFGH");
assertThat(modules).extracting("uuid").containsOnly("EFGH", "FGHI");
// From sub module
modules = underTest.selectEnabledDescendantModules(db.getSession(), "FGHI");
modules = underTest.selectEnabledDescendantModules(dbSession, "FGHI");
assertThat(modules).extracting("uuid").containsOnly("FGHI");
// Folder
assertThat(underTest.selectEnabledDescendantModules(db.getSession(), "GHIJ")).isEmpty();
assertThat(underTest.selectEnabledDescendantModules(db.getSession(), "unknown")).isEmpty();
assertThat(underTest.selectEnabledDescendantModules(dbSession, "GHIJ")).isEmpty();
assertThat(underTest.selectEnabledDescendantModules(dbSession, "unknown")).isEmpty();
}
@Test
@ -361,15 +363,15 @@ public class ComponentDaoTest {
db.prepareDbUnit(getClass(), "multi-modules.xml");
// From root project, disabled sub module is returned
List<ComponentDto> modules = underTest.selectDescendantModules(db.getSession(), "ABCD");
List<ComponentDto> modules = underTest.selectDescendantModules(dbSession, "ABCD");
assertThat(modules).extracting("uuid").containsOnly("ABCD", "EFGH", "FGHI", "IHGF");
// From module, disabled sub module is returned
modules = underTest.selectDescendantModules(db.getSession(), "EFGH");
modules = underTest.selectDescendantModules(dbSession, "EFGH");
assertThat(modules).extracting("uuid").containsOnly("EFGH", "FGHI", "IHGF");
// From removed sub module -> should not be returned
assertThat(underTest.selectDescendantModules(db.getSession(), "IHGF")).isEmpty();
assertThat(underTest.selectDescendantModules(dbSession, "IHGF")).isEmpty();
}
@Test
@ -377,30 +379,30 @@ public class ComponentDaoTest {
db.prepareDbUnit(getClass(), "select_module_files_tree.xml");
// From root project
List<FilePathWithHashDto> files = underTest.selectEnabledDescendantFiles(db.getSession(), "ABCD");
List<FilePathWithHashDto> files = underTest.selectEnabledDescendantFiles(dbSession, "ABCD");
assertThat(files).extracting("uuid").containsOnly("EFGHI", "HIJK");
assertThat(files).extracting("moduleUuid").containsOnly("EFGH", "FGHI");
assertThat(files).extracting("srcHash").containsOnly("srcEFGHI", "srcHIJK");
assertThat(files).extracting("path").containsOnly("src/org/struts/pom.xml", "src/org/struts/RequestContext.java");
// From module
files = underTest.selectEnabledDescendantFiles(db.getSession(), "EFGH");
files = underTest.selectEnabledDescendantFiles(dbSession, "EFGH");
assertThat(files).extracting("uuid").containsOnly("EFGHI", "HIJK");
assertThat(files).extracting("moduleUuid").containsOnly("EFGH", "FGHI");
assertThat(files).extracting("srcHash").containsOnly("srcEFGHI", "srcHIJK");
assertThat(files).extracting("path").containsOnly("src/org/struts/pom.xml", "src/org/struts/RequestContext.java");
// From sub module
files = underTest.selectEnabledDescendantFiles(db.getSession(), "FGHI");
files = underTest.selectEnabledDescendantFiles(dbSession, "FGHI");
assertThat(files).extracting("uuid").containsOnly("HIJK");
assertThat(files).extracting("moduleUuid").containsOnly("FGHI");
assertThat(files).extracting("srcHash").containsOnly("srcHIJK");
assertThat(files).extracting("path").containsOnly("src/org/struts/RequestContext.java");
// From directory
assertThat(underTest.selectEnabledDescendantFiles(db.getSession(), "GHIJ")).isEmpty();
assertThat(underTest.selectEnabledDescendantFiles(dbSession, "GHIJ")).isEmpty();
assertThat(underTest.selectEnabledDescendantFiles(db.getSession(), "unknown")).isEmpty();
assertThat(underTest.selectEnabledDescendantFiles(dbSession, "unknown")).isEmpty();
}
@Test
@ -408,69 +410,69 @@ public class ComponentDaoTest {
db.prepareDbUnit(getClass(), "select_module_files_tree.xml");
// From root project
List<FilePathWithHashDto> files = underTest.selectEnabledFilesFromProject(db.getSession(), "ABCD");
List<FilePathWithHashDto> files = underTest.selectEnabledFilesFromProject(dbSession, "ABCD");
assertThat(files).extracting("uuid").containsOnly("EFGHI", "HIJK");
assertThat(files).extracting("moduleUuid").containsOnly("EFGH", "FGHI");
assertThat(files).extracting("srcHash").containsOnly("srcEFGHI", "srcHIJK");
assertThat(files).extracting("path").containsOnly("src/org/struts/pom.xml", "src/org/struts/RequestContext.java");
// From module
assertThat(underTest.selectEnabledFilesFromProject(db.getSession(), "EFGH")).isEmpty();
assertThat(underTest.selectEnabledFilesFromProject(dbSession, "EFGH")).isEmpty();
// From sub module
assertThat(underTest.selectEnabledFilesFromProject(db.getSession(), "FGHI")).isEmpty();
assertThat(underTest.selectEnabledFilesFromProject(dbSession, "FGHI")).isEmpty();
// From directory
assertThat(underTest.selectEnabledFilesFromProject(db.getSession(), "GHIJ")).isEmpty();
assertThat(underTest.selectEnabledFilesFromProject(dbSession, "GHIJ")).isEmpty();
assertThat(underTest.selectEnabledFilesFromProject(db.getSession(), "unknown")).isEmpty();
assertThat(underTest.selectEnabledFilesFromProject(dbSession, "unknown")).isEmpty();
}
@Test
public void select_all_components_from_project() {
db.prepareDbUnit(getClass(), "multi-modules.xml");
List<ComponentDto> components = underTest.selectAllComponentsFromProjectKey(db.getSession(), "org.struts:struts");
List<ComponentDto> components = underTest.selectAllComponentsFromProjectKey(dbSession, "org.struts:struts");
// Removed components are included
assertThat(components).hasSize(8);
assertThat(underTest.selectAllComponentsFromProjectKey(db.getSession(), "UNKNOWN")).isEmpty();
assertThat(underTest.selectAllComponentsFromProjectKey(dbSession, "UNKNOWN")).isEmpty();
}
@Test
public void select_modules_from_project() {
db.prepareDbUnit(getClass(), "multi-modules.xml");
List<ComponentDto> components = underTest.selectEnabledModulesFromProjectKey(db.getSession(), "org.struts:struts");
List<ComponentDto> components = underTest.selectEnabledModulesFromProjectKey(dbSession, "org.struts:struts");
assertThat(components).hasSize(3);
assertThat(underTest.selectEnabledModulesFromProjectKey(db.getSession(), "UNKNOWN")).isEmpty();
assertThat(underTest.selectEnabledModulesFromProjectKey(dbSession, "UNKNOWN")).isEmpty();
}
@Test
public void select_views_and_sub_views() {
db.prepareDbUnit(getClass(), "shared_views.xml");
assertThat(underTest.selectAllViewsAndSubViews(db.getSession())).extracting("uuid").containsOnly("ABCD", "EFGH", "FGHI", "IJKL");
assertThat(underTest.selectAllViewsAndSubViews(db.getSession())).extracting("projectUuid").containsOnly("ABCD", "EFGH", "IJKL");
assertThat(underTest.selectAllViewsAndSubViews(dbSession)).extracting("uuid").containsOnly("ABCD", "EFGH", "FGHI", "IJKL");
assertThat(underTest.selectAllViewsAndSubViews(dbSession)).extracting("projectUuid").containsOnly("ABCD", "EFGH", "IJKL");
}
@Test
public void select_projects_from_view() {
db.prepareDbUnit(getClass(), "shared_views.xml");
assertThat(underTest.selectProjectsFromView(db.getSession(), "ABCD", "ABCD")).containsOnly("JKLM");
assertThat(underTest.selectProjectsFromView(db.getSession(), "EFGH", "EFGH")).containsOnly("KLMN", "JKLM");
assertThat(underTest.selectProjectsFromView(db.getSession(), "FGHI", "EFGH")).containsOnly("JKLM");
assertThat(underTest.selectProjectsFromView(db.getSession(), "IJKL", "IJKL")).isEmpty();
assertThat(underTest.selectProjectsFromView(db.getSession(), "Unknown", "Unknown")).isEmpty();
assertThat(underTest.selectProjectsFromView(dbSession, "ABCD", "ABCD")).containsOnly("JKLM");
assertThat(underTest.selectProjectsFromView(dbSession, "EFGH", "EFGH")).containsOnly("KLMN", "JKLM");
assertThat(underTest.selectProjectsFromView(dbSession, "FGHI", "EFGH")).containsOnly("JKLM");
assertThat(underTest.selectProjectsFromView(dbSession, "IJKL", "IJKL")).isEmpty();
assertThat(underTest.selectProjectsFromView(dbSession, "Unknown", "Unknown")).isEmpty();
}
@Test
public void select_projects() {
db.prepareDbUnit(getClass(), "select_provisioned_projects.xml");
List<ComponentDto> result = underTest.selectProjects(db.getSession());
List<ComponentDto> result = underTest.selectProjects(dbSession);
assertThat(result).extracting("id").containsOnly(42L, 1L);
}
@ -479,7 +481,7 @@ public class ComponentDaoTest {
public void select_provisioned_projects() {
db.prepareDbUnit(getClass(), "select_provisioned_projects.xml");
List<ComponentDto> result = underTest.selectProvisionedProjects(db.getSession(), 0, 10, null);
List<ComponentDto> result = underTest.selectProvisionedProjects(dbSession, 0, 10, null);
ComponentDto project = result.get(0);
assertThat(result).hasSize(1);
@ -490,7 +492,7 @@ public class ComponentDaoTest {
public void count_provisioned_projects() {
db.prepareDbUnit(getClass(), "select_provisioned_projects.xml");
int numberOfProjects = underTest.countProvisionedProjects(db.getSession(), null);
int numberOfProjects = underTest.countProvisionedProjects(dbSession, null);
assertThat(numberOfProjects).isEqualTo(1);
}
@ -499,18 +501,18 @@ public class ComponentDaoTest {
public void select_ghost_projects() {
db.prepareDbUnit(getClass(), "select_ghost_projects.xml");
List<ComponentDto> result = underTest.selectGhostProjects(db.getSession(), 0, 10, null);
List<ComponentDto> result = underTest.selectGhostProjects(dbSession, 0, 10, null);
assertThat(result).hasSize(1);
assertThat(result.get(0).key()).isEqualTo("org.ghost.project");
assertThat(underTest.countGhostProjects(db.getSession(), null)).isEqualTo(1);
assertThat(underTest.countGhostProjects(dbSession, null)).isEqualTo(1);
}
@Test
public void selectResourcesByRootId() {
db.prepareDbUnit(getClass(), "shared.xml");
List<ComponentDto> resources = underTest.selectByProjectUuid("ABCD", db.getSession());
List<ComponentDto> resources = underTest.selectByProjectUuid("ABCD", dbSession);
assertThat(resources).extracting("id").containsOnly(1l, 2l, 3l, 4l);
}
@ -540,8 +542,8 @@ public class ComponentDaoTest {
.setCreatedAt(DateUtils.parseDate("2014-06-18"))
.setAuthorizationUpdatedAt(123456789L);
underTest.insert(db.getSession(), componentDto);
db.getSession().commit();
underTest.insert(dbSession, componentDto);
dbSession.commit();
assertThat(componentDto.getId()).isNotNull();
db.assertDbUnit(getClass(), "insert-result.xml", "projects");
@ -569,8 +571,8 @@ public class ComponentDaoTest {
.setCreatedAt(DateUtils.parseDate("2014-06-18"))
.setAuthorizationUpdatedAt(123456789L);
underTest.insert(db.getSession(), componentDto);
db.getSession().commit();
underTest.insert(dbSession, componentDto);
dbSession.commit();
assertThat(componentDto.getId()).isNotNull();
db.assertDbUnit(getClass(), "insert_disabled_component-result.xml", "projects");
@ -599,15 +601,27 @@ public class ComponentDaoTest {
.setEnabled(false)
.setAuthorizationUpdatedAt(12345678910L);
underTest.update(db.getSession(), componentDto);
db.getSession().commit();
underTest.update(dbSession, componentDto);
dbSession.commit();
db.assertDbUnit(getClass(), "update-result.xml", "projects");
}
@Test
public void delete() throws Exception {
ComponentDto project1= insertProject(newProjectDto().setKey("PROJECT_1"));
insertProject(newProjectDto().setKey("PROJECT_2"));
underTest.delete(dbSession, project1.getId());
dbSession.commit();
assertThat(underTest.selectByKey(dbSession, "PROJECT_1")).isAbsent();
assertThat(underTest.selectByKey(dbSession, "PROJECT_2")).isPresent();
}
@Test
public void select_components_with_paging_query_and_qualifiers() {
DbSession session = db.getSession();
DbSession session = dbSession;
underTest.insert(session, newProjectDto().setName("aaaa-name"));
underTest.insert(session, newView());
underTest.insert(session, newDeveloper("project-name"));
@ -620,4 +634,10 @@ public class ComponentDaoTest {
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;
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.test.DbTests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
/**
* On H2, the index on PROJECTS.KEE is unique. In order to simulate the MySQL behaviour where the index is not unique, we need to create a schema where there's no unique index on PROJECTS.KEE
*/
@Category(DbTests.class)
public class ComponentDaoWithDuplicatedKeysTest {
static final String PROJECT_KEY = "PROJECT_KEY";
@Rule
public DbTester db = DbTester.createForSchema(System2.INSTANCE, ComponentDaoWithDuplicatedKeysTest.class, "schema.sql");
DbClient dbClient = db.getDbClient();
DbSession dbSession = db.getSession();
ComponentDao underTest = new ComponentDao();
@Test
public void select_components_having_same_key() {
insertProject(newProjectDto().setKey(PROJECT_KEY));
insertProject(newProjectDto().setKey(PROJECT_KEY));
insertProject(newProjectDto().setKey(PROJECT_KEY));
insertProject(newProjectDto().setKey("ANOTHER_PROJECT_KEY"));
assertThat(underTest.selectComponentsHavingSameKeyOrderedById(db.getSession(), PROJECT_KEY)).hasSize(3);
}
@Test
public void return_nothing() throws Exception {
assertThat(underTest.selectComponentsHavingSameKeyOrderedById(db.getSession(), PROJECT_KEY)).isEmpty();
}
private ComponentDto insertProject(ComponentDto project) {
dbClient.componentDao().insert(dbSession, project);
dbSession.commit();
return project;
}
}

View File

@ -1,94 +1,23 @@
<dataset>
<!-- Struts projects is authorized for all user -->
<group_roles id="1" group_id="[null]" resource_id="1" role="user"/>
<!-- root project -->
<projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts" deprecated_kee="org.struts:struts"
uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD."
description="the description" long_name="Apache Struts"
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" created_at="[null]" authorization_updated_at="123456789"/>
<snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
status="P" islast="[true]" purge_status="[null]"
period1_mode="[null]" period1_param="[null]" period1_date="[null]"
period2_mode="[null]" period2_param="[null]" period2_date="[null]"
period3_mode="[null]" period3_param="[null]" period3_date="[null]"
period4_mode="[null]" period4_param="[null]" period4_date="[null]"
period5_mode="[null]" period5_param="[null]" period5_date="[null]"
depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
version="[null]" path=""/>
<snapshots id="10" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
status="P" islast="[false]" purge_status="[null]"
period1_mode="[null]" period1_param="[null]" period1_date="[null]"
period2_mode="[null]" period2_param="[null]" period2_date="[null]"
period3_mode="[null]" period3_param="[null]" period3_date="[null]"
period4_mode="[null]" period4_param="[null]" period4_date="[null]"
period5_mode="[null]" period5_param="[null]" period5_date="[null]"
depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228136280000" build_date="1228136280000"
version="[null]" path=""/>
<!-- module -->
<projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core" deprecated_kee="[null]"
<projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
uuid="EFGH" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD.EFGH."
scope="PRJ" qualifier="BRC" long_name="Struts Core"
description="[null]" enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" created_at="[null]" authorization_updated_at="[null]"/>
<snapshots id="2" project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
status="P" islast="[true]" purge_status="[null]"
period1_mode="[null]" period1_param="[null]" period1_date="[null]"
period2_mode="[null]" period2_param="[null]" period2_date="[null]"
period3_mode="[null]" period3_param="[null]" period3_date="[null]"
period4_mode="[null]" period4_param="[null]" period4_date="[null]"
period5_mode="[null]" period5_param="[null]" period5_date="[null]"
depth="[null]" scope="PRJ" qualifier="BRC" created_at="1228222680000" build_date="1228222680000"
version="[null]" path="1."/>
description="[null]" enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" authorization_updated_at="[null]"/>
<!-- directory -->
<projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:src/org/struts" deprecated_kee="[null]"
<projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:src/org/struts"
uuid="GHIJ" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
name="src/org/struts" root_id="2"
description="[null]"
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="src/org/struts" created_at="[null]" authorization_updated_at="[null]"/>
<snapshots id="3" project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
status="P" islast="[true]" purge_status="[null]"
period1_mode="[null]" period1_param="[null]" period1_date="[null]"
period2_mode="[null]" period2_param="[null]" period2_date="[null]"
period3_mode="[null]" period3_param="[null]" period3_date="[null]"
period4_mode="[null]" period4_param="[null]" period4_date="[null]"
period5_mode="[null]" period5_param="[null]" period5_date="[null]"
depth="[null]" scope="DIR" qualifier="PAC" created_at="1228222680000" build_date="1228222680000"
version="[null]" path="1.2."/>
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="src/org/struts" authorization_updated_at="[null]"/>
<!-- file -->
<!--<projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="FIL" kee="org.struts:struts-core:src/org/struts/RequestContext.java"-->
<!--uuid="KLMN" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."-->
<!--name="RequestContext.java" root_id="2"-->
<!--description="[null]"-->
<!--enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="src/org/struts/RequestContext.java" authorization_updated_at="[null]" />-->
<snapshots id="4" project_id="4" parent_snapshot_id="3" root_project_id="1" root_snapshot_id="1"
status="P" islast="[true]" purge_status="[null]"
period1_mode="[null]" period1_param="[null]" period1_date="[null]"
period2_mode="[null]" period2_param="[null]" period2_date="[null]"
period3_mode="[null]" period3_param="[null]" period3_date="[null]"
period4_mode="[null]" period4_param="[null]" period4_date="[null]"
period5_mode="[null]" period5_param="[null]" period5_date="[null]"
depth="[null]" scope="FIL" qualifier="CLA" created_at="1228222680000" build_date="1228222680000"
version="[null]" path="1.2.3."/>
<projects id="10" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.disabled.project" name="Disabled Project"
uuid="DCBA" project_uuid="DCBA" module_uuid="[null]" module_uuid_path="."
description="the description" long_name="Disabled project"
enabled="[false]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" authorization_updated_at="123456789"/>
<!-- Developer and technical project copy -->
<projects id="11" root_id="[null]" scope="PRJ" qualifier="DEV" kee="DEV:anakin@skywalker.name" name="Anakin Skywalker"
uuid="OPQR" project_uuid="OPQR" module_uuid="[null]" module_uuid_path=".OPQR."
description="the description" long_name="Anakin Skywalker"
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" authorization_updated_at="123456789"/>
<projects id="12" root_id="11" scope="PRJ" qualifier="DEV_PRJ" kee="DEV:anakin@skywalker.name:org.struts:struts" name="Apache Struts"
uuid="STUV" project_uuid="OPQR" module_uuid="OPQR" module_uuid_path=".OPQR."
description="the description" long_name="Apache Struts"
enabled="[true]" language="[null]" copy_resource_id="1" person_id="11" path="[null]" authorization_updated_at="123456789"/>
<projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="FIL" kee="org.struts:struts-core:src/org/struts/RequestContext.java"
uuid="KLMN" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
name="RequestContext.java" root_id="2"
description="[null]"
enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="src/org/struts/RequestContext.java" authorization_updated_at="[null]"/>
</dataset>

View File

@ -0,0 +1,29 @@
<dataset>
<!-- root project -->
<projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" deprecated_kee="org.struts:struts" name="Struts"
uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD."
description="the description" long_name="Apache Struts"
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" authorization_updated_at="123456789"/>
<!-- module -->
<projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
uuid="EFGH" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD.EFGH."
scope="PRJ" qualifier="BRC" long_name="Struts Core"
description="[null]" enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" authorization_updated_at="[null]"/>
<!-- directory -->
<projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:src/org/struts"
uuid="GHIJ" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
name="src/org/struts" root_id="2"
description="[null]"
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="src/org/struts" authorization_updated_at="[null]"/>
<!-- file -->
<projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="FIL" kee="org.struts:struts-core:src/org/struts/RequestContext.java"
uuid="KLMN" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
name="RequestContext.java" root_id="2"
description="[null]"
enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="src/org/struts/RequestContext.java" authorization_updated_at="[null]"/>
</dataset>

View File

@ -0,0 +1,34 @@
CREATE TABLE "PROJECTS" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"KEE" VARCHAR(400),
"ROOT_ID" INTEGER,
"UUID" VARCHAR(50),
"PROJECT_UUID" VARCHAR(50),
"MODULE_UUID" VARCHAR(50),
"MODULE_UUID_PATH" VARCHAR(4000),
"NAME" VARCHAR(256),
"DESCRIPTION" VARCHAR(2000),
"ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
"SCOPE" VARCHAR(3),
"QUALIFIER" VARCHAR(10),
"DEPRECATED_KEE" VARCHAR(400),
"PATH" VARCHAR(2000),
"LANGUAGE" VARCHAR(20),
"COPY_RESOURCE_ID" INTEGER,
"LONG_NAME" VARCHAR(256),
"PERSON_ID" INTEGER,
"CREATED_AT" TIMESTAMP,
"AUTHORIZATION_UPDATED_AT" BIGINT
);
CREATE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
CREATE INDEX "PROJECTS_ROOT_ID" ON "PROJECTS" ("ROOT_ID");
CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");