]> source.dussan.org Git - sonarqube.git/commitdiff
IN claus and group of OR should have constant order and no duplicate
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 28 Jan 2016 10:39:17 +0000 (11:39 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 29 Jan 2016 12:56:54 +0000 (13:56 +0100)
sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java
sonar-db/src/test/java/org/sonar/db/DatabaseUtilsTest.java
sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleKeyTest.java

index 7f45c22125d35b41b9df8558ab1564b0778f6bc0..5977fd7364224408e6238aeb3971bab335e02a4a 100644 (file)
 package org.sonar.db;
 
 import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.Sets;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -31,6 +34,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.utils.log.Logger;
@@ -117,19 +121,41 @@ public class DatabaseUtils {
    * The goal is to prevent issue with ORACLE when there's more than 1000 elements in a 'in ('X', 'Y', ...)'
    * and with MsSQL when there's more than 2000 parameters in a query
    */
-  public static <OUTPUT, INPUT> List<OUTPUT> executeLargeInputs(Collection<INPUT> input, Function<List<INPUT>, List<OUTPUT>> function) {
+  public static <OUTPUT, INPUT extends Comparable<INPUT>> List<OUTPUT> executeLargeInputs(Collection<INPUT> input, Function<List<INPUT>, List<OUTPUT>> function) {
     if (input.isEmpty()) {
       return Collections.emptyList();
     }
     List<OUTPUT> results = new ArrayList<>(input.size());
-    List<List<INPUT>> partitionList = Lists.partition(newArrayList(input), PARTITION_SIZE_FOR_ORACLE);
-    for (List<INPUT> partition : partitionList) {
+    for (List<INPUT> partition : toUniqueAndSortedPartitions(input)) {
       List<OUTPUT> subResults = function.apply(partition);
-      results.addAll(subResults);
+      if (subResults != null) {
+        results.addAll(subResults);
+      }
     }
     return results;
   }
 
+  /**
+   * Ensure values {@code inputs} are unique (which avoids useless arguments) and sorted before creating the partition.
+   */
+  private static <INPUT extends Comparable<INPUT>> Iterable<List<INPUT>> toUniqueAndSortedPartitions(Collection<INPUT> inputs) {
+    return Iterables.partition(toUniqueAndSortedList(inputs), PARTITION_SIZE_FOR_ORACLE);
+  }
+
+  /**
+   * Ensure values {@code inputs} are unique (which avoids useless arguments) and sorted so that there is little
+   * variations of SQL requests over time as possible with a IN clause and/or a group of OR clauses. Such requests can
+   * then be more easily optimized by the SGDB engine.
+   */
+  public static <INPUT extends Comparable<INPUT>> List<INPUT> toUniqueAndSortedList(Iterable<INPUT> inputs) {
+    if (inputs instanceof Set) {
+      // inputs are unique but order is not enforced
+      return Ordering.natural().immutableSortedCopy(inputs);
+    }
+    // inputs are not unique and order is not guaranteed
+    return Ordering.natural().immutableSortedCopy(Sets.newHashSet(inputs));
+  }
+
   /**
    * Partition by 1000 elements a list of input and execute a function on each part.
    * The function has not output (ex: delete operation)
index e6097677629475115793035d4043e2f62dcdb7dc..31c928808f149987a7b7a268dd15730cb9bef7dc 100644 (file)
@@ -27,10 +27,14 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
+import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.rules.ExpectedException;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
@@ -39,12 +43,14 @@ import org.sonar.db.dialect.Oracle;
 import org.sonar.test.DbTests;
 
 import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.fail;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.sonar.db.DatabaseUtils.buildLikeValue;
+import static org.sonar.db.DatabaseUtils.toUniqueAndSortedList;
 import static org.sonar.db.WildcardPosition.AFTER;
 import static org.sonar.db.WildcardPosition.BEFORE;
 import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
@@ -54,7 +60,8 @@ public class DatabaseUtilsTest {
 
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
   @Rule
   public LogTester logTester = new LogTester();
 
@@ -123,6 +130,83 @@ public class DatabaseUtilsTest {
     verify(rs).close(); // just to be sure
   }
 
+  @Test
+  public void toUniqueAndSortedList_throws_NPE_if_arg_is_null() {
+    expectedException.expect(NullPointerException.class);
+
+    toUniqueAndSortedList(null);
+  }
+
+  @Test
+  public void toUniqueAndSortedList_throws_NPE_if_arg_contains_a_null() {
+    expectedException.expect(NullPointerException.class);
+
+    toUniqueAndSortedList(asList("A", null, "C"));
+  }
+
+  @Test
+  public void toUniqueAndSortedList_throws_NPE_if_arg_is_a_set_containing_a_null() {
+    expectedException.expect(NullPointerException.class);
+
+    toUniqueAndSortedList(new HashSet<Comparable>(asList("A", null, "C")));
+  }
+
+  @Test
+  public void toUniqueAndSortedList_enforces_natural_order() {
+    assertThat(toUniqueAndSortedList(asList("A", "B", "C"))).containsExactly("A", "B", "C");
+    assertThat(toUniqueAndSortedList(asList("B", "A", "C"))).containsExactly("A", "B", "C");
+    assertThat(toUniqueAndSortedList(asList("B", "C", "A"))).containsExactly("A", "B", "C");
+  }
+
+  @Test
+  public void toUniqueAndSortedList_removes_duplicates() {
+    assertThat(toUniqueAndSortedList(asList("A", "A", "A"))).containsExactly("A");
+    assertThat(toUniqueAndSortedList(asList("A", "C", "A"))).containsExactly("A", "C");
+    assertThat(toUniqueAndSortedList(asList("C", "C", "B", "B", "A", "N", "C", "A"))).containsExactly("A", "B", "C", "N");
+  }
+
+  @Test
+  public void toUniqueAndSortedList_removes_duplicates_and_apply_natural_order_of_any_Comparable() {
+    assertThat(
+      toUniqueAndSortedList(asList(myComparable(2), myComparable(5), myComparable(2), myComparable(4), myComparable(-1), myComparable(10))))
+      .containsExactly(
+        myComparable(-1), myComparable(2), myComparable(4), myComparable(5), myComparable(10));
+  }
+
+  private static DatabaseUtilsTest.MyComparable myComparable(int ordinal) {
+    return new DatabaseUtilsTest.MyComparable(ordinal);
+  }
+
+  private static final class MyComparable implements Comparable<MyComparable> {
+    private final int ordinal;
+
+    private MyComparable(int ordinal) {
+      this.ordinal = ordinal;
+    }
+
+    @Override
+    public int compareTo(MyComparable o) {
+      return ordinal - o.ordinal;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      MyComparable that = (MyComparable) o;
+      return ordinal == that.ordinal;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(ordinal);
+    }
+  }
+
   /**
    * Connection.isClosed() has been introduced in java 1.6
    */
index 6e78677885b0e88a2e8a34daed3de904ad45ad97..56517648f1907e3325b005b48daf58702f994e81 100644 (file)
@@ -21,8 +21,8 @@ package org.sonar.api.rule;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-
 import java.io.Serializable;
+import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
 /**
@@ -31,7 +31,7 @@ import javax.annotation.concurrent.Immutable;
  * @since 3.6
  */
 @Immutable
-public class RuleKey implements Serializable {
+public class RuleKey implements Serializable, Comparable<RuleKey> {
 
   public static final String MANUAL_REPOSITORY_KEY = "manual";
   private final String repository;
@@ -82,7 +82,7 @@ public class RuleKey implements Serializable {
   }
 
   @Override
-  public boolean equals(Object o) {
+  public boolean equals(@Nullable Object o) {
     if (this == o) {
       return true;
     }
@@ -90,10 +90,7 @@ public class RuleKey implements Serializable {
       return false;
     }
     RuleKey ruleKey = (RuleKey) o;
-    if (!repository.equals(ruleKey.repository)) {
-      return false;
-    }
-    return rule.equals(ruleKey.rule);
+    return repository.equals(ruleKey.repository) && rule.equals(ruleKey.rule);
   }
 
   @Override
@@ -110,4 +107,13 @@ public class RuleKey implements Serializable {
   public String toString() {
     return String.format("%s:%s", repository, rule);
   }
+
+  @Override
+  public int compareTo(RuleKey o) {
+    int compareRepositories = this.repository.compareTo(o.repository);
+    if (compareRepositories == 0) {
+      return this.rule.compareTo(o.rule);
+    }
+    return compareRepositories;
+  }
 }
index 52ef4baabeeded88f14b15ab2ea4756800e77384..1547649378310f820f5fa661f855b3b0fd99cca1 100644 (file)
@@ -121,4 +121,17 @@ public class RuleKeyTest {
     assertThat(key1.hashCode()).isEqualTo(key1.hashCode());
     assertThat(key1.hashCode()).isEqualTo(key2.hashCode());
   }
+
+  @Test
+  public void test_compareTo() {
+    RuleKey aa = RuleKey.of("A", "A");
+    RuleKey ab = RuleKey.of("A", "B");
+
+    assertThat(ab).isGreaterThan(aa);
+    assertThat(aa).isLessThan(ab);
+    assertThat(aa).isNotEqualTo(ab);
+    assertThat(ab).isNotEqualTo(aa);
+    assertThat(aa).isEqualTo(aa);
+    assertThat(ab).isEqualTo(ab);
+  }
 }