]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9040 make CE_QUEUE.EXECUTION_COUNT not nullable
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Mon, 27 Mar 2017 09:22:16 +0000 (11:22 +0200)
committerEric Hartmann <hartmann.eric@gmail.Com>
Thu, 27 Apr 2017 07:23:18 +0000 (09:23 +0200)
12 files changed:
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDtoTest.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/AddCeQueueWorkerUuidAndExecutionCount.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullable.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64Test.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullableTest/ce_queue.sql [new file with mode: 0644]

index 8bc83c2586cd8c288ecf22c07b85ed9b9136e233..d721494387df2c18e03e59f351c2c7dc6896b9b5 100644 (file)
@@ -563,7 +563,7 @@ CREATE TABLE "CE_QUEUE" (
   "STATUS" VARCHAR(15) NOT NULL,
   "SUBMITTER_LOGIN" VARCHAR(255) NULL,
   "WORKER_UUID" VARCHAR(40) NULL,
-  "EXECUTION_COUNT" INTEGER NULL,
+  "EXECUTION_COUNT" INTEGER NOT NULL,
   "STARTED_AT" BIGINT NULL,
   "CREATED_AT" BIGINT NOT NULL,
   "UPDATED_AT" BIGINT NOT NULL
index d494515230e222aa5b4734dd3a27674abfd35793..7a4073fe5637f9e31ea2a4eabfbbd5e5592faf92 100644 (file)
@@ -81,8 +81,9 @@ public class CeQueueDao implements Dao {
 
   public CeQueueDto insert(DbSession session, CeQueueDto dto) {
     if (dto.getCreatedAt() == 0L || dto.getUpdatedAt() == 0L) {
-      dto.setCreatedAt(system2.now());
-      dto.setUpdatedAt(system2.now());
+      long now = system2.now();
+      dto.setCreatedAt(now);
+      dto.setUpdatedAt(now);
     }
 
     mapper(session).insert(dto);
index d39b730e30c738112e922c25c05ab0ac5742a0ee..edda4a27b6c8139ab1456af258ebc5f4b0bea3ec 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.db.ce;
 
-import com.google.common.base.MoreObjects;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
@@ -36,6 +35,14 @@ public class CeQueueDto {
   private String componentUuid;
   private Status status;
   private String submitterLogin;
+  /**
+   * UUID of the worker that is executing, or of the last worker that executed, the current task.
+   */
+  private String workerUuid;
+  /**
+   * This counter is incremented by 1 each time the tasks switches to status {@link Status#IN_PROGRESS IN_PROGRESS}.
+   */
+  private int executionCount = 0;
   private Long startedAt;
   private long createdAt;
   private long updatedAt;
@@ -91,6 +98,26 @@ public class CeQueueDto {
     return this;
   }
 
+  public String getWorkerUuid() {
+    return workerUuid;
+  }
+
+  public CeQueueDto setWorkerUuid(@Nullable String workerUuid) {
+    checkArgument(workerUuid == null || workerUuid.length() <= 40, "worker uuid is too long: %s", workerUuid);
+    this.workerUuid = workerUuid;
+    return this;
+  }
+
+  public int getExecutionCount() {
+    return executionCount;
+  }
+
+  public CeQueueDto setExecutionCount(int executionCount) {
+    checkArgument(executionCount >= 0, "execution count can't be < 0");
+    this.executionCount = executionCount;
+    return this;
+  }
+
   @CheckForNull
   public Long getStartedAt() {
     return startedAt;
@@ -121,16 +148,18 @@ public class CeQueueDto {
 
   @Override
   public String toString() {
-    return MoreObjects.toStringHelper(this)
-      .add("uuid", uuid)
-      .add("taskType", taskType)
-      .add("componentUuid", componentUuid)
-      .add("status", status)
-      .add("submitterLogin", submitterLogin)
-      .add("startedAt", startedAt)
-      .add("createdAt", createdAt)
-      .add("updatedAt", updatedAt)
-      .toString();
+    return "CeQueueDto{" +
+      "uuid='" + uuid + '\'' +
+      ", taskType='" + taskType + '\'' +
+      ", componentUuid='" + componentUuid + '\'' +
+      ", status=" + status +
+      ", submitterLogin='" + submitterLogin + '\'' +
+      ", workerUuid='" + workerUuid + '\'' +
+      ", executionCount=" + executionCount +
+      ", startedAt=" + startedAt +
+      ", createdAt=" + createdAt +
+      ", updatedAt=" + updatedAt +
+      '}';
   }
 
   @Override
index 8e03d719f5cb762a9f0d29826b7a3cbcac797584..f632e08e3b1babd706e6c1c90983b84030d7fa18 100644 (file)
@@ -9,6 +9,8 @@
     cq.component_uuid as componentUuid,
     cq.status as status,
     cq.submitter_login as submitterLogin,
+    cq.worker_uuid as workerUuid,
+    cq.execution_count as executionCount,
     cq.started_at as startedAt,
     cq.created_at as createdAt,
     cq.updated_at as updatedAt
       component_uuid,
       status,
       submitter_login,
+      worker_uuid,
+      execution_count,
       started_at,
       created_at,
       updated_at
       #{componentUuid,jdbcType=VARCHAR},
       #{status,jdbcType=VARCHAR},
       #{submitterLogin,jdbcType=VARCHAR},
+      #{workerUuid,jdbcType=VARCHAR},
+      #{executionCount,jdbcType=INTEGER},
       #{startedAt,jdbcType=BIGINT},
       #{createdAt,jdbcType=BIGINT},
       #{updatedAt,jdbcType=BIGINT}
index 42fddaa6e27cd7165bbd1cb43a615efb08534845..2459f78ec02faf6fd014b8eaa45208d85ce71e8c 100644 (file)
@@ -26,9 +26,12 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.utils.System2;
 import org.sonar.api.utils.internal.TestSystem2;
 import org.sonar.db.DbTester;
 
@@ -36,6 +39,8 @@ import static com.google.common.collect.FluentIterable.from;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.util.Collections.singletonList;
 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.ce.CeQueueDto.Status.IN_PROGRESS;
 import static org.sonar.db.ce.CeQueueDto.Status.PENDING;
 import static org.sonar.db.ce.CeQueueTesting.newCeQueueDto;
@@ -46,7 +51,11 @@ public class CeQueueDaoTest {
   private static final String TASK_UUID_2 = "TASK_2";
   private static final String COMPONENT_UUID_1 = "PROJECT_1";
   private static final String COMPONENT_UUID_2 = "PROJECT_2";
-  public static final String TASK_UUID_3 = "TASK_3";
+  private static final String TASK_UUID_3 = "TASK_3";
+  private static final String SELECT_QUEUE_UUID_AND_STATUS_QUERY = "select uuid,status from ce_queue";
+  private static final String SUBMITTER_LOGIN = "henri";
+  private static final String WORKER_UUID = "worker uuid";
+  private static final int EXECUTION_COUNT = 42;
 
   private TestSystem2 system2 = new TestSystem2().setNow(INIT_TIME);
 
@@ -54,14 +63,61 @@ public class CeQueueDaoTest {
   public DbTester db = DbTester.create(system2);
 
   private CeQueueDao underTest = new CeQueueDao(system2);
-  private static final String SELECT_QUEUE_UUID_AND_STATUS_QUERY = "select uuid,status from ce_queue";
 
   @Test
-  public void test_insert() {
-    insert(TASK_UUID_1, COMPONENT_UUID_1, PENDING);
+  public void insert_populates_createdAt_and_updateAt_from_System2_with_same_value_if_any_is_not_set() {
+    System2 system2 = mock(System2.class);
+    CeQueueDao ceQueueDao = new CeQueueDao(system2);
+    long now = 1_334_333L;
+    CeQueueDto dto = new CeQueueDto()
+      .setTaskType(CeTaskTypes.REPORT)
+      .setComponentUuid(COMPONENT_UUID_1)
+      .setStatus(PENDING)
+      .setSubmitterLogin(SUBMITTER_LOGIN)
+      .setWorkerUuid(WORKER_UUID)
+      .setExecutionCount(EXECUTION_COUNT);
+
+    mockSystem2ForSingleCall(system2, now);
+    ceQueueDao.insert(db.getSession(), dto.setUuid(TASK_UUID_1));
+    mockSystem2ForSingleCall(system2, now);
+    ceQueueDao.insert(db.getSession(), dto.setUuid(TASK_UUID_2).setCreatedAt(8_000_999L).setUpdatedAt(0));
+    mockSystem2ForSingleCall(system2, now);
+    ceQueueDao.insert(db.getSession(), dto.setUuid(TASK_UUID_3).setCreatedAt(0).setUpdatedAt(8_000_999L));
+    mockSystem2ForSingleCall(system2, now);
+    String uuid4 = "uuid 4";
+    ceQueueDao.insert(db.getSession(), dto.setUuid(uuid4).setCreatedAt(6_888_777L).setUpdatedAt(8_000_999L));
+    db.getSession().commit();
+
+    Stream.of(TASK_UUID_1, TASK_UUID_2, TASK_UUID_3)
+      .forEach(uuid -> {
+        CeQueueDto saved = underTest.selectByUuid(db.getSession(), uuid).get();
+        assertThat(saved.getUuid()).isEqualTo(uuid);
+        assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
+        assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
+        assertThat(saved.getStatus()).isEqualTo(PENDING);
+        assertThat(saved.getSubmitterLogin()).isEqualTo(SUBMITTER_LOGIN);
+        assertThat(saved.getWorkerUuid()).isEqualTo(WORKER_UUID);
+        assertThat(saved.getExecutionCount()).isEqualTo(EXECUTION_COUNT);
+        assertThat(saved.getCreatedAt()).isEqualTo(now);
+        assertThat(saved.getUpdatedAt()).isEqualTo(now);
+        assertThat(saved.getStartedAt()).isNull();
+      });
+    CeQueueDto saved = underTest.selectByUuid(db.getSession(), uuid4).get();
+    assertThat(saved.getUuid()).isEqualTo(uuid4);
+    assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
+    assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
+    assertThat(saved.getStatus()).isEqualTo(PENDING);
+    assertThat(saved.getSubmitterLogin()).isEqualTo(SUBMITTER_LOGIN);
+    assertThat(saved.getWorkerUuid()).isEqualTo(WORKER_UUID);
+    assertThat(saved.getExecutionCount()).isEqualTo(EXECUTION_COUNT);
+    assertThat(saved.getCreatedAt()).isEqualTo(6_888_777L);
+    assertThat(saved.getUpdatedAt()).isEqualTo(8_000_999L);
+    assertThat(saved.getStartedAt()).isNull();
+  }
 
-    Optional<CeQueueDto> saved = underTest.selectByUuid(db.getSession(), TASK_UUID_1);
-    assertThat(saved.isPresent()).isTrue();
+  private void mockSystem2ForSingleCall(System2 system2, long now) {
+    Mockito.reset(system2);
+    when(system2.now()).thenReturn(now).thenThrow(new IllegalStateException("now should be called only once"));
   }
 
   @Test
@@ -75,6 +131,8 @@ public class CeQueueDaoTest {
     assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
     assertThat(saved.getStatus()).isEqualTo(PENDING);
     assertThat(saved.getSubmitterLogin()).isEqualTo("henri");
+    assertThat(saved.getWorkerUuid()).isNull();
+    assertThat(saved.getExecutionCount()).isEqualTo(0);
     assertThat(saved.getCreatedAt()).isEqualTo(INIT_TIME);
     assertThat(saved.getUpdatedAt()).isEqualTo(INIT_TIME);
     assertThat(saved.getStartedAt()).isNull();
@@ -357,6 +415,6 @@ public class CeQueueDaoTest {
   }
 
   private static Map<String, Object> rowMap(String uuid, CeQueueDto.Status status) {
-    return ImmutableMap.<String, Object>of("UUID", uuid, "STATUS", status.name());
+    return ImmutableMap.of("UUID", uuid, "STATUS", status.name());
   }
 }
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDtoTest.java
new file mode 100644 (file)
index 0000000..60a78d7
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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.ce;
+
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class CeQueueDtoTest {
+  private static final String STR_15_CHARS = "012345678901234";
+  private static final String STR_40_CHARS = "0123456789012345678901234567890123456789";
+  private static final String STR_255_CHARS = STR_40_CHARS + STR_40_CHARS + STR_40_CHARS + STR_40_CHARS
+      + STR_40_CHARS + STR_40_CHARS + STR_15_CHARS;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private CeQueueDto underTest = new CeQueueDto();
+
+  @Test
+  public void setComponentUuid_accepts_null_empty_and_string_40_chars_or_less() {
+    underTest.setComponentUuid(null);
+    underTest.setComponentUuid("");
+    underTest.setComponentUuid("bar");
+    underTest.setComponentUuid(STR_40_CHARS);
+  }
+
+  @Test
+  public void setComponentUuid_throws_IAE_if_value_is_41_chars() {
+    String str_41_chars = STR_40_CHARS + "a";
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Value of component UUID is too long: " + str_41_chars);
+
+    underTest.setComponentUuid(str_41_chars);
+  }
+
+  @Test
+  public void setTaskType_throws_NPE_if_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+
+    underTest.setTaskType(null);
+  }
+
+  @Test
+  public void setTaskType_accepts_empty_and_string_15_chars_or_less() {
+    underTest.setTaskType("");
+    underTest.setTaskType("bar");
+    underTest.setTaskType(STR_15_CHARS);
+  }
+
+  @Test
+  public void setTaskType_throws_IAE_if_value_is_41_chars() {
+    String str_16_chars = STR_15_CHARS + "a";
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Value of task type is too long: " + str_16_chars);
+
+    underTest.setTaskType(str_16_chars);
+  }
+
+  @Test
+  public void setSubmitterLogin_accepts_null_empty_and_string_255_chars_or_less() {
+    underTest.setSubmitterLogin(null);
+    underTest.setSubmitterLogin("");
+    underTest.setSubmitterLogin("bar");
+    underTest.setSubmitterLogin(STR_255_CHARS);
+  }
+
+  @Test
+  public void setSubmitterLogin_throws_IAE_if_value_is_41_chars() {
+    String str_256_chars = STR_255_CHARS + "a";
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Value of submitter login is too long: " + str_256_chars);
+
+    underTest.setSubmitterLogin(str_256_chars);
+  }
+
+  @Test
+  public void setWorkerUuid_accepts_null_empty_and_string_40_chars_or_less() {
+    underTest.setWorkerUuid(null);
+    underTest.setWorkerUuid("");
+    underTest.setWorkerUuid("bar");
+    underTest.setWorkerUuid(STR_40_CHARS);
+  }
+
+  @Test
+  public void setWorkerUuid_throws_IAE_if_value_is_41_chars() {
+    String str_41_chars = STR_40_CHARS + "a";
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("worker uuid is too long: " + str_41_chars);
+
+    underTest.setWorkerUuid(str_41_chars);
+  }
+
+  @Test
+  public void setExecutionCount_throws_IAE_if_value_is_less_than_0() {
+    int lessThanZero = -1-(Math.abs(new Random().nextInt()));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("execution count can't be < 0");
+
+    underTest.setExecutionCount(lessThanZero);
+  }
+}
index 21faaebe6cf1cd6a94448fa42706a16c8ffe7623..0f95848a2acadb618113cac5720c3a442c6428a8 100644 (file)
@@ -21,11 +21,12 @@ package org.sonar.server.platform.db.migration.version.v64;
 
 import java.sql.SQLException;
 import org.sonar.db.Database;
-import org.sonar.server.platform.db.migration.def.IntegerColumnDef;
 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.IntegerColumnDef.newIntegerColumnDefBuilder;
+
 public class AddCeQueueWorkerUuidAndExecutionCount extends DdlChange {
 
   private static final String TABLE_CE_QUEUE = "ce_queue";
@@ -42,7 +43,7 @@ public class AddCeQueueWorkerUuidAndExecutionCount extends DdlChange {
         .setLimit(VarcharColumnDef.UUID_SIZE)
         .setIsNullable(true)
         .build())
-      .addColumn(IntegerColumnDef.newIntegerColumnDefBuilder()
+      .addColumn(newIntegerColumnDefBuilder()
         .setColumnName("execution_count")
         .setIsNullable(true)
         .build())
index a1101fd8932d72537cf893fa332e6f60fb942098..4c697448cef2bb9318413f9a2f0a6cec54c25366 100644 (file)
@@ -59,5 +59,6 @@ public class DbVersion64 implements DbVersion {
       .add(1627, "Delete permission templates linked to removed users", DeletePermissionTemplatesLinkedToRemovedUsers.class)
     ;
       .add(1628, "Add columns CE_QUEUE.WORKER_UUID and EXECUTION_COUNT", AddCeQueueWorkerUuidAndExecutionCount.class);
+      .add(1629, "Make CE_QUEUE.EXECUTION_COUNT not nullable", MakeCeQueueExecutionCountNotNullable.class);
   }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullable.java
new file mode 100644 (file)
index 0000000..cbe5e83
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.server.platform.db.migration.version.v64;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+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.IntegerColumnDef.newIntegerColumnDefBuilder;
+
+public class MakeCeQueueExecutionCountNotNullable extends DdlChange {
+
+  private static final String TABLE_CE_QUEUE = "ce_queue";
+
+  public MakeCeQueueExecutionCountNotNullable(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute("update ce_queue set execution_count = 0 where execution_count is null");
+
+    context.execute(new AlterColumnsBuilder(getDialect(), TABLE_CE_QUEUE)
+      .updateColumn(newIntegerColumnDefBuilder()
+        .setColumnName("execution_count")
+        .setIsNullable(false)
+        .build())
+      .build());
+  }
+}
index e6e311a60b87148af38143463ccb968f94b86b0e..e847aad7636f3a02e5af0a1500f0cd1af368458d 100644 (file)
@@ -35,7 +35,7 @@ public class DbVersion64Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 28);
+    verifyMigrationCount(underTest, 29);
   }
-
 }
+
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullableTest.java
new file mode 100644 (file)
index 0000000..5523e9a
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.server.platform.db.migration.version.v64;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MakeCeQueueExecutionCountNotNullableTest {
+
+  private static final String TABLE_CE_QUEUE = "ce_queue";
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(MakeCeQueueExecutionCountNotNullableTest.class, "ce_queue.sql");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private MakeCeQueueExecutionCountNotNullable underTest = new MakeCeQueueExecutionCountNotNullable(db.database());
+
+  @Test
+  public void execute_makes_column_execution_count_not_nullable_when_table_is_empty() throws SQLException {
+    underTest.execute();
+
+    verifyColumnDefinition();
+  }
+
+  @Test
+  public void execute_set_column_execution_count_to_0_and_not_nullable_no_matter_status_of_the_task() throws SQLException {
+    insertCeQueue("u1", Status.IN_PROGRESS);
+    insertCeQueue("u2", Status.PENDING);
+
+    underTest.execute();
+
+    verifyColumnDefinition();
+    assertThat(getUuidsForExecutionCount(0)).containsOnly("u1", "u2");
+    assertThat(getUuidsForExecutionCount(1)).isEmpty();
+  }
+
+  private List<Object> getUuidsForExecutionCount(int executionCount) {
+    return db.select("select uuid as \"UUID\" from ce_queue where execution_count=" + executionCount)
+        .stream()
+        .flatMap(row -> Stream.of(row.get("UUID")))
+        .collect(MoreCollectors.toList());
+  }
+
+  private void verifyColumnDefinition() {
+    db.assertColumnDefinition(TABLE_CE_QUEUE, "execution_count", Types.INTEGER, null, false);
+  }
+
+  private void insertCeQueue(String uuid, Status status) {
+    db.executeInsert(TABLE_CE_QUEUE,
+        "UUID", uuid,
+        "TASK_TYPE", uuid + "_type",
+        "STATUS", status.name(),
+        "CREATED_AT", new Random().nextLong() + "",
+        "UPDATED_AT", new Random().nextLong() + "");
+  }
+
+  public enum Status {
+    PENDING, IN_PROGRESS
+  }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullableTest/ce_queue.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/MakeCeQueueExecutionCountNotNullableTest/ce_queue.sql
new file mode 100644 (file)
index 0000000..61e8ab0
--- /dev/null
@@ -0,0 +1,16 @@
+CREATE TABLE "CE_QUEUE" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_TYPE" VARCHAR(15) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(40) NULL,
+  "STATUS" VARCHAR(15) NOT NULL,
+  "SUBMITTER_LOGIN" VARCHAR(255) NULL,
+  "WORKER_UUID" VARCHAR(40) NULL,
+  "EXECUTION_COUNT" INTEGER NULL,
+  "STARTED_AT" BIGINT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
+CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID");
+CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS");