diff options
author | Eric Hartmann <hartmann.eric@gmail.com> | 2017-06-19 16:41:19 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-07-05 21:02:58 +0200 |
commit | 884e73d80752e779949284176173028f5feb0100 (patch) | |
tree | e341f05bb80751f4056a7615d360b6fc9dd7d32e /server/sonar-db-dao | |
parent | e964104ca94fdd7b975dedbbe8b15eb578b41f53 (diff) | |
download | sonarqube-884e73d80752e779949284176173028f5feb0100.tar.gz sonarqube-884e73d80752e779949284176173028f5feb0100.zip |
MMF-935 experiment ES resilience of user creation
Diffstat (limited to 'server/sonar-db-dao')
18 files changed, 599 insertions, 127 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index d6ad5559e52..9ee4a9586cf 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -35,6 +35,7 @@ import org.sonar.db.duplication.DuplicationDao; import org.sonar.db.event.EventDao; import org.sonar.db.issue.IssueChangeDao; import org.sonar.db.issue.IssueDao; +import org.sonar.db.es.EsQueueDao; import org.sonar.db.loadedtemplate.LoadedTemplateDao; import org.sonar.db.measure.MeasureDao; import org.sonar.db.measure.custom.CustomMeasureDao; @@ -86,6 +87,7 @@ public class DaoModule extends Module { CustomMeasureDao.class, DefaultQProfileDao.class, DuplicationDao.class, + EsQueueDao.class, EventDao.class, FileSourceDao.class, GroupDao.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index f2eb34c6bb1..6eb7c664a25 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -34,6 +34,7 @@ import org.sonar.db.duplication.DuplicationDao; import org.sonar.db.event.EventDao; import org.sonar.db.issue.IssueChangeDao; import org.sonar.db.issue.IssueDao; +import org.sonar.db.es.EsQueueDao; import org.sonar.db.loadedtemplate.LoadedTemplateDao; import org.sonar.db.measure.MeasureDao; import org.sonar.db.measure.custom.CustomMeasureDao; @@ -118,6 +119,7 @@ public class DbClient { private final UserPermissionDao userPermissionDao; private final WebhookDeliveryDao webhookDeliveryDao; private final DefaultQProfileDao defaultQProfileDao; + private final EsQueueDao esQueueDao; public DbClient(Database database, MyBatis myBatis, Dao... daos) { this.database = database; @@ -173,6 +175,7 @@ public class DbClient { userPermissionDao = getDao(map, UserPermissionDao.class); webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class); defaultQProfileDao = getDao(map, DefaultQProfileDao.class); + esQueueDao = getDao(map, EsQueueDao.class); } public DbSession openSession(boolean batch) { @@ -367,6 +370,10 @@ public class DbClient { return defaultQProfileDao; } + public EsQueueDao esQueueDao() { + return esQueueDao; + } + protected <K extends Dao> K getDao(Map<Class, Dao> map, Class<K> clazz) { return (K) map.get(clazz); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index e99aec847ea..044086a79b3 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -55,6 +55,7 @@ import org.sonar.db.issue.IssueChangeDto; import org.sonar.db.issue.IssueChangeMapper; import org.sonar.db.issue.IssueDto; import org.sonar.db.issue.IssueMapper; +import org.sonar.db.es.EsQueueMapper; import org.sonar.db.loadedtemplate.LoadedTemplateDto; import org.sonar.db.loadedtemplate.LoadedTemplateMapper; import org.sonar.db.measure.MeasureDto; @@ -197,6 +198,7 @@ public class MyBatis implements Startable { CustomMeasureMapper.class, DefaultQProfileMapper.class, DuplicationMapper.class, + EsQueueMapper.class, EventMapper.class, FileSourceMapper.class, GroupMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueDao.java new file mode 100644 index 00000000000..c47e087047d --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueDao.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.es; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; + +import static java.util.Collections.singletonList; +import static org.sonar.core.util.stream.MoreCollectors.toArrayList; +import static org.sonar.db.DatabaseUtils.executeLargeUpdates; + +public class EsQueueDao implements Dao { + + private final System2 system2; + private final UuidFactory uuidFactory; + + public EsQueueDao(System2 system2, UuidFactory uuidFactory) { + this.system2 = system2; + this.uuidFactory = uuidFactory; + } + + public EsQueueDto insert(DbSession dbSession, EsQueueDto item) { + insert(dbSession, singletonList(item)); + return item; + } + + public Collection<EsQueueDto> insert(DbSession dbSession, Collection<EsQueueDto> items) { + long now = system2.now(); + EsQueueMapper mapper = mapper(dbSession); + items.forEach(item -> { + item.setUuid(uuidFactory.create()); + mapper.insert(item, now); + }); + return items; + } + + public void delete(DbSession dbSession, EsQueueDto item) { + delete(dbSession, singletonList(item)); + } + + public void delete(DbSession dbSession, Collection<EsQueueDto> items) { + EsQueueMapper mapper = mapper(dbSession); + List<String> uuids = items.stream() + .map(EsQueueDto::getUuid) + .filter(Objects::nonNull) + .collect(toArrayList(items.size())); + executeLargeUpdates(uuids, mapper::delete); + } + + public Collection<EsQueueDto> selectForRecovery(DbSession dbSession, long beforeDate, long limit) { + return mapper(dbSession).selectForRecovery(beforeDate, limit); + } + + private static EsQueueMapper mapper(DbSession dbSession) { + return dbSession.getMapper(EsQueueMapper.class); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueDto.java new file mode 100644 index 00000000000..feb3148fa57 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueDto.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.es; + +public final class EsQueueDto { + + public enum Type { + USER + } + + private String uuid; + private Type docType; + private String docUuid; + + public String getUuid() { + return uuid; + } + + EsQueueDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public Type getDocType() { + return docType; + } + + private EsQueueDto setDocType(Type t) { + this.docType = t; + return this; + } + + public String getDocUuid() { + return docUuid; + } + + private EsQueueDto setDocUuid(String s) { + this.docUuid = s; + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("EsQueueDto{"); + sb.append("uuid='").append(uuid).append('\''); + sb.append(", docType=").append(docType); + sb.append(", docUuid='").append(docUuid).append('\''); + sb.append('}'); + return sb.toString(); + } + + public static EsQueueDto create(Type docType, String docUuid) { + return new EsQueueDto().setDocType(docType).setDocUuid(docUuid); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueMapper.java new file mode 100644 index 00000000000..04837587182 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/es/EsQueueMapper.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.es; + +import java.util.Collection; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +public interface EsQueueMapper { + + void insert(@Param("dto") EsQueueDto dto, @Param("now") long now); + + void delete(@Param("uuids") List<String> uuids); + + Collection<EsQueueDto> selectForRecovery(@Param("beforeDate") long beforeDate, @Param("limit") long limit); +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/es/package-info.java b/server/sonar-db-dao/src/main/java/org/sonar/db/es/package-info.java new file mode 100644 index 00000000000..5b01ba2c8f2 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/es/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.db.es; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberDao.java index abc23c06245..4b0c91bcf32 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberDao.java @@ -20,6 +20,7 @@ package org.sonar.db.organization; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -70,7 +71,7 @@ public class OrganizationMemberDao implements Dao { * * @param loginOrganizationConsumer {@link BiConsumer}<String,String> (login, organization uuid) */ - public void selectForUserIndexing(DbSession dbSession, List<String> logins, BiConsumer<String, String> loginOrganizationConsumer) { + public void selectForUserIndexing(DbSession dbSession, Collection<String> logins, BiConsumer<String, String> loginOrganizationConsumer) { executeLargeInputsWithoutOutput(logins, list -> mapper(dbSession).selectForIndexing(list) .forEach(row -> loginOrganizationConsumer.accept(row.get("login"), row.get("organizationUuid")))); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java index b0156a8c79a..966b5a564b0 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java @@ -33,12 +33,12 @@ import javax.annotation.Nullable; import org.sonar.api.resources.Scopes; import org.sonar.api.utils.System2; import org.sonar.db.Dao; -import org.sonar.db.DatabaseUtils; import org.sonar.db.DbSession; import org.sonar.db.MyBatis; import org.sonar.db.WildcardPosition; import static com.google.common.base.Preconditions.checkArgument; +import static org.apache.commons.lang.StringUtils.repeat; import static org.sonar.db.DaoDatabaseUtils.buildLikeValue; import static org.sonar.db.DatabaseUtils.executeLargeInputs; import static org.sonar.db.DatabaseUtils.executeLargeInputsWithoutOutput; @@ -89,7 +89,7 @@ public class PropertiesDao implements Dao { String sql = "SELECT count(1) FROM properties pp " + "left outer join projects pj on pp.resource_id = pj.id " + "where pp.user_id is not null and (pp.resource_id is null or pj.uuid=?) " + - "and (" + DatabaseUtils.repeatCondition("pp.prop_key like ?", dispatcherKeys.size(), "or") + ")"; + "and (" + repeat("pp.prop_key like ?", " or ", dispatcherKeys.size()) + ")"; PreparedStatement res = connection.prepareStatement(sql); res.setString(1, projectUuid); int index = 2; @@ -282,7 +282,7 @@ public class PropertiesDao implements Dao { executeLargeInputsWithoutOutput(ids, list -> getMapper(dbSession).deleteByIds(list)); } - public void deleteByKeyAndValue(DbSession dbSession, String key, String value){ + public void deleteByKeyAndValue(DbSession dbSession, String key, String value) { getMapper(dbSession).deleteByKeyAndValue(key, value); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java index ecc767b006f..ef7c49d07de 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java @@ -19,13 +19,15 @@ */ package org.sonar.db.user; -import com.google.common.base.Function; -import com.google.common.base.Predicates; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import org.sonar.api.user.UserQuery; @@ -34,8 +36,8 @@ import org.sonar.db.Dao; import org.sonar.db.DbSession; import org.sonar.db.RowNotFoundException; -import static com.google.common.collect.FluentIterable.from; import static org.sonar.db.DatabaseUtils.executeLargeInputs; +import static org.sonar.db.DatabaseUtils.executeLargeInputsWithoutOutput; public class UserDao implements Dao { @@ -82,7 +84,10 @@ public class UserDao implements Dao { */ public List<UserDto> selectByOrderedLogins(DbSession session, Collection<String> logins) { List<UserDto> unordered = selectByLogins(session, logins); - return from(logins).transform(new LoginToUser(unordered)).filter(Predicates.notNull()).toList(); + return logins.stream() + .map(new LoginToUser(unordered)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } public List<UserDto> selectUsers(DbSession dbSession, UserQuery query) { @@ -94,12 +99,17 @@ public class UserDao implements Dao { } public UserDto insert(DbSession session, UserDto dto) { - mapper(session).insert(dto); + long now = system2.now(); + mapper(session).insert(dto, now); + dto.setCreatedAt(now); + dto.setUpdatedAt(now); return dto; } public UserDto update(DbSession session, UserDto dto) { - mapper(session).update(dto); + long now = system2.now(); + mapper(session).update(dto, now); + dto.setUpdatedAt(now); return dto; } @@ -107,8 +117,8 @@ public class UserDao implements Dao { mapper(session).setRoot(login, root, system2.now()); } - public void deactivateUserById(DbSession dbSession, int userId) { - mapper(dbSession).deactivateUser(userId, system2.now()); + public void deactivateUser(DbSession dbSession, UserDto user) { + mapper(dbSession).deactivateUser(user.getLogin(), system2.now()); } @CheckForNull @@ -140,6 +150,22 @@ public class UserDao implements Dao { return mapper(dbSession).countByEmail(email.toLowerCase(Locale.ENGLISH)) > 0; } + public void scrollByLogins(DbSession dbSession, Collection<String> logins, Consumer<UserDto> consumer) { + UserMapper mapper = mapper(dbSession); + + executeLargeInputsWithoutOutput(logins, + pageOfLogins -> mapper + .selectByLogins(pageOfLogins) + .forEach(consumer)); + } + + public void scrollAll(DbSession dbSession, Consumer<UserDto> consumer) { + mapper(dbSession).scrollAll(context -> { + UserDto user = (UserDto) context.getResultObject(); + consumer.accept(user); + }); + } + private static UserMapper mapper(DbSession session) { return session.getMapper(UserMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java index e85f6ed8785..fc3dd43df6f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java @@ -189,7 +189,7 @@ public class UserDto { return createdAt; } - public UserDto setCreatedAt(Long createdAt) { + UserDto setCreatedAt(long createdAt) { this.createdAt = createdAt; return this; } @@ -198,7 +198,7 @@ public class UserDto { return updatedAt; } - public UserDto setUpdatedAt(Long updatedAt) { + UserDto setUpdatedAt(long updatedAt) { this.updatedAt = updatedAt; return this; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java index 17c8cc0bbec..e2938d4a776 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java @@ -22,6 +22,7 @@ package org.sonar.db.user; import java.util.List; import javax.annotation.CheckForNull; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.session.ResultHandler; import org.sonar.api.user.UserQuery; public interface UserMapper { @@ -51,6 +52,8 @@ public interface UserMapper { List<UserDto> selectByIds(@Param("ids") List<Integer> ids); + void scrollAll(ResultHandler handler); + long countByEmail(String email); /** @@ -58,14 +61,12 @@ public interface UserMapper { */ long countRootUsersButLogin(@Param("login") String login); - void insert(UserDto userDto); + void insert(@Param("user") UserDto userDto, @Param("now") long now); - void update(UserDto userDto); + void update(@Param("user") UserDto userDto, @Param("now") long now); void setRoot(@Param("login") String login, @Param("root") boolean root, @Param("now") long now); - void deleteOrganisationMembership(int userId); - - void deactivateUser(@Param("id") int userId, @Param("now") long now); + void deactivateUser(@Param("login") String login, @Param("now") long now); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/es/EsQueueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/es/EsQueueMapper.xml new file mode 100644 index 00000000000..08b3761380c --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/es/EsQueueMapper.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + +<mapper namespace="org.sonar.db.es.EsQueueMapper"> + + <sql id="esQueueColumns"> + uuid, + doc_type as docType, + doc_uuid as docUuid, + created_at as createdAt + </sql> + + <insert id="insert" parameterType="map" useGeneratedKeys="false"> + insert into es_queue ( + uuid, + doc_type, + doc_uuid, + created_at + ) values ( + #{dto.uuid, jdbcType=VARCHAR}, + #{dto.docType, jdbcType=VARCHAR}, + #{dto.docUuid, jdbcType=VARCHAR}, + #{now, jdbcType=BIGINT} + ) + </insert> + + <delete id="delete" parameterType="string"> + delete from es_queue + where uuid in + <foreach item="uuid" collection="uuids" open="(" separator="," close=")"> + #{uuid, jdbcType=VARCHAR} + </foreach> + </delete> + + <select id="selectForRecovery" parameterType="map" resultType="org.sonar.db.es.EsQueueDto"> + select <include refid="esQueueColumns" /> + from es_queue + where + created_at <= #{beforeDate, jdbcType=BIGINT} + order by created_at desc + limit #{limit, jdbcType=INTEGER} + </select> + + <select id="selectForRecovery" parameterType="map" resultType="org.sonar.db.es.EsQueueDto" databaseId="oracle"> + select * from ( + select rownum as rn, t.* from ( + select <include refid="esQueueColumns" /> + from es_queue + where + created_at <= #{beforeDate, jdbcType=BIGINT} + order by created_at desc + ) t + ) t + where + t.rn <= #{limit, jdbcType=INTEGER} + </select> + + <select id="selectForRecovery" parameterType="map" resultType="org.sonar.db.es.EsQueueDto" databaseId="mssql"> + select top(#{limit, jdbcType=INTEGER}) <include refid="esQueueColumns" /> + from es_queue + where + created_at <= #{beforeDate, jdbcType=BIGINT} + order by created_at desc + </select> + +</mapper> + diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml index ef9d08398df..9dddabd501d 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml @@ -62,6 +62,11 @@ </foreach> </select> + <select id="scrollAll" resultType="User" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY"> + select <include refid="userColumns"/> + from users u + </select> + <select id="selectByIds" parameterType="string" resultType="User"> SELECT <include refid="userColumns"/> @@ -116,35 +121,31 @@ and u.login <> #{login} </select> - <delete id="deleteOrganisationMembership" parameterType="int"> - DELETE FROM organization_members WHERE user_id=#{id,jdbcType=BIGINT} - </delete> - <update id="deactivateUser" parameterType="map"> - UPDATE users SET - active=${_false}, - email=null, - scm_accounts=null, - external_identity=null, - external_identity_provider=null, - salt=null, - crypted_password=null, - updated_at=#{now,jdbcType=BIGINT} - WHERE - id=#{id,jdbcType=INTEGER} + update users set + active = ${_false}, + email = null, + scm_accounts = null, + external_identity = null, + external_identity_provider = null, + salt = null, + crypted_password = null, + updated_at = #{now, jdbcType=BIGINT} + where + login = #{login, jdbcType=VARCHAR} </update> <update id="setRoot"> update users set - is_root = #{root,jdbcType=BOOLEAN}, - updated_at=#{now,jdbcType=BIGINT} + is_root = #{root, jdbcType=BOOLEAN}, + updated_at = #{now, jdbcType=BIGINT} where - login = #{login,jdbcType=VARCHAR} + login = #{login, jdbcType=VARCHAR} and active = ${_true} </update> - <insert id="insert" parameterType="User" keyColumn="id" useGeneratedKeys="true" keyProperty="id"> - INSERT INTO users ( + <insert id="insert" parameterType="map" keyColumn="id" useGeneratedKeys="true" keyProperty="user.id"> + insert into users ( login, name, email, @@ -159,40 +160,39 @@ onboarded, created_at, updated_at - ) - VALUES ( - #{login,jdbcType=VARCHAR}, - #{name,jdbcType=VARCHAR}, - #{email,jdbcType=VARCHAR}, - #{active,jdbcType=BOOLEAN}, - #{scmAccounts,jdbcType=VARCHAR}, - #{externalIdentity,jdbcType=VARCHAR}, - #{externalIdentityProvider,jdbcType=VARCHAR}, - #{local,jdbcType=BOOLEAN}, - #{salt,jdbcType=VARCHAR}, - #{cryptedPassword,jdbcType=VARCHAR}, - #{root,jdbcType=BOOLEAN}, - #{onboarded,jdbcType=BOOLEAN}, - #{createdAt,jdbcType=BIGINT}, - #{updatedAt,jdbcType=BIGINT} + ) values ( + #{user.login,jdbcType=VARCHAR}, + #{user.name,jdbcType=VARCHAR}, + #{user.email,jdbcType=VARCHAR}, + #{user.active,jdbcType=BOOLEAN}, + #{user.scmAccounts,jdbcType=VARCHAR}, + #{user.externalIdentity,jdbcType=VARCHAR}, + #{user.externalIdentityProvider,jdbcType=VARCHAR}, + #{user.local,jdbcType=BOOLEAN}, + #{user.salt,jdbcType=VARCHAR}, + #{user.cryptedPassword,jdbcType=VARCHAR}, + #{user.root,jdbcType=BOOLEAN}, + #{user.onboarded,jdbcType=BOOLEAN}, + #{now,jdbcType=BIGINT}, + #{now,jdbcType=BIGINT} ) </insert> - <insert id="update" parameterType="User" useGeneratedKeys="false"> - UPDATE users set - name=#{name,jdbcType=VARCHAR}, - email=#{email,jdbcType=VARCHAR}, - active=#{active,jdbcType=BOOLEAN}, - scm_accounts=#{scmAccounts,jdbcType=VARCHAR}, - external_identity=#{externalIdentity,jdbcType=VARCHAR}, - external_identity_provider=#{externalIdentityProvider,jdbcType=VARCHAR}, - user_local=#{local,jdbcType=BOOLEAN}, - onboarded=#{onboarded,jdbcType=BOOLEAN}, - salt=#{salt,jdbcType=VARCHAR}, - crypted_password=#{cryptedPassword,jdbcType=BIGINT}, - updated_at=#{updatedAt,jdbcType=BIGINT} - WHERE - login = #{login,jdbcType=VARCHAR} - </insert> + <update id="update" parameterType="map"> + update users set + name = #{user.name, jdbcType=VARCHAR}, + email = #{user.email, jdbcType=VARCHAR}, + active = #{user.active, jdbcType=BOOLEAN}, + scm_accounts = #{user.scmAccounts, jdbcType=VARCHAR}, + external_identity = #{user.externalIdentity, jdbcType=VARCHAR}, + external_identity_provider = #{user.externalIdentityProvider, jdbcType=VARCHAR}, + user_local = #{user.local, jdbcType=BOOLEAN}, + onboarded = #{user.onboarded, jdbcType=BOOLEAN}, + salt = #{user.salt, jdbcType=VARCHAR}, + crypted_password = #{user.cryptedPassword, jdbcType=BIGINT}, + updated_at = #{now, jdbcType=BIGINT} + where + login = #{user.login, jdbcType=VARCHAR} + </update> </mapper> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java index 4e2c7cb812e..2ffc454abd9 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java @@ -29,6 +29,6 @@ public class DaoModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new DaoModule().configure(container); - assertThat(container.size()).isEqualTo(2 + 46); + assertThat(container.size()).isEqualTo(2 + 47); } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/es/EsQueueDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/es/EsQueueDaoTest.java new file mode 100644 index 00000000000..373a88b2013 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/es/EsQueueDaoTest.java @@ -0,0 +1,129 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.es; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.internal.TestSystem2; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EsQueueDaoTest { + + private static final int LIMIT = 10; + private static TestSystem2 system2 = new TestSystem2().setNow(1_000); + + @Rule + public DbTester dbTester = DbTester.create(system2); + + private DbSession dbSession = dbTester.getSession(); + private EsQueueDao underTest = dbTester.getDbClient().esQueueDao(); + + @Test + public void insert_data() { + int nbOfInsert = 10 + new Random().nextInt(20); + List<EsQueueDto> esQueueDtos = new ArrayList<>(); + IntStream.rangeClosed(1, nbOfInsert).forEach( + i -> esQueueDtos.add(EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())) + ); + underTest.insert(dbSession, esQueueDtos); + + assertThat(dbTester.countSql(dbSession, "select count(*) from es_queue")).isEqualTo(nbOfInsert); + } + + @Test + public void delete_unknown_EsQueueDto_does_not_throw_exception() { + int nbOfInsert = 10 + new Random().nextInt(20); + List<EsQueueDto> esQueueDtos = new ArrayList<>(); + IntStream.rangeClosed(1, nbOfInsert).forEach( + i -> esQueueDtos.add(EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())) + ); + underTest.insert(dbSession, esQueueDtos); + + underTest.delete(dbSession, EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())); + + assertThat(dbTester.countSql(dbSession, "select count(*) from es_queue")).isEqualTo(nbOfInsert); + } + + @Test + public void delete_EsQueueDto_does_not_throw_exception() { + int nbOfInsert = 10 + new Random().nextInt(20); + List<EsQueueDto> esQueueDtos = new ArrayList<>(); + IntStream.rangeClosed(1, nbOfInsert).forEach( + i -> esQueueDtos.add(EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())) + ); + underTest.insert(dbSession, esQueueDtos); + assertThat(dbTester.countSql(dbSession, "select count(*) from es_queue")).isEqualTo(nbOfInsert); + + underTest.delete(dbSession, esQueueDtos); + + assertThat(dbTester.countSql(dbSession, "select count(*) from es_queue")).isEqualTo(0); + } + + @Test + public void selectForRecovery_must_return_limit_when_there_are_more_rows() { + system2.setNow(1_000L); + EsQueueDto i1 = underTest.insert(dbSession, EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())); + system2.setNow(1_001L); + EsQueueDto i2 = underTest.insert(dbSession, EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())); + system2.setNow(1_002L); + EsQueueDto i3 = underTest.insert(dbSession, EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())); + + assertThat(underTest.selectForRecovery(dbSession, 2_000, 1)) + .extracting(EsQueueDto::getUuid) + .containsExactly(i3.getUuid()); + + assertThat(underTest.selectForRecovery(dbSession, 2_000, 2)) + .extracting(EsQueueDto::getUuid) + .containsExactly(i3.getUuid(), i2.getUuid()); + + assertThat(underTest.selectForRecovery(dbSession, 2_000, 10)) + .extracting(EsQueueDto::getUuid) + .containsExactly(i3.getUuid(), i2.getUuid(), i1.getUuid()); + } + + @Test + public void selectForRecovery_returns_ordered_rows_created_before_date() { + system2.setNow(1_000L); + EsQueueDto i1 = underTest.insert(dbSession, EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())); + system2.setNow(1_001L); + EsQueueDto i2 = underTest.insert(dbSession, EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())); + system2.setNow(1_002L); + EsQueueDto i3 = underTest.insert(dbSession, EsQueueDto.create(EsQueueDto.Type.USER, UuidFactoryFast.getInstance().create())); + + assertThat(underTest.selectForRecovery(dbSession, 999, LIMIT)).hasSize(0); + assertThat(underTest.selectForRecovery(dbSession, 1_000, LIMIT)) + .extracting(EsQueueDto::getUuid) + .containsExactly(i1.getUuid()); + assertThat(underTest.selectForRecovery(dbSession, 1_001, LIMIT)) + .extracting(EsQueueDto::getUuid) + .containsExactly(i2.getUuid(), i1.getUuid()); + assertThat(underTest.selectForRecovery(dbSession, 2_000, LIMIT)) + .extracting(EsQueueDto::getUuid) + .containsExactly(i3.getUuid(), i2.getUuid(), i1.getUuid()); + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/RootFlagAssertions.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/RootFlagAssertions.java index 42c78876717..0885c813365 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/RootFlagAssertions.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/RootFlagAssertions.java @@ -31,10 +31,6 @@ public class RootFlagAssertions { this.db = db; } - public void verifyUnchanged(UserDto user) { - verify(user, user.isRoot(), user.getUpdatedAt()); - } - public void verify(UserDto userDto, boolean root, long updatedAt) { Map<String, Object> row = db.selectFirst("select is_root as \"isRoot\", updated_at as \"updatedAt\" from users where login = '" + userDto.getLogin() + "'"); Object isRoot = row.get("isRoot"); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java index ef3d48316b9..773c0eb1fa4 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java @@ -19,6 +19,7 @@ */ package org.sonar.db.user; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.junit.Before; @@ -28,6 +29,7 @@ import org.junit.rules.ExpectedException; import org.sonar.api.user.UserQuery; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; +import org.sonar.db.DatabaseUtils; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; @@ -37,6 +39,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -58,7 +61,7 @@ public class UserDaoTest { private UserDao underTest = db.getDbClient().userDao(); @Before - public void setUp() throws Exception { + public void setUp() { when(system2.now()).thenReturn(NOW); } @@ -337,23 +340,19 @@ public class UserDaoTest { assertThat(user.getExternalIdentityProvider()).isEqualTo("github"); assertThat(user.isLocal()).isTrue(); assertThat(user.isRoot()).isFalse(); - assertThat(user.getCreatedAt()).isEqualTo(date); - assertThat(user.getUpdatedAt()).isEqualTo(date); } @Test public void update_user() { - UserDto existingUser = db.users().insertUser(user -> user + UserDto user = db.users().insertUser(u -> u .setLogin("john") .setName("John") .setEmail("jo@hn.com") - .setCreatedAt(1418215735482L) - .setUpdatedAt(1418215735482L) .setActive(true) .setLocal(true) .setOnboarded(false)); - UserDto userDto = new UserDto() + UserDto userUpdate = new UserDto() .setId(1) .setLogin("john") .setName("John Doo") @@ -365,38 +364,34 @@ public class UserDaoTest { .setCryptedPassword("abcde") .setExternalIdentity("johngithub") .setExternalIdentityProvider("github") - .setLocal(false) - .setUpdatedAt(1500000000000L); - underTest.update(db.getSession(), userDto); - db.getSession().commit(); - - UserDto user = underTest.selectUserById(db.getSession(), existingUser.getId()); - assertThat(user).isNotNull(); - assertThat(user.getId()).isEqualTo(existingUser.getId()); - assertThat(user.getLogin()).isEqualTo("john"); - assertThat(user.getName()).isEqualTo("John Doo"); - assertThat(user.getEmail()).isEqualTo("jodoo@hn.com"); - assertThat(user.isActive()).isFalse(); - assertThat(user.isOnboarded()).isTrue(); - assertThat(user.getScmAccounts()).isEqualTo(",jo.hn,john2,johndoo,"); - assertThat(user.getSalt()).isEqualTo("12345"); - assertThat(user.getCryptedPassword()).isEqualTo("abcde"); - assertThat(user.getExternalIdentity()).isEqualTo("johngithub"); - assertThat(user.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(user.isLocal()).isFalse(); - assertThat(user.isRoot()).isFalse(); - assertThat(user.getCreatedAt()).isEqualTo(1418215735482L); - assertThat(user.getUpdatedAt()).isEqualTo(1500000000000L); - } - - @Test - public void deactivate_user() throws Exception { - UserDto user = newActiveUser(); + .setLocal(false); + underTest.update(db.getSession(), userUpdate); + + UserDto reloaded = underTest.selectByLogin(db.getSession(), user.getLogin()); + assertThat(reloaded).isNotNull(); + assertThat(reloaded.getId()).isEqualTo(user.getId()); + assertThat(reloaded.getLogin()).isEqualTo(user.getLogin()); + assertThat(reloaded.getName()).isEqualTo("John Doo"); + assertThat(reloaded.getEmail()).isEqualTo("jodoo@hn.com"); + assertThat(reloaded.isActive()).isFalse(); + assertThat(reloaded.isOnboarded()).isTrue(); + assertThat(reloaded.getScmAccounts()).isEqualTo(",jo.hn,john2,johndoo,"); + assertThat(reloaded.getSalt()).isEqualTo("12345"); + assertThat(reloaded.getCryptedPassword()).isEqualTo("abcde"); + assertThat(reloaded.getExternalIdentity()).isEqualTo("johngithub"); + assertThat(reloaded.getExternalIdentityProvider()).isEqualTo("github"); + assertThat(reloaded.isLocal()).isFalse(); + assertThat(reloaded.isRoot()).isFalse(); + } + + @Test + public void deactivate_user() { + UserDto user = insertActiveUser(); insertUserGroup(user); - UserDto otherUser = newActiveUser(); + UserDto otherUser = insertActiveUser(); session.commit(); - underTest.deactivateUserById(session, user.getId()); + underTest.deactivateUser(session, user); UserDto userReloaded = underTest.selectUserById(session, user.getId()); assertThat(userReloaded.isActive()).isFalse(); @@ -413,7 +408,7 @@ public class UserDaoTest { @Test public void does_not_fail_to_deactivate_missing_user() { - underTest.deactivateUserById(session, 123); + underTest.deactivateUser(session, UserTesting.newUserDto()); } @Test @@ -425,13 +420,11 @@ public class UserDaoTest { .setActive(true) .setScmAccounts("\nma\nmarius33\n") .setSalt("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365") - .setCryptedPassword("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg") - .setCreatedAt(1418215735482L) - .setUpdatedAt(1418215735485L)); - UserDto user2 = db.users().insertUser(user -> user.setLogin("sbrandhof")); + .setCryptedPassword("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg")); + UserDto user2 = db.users().insertUser(); underTest.setRoot(session, user2.getLogin(), true); - UserDto dto = underTest.selectOrFailByLogin(session, "marius"); + UserDto dto = underTest.selectOrFailByLogin(session, user1.getLogin()); assertThat(dto.getId()).isEqualTo(user1.getId()); assertThat(dto.getLogin()).isEqualTo("marius"); assertThat(dto.getName()).isEqualTo("Marius"); @@ -441,10 +434,10 @@ public class UserDaoTest { assertThat(dto.getSalt()).isEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365"); assertThat(dto.getCryptedPassword()).isEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"); assertThat(dto.isRoot()).isFalse(); - assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L); - assertThat(dto.getUpdatedAt()).isEqualTo(1418215735485L); + assertThat(dto.getCreatedAt()).isEqualTo(user1.getCreatedAt()); + assertThat(dto.getUpdatedAt()).isEqualTo(user1.getUpdatedAt()); - dto = underTest.selectOrFailByLogin(session, "sbrandhof"); + dto = underTest.selectOrFailByLogin(session, user2.getLogin()); assertThat(dto.isRoot()).isTrue(); } @@ -490,8 +483,8 @@ public class UserDaoTest { } @Test - public void exists_by_email() throws Exception { - UserDto activeUser = newActiveUser(); + public void exists_by_email() { + UserDto activeUser = insertActiveUser(); UserDto disableUser = insertUser(false); assertThat(underTest.doesEmailExist(session, activeUser.getEmail())).isTrue(); @@ -507,8 +500,8 @@ public class UserDaoTest { @Test public void setRoot_set_root_flag_of_specified_user_to_specified_value_and_updates_udpateAt() { - String login = newActiveUser().getLogin(); - UserDto otherUser = newActiveUser(); + String login = insertActiveUser().getLogin(); + UserDto otherUser = insertActiveUser(); assertThat(underTest.selectByLogin(session, login).isRoot()).isEqualTo(false); assertThat(underTest.selectByLogin(session, otherUser.getLogin()).isRoot()).isEqualTo(false); @@ -550,7 +543,7 @@ public class UserDaoTest { assertThat(underTest.selectByLogin(session, nonRootInactiveUser).isRoot()).isFalse(); // create inactive root user - UserDto rootUser = newActiveUser(); + UserDto rootUser = insertActiveUser(); commit(() -> underTest.setRoot(session, rootUser.getLogin(), true)); rootUser.setActive(false); commit(() -> underTest.update(session, rootUser)); @@ -562,12 +555,52 @@ public class UserDaoTest { assertThat(underTest.selectByLogin(session, inactiveRootUser.getLogin()).isRoot()).isTrue(); } + @Test + public void scrollByLogins() { + UserDto u1 = insertUser(true); + UserDto u2 = insertUser(false); + UserDto u3 = insertUser(false); + + List<UserDto> result = new ArrayList<>(); + underTest.scrollByLogins(db.getSession(), asList(u2.getLogin(), u3.getLogin(), "does not exist"), result::add); + + assertThat(result).extracting(UserDto::getLogin, UserDto::getName) + .containsExactlyInAnyOrder(tuple(u2.getLogin(), u2.getName()), tuple(u3.getLogin(), u3.getName())); + } + + @Test + public void scrollByLogins_scrolls_by_pages_of_1000_logins() { + List<String> logins = new ArrayList<>(); + for (int i = 0; i < DatabaseUtils.PARTITION_SIZE_FOR_ORACLE + 10; i++) { + logins.add(insertUser(true).getLogin()); + } + + List<UserDto> result = new ArrayList<>(); + underTest.scrollByLogins(db.getSession(), logins, result::add); + + assertThat(result) + .extracting(UserDto::getLogin) + .containsExactlyInAnyOrder(logins.toArray(new String[0])); + } + + @Test + public void scrollAll() { + UserDto u1 = insertUser(true); + UserDto u2 = insertUser(false); + + List<UserDto> result = new ArrayList<>(); + underTest.scrollAll(db.getSession(), result::add); + + assertThat(result).extracting(UserDto::getLogin, UserDto::getName) + .containsExactlyInAnyOrder(tuple(u1.getLogin(), u1.getName()), tuple(u2.getLogin(), u2.getName())); + } + private void commit(Runnable runnable) { runnable.run(); session.commit(); } - private UserDto newActiveUser() { + private UserDto insertActiveUser() { return insertUser(true); } |