import org.junit.runner.RunWith;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
+import org.sonar.db.OffsetBasedPagination;
+import org.sonar.db.Pagination;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
{9, 3, 3, List.of("4", "5", "6")},
{9, 7, 3, List.of("8", "9")},
{5, 5, 20, List.of()},
- {5, 0, 0, List.of()},
};
}
public void findScimUsers_whenPaginationAndStartIndex_shouldReturnTheCorrectNumberOfScimUsers(int totalScimUsers, int offset, int pageSize, List<String> expectedScimUserUuids) {
generateScimUsers(totalScimUsers);
- List<ScimUserDto> scimUserDtos = scimUserDao.findScimUsers(dbSession, ScimUserQuery.empty(), offset, pageSize);
+ List<ScimUserDto> scimUserDtos = scimUserDao.findScimUsers(dbSession, ScimUserQuery.empty(), OffsetBasedPagination.forOffset(offset, pageSize));
List<String> scimUsersUuids = toScimUsersUuids(scimUserDtos);
assertThat(scimUsersUuids).containsExactlyElementsOf(expectedScimUserUuids);
insertScimUsersWithUsers(userLogins);
ScimUserQuery query = ScimUserQuery.builder().userName(search).build();
- List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, 100);
+ List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, Pagination.all());
List<String> scimUsersUuids = toScimUsersUuids(scimUsersByQuery);
assertThat(scimUsersUuids).containsExactlyElementsOf(expectedScimUserUuids);
ScimUserQuery query = ScimUserQuery.builder().groupUuid(group1dto.getUuid()).build();
- List<ScimUserDto> scimUsers = scimUserDao.findScimUsers(dbSession, query, 0, 100);
+ List<ScimUserDto> scimUsers = scimUserDao.findScimUsers(dbSession, query, Pagination.all());
List<String> scimUsersUuids = toScimUsersUuids(scimUsers);
assertThat(scimUsersUuids).containsExactlyInAnyOrder(
ScimUserQuery query = ScimUserQuery.builder().scimUserUuids(expectedScimUserUuids).build();
- List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, Integer.MAX_VALUE);
+ List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, Pagination.all());
List<String> scimUsersUuids = toScimUsersUuids(scimUsersByQuery);
assertThat(scimUsersByQuery)
ScimUserQuery query = ScimUserQuery.builder().scimUserUuids(scimUserUuids).userName("username_5").build();
- List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, Integer.MAX_VALUE);
+ List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, Pagination.all());
assertThat(scimUsersByQuery).hasSize(1)
.extracting(ScimUserDto::getScimUserUuid)
ScimUserQuery query = ScimUserQuery.builder().userUuids(allUsersUuid).build();
- List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, Integer.MAX_VALUE);
+ List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, Pagination.all());
assertThat(scimUsersByQuery)
.hasSameSizeAs(allUsersUuid)
ScimUserQuery query = ScimUserQuery.builder().userUuids(allUsersUuid).userName("username_5").build();
- List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, Integer.MAX_VALUE);
+ List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, Pagination.all());
assertThat(scimUsersByQuery).hasSize(1)
.extracting(ScimUserDto::getScimUserUuid)
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class OffsetBasedPagination implements Pagineable {
+
+ private final int offset;
+ private final int pageSize;
+
+ private OffsetBasedPagination(int offset, int pageSize) {
+ this.offset = offset;
+ this.pageSize = pageSize;
+ }
+
+ /**
+ * @param offset as meant by database sql offset: how many rows will be skipped before selecting the first result. offset=0 means no element would be skipped
+ * @param pageSize how many rows should be returned
+ * @return
+ */
+ public static OffsetBasedPagination forOffset(int offset, int pageSize) {
+ checkArgument(offset >= 0, "offset must be >= 0");
+ checkArgument(pageSize >= 1, "page size must be >= 1");
+ return new OffsetBasedPagination(offset, pageSize);
+ }
+
+ /**
+ * @param startRowNumber index of the first element to be returned. startRowNumber = 1 means no element would be skipped
+ * @param pageSize how many rows should be returned
+ * @return
+ */
+ public static OffsetBasedPagination forStartRowNumber(int startRowNumber, int pageSize) {
+ checkArgument(startRowNumber >= 1, "startRowNumber must be >= 1");
+ checkArgument(pageSize >= 1, "page size must be >= 1");
+ return new OffsetBasedPagination(startRowNumber - 1, pageSize);
+ }
+
+ @Override
+ public int getStartRowNumber() {
+ return offset + 1;
+ }
+
+ @Override
+ public int getOffset() {
+ return offset;
+ }
+
+ @Override
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OffsetBasedPagination that = (OffsetBasedPagination) o;
+ return offset == that.offset && pageSize == that.pageSize;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(offset, pageSize);
+ }
+}
import static com.google.common.base.Preconditions.checkArgument;
@Immutable
-public final class Pagination {
+public final class Pagination implements Pagineable {
private static final Pagination ALL = new Builder(1).andSize(Integer.MAX_VALUE);
private static final Pagination FIRST = new Builder(1).andSize(1);
return page;
}
+ @Override
public int getPageSize() {
return pageSize;
}
+ @Override
public int getOffset() {
return (page - 1) * pageSize;
}
+ @Override
public int getStartRowNumber() {
return getOffset() + 1;
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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;
+
+public interface Pagineable {
+
+ int getStartRowNumber();
+
+ int getOffset();
+
+ int getPageSize();
+
+}
import java.util.List;
import java.util.Optional;
-import org.apache.ibatis.session.RowBounds;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
+import org.sonar.db.Pagineable;
public class ScimGroupDao implements Dao {
private final UuidFactory uuidFactory;
return mapper(dbSession).findAll();
}
- public List<ScimGroupDto> findScimGroups(DbSession dbSession, ScimGroupQuery query, int offset, int limit) {
- return mapper(dbSession).findScimGroups(query, new RowBounds(offset, limit));
+ public List<ScimGroupDto> findScimGroups(DbSession dbSession, ScimGroupQuery query, Pagineable pagination) {
+ return mapper(dbSession).findScimGroups(query, pagination);
}
public Optional<ScimGroupDto> findByScimUuid(DbSession dbSession, String scimGroupUuid) {
import java.util.List;
import javax.annotation.CheckForNull;
import org.apache.ibatis.annotations.Param;
-import org.apache.ibatis.session.RowBounds;
+import org.sonar.db.Pagineable;
public interface ScimGroupMapper {
@CheckForNull
ScimGroupDto findByGroupUuid(@Param("groupUuid") String groupUuid);
- List<ScimGroupDto> findScimGroups(@Param("query") ScimGroupQuery query, RowBounds rowBounds);
+ List<ScimGroupDto> findScimGroups(@Param("query") ScimGroupQuery query, @Param("pagination") Pagineable pagination);
int countScimGroups(@Param("query") ScimGroupQuery query);
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
-import org.apache.ibatis.session.RowBounds;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
+import org.sonar.db.Pagineable;
import static org.sonar.api.utils.Preconditions.checkState;
import static org.sonar.db.DatabaseUtils.executeLargeInputs;
return scimUserDto;
}
- public List<ScimUserDto> findScimUsers(DbSession dbSession, ScimUserQuery scimUserQuery, int offset, int limit) {
+ public List<ScimUserDto> findScimUsers(DbSession dbSession, ScimUserQuery scimUserQuery, Pagineable pagination) {
checkState(scimUserQuery.getUserUuids() == null || scimUserQuery.getScimUserUuids() == null,
"Only one of userUuids & scimUserUuids request parameter is supported.");
if (scimUserQuery.getScimUserUuids() != null) {
scimUserQuery.getScimUserUuids(),
partialSetOfUsers -> createPartialQuery(scimUserQuery, partialSetOfUsers,
(builder, scimUserUuids) -> builder.scimUserUuids(new HashSet<>(scimUserUuids)),
- dbSession, offset, limit)
+ dbSession, pagination)
);
}
if (scimUserQuery.getUserUuids() != null) {
scimUserQuery.getUserUuids(),
partialSetOfUsers -> createPartialQuery(scimUserQuery, partialSetOfUsers,
(builder, userUuids) -> builder.userUuids(new HashSet<>(userUuids)),
- dbSession, offset, limit)
+ dbSession, pagination)
);
}
- return mapper(dbSession).findScimUsers(scimUserQuery, new RowBounds(offset, limit));
+
+ return mapper(dbSession).findScimUsers(scimUserQuery, pagination);
}
private static List<ScimUserDto> createPartialQuery(ScimUserQuery completeQuery, List<String> strings,
BiFunction<ScimUserQuery.ScimUserQueryBuilder, List<String>, ScimUserQuery.ScimUserQueryBuilder> queryModifier,
- DbSession dbSession, int offset, int limit) {
+ DbSession dbSession, Pagineable pagination) {
ScimUserQuery.ScimUserQueryBuilder partialScimUserQuery = ScimUserQuery.builder()
.userName(completeQuery.getUserName());
partialScimUserQuery = queryModifier.apply(partialScimUserQuery, strings);
- return mapper(dbSession).findScimUsers(partialScimUserQuery.build(), new RowBounds(offset, limit));
+ return mapper(dbSession).findScimUsers(partialScimUserQuery.build(), pagination);
}
public int countScimUsers(DbSession dbSession, ScimUserQuery scimUserQuery) {
import java.util.List;
import javax.annotation.CheckForNull;
import org.apache.ibatis.annotations.Param;
-import org.apache.ibatis.session.RowBounds;
+import org.sonar.db.Pagineable;
public interface ScimUserMapper {
void insert(@Param("scimUserDto") ScimUserDto scimUserDto);
- List<ScimUserDto> findScimUsers(@Param("query") ScimUserQuery scimUserQuery, RowBounds rowBounds);
+ List<ScimUserDto> findScimUsers(@Param("query") ScimUserQuery scimUserQuery, @Param("pagination") Pagineable pagination);
int countScimUsers(@Param("query") ScimUserQuery scimUserQuery);
where g.name = #{query.displayName,jdbcType=VARCHAR}
</if>
order by s.scim_uuid asc
+ <include refid="pagination"/>
</select>
+ <sql id="pagination">
+ offset #{pagination.offset,jdbcType=INTEGER} rows fetch next #{pagination.pageSize,jdbcType=INTEGER} rows only
+ </sql>
+
<select id="countScimGroups" resultType="int">
select count(1)
from scim_groups s
)
</insert>
- <select id="findScimUsers" parameterType="map" resultType="org.sonar.db.scim.ScimUserDto">
+ <select id="findScimUsers" resultType="org.sonar.db.scim.ScimUserDto">
select
<include refid="scimUsersColumns"/>
<include refid="sqlSelectByQuery"/>
order by s.scim_uuid asc
+ <include refid="pagination"/>
</select>
+ <sql id="pagination">
+ offset #{pagination.offset,jdbcType=INTEGER} rows fetch next #{pagination.pageSize,jdbcType=INTEGER} rows only
+ </sql>
+
<select id="countScimUsers" parameterType="map" resultType="int">
select count(1)
<include refid="sqlSelectByQuery"/>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class OffsetBasedPaginationTest {
+
+ @Test
+ public void forOffset_whenNegativeOffset_shouldfailsWithIAE() {
+ assertThatThrownBy(() -> OffsetBasedPagination.forOffset(-1, 10))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("offset must be >= 0");
+ }
+
+ @Test
+ public void forOffset_whenPageSizeIsZero_shouldfailsWithIAE() {
+ assertThatThrownBy(() -> OffsetBasedPagination.forOffset(1, 0))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("page size must be >= 1");
+ }
+
+ @Test
+ public void forOffset_whenNegativePageSize_shouldfailsWithIAE() {
+ assertThatThrownBy(() -> OffsetBasedPagination.forOffset(1, -1))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("page size must be >= 1");
+ }
+
+ @Test
+ public void forOffset_whenZeroOffset_shouldStartRowNumberAtOne() {
+ assertThat(OffsetBasedPagination.forOffset(0, 100))
+ .extracting(p -> p.getStartRowNumber(), p -> p.getOffset(), p -> p.getPageSize())
+ .containsExactly(1, 0, 100);
+ }
+
+ @Test
+ public void forStartRowNumber_whenStartRowNumberLowerThanOne_shouldfailsWithIAE() {
+ assertThatThrownBy(() -> OffsetBasedPagination.forStartRowNumber(0, 10))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("startRowNumber must be >= 1");
+ }
+
+ @Test
+ public void forStartRowNumber_whenPageSizeIsZero_shouldfailsWithIAE() {
+ assertThatThrownBy(() -> OffsetBasedPagination.forStartRowNumber(1, 0))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("page size must be >= 1");
+ }
+
+ @Test
+ public void forStartRowNumber_whenNegativePageSize_shouldfailsWithIAE() {
+ assertThatThrownBy(() -> OffsetBasedPagination.forStartRowNumber(1, -1))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("page size must be >= 1");
+ }
+
+ @Test
+ public void forStartRowNumber_whenZeroOffset_shouldStartRowNumberAtOne() {
+ assertThat(OffsetBasedPagination.forStartRowNumber(1, 100))
+ .extracting(p -> p.getStartRowNumber(), p -> p.getOffset(), p -> p.getPageSize())
+ .containsExactly(1, 0, 100);
+ }
+
+ @Test
+ public void equals_whenSameParameters_shouldBeTrue() {
+ Assertions.assertThat(OffsetBasedPagination.forStartRowNumber(15, 20))
+ .isEqualTo(OffsetBasedPagination.forOffset(14, 20));
+ }
+
+ @Test
+ public void equals_whenSameObjects_shouldBeTrue() {
+ OffsetBasedPagination offsetBasedPagination = OffsetBasedPagination.forStartRowNumber(15, 20);
+ Assertions.assertThat(offsetBasedPagination).isEqualTo(offsetBasedPagination);
+ }
+
+ @Test
+ public void hashcode_whenSameObjects_shouldBeEquals() {
+ OffsetBasedPagination offsetBasedPagination = OffsetBasedPagination.forStartRowNumber(15, 20);
+ Assertions.assertThat(offsetBasedPagination).hasSameHashCodeAs(offsetBasedPagination);
+ }
+
+ @Test
+ public void equals_whenDifferentClasses_shouldBeFalse() {
+ Assertions.assertThat(OffsetBasedPagination.forStartRowNumber(15, 20)).isNotEqualTo("not an OffsetBasedPagination object");
+ }
+
+ @Test
+ public void equals_whenDifferentPageSize_shouldBeFalse() {
+ Assertions.assertThat(OffsetBasedPagination.forStartRowNumber(15, 21))
+ .isNotEqualTo(OffsetBasedPagination.forOffset(14, 20));
+ }
+
+ @Test
+ public void equals_whenDifferentOffset_shouldBeFalse() {
+ Assertions.assertThat(OffsetBasedPagination.forOffset(30, 20))
+ .isNotEqualTo(OffsetBasedPagination.forOffset(15, 20));
+ }
+
+ @Test
+ public void hashcode_whenSameParameters_shouldBeEquals() {
+ Assertions.assertThat(OffsetBasedPagination.forStartRowNumber(1, 20))
+ .hasSameHashCodeAs(OffsetBasedPagination.forOffset(0, 20));
+ }
+
+ @Test
+ public void hashcode_whenDifferentOffset_shouldBeNotEquals() {
+ Assertions.assertThat(OffsetBasedPagination.forOffset(10, 20))
+ .doesNotHaveSameHashCodeAs(OffsetBasedPagination.forOffset(15, 20));
+ }
+
+ @Test
+ public void hashcode_whenDifferentPageSize_shouldBeNotEquals() {
+ Assertions.assertThat(OffsetBasedPagination.forOffset(0, 20))
+ .doesNotHaveSameHashCodeAs(OffsetBasedPagination.forOffset(0, 40));
+ }
+
+
+
+}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.db.DbTester;
+import org.sonar.db.OffsetBasedPagination;
+import org.sonar.db.Pagination;
import org.sonar.db.user.GroupDto;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
{9, 0, 5, List.of("1", "2", "3", "4", "5")},
{9, 3, 3, List.of("4", "5", "6")},
{9, 7, 3, List.of("8", "9")},
- {5, 5, 20, List.of()},
- {5, 0, 0, List.of()}
+ {5, 5, 20, List.of()}
};
}
List<String> expectedScimGroupUuidSuffixes) {
generateScimGroups(totalScimGroups);
- List<ScimGroupDto> scimGroupDtos = scimGroupDao.findScimGroups(db.getSession(), ScimGroupQuery.ALL, offset, pageSize);
+ List<ScimGroupDto> scimGroupDtos = scimGroupDao.findScimGroups(db.getSession(), ScimGroupQuery.ALL, OffsetBasedPagination.forOffset(offset, pageSize));
List<String> actualScimGroupsUuids = toScimGroupsUuids(scimGroupDtos);
List<String> expectedScimGroupUuids = toExpectedscimGroupUuids(expectedScimGroupUuidSuffixes);
insertGroupAndScimGroup("group2");
ScimGroupQuery query = ScimGroupQuery.fromScimFilter(DISPLAY_NAME_FILTER);
- List<ScimGroupDto> scimGroups = scimGroupDao.findScimGroups(db.getSession(), query, 0, 100);
+ List<ScimGroupDto> scimGroups = scimGroupDao.findScimGroups(db.getSession(), query, Pagination.all());
assertThat(scimGroups).hasSize(1);
assertThat(scimGroups.get(0).getScimGroupUuid()).isEqualTo(createScimGroupUuid("group2"));