]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9943 add InternalPropertiesDao#selectByKeys
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 12 Oct 2017 09:35:31 +0000 (11:35 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 23 Oct 2017 15:01:13 +0000 (08:01 -0700)
server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalPropertiesDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/property/InternalPropertiesMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/property/InternalPropertiesMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/property/InternalPropertiesDaoTest.java

index e9e500bc3e5a09b255659e9067a093aefc02cbca..b2bd5fe16c01876be2e97adec4482a1657731769 100644 (file)
  */
 package org.sonar.db.property;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.Loggers;
@@ -27,6 +38,8 @@ import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Collections.singletonList;
 
 public class InternalPropertiesDao implements Dao {
 
@@ -76,6 +89,50 @@ public class InternalPropertiesDao implements Dao {
     mapper.insertAsEmpty(key, system2.now());
   }
 
+  /**
+   * @return a Map with an {link Optional<String>} for each String in {@code keys}.
+   */
+  public Map<String, Optional<String>> selectByKeys(DbSession dbSession, @Nullable Set<String> keys) {
+    if (keys == null || keys.isEmpty()) {
+      return Collections.emptyMap();
+    }
+    if (keys.size() == 1) {
+      String key = keys.iterator().next();
+      return ImmutableMap.of(key, selectByKey(dbSession, key));
+    }
+    keys.forEach(InternalPropertiesDao::checkKey);
+
+    InternalPropertiesMapper mapper = getMapper(dbSession);
+    List<InternalPropertyDto> res = mapper.selectAsText(ImmutableList.copyOf(keys));
+    Map<String, Optional<String>> builder = new HashMap<>(keys.size());
+    res.forEach(internalPropertyDto -> {
+      String key = internalPropertyDto.getKey();
+      if (internalPropertyDto.isEmpty()) {
+        builder.put(key, OPTIONAL_OF_EMPTY_STRING);
+      }
+      if (internalPropertyDto.getValue() != null) {
+        builder.put(key, Optional.of(internalPropertyDto.getValue()));
+      }
+    });
+    // return Optional.empty() for all keys without a DB entry
+    Sets.difference(keys, res.stream().map(InternalPropertyDto::getKey).collect(Collectors.toSet()))
+      .forEach(key -> builder.put(key, Optional.empty()));
+    // keys for which there isn't a text or empty value found yet
+    List<String> keyWithClobValue = ImmutableList.copyOf(Sets.difference(keys, builder.keySet()));
+    if (keyWithClobValue.isEmpty()) {
+      return ImmutableMap.copyOf(builder);
+    }
+
+    // retrieve properties with a clob value
+    res = mapper.selectAsClob(keyWithClobValue);
+    res.forEach(internalPropertyDto -> builder.put(internalPropertyDto.getKey(), Optional.of(internalPropertyDto.getValue())));
+
+    // return Optional.empty() for all key with a DB entry which neither has text value, nor is empty nor has clob value
+    Sets.difference(ImmutableSet.copyOf(keyWithClobValue), builder.keySet()).forEach(key -> builder.put(key, Optional.empty()));
+
+    return ImmutableMap.copyOf(builder);
+  }
+
   /**
    * No streaming of value
    */
@@ -83,7 +140,7 @@ public class InternalPropertiesDao implements Dao {
     checkKey(key);
 
     InternalPropertiesMapper mapper = getMapper(dbSession);
-    InternalPropertyDto res = mapper.selectAsText(key);
+    InternalPropertyDto res = enforceSingleElement(key, mapper.selectAsText(singletonList(key)));
     if (res == null) {
       return Optional.empty();
     }
@@ -93,16 +150,26 @@ public class InternalPropertiesDao implements Dao {
     if (res.getValue() != null) {
       return Optional.of(res.getValue());
     }
-    res = mapper.selectAsClob(key);
+    res = enforceSingleElement(key, mapper.selectAsClob(singletonList(key)));
     if (res == null) {
       Loggers.get(InternalPropertiesDao.class)
         .debug("Internal property {} has been found in db but has neither text value nor is empty. " +
-          "Still we couldn't be retrieved with clob value. Ignoring the property.", key);
+          "Still it couldn't be retrieved with clob value. Ignoring the property.", key);
       return Optional.empty();
     }
     return Optional.of(res.getValue());
   }
 
+  @CheckForNull
+  private static InternalPropertyDto enforceSingleElement(String key, List<InternalPropertyDto> rows) {
+    if (rows.isEmpty()) {
+      return null;
+    }
+    int size = rows.size();
+    checkState(size <= 1, "%s rows retrieved for single property %s", size, key);
+    return rows.iterator().next();
+  }
+
   private static void checkKey(@Nullable String key) {
     checkArgument(key != null && !key.isEmpty(), "key can't be null nor empty");
   }
index be1e5b122b4e40d62cd850790184c662a79493b0..e329b1fc361d237215d52f50236c45f5d9d55eec 100644 (file)
  */
 package org.sonar.db.property;
 
+import java.util.List;
 import org.apache.ibatis.annotations.Param;
 
 public interface InternalPropertiesMapper {
-  InternalPropertyDto selectAsText(@Param("key") String key);
+  List<InternalPropertyDto> selectAsText(@Param("keys") List<String> key);
 
-  InternalPropertyDto selectAsClob(@Param("key") String key);
+  List<InternalPropertyDto> selectAsClob(@Param("keys") List<String> key);
 
   void insertAsEmpty(@Param("key") String key, @Param("createdAt") long createdAt);
 
index 7c0ddfcd31802d4190136a86ead96c467c382c8b..a606d9148a62597ce81dd76786f54957d671f58d 100644 (file)
@@ -5,24 +5,26 @@
 
   <select id="selectAsText" parameterType="map" resultType="InternalProperty">
     select
+      kee as "key",
       is_empty as empty,
       text_value as value,
       created_at as createdAt
     from
       internal_properties
     where
-      kee = #{key,jdbcType=VARCHAR}
+      kee in <foreach collection="keys" open="(" close=")" item="key" separator=",">#{key, jdbcType=VARCHAR}</foreach>
   </select>
 
   <select id="selectAsClob" parameterType="map" resultType="InternalProperty">
     select
+      kee as "key",
       is_empty as empty,
       clob_value as value,
       created_at as createdAt
     from
       internal_properties
     where
-      kee = #{key,jdbcType=VARCHAR}
+      kee in <foreach collection="keys" open="(" close=")" item="key" separator=",">#{key, jdbcType=VARCHAR}</foreach>
   </select>
 
   <insert id="insertAsEmpty" parameterType="Map" useGeneratedKeys="false">
index 53e74704c577c8e12c05e1d8f4cb935e0d16d618..babea6ffa755950d4b655d96fb7a2ce1f552ff08 100644 (file)
  */
 package org.sonar.db.property;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.assertj.core.api.AbstractAssert;
@@ -34,20 +44,26 @@ import org.sonar.db.DbTester;
 import static java.lang.Boolean.FALSE;
 import static java.lang.Boolean.TRUE;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 public class InternalPropertiesDaoTest {
 
   private static final String EMPTY_STRING = "";
   private static final String A_KEY = "a_key";
+  private static final String ANOTHER_KEY = "another_key";
   private static final String VALUE_1 = "one";
   private static final String VALUE_2 = "two";
   private static final long DATE_1 = 1_500_000_000_000L;
   private static final long DATE_2 = 1_600_000_000_000L;
   private static final String VALUE_SMALL = "some small value";
+  private static final String OTHER_VALUE_SMALL = "other small value";
   private static final String VALUE_SIZE_4000 = String.format("%1$4000.4000s", "*");
   private static final String VALUE_SIZE_4001 = VALUE_SIZE_4000 + "P";
+  private static final String OTHER_VALUE_SIZE_4001 = VALUE_SIZE_4000 + "D";
 
   private System2 system2 = mock(System2.class);
 
@@ -275,6 +291,111 @@ public class InternalPropertiesDaoTest {
     assertThat(underTest.selectByKey(dbSession, A_KEY)).contains(VALUE_SIZE_4001);
   }
 
+  @Test
+  public void selectByKeys_returns_empty_map_if_keys_is_null() {
+    assertThat(underTest.selectByKeys(dbSession, null)).isEmpty();
+  }
+
+  @Test
+  public void selectByKeys_returns_empty_map_if_keys_is_empty() {
+    assertThat(underTest.selectByKeys(dbSession, Collections.emptySet())).isEmpty();
+  }
+
+  @Test
+  public void selectByKeys_throws_IAE_when_keys_contains_null() {
+    Random random = new Random();
+    Set<String> keysIncludingANull = Stream.of(
+      IntStream.range(0, random.nextInt(10)).mapToObj(i -> "b_" + i),
+      Stream.of((String) null),
+      IntStream.range(0, random.nextInt(10)).mapToObj(i -> "a_" + i))
+      .flatMap(s -> s)
+      .collect(Collectors.toSet());
+
+    expectKeyNullOrEmptyIAE();
+
+    underTest.selectByKeys(dbSession, keysIncludingANull);
+  }
+
+  @Test
+  public void selectByKeys_throws_IAE_when_keys_contains_empty_string() {
+    Random random = new Random();
+    Set<String> keysIncludingAnEmptyString = Stream.of(
+      IntStream.range(0, random.nextInt(10)).mapToObj(i -> "b_" + i),
+      Stream.of(""),
+      IntStream.range(0, random.nextInt(10)).mapToObj(i -> "a_" + i))
+      .flatMap(s -> s)
+      .collect(Collectors.toSet());
+
+    expectKeyNullOrEmptyIAE();
+
+    underTest.selectByKeys(dbSession, keysIncludingAnEmptyString);
+  }
+
+  @Test
+  public void selectByKeys_returns_empty_optional_when_property_does_not_exist_in_DB() {
+    assertThat(underTest.selectByKeys(dbSession, ImmutableSet.of(A_KEY, ANOTHER_KEY)))
+      .containsOnly(
+        entry(A_KEY, Optional.empty()),
+        entry(ANOTHER_KEY, Optional.empty()));
+  }
+
+  @Test
+  public void selectByKeys_returns_empty_string_when_property_is_empty_in_DB() {
+    underTest.saveAsEmpty(dbSession, A_KEY);
+    underTest.saveAsEmpty(dbSession, ANOTHER_KEY);
+
+    assertThat(underTest.selectByKeys(dbSession, ImmutableSet.of(A_KEY, ANOTHER_KEY)))
+      .containsOnly(
+        entry(A_KEY, Optional.of("")),
+        entry(ANOTHER_KEY, Optional.of("")));
+  }
+
+  @Test
+  public void selectByKeys_returns_value_when_property_has_value_stored_in_varchar() {
+    underTest.save(dbSession, A_KEY, VALUE_SMALL);
+    underTest.save(dbSession, ANOTHER_KEY, OTHER_VALUE_SMALL);
+
+    assertThat(underTest.selectByKeys(dbSession, ImmutableSet.of(A_KEY, ANOTHER_KEY)))
+      .containsOnly(
+        entry(A_KEY, Optional.of(VALUE_SMALL)),
+        entry(ANOTHER_KEY, Optional.of(OTHER_VALUE_SMALL)));
+  }
+
+  @Test
+  public void selectByKeys_returns_values_when_properties_has_value_stored_in_clob() {
+    underTest.save(dbSession, A_KEY, VALUE_SIZE_4001);
+    underTest.save(dbSession, ANOTHER_KEY, OTHER_VALUE_SIZE_4001);
+
+    assertThat(underTest.selectByKeys(dbSession, ImmutableSet.of(A_KEY, ANOTHER_KEY)))
+      .containsOnly(
+        entry(A_KEY, Optional.of(VALUE_SIZE_4001)),
+        entry(ANOTHER_KEY, Optional.of(OTHER_VALUE_SIZE_4001)));
+  }
+
+  @Test
+  public void selectByKeys_queries_only_clob_properties_with_clob_SQL_query() {
+    underTest.saveAsEmpty(dbSession, A_KEY);
+    underTest.save(dbSession, "key2", VALUE_SMALL);
+    underTest.save(dbSession, "key3", VALUE_SIZE_4001);
+    Set<String> keys = ImmutableSet.of(A_KEY, "key2", "key3", "non_existent_key");
+    List<InternalPropertyDto> allInternalPropertyDtos = dbSession.getMapper(InternalPropertiesMapper.class).selectAsText(ImmutableList.copyOf(keys));
+    List<InternalPropertyDto> clobPropertyDtos = dbSession.getMapper(InternalPropertiesMapper.class).selectAsClob(ImmutableList.of("key3"));
+
+    InternalPropertiesMapper mapperMock = mock(InternalPropertiesMapper.class);
+    DbSession dbSessionMock = mock(DbSession.class);
+    when(dbSessionMock.getMapper(InternalPropertiesMapper.class)).thenReturn(mapperMock);
+    when(mapperMock.selectAsText(ImmutableList.copyOf(keys)))
+      .thenReturn(allInternalPropertyDtos);
+    when(mapperMock.selectAsClob(ImmutableList.of("key3")))
+      .thenReturn(clobPropertyDtos);
+
+    underTest.selectByKeys(dbSessionMock, keys);
+
+    verify(mapperMock).selectAsText(ImmutableList.copyOf(keys));
+    verify(mapperMock).selectAsClob(ImmutableList.of("key3"));
+    verifyNoMoreInteractions(mapperMock);
+  }
+
   private void expectKeyNullOrEmptyIAE() {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("key can't be null nor empty");