]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7676 PropertiesDao supports IS_EMPTY, TEXT_VALUE and CLOB_VALUE
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 2 Sep 2016 12:00:58 +0000 (14:00 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 9 Sep 2016 07:11:42 +0000 (09:11 +0200)
66 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/db/ReadOnlyPropertiesDao.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/db/ReadOnlyPropertiesDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/platform/PersistentSettings.java
server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataPersister.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java
server/sonar-server/src/main/java/org/sonar/server/serverid/ws/GenerateAction.java
server/sonar-server/src/main/java/org/sonar/server/setting/ws/SetAction.java
server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/SettingsRepositoryTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/PersistentSettingsTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/QgateProjectFinderTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGatesTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/DeselectActionTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/GetByProjectActionTest.java
server/sonar-server/src/test/java/org/sonar/server/setting/DatabaseSettingLoaderTest.java
server/sonar-server/src/test/java/org/sonar/server/setting/ws/SettingsFinderTest.java
server/sonar-server/src/test/java/org/sonar/server/startup/ClearRulesOverloadedDebtTest.java
server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentNavigationActionTest.java
server/sonar-server/src/test/resources/org/sonar/server/measure/MeasureFilterExecutorTest/shared.xml
server/sonar-server/src/test/resources/org/sonar/server/platform/BackendCleanupMediumTest/shared.xml
sonar-db/src/main/java/org/sonar/core/properties/PropertiesDao.java
sonar-db/src/main/java/org/sonar/db/MyBatis.java
sonar-db/src/main/java/org/sonar/db/property/PropertiesDao.java
sonar-db/src/main/java/org/sonar/db/property/PropertiesMapper.java
sonar-db/src/main/java/org/sonar/db/property/PropertyDto.java
sonar-db/src/main/java/org/sonar/db/property/ScrapPropertyDto.java [new file with mode: 0644]
sonar-db/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
sonar-db/src/test/java/org/sonar/db/property/PropertiesDaoTest.java
sonar-db/src/test/java/org/sonar/db/property/PropertiesRow.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/property/PropertiesRowAssert.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/property/PropertyDbTester.java
sonar-db/src/test/java/org/sonar/db/property/PropertyDtoAssert.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java
sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectPropertiesByResourceId.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_by_query.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_module_properties_tree.xml
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue-result.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue.xml [deleted file]
sonar-db/src/test/resources/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest/shared.xml

index b33bbff2ec86e15b01aaf092d12a9cfc67909b76..9049e0e47f29752cd73746ccbdb81a4b11ecfb3f 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.ce.db;
 
 import java.util.Map;
+import org.sonar.api.utils.System2;
 import org.sonar.core.properties.PropertiesDao;
 import org.sonar.db.DbSession;
 import org.sonar.db.MyBatis;
@@ -35,17 +36,17 @@ import org.sonar.db.property.PropertyDto;
  * </p>
  */
 public class ReadOnlyPropertiesDao extends PropertiesDao {
-  public ReadOnlyPropertiesDao(MyBatis mybatis) {
-    super(mybatis);
+  public ReadOnlyPropertiesDao(MyBatis mybatis, System2 system2) {
+    super(mybatis, system2);
   }
 
   @Override
-  public void insertProperty(DbSession session, PropertyDto property) {
+  public void saveProperty(DbSession session, PropertyDto property) {
     // do nothing
   }
 
   @Override
-  public void insertProperty(PropertyDto property) {
+  public void saveProperty(PropertyDto property) {
     // do nothing
   }
 
@@ -80,7 +81,7 @@ public class ReadOnlyPropertiesDao extends PropertiesDao {
   }
 
   @Override
-  public void insertGlobalProperties(Map<String, String> properties) {
+  public void saveGlobalProperties(Map<String, String> properties) {
     // do nothing
   }
 
index 258b34ed9e09d75e8dfaf277882c3f23ea1f15c9..fed55130524f70b0e098230f08764665db8f0fce 100644 (file)
@@ -121,7 +121,7 @@ public class ComputeEngineContainerImplTest {
 
   private void insertProperty(String key, String value) {
     PropertyDto dto = new PropertyDto().setKey(key).setValue(value);
-    dbTester.getDbClient().propertiesDao().insertProperty(dbTester.getSession(), dto);
+    dbTester.getDbClient().propertiesDao().saveProperty(dbTester.getSession(), dto);
     dbTester.commit();
   }
 }
index 68cb9b4635077c9eca4e6a54993bbfcf8d9e4bd8..405175939205d349551cf31b139a5b0d6771d3f5 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.ce.db;
 
 import org.junit.Test;
+import org.sonar.api.utils.System2;
 import org.sonar.db.DbSession;
 import org.sonar.db.MyBatis;
 import org.sonar.db.property.PropertyDto;
@@ -32,18 +33,18 @@ public class ReadOnlyPropertiesDaoTest {
   private DbSession dbSession = mock(DbSession.class);
   private PropertyDto propertyDto = mock(PropertyDto.class);
   private org.sonar.core.properties.PropertyDto oldPropertyDto = mock(org.sonar.core.properties.PropertyDto.class);
-  private ReadOnlyPropertiesDao underTest = new ReadOnlyPropertiesDao(myBatis);
+  private ReadOnlyPropertiesDao underTest = new ReadOnlyPropertiesDao(myBatis, System2.INSTANCE);
 
   @Test
   public void insertProperty() {
-    underTest.insertProperty(dbSession, propertyDto);
+    underTest.saveProperty(dbSession, propertyDto);
 
     assertNoInteraction();
   }
 
   @Test
   public void insertProperty1() {
-    underTest.insertProperty(propertyDto);
+    underTest.saveProperty(propertyDto);
 
     assertNoInteraction();
   }
@@ -98,7 +99,7 @@ public class ReadOnlyPropertiesDaoTest {
 
   @Test
   public void insertGlobalProperties() {
-    underTest.insertGlobalProperties(null);
+    underTest.saveGlobalProperties(null);
 
     assertNoInteraction();
 
@@ -112,12 +113,18 @@ public class ReadOnlyPropertiesDaoTest {
 
   }
 
+  @Test
+  public void saveProperty() {
+    underTest.saveProperty(oldPropertyDto);
+
+    assertNoInteraction();
+  }
+
   @Test
   public void setProperty() {
     underTest.setProperty(oldPropertyDto);
 
     assertNoInteraction();
-
   }
 
   private void assertNoInteraction() {
index 454a8c11fcb1a6121fc7b71befa51710e10fd5b2..006ca5b87916fb7706de3911104ffb2a9eab7d46 100644 (file)
@@ -49,11 +49,7 @@ public class PersistentSettings {
    * are executed.
    */
   public PersistentSettings saveProperty(DbSession dbSession, String key, @Nullable String value) {
-    if (value == null) {
-      dbClient.propertiesDao().deleteGlobalProperty(key, dbSession);
-    } else {
-      dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(key).setValue(value));
-    }
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(key).setValue(value));
     // refresh the cache of settings
     delegate.setProperty(key, value);
 
index 10a63b2fd696d6ab84cd1f09167cc862d7321836..ac9ee2861ee97daab834bb281d1b0c4f2c310649 100644 (file)
@@ -55,7 +55,7 @@ public class StartupMetadataPersister implements Startable {
   }
 
   private void save(String key, String value) {
-    dbClient.propertiesDao().insertProperty(new PropertyDto().setKey(key).setValue(value));
+    dbClient.propertiesDao().saveProperty(new PropertyDto().setKey(key).setValue(value));
   }
 
   @Override
index aa49cd36df39faba39db965d013abb24017865bf..a82529336e82cf198a8dbbc579312e2f29c7f11d 100644 (file)
@@ -154,7 +154,7 @@ public class QualityGates {
       propertiesDao.deleteGlobalProperty(SONAR_QUALITYGATE_PROPERTY);
     } else {
       QualityGateDto newDefault = getNonNullQgate(idToUseAsDefault);
-      propertiesDao.insertProperty(new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY).setValue(newDefault.getId().toString()));
+      propertiesDao.saveProperty(new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY).setValue(newDefault.getId().toString()));
     }
   }
 
@@ -225,7 +225,7 @@ public class QualityGates {
     try {
       getNonNullQgate(qGateId);
       checkPermission(projectId, session);
-      propertiesDao.insertProperty(new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY).setResourceId(projectId).setValue(qGateId.toString()));
+      propertiesDao.saveProperty(new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY).setResourceId(projectId).setValue(qGateId.toString()));
     } finally {
       MyBatis.closeQuietly(session);
     }
index 0dcea16e9c1f12a72c015c325a5ad70041111fe1..8791715abcbe604dd72cc59f2f29bd6c335e0a8d 100644 (file)
@@ -95,7 +95,7 @@ public class SelectAction implements QualityGatesWsAction {
       checkQualityGate(dbClient, request.getGateId());
       ComponentDto project = getProject(dbSession, request.getProjectId(), request.getProjectKey());
 
-      dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto()
+      dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
         .setKey(SONAR_QUALITYGATE_PROPERTY)
         .setResourceId(project.getId())
         .setValue(String.valueOf(request.getGateId())));
index bfe0690f728e0b264d1fcb1e05d23f755d3500a9..4b6b50242ce18aac06d92ef2af34c57b89189b3d 100644 (file)
@@ -91,9 +91,9 @@ public class GenerateAction implements ServerIdWsAction {
 
   private GenerateWsResponse doHandle(DbSession dbSession, GenerateRequest request) {
     String serverId = generator.generate(request.getOrganization(), request.getIp());
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(PERMANENT_SERVER_ID).setValue(serverId));
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(ORGANISATION).setValue(request.getOrganization()));
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(SERVER_ID_IP_ADDRESS).setValue(request.getIp()));
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(PERMANENT_SERVER_ID).setValue(serverId));
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(ORGANISATION).setValue(request.getOrganization()));
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(SERVER_ID_IP_ADDRESS).setValue(request.getIp()));
     dbSession.commit();
     LOG.info("Generated new server ID={}", serverId);
 
index 2a9cd7337ef1061516fe1d433617f3f2682c4ade..142659594bd53d9e1dca13c2e3c5f59c65afe2eb 100644 (file)
@@ -168,7 +168,7 @@ public class SetAction implements SettingsWsAction {
       validate(request);
       PropertyDto property = toProperty(request, component);
       value = property.getValue();
-      dbClient.propertiesDao().insertProperty(dbSession, property);
+      dbClient.propertiesDao().saveProperty(dbSession, property);
     }
 
     dbSession.commit();
@@ -187,13 +187,13 @@ public class SetAction implements SettingsWsAction {
     Long componentId = component.isPresent() ? component.get().getId() : null;
 
     deleteSettings(dbSession, component, key);
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(key).setValue(inlinedFieldKeys).setResourceId(componentId));
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(key).setValue(inlinedFieldKeys).setResourceId(componentId));
 
     List<String> fieldValues = request.getFieldValues();
     IntStream.of(fieldIds).boxed()
       .flatMap(i -> readOneFieldValues(fieldValues.get(i - 1), request.getKey()).entrySet().stream()
-        .map(entry -> new KeyValue(key + "." + i + "." + entry.getKey(), entry.getValue())))
-      .forEach(keyValue -> dbClient.propertiesDao().insertProperty(dbSession, toFieldProperty(keyValue, componentId)));
+      .map(entry -> new KeyValue(key + "." + i + "." + entry.getKey(), entry.getValue())))
+      .forEach(keyValue -> dbClient.propertiesDao().saveProperty(dbSession, toFieldProperty(keyValue, componentId)));
 
     return inlinedFieldKeys;
   }
index 59c2f1c992d8c539e58dbef3c70c898b17856613..47759adbd9b1d6ff1e836252e749e4e1e060f085 100644 (file)
@@ -87,9 +87,9 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
     dbSession.commit();
 
@@ -109,9 +109,9 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
     dbSession.commit();
 
@@ -131,9 +131,9 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
     dbSession.commit();
 
@@ -151,18 +151,18 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
 
     ComponentDto module = ComponentTesting.newModuleDto(project);
     tester.get(DbClient.class).componentDao().insert(dbSession, module);
 
     // Module properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER").setResourceId(module.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(module.getId()));
 
     dbSession.commit();
@@ -185,9 +185,9 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
 
     ComponentDto module = ComponentTesting.newModuleDto(project);
@@ -214,25 +214,25 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
 
     ComponentDto module = ComponentTesting.newModuleDto(project);
     tester.get(DbClient.class).componentDao().insert(dbSession, module);
 
     // Module properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER").setResourceId(module.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(module.getId()));
 
     ComponentDto subModule = ComponentTesting.newModuleDto(module);
     tester.get(DbClient.class).componentDao().insert(dbSession, subModule);
 
     // Sub module properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(
+    tester.get(DbClient.class).propertiesDao().saveProperty(
       dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER-DAO").setResourceId(subModule.getId()));
 
     dbSession.commit();
@@ -259,25 +259,25 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
 
     ComponentDto module1 = ComponentTesting.newModuleDto(project);
     tester.get(DbClient.class).componentDao().insert(dbSession, module1);
 
     // Module 1 properties
     tester.get(DbClient.class).propertiesDao()
-      .insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER").setResourceId(module1.getId()));
+      .saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER").setResourceId(module1.getId()));
     // This property should not be found on the other module
     tester.get(DbClient.class).propertiesDao()
-      .insertProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(module1.getId()));
+      .saveProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(module1.getId()));
 
     ComponentDto module2 = ComponentTesting.newModuleDto(project);
     tester.get(DbClient.class).componentDao().insert(dbSession, module2);
 
     // Module 2 property
     tester.get(DbClient.class).propertiesDao()
-      .insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-APPLICATION").setResourceId(module2.getId()));
+      .saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-APPLICATION").setResourceId(module2.getId()));
 
     dbSession.commit();
 
@@ -303,8 +303,8 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
 
     dbSession.commit();
 
@@ -330,10 +330,10 @@ public class ProjectDataLoaderMediumTest {
     tester.get(DbClient.class).componentDao().insert(dbSession, subModule);
 
     // Sub module properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(subModule.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(subModule.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(subModule.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(subModule.getId()));
     tester.get(DbClient.class).propertiesDao()
-      .insertProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(subModule.getId()));
+      .saveProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(subModule.getId()));
 
     dbSession.commit();
 
@@ -353,13 +353,13 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project property
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
 
     ComponentDto module = ComponentTesting.newModuleDto(project);
     tester.get(DbClient.class).componentDao().insert(dbSession, module);
 
     // Module property
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(module.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(module.getId()));
 
     ComponentDto subModule = ComponentTesting.newModuleDto(module);
     userSessionRule.login("john").setGlobalPermissions(SCAN_EXECUTION);
@@ -367,7 +367,7 @@ public class ProjectDataLoaderMediumTest {
 
     // Sub module properties
     tester.get(DbClient.class).propertiesDao()
-      .insertProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(subModule.getId()));
+      .saveProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(subModule.getId()));
 
     dbSession.commit();
 
@@ -387,10 +387,10 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
     tester.get(DbClient.class).propertiesDao()
-      .insertProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(project.getId()));
+      .saveProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(project.getId()));
 
     ComponentDto module = ComponentTesting.newModuleDto(project);
     tester.get(DbClient.class).componentDao().insert(dbSession, module);
@@ -419,15 +419,15 @@ public class ProjectDataLoaderMediumTest {
     addDefaultProfile();
 
     // Project properties
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()));
     tester.get(DbClient.class).propertiesDao()
-      .insertProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(project.getId()));
+      .saveProperty(dbSession, new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java").setResourceId(project.getId()));
 
     ComponentDto module = ComponentTesting.newModuleDto(project);
     tester.get(DbClient.class).componentDao().insert(dbSession, module);
 
     // Module property
-    tester.get(DbClient.class).propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER").setResourceId(module.getId()));
+    tester.get(DbClient.class).propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER").setResourceId(module.getId()));
 
     ComponentDto subModule = ComponentTesting.newModuleDto(module);
     userSessionRule.login("john").setGlobalPermissions(SCAN_EXECUTION);
index c89fdb24d755430cd48b9badc6382ae1dc59c3f3..212ce5ae9965495eb2ee58f4cd3f27ff314cda9e 100644 (file)
@@ -78,7 +78,7 @@ public class SettingsRepositoryTest {
   public void get_project_settings_from_db() {
     ComponentDto project = ComponentTesting.newProjectDto().setKey(ROOT.getKey());
     dbClient.componentDao().insert(session, project);
-    dbClient.propertiesDao().insertProperty(session, new PropertyDto().setResourceId(project.getId()).setKey("key").setValue("value"));
+    dbClient.propertiesDao().saveProperty(session, new PropertyDto().setResourceId(project.getId()).setKey("key").setValue("value"));
     session.commit();
 
     Settings settings = underTest.getSettings(ROOT);
index 0239f6d8cbf225a5c43514fc11b0af50e84fabd7..044a1cfeaeda8634ae6269321c236faf56cde3bc 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.api.config.MapSettings;
 import org.sonar.api.config.Settings;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -54,7 +55,8 @@ public class PersistentSettingsTest {
     underTest.saveProperty("foo", null);
 
     assertThat(underTest.getString("foo")).isNull();
-    assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo")).isNull();
+    PropertyDto dto = dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo");
+    assertThat(dto.getValue()).isEmpty();
     verify(changeNotifier).onGlobalPropertyChange("foo", null);
   }
 
index a35f3b095f86487d436d268d54925de2cd62af8f..4a02d85b076415eb4d2b7b403bbfe6ee0873f9e1 100644 (file)
@@ -237,7 +237,7 @@ public class QgateProjectFinderTest {
   }
 
   private void associateProjectToQualitGate(long projectId) {
-    dbClient.propertiesDao().insertProperty(
+    dbClient.propertiesDao().saveProperty(
       new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY)
         .setResourceId(projectId)
         .setValue(Long.toString(qGate.getId())));
index 8d07a418e9d47233552d9fb028bf66dee9193cfa..61f22762d9a0e5eb690e0b0d9585529e94431d98 100644 (file)
@@ -233,7 +233,7 @@ public class QualityGatesTest {
 
     verify(dao).selectById(defaultId);
     ArgumentCaptor<PropertyDto> propertyCaptor = ArgumentCaptor.forClass(PropertyDto.class);
-    verify(propertiesDao).insertProperty(propertyCaptor.capture());
+    verify(propertiesDao).saveProperty(propertyCaptor.capture());
 
     assertThat(propertyCaptor.getValue().getKey()).isEqualTo("sonar.qualitygate");
     assertThat(propertyCaptor.getValue().getValue()).isEqualTo("42");
@@ -551,7 +551,7 @@ public class QualityGatesTest {
     underTest.associateProject(qGateId, projectId);
     verify(dao).selectById(qGateId);
     ArgumentCaptor<PropertyDto> propertyCaptor = ArgumentCaptor.forClass(PropertyDto.class);
-    verify(propertiesDao).insertProperty(propertyCaptor.capture());
+    verify(propertiesDao).saveProperty(propertyCaptor.capture());
     PropertyDto property = propertyCaptor.getValue();
     assertThat(property.getKey()).isEqualTo("sonar.qualitygate");
     assertThat(property.getResourceId()).isEqualTo(projectId);
@@ -568,7 +568,7 @@ public class QualityGatesTest {
     underTest.associateProject(qGateId, projectId);
     verify(dao).selectById(qGateId);
     ArgumentCaptor<PropertyDto> propertyCaptor = ArgumentCaptor.forClass(PropertyDto.class);
-    verify(propertiesDao).insertProperty(propertyCaptor.capture());
+    verify(propertiesDao).saveProperty(propertyCaptor.capture());
     PropertyDto property = propertyCaptor.getValue();
     assertThat(property.getKey()).isEqualTo("sonar.qualitygate");
     assertThat(property.getResourceId()).isEqualTo(projectId);
index b71b39365269a465f883e2fc6edc198d01b74a84..a37282721f6e33e1bcbd3929ccbe9ae929c79288 100644 (file)
@@ -244,7 +244,7 @@ public class DeselectActionTest {
   }
 
   private void associateProjectToQualityGate(long projectId, String gateId) {
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto()
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
       .setResourceId(projectId)
       .setValue(gateId)
       .setKey(SONAR_QUALITYGATE_PROPERTY));
index c0c3931d71570dcdfd611868dcbf77e38e9f22c9..d07e2cec6f90c6c517c6fffa89b282d7e5df564d 100644 (file)
@@ -228,7 +228,7 @@ public class GetByProjectActionTest {
   }
 
   private void associateProjectToQualityGate(long componentId, long qualityGateId) {
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto()
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
       .setKey("sonar.qualitygate")
       .setResourceId(componentId)
       .setValue(String.valueOf(qualityGateId)));
@@ -236,7 +236,7 @@ public class GetByProjectActionTest {
   }
 
   private void setDefaultQualityGate(long qualityGateId) {
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto()
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
       .setKey("sonar.qualitygate")
       .setValue(String.valueOf(qualityGateId)));
     db.commit();
index 5cc2a1c6b5bdd3256f3043bee4fcbe832b99f2fe..267089d228e06e0177ff4b23e838ddb5cf0a3a5b 100644 (file)
@@ -25,7 +25,6 @@ import org.junit.Test;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbTester;
 import org.sonar.db.property.PropertyDto;
-import org.sonar.server.setting.DatabaseSettingLoader;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.data.MapEntry.entry;
@@ -76,7 +75,7 @@ public class DatabaseSettingLoaderTest {
  }
 
   private void insertPropertyIntoDb(String key, String value) {
-    dbTester.getDbClient().propertiesDao().insertProperty(new PropertyDto().setKey(key).setValue(value));
+    dbTester.getDbClient().propertiesDao().saveProperty(new PropertyDto().setKey(key).setValue(value));
   }
 
 }
index f70a47fddb5859e8649fedf9e289d2575ad53ef6..83e7fe29a4f22bb8bb24aa2014e426bd130decb5 100644 (file)
@@ -197,7 +197,7 @@ public class SettingsFinderTest {
 
   private void insertProperties(PropertyDto... properties) {
     for (PropertyDto propertyDto : properties) {
-      dbClient.propertiesDao().insertProperty(dbSession, propertyDto);
+      dbClient.propertiesDao().saveProperty(dbSession, propertyDto);
     }
     dbSession.commit();
   }
index 85260daa9bf49cd4c5040cf3daea70c178a96c1b..64531b1483ce5c85c01cb6b73aae8febc563689f 100644 (file)
@@ -148,7 +148,7 @@ public class ClearRulesOverloadedDebtTest {
   }
 
   private void insertSqaleProperty() {
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.sqale.licenseHash.secured").setValue("ABCD"));
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("sonar.sqale.licenseHash.secured").setValue("ABCD"));
     dbSession.commit();
   }
 
index d9e0d745f66c3cca0de03797ad5474d4951464e0..a30fe1ea202d3fbdd7ec35e8a87e15b0659786cc 100644 (file)
@@ -128,7 +128,7 @@ public class ComponentNavigationActionTest {
     ComponentDto project = ComponentTesting.newProjectDto("abcd")
       .setKey("polop").setName("Polop");
     dbClient.componentDao().insert(dbTester.getSession(), project);
-    dbClient.propertiesDao().insertProperty(dbTester.getSession(), new PropertyDto().setKey("favourite").setResourceId(project.getId()).setUserId((long) userId));
+    dbClient.propertiesDao().saveProperty(dbTester.getSession(), new PropertyDto().setKey("favourite").setResourceId(project.getId()).setUserId((long) userId));
     dbTester.getSession().commit();
 
     userSessionRule.login("obiwan").setUserId(userId).addProjectUuidPermissions(UserRole.USER, "abcd");
index 8a870e0ab2cdae4c54277eebb9616bf96d64aec4..1dc8bd5a4d8206a33117de217fcc90d8aa7862cc 100644 (file)
   <properties id="1"
               prop_key="favourite"
               resource_id="3"
+              user_id="50"
               text_value="[null]"
-              user_id="50"/>
+              is_empty="[true]"/>
   <properties id="2"
               prop_key="favourite"
               resource_id="10"
+              user_id="50"
               text_value="[null]"
-              user_id="50"/>
+              is_empty="[true]"/>
 
   <!-- another properties -->
   <properties id="3"
               prop_key="favourite"
               resource_id="1"
+              user_id="1234"
               text_value="[null]"
-              user_id="1234"/>
+              is_empty="[true]"/>
   <properties id="4"
               prop_key="sonar.profile"
               resource_id="1"
+              user_id="[null]"
               text_value="Sonar way"
-              user_id="[null]"/>
+              is_empty="[true]"/>
 
 </dataset>
index 24e2b2ac42fb0c67ca045890766b5fbb2e80b398..c34799a9cff191abe0c3d6fabea7d967a3d154f9 100644 (file)
@@ -54,8 +54,9 @@
 
   <properties id="1"
               prop_key="sonar.profile.java"
-              text_value="Sonar Way"
               resource_id="1"
-              user_id="[null]"/>
+              user_id="[null]"
+              is_empty="[true]"
+              text_value="Sonar Way"/>
 
 </dataset>
index 2c97e6f2561a60a4c0b07bca3e25be54f60ff90d..2c64a11ec9c3b78da4b6c5e1dace6dfa3531f29f 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.core.properties;
 
+import org.sonar.api.utils.System2;
 import org.sonar.db.MyBatis;
 
 /**
@@ -29,11 +30,11 @@ import org.sonar.db.MyBatis;
 @Deprecated
 public class PropertiesDao extends org.sonar.db.property.PropertiesDao {
 
-  public PropertiesDao(MyBatis mybatis) {
-    super(mybatis);
+  public PropertiesDao(MyBatis mybatis, System2 system2) {
+    super(mybatis, system2);
   }
 
   public void setProperty(PropertyDto property) {
-    super.insertProperty(property);
+    super.saveProperty(property);
   }
 }
index c9df79927b755cdb9f4fdead413098a2f7f38e10..e2d37996d3cfdb3e320e6a9c8ed16b888b73d88d 100644 (file)
@@ -95,7 +95,7 @@ import org.sonar.db.permission.template.PermissionTemplateUserDto;
 import org.sonar.db.property.InternalPropertiesMapper;
 import org.sonar.db.property.InternalPropertyDto;
 import org.sonar.db.property.PropertiesMapper;
-import org.sonar.db.property.PropertyDto;
+import org.sonar.db.property.ScrapPropertyDto;
 import org.sonar.db.purge.IdUuidPair;
 import org.sonar.db.purge.PurgeMapper;
 import org.sonar.db.purge.PurgeableAnalysisDto;
@@ -176,7 +176,7 @@ public class MyBatis {
     confBuilder.loadAlias("MeasureFilter", MeasureFilterDto.class);
     confBuilder.loadAlias("MeasureFilterFavourite", MeasureFilterFavouriteDto.class);
     confBuilder.loadAlias("NotificationQueue", NotificationQueueDto.class);
-    confBuilder.loadAlias("Property", PropertyDto.class);
+    confBuilder.loadAlias("ScrapProperty", ScrapPropertyDto.class);
     confBuilder.loadAlias("InternalProperty", InternalPropertyDto.class);
     confBuilder.loadAlias("PurgeableAnalysis", PurgeableAnalysisDto.class);
     confBuilder.loadAlias("QualityGate", QualityGateDto.class);
index 86d8c594e96f4552fb0666a0336a891afbd02e50..7af8439133122cf70cc965d34765a8721fca58fe 100644 (file)
@@ -30,8 +30,8 @@ import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
 import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.System2;
 import org.sonar.db.Dao;
 import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbSession;
@@ -43,10 +43,14 @@ import static org.sonar.db.DatabaseUtils.executeLargeInputs;
 public class PropertiesDao implements Dao {
 
   private static final String NOTIFICATION_PREFIX = "notification.";
-  private MyBatis mybatis;
+  private static final int VARCHAR_MAXSIZE = 4000;
 
-  public PropertiesDao(MyBatis mybatis) {
+  private final MyBatis mybatis;
+  private final System2 system2;
+
+  public PropertiesDao(MyBatis mybatis, System2 system2) {
     this.mybatis = mybatis;
+    this.system2 = system2;
   }
 
   /**
@@ -130,15 +134,15 @@ public class PropertiesDao implements Dao {
   }
 
   @CheckForNull
-  public PropertyDto selectProjectProperty(long resourceId, String propertyKey) {
+  public PropertyDto selectProjectProperty(long componentId, String propertyKey) {
     try (DbSession session = mybatis.openSession(false)) {
-      return selectProjectProperty(session, resourceId, propertyKey);
+      return selectProjectProperty(session, componentId, propertyKey);
     }
   }
 
   @CheckForNull
-  public PropertyDto selectProjectProperty(DbSession dbSession, long resourceId, String propertyKey) {
-    return getMapper(dbSession).selectByKey(new PropertyDto().setKey(propertyKey).setResourceId(resourceId));
+  public PropertyDto selectProjectProperty(DbSession dbSession, long componentId, String propertyKey) {
+    return getMapper(dbSession).selectByKey(new PropertyDto().setKey(propertyKey).setResourceId(componentId));
   }
 
   public List<PropertyDto> selectByQuery(PropertyQuery query, DbSession session) {
@@ -162,24 +166,57 @@ public class PropertiesDao implements Dao {
     return executeLargeInputs(keys, partitionKeys -> getMapper(session).selectByKeys(partitionKeys, componentId));
   }
 
-  public void insertProperty(DbSession session, PropertyDto property) {
-    PropertiesMapper mapper = getMapper(session);
-    PropertyDto persistedProperty = mapper.selectByKey(property);
-    if (persistedProperty != null && !StringUtils.equals(persistedProperty.getValue(), property.getValue())) {
-      persistedProperty.setValue(property.getValue());
-      mapper.update(persistedProperty);
-    } else if (persistedProperty == null) {
-      mapper.insert(property);
+  /**
+   * Saves the specified property and its value.
+   * <p>
+   * If {@link PropertyDto#getValue()} is {@code null} or empty, the properties is persisted as empty.
+   * </p>
+   *
+   * @throws IllegalArgumentException if {@link PropertyDto#getKey()} is {@code null} or empty
+   */
+  public void saveProperty(DbSession session, PropertyDto property) {
+    save(getMapper(session), property.getKey(), property.getUserId(), property.getResourceId(), property.getValue());
+  }
+
+  private void save(PropertiesMapper mapper,
+    String key, @Nullable Long userId, @Nullable Long componentId,
+    @Nullable String value) {
+    checkKey(key);
+
+    long now = system2.now();
+    mapper.delete(key, userId, componentId);
+    if (isEmpty(value)) {
+      mapper.insertAsEmpty(key, userId, componentId, now);
+    } else if (mustBeStoredInClob(value)) {
+      mapper.insertAsClob(key, userId, componentId, value, now);
+    } else {
+      mapper.insertAsText(key, userId, componentId, value, now);
     }
   }
 
-  public void insertProperty(PropertyDto property) {
+  private static boolean mustBeStoredInClob(String value) {
+    return value.length() > VARCHAR_MAXSIZE;
+  }
+
+  private static void checkKey(@Nullable String key) {
+    checkArgument(!isEmpty(key), "key can't be null nor empty");
+  }
+
+  private static boolean isEmpty(@Nullable String str) {
+    return str == null || str.isEmpty();
+  }
+
+  public void saveProperty(PropertyDto property) {
     try (DbSession session = mybatis.openSession(false)) {
-      insertProperty(session, property);
+      saveProperty(session, property);
       session.commit();
     }
   }
 
+  public int delete(DbSession dbSession, PropertyDto dto) {
+    return getMapper(dbSession).delete(dto.getKey(), dto.getUserId(), dto.getResourceId());
+  }
+
   public void deleteById(DbSession dbSession, long id) {
     getMapper(dbSession).deleteById(id);
   }
@@ -217,23 +254,17 @@ public class PropertiesDao implements Dao {
     }
   }
 
-  public void insertGlobalProperties(Map<String, String> properties) {
+  public void saveGlobalProperties(Map<String, String> properties) {
     try (DbSession session = mybatis.openSession(false)) {
       PropertiesMapper mapper = getMapper(session);
-      properties.entrySet().forEach(entry -> delete(mapper, entry));
-      properties.entrySet().forEach(entry -> insert(mapper, entry));
+      properties.entrySet().forEach(entry -> {
+        mapper.deleteGlobalProperty(entry.getKey());
+        save(mapper, entry.getKey(), null, null, entry.getValue());
+      });
       session.commit();
     }
   }
 
-  private static void delete(PropertiesMapper mapper, Map.Entry<String, String> entry) {
-    mapper.deleteGlobalProperty(entry.getKey());
-  }
-
-  private static void insert(PropertiesMapper mapper, Map.Entry<String, String> entry) {
-    mapper.insert(new PropertyDto().setKey(entry.getKey()).setValue(entry.getValue()));
-  }
-
   public void renamePropertyKey(String oldKey, String newKey) {
     checkArgument(!Strings.isNullOrEmpty(oldKey), "Old property key must not be empty");
     checkArgument(!Strings.isNullOrEmpty(newKey), "New property key must not be empty");
index bb58987065d37545cc5d5d0437a1b1df8fa26dca..e438c73c8c907268d6949ac6cd0dc23303fd58c2 100644 (file)
@@ -44,18 +44,24 @@ public interface PropertiesMapper {
   List<PropertyDto> selectDescendantModuleProperties(@Param("moduleUuid") String moduleUuid, @Param(value = "scope") String scope,
     @Param(value = "excludeDisabled") boolean excludeDisabled);
 
-  void update(PropertyDto property);
+  void insertAsEmpty(@Param("key") String key, @Nullable @Param("userId") Long userId, @Nullable @Param("componentId") Long componentId,
+    @Param("now") long now);
 
-  void insert(PropertyDto property);
+  void insertAsText(@Param("key") String key, @Nullable @Param("userId") Long userId, @Nullable @Param("componentId") Long componentId,
+    @Param("value") String value, @Param("now") long now);
 
-  void deleteById(long id);
+  void insertAsClob(@Param("key") String key, @Nullable @Param("userId") Long userId, @Nullable @Param("componentId") Long componentId,
+    @Param("value") String value, @Param("now") long now);
 
-  void deleteProjectProperty(@Param("key") String key, @Param("rId") Long resourceId);
+  int delete(@Param("key") String key, @Nullable @Param("userId") Long userId, @Nullable @Param("componentId") Long componentId);
 
-  void deleteProjectProperties(@Param("key") String key, @Param("value") String value);
+  int deleteById(long id);
 
-  void deleteGlobalProperty(String key);
+  int deleteProjectProperty(@Param("key") String key, @Param("rId") Long componentId);
 
-  void renamePropertyKey(@Param("oldKey") String oldKey, @Param("newKey") String newKey);
+  int deleteProjectProperties(@Param("key") String key, @Param("value") String value);
 
+  int deleteGlobalProperty(@Param("key") String key);
+
+  int renamePropertyKey(@Param("oldKey") String oldKey, @Param("newKey") String newKey);
 }
index 404be7e30ada653ab4397f3567c69589a959f5bb..22f0998b3532d63bf539995743670337abd0cc86 100644 (file)
@@ -58,7 +58,7 @@ public class PropertyDto {
     return value;
   }
 
-  public PropertyDto setValue(String value) {
+  public PropertyDto setValue(@Nullable String value) {
     this.value = value;
     return this;
   }
@@ -91,12 +91,11 @@ public class PropertyDto {
     if (getClass() != obj.getClass()) {
       return false;
     }
-    final PropertyDto other = (PropertyDto) obj;
-
+    PropertyDto other = (PropertyDto) obj;
     return Objects.equals(this.key, other.key)
-      && Objects.equals(this.value, other.value)
       && Objects.equals(this.userId, other.userId)
-      && Objects.equals(this.resourceId, other.resourceId);
+      && Objects.equals(this.resourceId, other.resourceId)
+      && Objects.equals(this.value, other.value);
   }
 
   @Override
diff --git a/sonar-db/src/main/java/org/sonar/db/property/ScrapPropertyDto.java b/sonar-db/src/main/java/org/sonar/db/property/ScrapPropertyDto.java
new file mode 100644 (file)
index 0000000..ded0ac7
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.property;
+
+import javax.annotation.Nullable;
+
+public class ScrapPropertyDto extends PropertyDto {
+  public void setEmpty(boolean flag) {
+    if (flag) {
+      setValue("");
+    }
+  }
+
+  public void setTextValue(@Nullable String value) {
+    if (value != null) {
+      setValue(value);
+    }
+  }
+
+  public void setClobValue(@Nullable String value) {
+    if (value != null) {
+      setValue(value);
+    }
+  }
+
+}
index e94a1cc31d1ba8bda3252aa872d49fefc24ddb70..e3bae5b1baa37375b41e2f934ee7dc0852347d7e 100644 (file)
 <mapper namespace="org.sonar.db.property.PropertiesMapper">
 
   <select id="findUsersForNotification" parameterType="map" resultType="String">
-    select u.login
-    from users u
-    inner join properties p on p.user_id=u.id
-    <if test="projectUuid == null">
-      where p.prop_key = #{notifKey} and p.text_value LIKE 'true' and p.resource_id is null
-    </if>
-    <if test="projectUuid != null">
-      inner join projects c on c.id=p.resource_id
-      where p.prop_key = #{notifKey} AND p.text_value LIKE 'true'
-      and c.uuid = #{projectUuid} and p.resource_id is not null
-    </if>
+    select
+      u.login
+    from
+      users u
+    inner join properties p on
+      p.user_id=u.id
+    <choose>
+      <when test="projectUuid == null">
+        where
+          p.prop_key = #{notifKey}
+          and p.text_value = 'true'
+          and p.resource_id is null
+      </when>
+      <otherwise>
+        inner join projects c on
+          c.id=p.resource_id
+        where
+          p.prop_key = #{notifKey}
+          and p.text_value = 'true'
+          and c.uuid = #{projectUuid}
+          and p.resource_id is not null
+      </otherwise>
+    </choose>
   </select>
 
   <select id="findNotificationSubscribers" parameterType="map" resultType="String">
-    SELECT U.login
-    FROM properties P, users U
-    WHERE P.user_id = U.id AND P.prop_key = #{propKey} AND P.text_value LIKE 'true'
-    AND (
-    P.resource_id is null
-    <if test="componentKey != null">
-      OR P.resource_id in (select id from projects where kee=#{componentKey})
-    </if>
-    )
+    select
+      u.login
+    from
+      properties p,
+      users u
+    where
+      p.user_id = u.id
+      and p.prop_key = #{propKey}
+      and p.text_value like 'true'
+      and (
+        p.resource_id is null
+        <if test="componentKey != null">
+          or p.resource_id in (select id from projects where kee=#{componentKey})
+        </if>
+      )
   </select>
 
-  <select id="selectGlobalProperties" resultType="Property">
-    select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
-    from properties p
-    where p.resource_id is null and p.user_id is null
+  <sql id="columnsToScrapPropertyDto">
+    p.id as id,
+    p.prop_key as "key",
+    p.is_empty as empty,
+    p.text_value as textValue,
+    p.clob_value as clobValue,
+    p.resource_id as resourceId,
+    p.user_id as userId
+  </sql>
+
+  <select id="selectGlobalProperties" resultType="ScrapProperty">
+    select
+      <include refid="columnsToScrapPropertyDto"/>
+    from
+      properties p
+    where
+      p.resource_id is null
+      and p.user_id is null
   </select>
 
-  <select id="selectProjectProperties" parameterType="String" resultType="Property">
-    select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
-    from properties p, projects r
-    where p.resource_id=r.id and p.user_id is null and r.kee=#{resourceKey}
+  <select id="selectProjectProperties" parameterType="String" resultType="ScrapProperty">
+    select
+      <include refid="columnsToScrapPropertyDto"/>
+    from
+      properties p,
+      projects r
+    where
+      p.resource_id=r.id
+      and p.user_id is null
+      and r.kee=#{resourceKey}
   </select>
 
-  <select id="selectDescendantModuleProperties" parameterType="String" resultType="Property">
-    SELECT prop.id as id, prop.prop_key as "key", prop.text_value as value, prop.resource_id as resourceId, prop.user_id
-    as userId
-    FROM properties prop
-    INNER JOIN (SELECT p.id FROM projects p<include refid="org.sonar.db.component.ComponentMapper.modulesTreeQuery"/>)
-    modules on modules.id=prop.resource_id
-    WHERE prop.user_id IS NULL
+  <select id="selectDescendantModuleProperties" parameterType="String" resultType="ScrapProperty">
+    select
+      <include refid="columnsToScrapPropertyDto"/>
+    from
+      properties p
+    inner join (select p.id from projects p<include refid="org.sonar.db.component.ComponentMapper.modulesTreeQuery"/>) modules
+      on modules.id=p.resource_id
+    where
+      p.user_id is null
   </select>
 
-  <select id="selectByKey" parameterType="map" resultType="Property">
-    select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
-    from properties p
-    where p.prop_key=#{key}
-    <if test="resourceId == null">
-      AND p.resource_id is null
-    </if>
-    <if test="resourceId != null">
-      AND p.resource_id=#{resourceId}
-    </if>
-    <if test="userId == null">
-      AND p.user_id is null
-    </if>
-    <if test="userId != null">
-      AND p.user_id=#{userId}
-    </if>
+  <select id="selectByKey" parameterType="map" resultType="ScrapProperty">
+    select
+      <include refid="columnsToScrapPropertyDto"/>
+    from 
+      properties p
+    where 
+      p.prop_key=#{key}
+      <if test="resourceId == null">
+        and p.resource_id is null
+      </if>
+      <if test="resourceId != null">
+        and p.resource_id=#{resourceId}
+      </if>
+      <if test="userId == null">
+        and p.user_id is null
+      </if>
+      <if test="userId != null">
+        and p.user_id=#{userId}
+      </if>
   </select>
 
-  <select id="selectByKeys" parameterType="map" resultType="Property">
-    SELECT p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
-    FROM properties p
-    <where>
-      AND p.prop_key in
-      <foreach collection="keys" open="(" close=")" item="key" separator=",">
-        #{key}
-      </foreach>
+  <select id="selectByKeys" parameterType="map" resultType="ScrapProperty">
+    select
+      <include refid="columnsToScrapPropertyDto"/>
+    from
+      properties p
+    where
+      p.prop_key in
+        <foreach collection="keys" open="(" close=")" item="key" separator=",">
+          #{key}
+        </foreach>
       <if test="componentId == null">
-        AND p.resource_id is null
+        and p.resource_id is null
       </if>
       <if test="componentId != null">
-        AND p.resource_id=#{componentId}
+        and p.resource_id=#{componentId}
       </if>
-      AND p.user_id is null
-    </where>
+      and p.user_id is null
   </select>
 
-  <select id="selectByKeysAndComponentIds" parameterType="map" resultType="Property">
-    SELECT p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
-    FROM properties p
-    <where>
-      AND p.prop_key in
-      <foreach collection="keys" open="(" close=")" item="key" separator=",">
-        #{key}
-      </foreach>
-      AND p.resource_id in
-      <foreach collection="componentIds" open="(" close=")" item="componentId" separator=",">
-        #{componentId}
-      </foreach>
-      AND p.user_id is null
-    </where>
+  <select id="selectByKeysAndComponentIds" parameterType="map" resultType="ScrapProperty">
+    select
+      <include refid="columnsToScrapPropertyDto"/>
+    from 
+      properties p
+    where
+      p.prop_key in
+        <foreach collection="keys" open="(" close=")" item="key" separator=",">
+          #{key}
+        </foreach>
+      and p.resource_id in
+        <foreach collection="componentIds" open="(" close=")" item="componentId" separator=",">
+          #{componentId}
+        </foreach>
+      and p.user_id is null
   </select>
 
-  <select id="selectByQuery" parameterType="map" resultType="Property">
-    select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
-    from properties p
+  <select id="selectByQuery" parameterType="map" resultType="ScrapProperty">
+    select
+      <include refid="columnsToScrapPropertyDto"/>
+    from
+      properties p
     <where>
       <if test="query.key() != null">
-        AND p.prop_key=#{query.key}
+        and p.prop_key=#{query.key}
       </if>
       <if test="query.componentId() != null">
-        AND p.resource_id=#{query.componentId}
+        and p.resource_id=#{query.componentId}
       </if>
       <if test="query.userId() != null">
-        AND p.user_id=#{query.userId}
+        and p.user_id=#{query.userId}
       </if>
     </where>
   </select>
 
-  <update id="update" parameterType="Property">
-    update properties set text_value = #{value} where id = #{id}
-  </update>
+  <insert id="insertAsEmpty" parameterType="Map" useGeneratedKeys="false">
+    insert into properties
+    (
+      prop_key,
+      resource_id,
+      user_id,
+      is_empty,
+      created_at
+    )
+    values (
+      #{key},
+      #{componentId},
+      #{userId},
+      ${_true},
+      #{now}
+    )
+  </insert>
 
-  <insert id="insert" parameterType="Property" useGeneratedKeys="false">
-    INSERT INTO properties (prop_key, resource_id, user_id, text_value)
-    VALUES (#{key}, #{resourceId}, #{userId}, #{value})
+  <insert id="insertAsText" parameterType="Map" useGeneratedKeys="false">
+    insert into properties
+    (
+      prop_key,
+      resource_id,
+      user_id,
+      is_empty,
+      text_value,
+      created_at
+    )
+    values (
+      #{key},
+      #{componentId},
+      #{userId},
+      ${_false},
+      #{value},
+      #{now}
+    )
   </insert>
 
+  <insert id="insertAsClob" parameterType="Map" useGeneratedKeys="false">
+   insert into properties
+    (
+      prop_key,
+      resource_id,
+      user_id,
+      is_empty,
+      clob_value,
+      created_at
+    )
+    values (
+      #{key},
+      #{componentId},
+      #{userId},
+      ${_false},
+      #{value},
+      #{now}
+    )
+  </insert>
+
+  <delete id="delete" parameterType="map">
+    delete from properties
+    where
+      prop_key=#{key}
+    <choose>
+      <when test="componentId != null &amp;&amp; userId != null">
+        and resource_id=#{componentId}
+        and user_id=#{userId}
+      </when>
+      <when test="componentId != null">
+        and resource_id=#{componentId}
+        and user_id is null
+      </when>
+      <when test="userId != null">
+        and resource_id is null
+        and user_id=#{userId}
+      </when>
+      <otherwise>
+        and resource_id is null
+        and user_id is null
+      </otherwise>
+    </choose>
+  </delete>
+
   <delete id="deleteById" parameterType="long">
-    delete from properties where id=#{id}
+    delete from properties
+    where
+      id=#{id}
   </delete>
 
   <delete id="deleteProjectProperty" parameterType="map">
-    delete from properties where prop_key=#{key} and resource_id=#{rId} and user_id is null
+    delete from properties
+    where
+      prop_key=#{key}
+      and resource_id=#{rId}
+      and user_id is null
   </delete>
 
   <delete id="deleteProjectProperties" parameterType="map">
-    DELETE FROM properties
-    WHERE
-    prop_key=#{key}
-    AND text_value LIKE #{value}
-    AND resource_id IS NOT NULL
-    AND user_id IS NULL
+    delete from properties
+    where
+      prop_key=#{key}
+      and text_value = #{value}
+      and resource_id is not null
+      and user_id is null
   </delete>
 
   <delete id="deleteGlobalProperty" parameterType="string">
-    delete from properties where prop_key=#{id} and resource_id is null and user_id is null
+    delete from properties
+    where
+      prop_key=#{key}
+      and resource_id is null
+      and user_id is null
   </delete>
 
   <delete id="deleteGlobalProperties">
-    delete from properties where resource_id is null and user_id is null
+    delete from properties
+    where
+      resource_id is null
+      and user_id is null
   </delete>
 
   <update id="renamePropertyKey" parameterType="map">
-    update properties set prop_key=#{newKey} where prop_key=#{oldKey}
+    update properties set
+      prop_key=#{newKey}
+    where
+      prop_key=#{oldKey}
   </update>
 
 </mapper>
index 2a0b8c1b6bf3ca6fea4a1eed60c376493b563c37..4ad5862b347b4a1acf99e901382556aaaa549d92 100644 (file)
 package org.sonar.db.property;
 
 import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import javax.annotation.Nullable;
 import org.assertj.core.groups.Tuple;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -36,20 +45,30 @@ import org.sonar.db.user.UserDto;
 import org.sonar.db.user.UserTesting;
 
 import static com.google.common.collect.Sets.newHashSet;
-import static java.lang.String.valueOf;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
 import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
 import static org.sonar.db.property.PropertyTesting.newUserPropertyDto;
 
+@RunWith(DataProviderRunner.class)
 public class PropertiesDaoTest {
+  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 long DATE_1 = 1_555_000L;
+  private static final long DATE_2 = 1_666_000L;
+  private static final long DATE_3 = 1_777_000L;
+  private static final long DATE_4 = 1_888_000L;
+  private static final long DATE_5 = 1_999_000L;
+
+  private System2 system2 = mock(System2.class);
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
   @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  public DbTester dbTester = DbTester.create(system2);
 
   private DbClient dbClient = dbTester.getDbClient();
   private DbSession session = dbTester.getSession();
@@ -57,133 +76,162 @@ public class PropertiesDaoTest {
   private PropertiesDao underTest = dbTester.getDbClient().propertiesDao();
 
   @Test
-  public void shouldFindUsersForNotification() {
-    insertProject("uuid_45", 45);
-    insertProject("uuid_56", 56);
-    insertUser(1);
-    insertUser(2);
-    insertUser(3);
-    insertProperty(1, "notification.NewViolations.Email", "true", 45, 2);
-    insertProperty(2, "notification.NewViolations.Twitter", "true", null, 3);
-    insertProperty(3, "notification.NewViolations.Twitter", "true", 56, 1);
-    insertProperty(4, "notification.NewViolations.Twitter", "true", 56, 3);
-
-    List<String> users = underTest.selectUsersForNotification("NewViolations", "Email", null);
-    assertThat(users).isEmpty();
-
-    users = underTest.selectUsersForNotification("NewViolations", "Email", "uuid_78");
-    assertThat(users).isEmpty();
-
-    users = underTest.selectUsersForNotification("NewViolations", "Email", "uuid_45");
-    assertThat(users).hasSize(1);
-    assertThat(users).containsOnly("user2");
-
-    users = underTest.selectUsersForNotification("NewViolations", "Twitter", null);
-    assertThat(users).hasSize(1);
-    assertThat(users).containsOnly("user3");
-
-    users = underTest.selectUsersForNotification("NewViolations", "Twitter", "uuid_78");
-    assertThat(users).isEmpty();
-
-    users = underTest.selectUsersForNotification("NewViolations", "Twitter", "uuid_56");
-    assertThat(users).hasSize(2);
-    assertThat(users).containsOnly("user1", "user3");
+  public void shouldFindUsersForNotification() throws SQLException {
+    ComponentDto project1 = insertProject("uuid_45");
+    ComponentDto project2 = insertProject("uuid_56");
+    long userId1 = insertUser("user1");
+    long userId2 = insertUser("user2");
+    long userId3 = insertUser("user3");
+    insertProperty("notification.NewViolations.Email", "true", project1.getId(), userId2);
+    insertProperty("notification.NewViolations.Twitter", "true", null, userId3);
+    insertProperty("notification.NewViolations.Twitter", "true", project2.getId(), userId1);
+    insertProperty("notification.NewViolations.Twitter", "true", project2.getId(), userId3);
+
+    assertThat(underTest.selectUsersForNotification("NewViolations", "Email", null))
+      .isEmpty();
+
+    assertThat(underTest.selectUsersForNotification("NewViolations", "Email", "uuid_78"))
+      .isEmpty();
+
+    assertThat(underTest.selectUsersForNotification("NewViolations", "Email", "uuid_45"))
+      .hasSize(1).containsOnly("user2");
+
+    assertThat(underTest.selectUsersForNotification("NewViolations", "Twitter", null))
+      .hasSize(1)
+      .containsOnly("user3");
+
+    assertThat(underTest.selectUsersForNotification("NewViolations", "Twitter", "uuid_78"))
+      .isEmpty();
+
+    assertThat(underTest.selectUsersForNotification("NewViolations", "Twitter", "uuid_56"))
+      .hasSize(2)
+      .containsOnly("user1", "user3");
   }
 
   @Test
-  public void findNotificationSubscribers() {
-    insertUser(1);
-    insertUser(2);
-    insertProject("PROJECT_A", 42);
+  public void findNotificationSubscribers() throws SQLException {
+    long userId1 = insertUser("user1");
+    long userId2 = insertUser("user2");
+    ComponentDto projectDto = insertProject("PROJECT_A");
+    long projectId = projectDto.getId();
+    String projectKey = projectDto.key();
+
     // global subscription
-    insertProperty(1, "notification.DispatcherWithGlobalSubscribers.Email", "true", null, 2);
+    insertProperty("notification.DispatcherWithGlobalSubscribers.Email", "true", null, userId2);
     // project subscription
-    insertProperty(2, "notification.DispatcherWithProjectSubscribers.Email", "true", 42, 1);
-    insertProperty(3, "notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", 56, 1);
-    insertProperty(4, "notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", 42, 1);
+    insertProperty("notification.DispatcherWithProjectSubscribers.Email", "true", projectId, userId1);
+    insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", 56L, userId1);
+    insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", projectId, userId1);
     // global subscription
-    insertProperty(5, "notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", null, 2);
+    insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", null, userId2);
 
     // Nobody is subscribed
-    List<String> users = underTest.selectNotificationSubscribers("NotSexyDispatcher", "Email", "project42");
-    assertThat(users).isEmpty();
+    assertThat(underTest.selectNotificationSubscribers("NotSexyDispatcher", "Email", projectKey))
+      .isEmpty();
 
     // Global subscribers
-    users = underTest.selectNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", "project42");
-    assertThat(users).containsOnly("user2");
+    assertThat(underTest.selectNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", projectKey))
+      .containsOnly("user2");
 
-    users = underTest.selectNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", null);
-    assertThat(users).containsOnly("user2");
+    assertThat(underTest.selectNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", null))
+      .containsOnly("user2");
 
     // Project subscribers
-    users = underTest.selectNotificationSubscribers("DispatcherWithProjectSubscribers", "Email", "project42");
-    assertThat(users).containsOnly("user1");
+    assertThat(underTest.selectNotificationSubscribers("DispatcherWithProjectSubscribers", "Email", projectKey))
+      .containsOnly("user1");
 
     // Global + Project subscribers
-    users = underTest.selectNotificationSubscribers("DispatcherWithGlobalAndProjectSubscribers", "Email", "project42");
-    assertThat(users).containsOnly("user1", "user2");
+    assertThat(underTest.selectNotificationSubscribers("DispatcherWithGlobalAndProjectSubscribers", "Email", projectKey))
+      .containsOnly("user1", "user2");
   }
 
   @Test
-  public void hasNotificationSubscribers() {
-    insertUser(1);
-    insertUser(2);
-    insertProject("PROJECT_A", 42);
+  public void hasNotificationSubscribers() throws SQLException {
+    long userId1 = insertUser("user1");
+    long userId2 = insertUser("user2");
+    Long projectId = insertProject("PROJECT_A").getId();
     // global subscription
-    insertProperty(1, "notification.DispatcherWithGlobalSubscribers.Email", "true", null, 2);
+    insertProperty("notification.DispatcherWithGlobalSubscribers.Email", "true", null, userId2);
     // project subscription
-    insertProperty(2, "notification.DispatcherWithProjectSubscribers.Email", "true", 42, 1);
-    insertProperty(3, "notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", 56, 1);
-    insertProperty(4, "notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", 42, 1);
+    insertProperty("notification.DispatcherWithProjectSubscribers.Email", "true", projectId, userId1);
+    insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", 56L, userId1);
+    insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", projectId, userId1);
     // global subscription
-    insertProperty(5, "notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", null, 2);
+    insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", null, userId2);
 
     // Nobody is subscribed
-    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", singletonList("NotSexyDispatcher"))).isFalse();
+    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", singletonList("NotSexyDispatcher")))
+      .isFalse();
 
     // Global subscribers
-    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", singletonList("DispatcherWithGlobalSubscribers"))).isTrue();
+    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", singletonList("DispatcherWithGlobalSubscribers")))
+      .isTrue();
 
     // Project subscribers
-    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", singletonList("DispatcherWithProjectSubscribers"))).isTrue();
-    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", singletonList("DispatcherWithProjectSubscribers"))).isFalse();
+    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", singletonList("DispatcherWithProjectSubscribers")))
+      .isTrue();
+    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", singletonList("DispatcherWithProjectSubscribers")))
+      .isFalse();
 
     // Global + Project subscribers
-    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", singletonList("DispatcherWithGlobalAndProjectSubscribers"))).isTrue();
-    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", singletonList("DispatcherWithGlobalAndProjectSubscribers"))).isTrue();
+    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", singletonList("DispatcherWithGlobalAndProjectSubscribers")))
+      .isTrue();
+    assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", singletonList("DispatcherWithGlobalAndProjectSubscribers")))
+      .isTrue();
   }
 
   @Test
-  public void selectGlobalProperties() {
+  public void selectGlobalProperties() throws SQLException {
     // global
-    insertProperty(1, "global.one", "one", null, null);
-    insertProperty(2, "global.two", "two", null, null);
+    long id1 = insertProperty("global.one", "one", null, null);
+    long id2 = insertProperty("global.two", "two", null, null);
 
     List<PropertyDto> properties = underTest.selectGlobalProperties();
-    assertThat(properties.size()).isEqualTo(2);
-
-    PropertyDto first = findById(properties, 1);
-    assertThat(first.getKey()).isEqualTo("global.one");
-    assertThat(first.getValue()).isEqualTo("one");
+    assertThat(properties.size())
+      .isEqualTo(2);
+
+    assertThatDto(findById(properties, id1))
+      .hasKey("global.one")
+      .hasNoUserId()
+      .hasNoResourceId()
+      .hasValue("one");
+
+    assertThatDto(findById(properties, id2))
+      .hasKey("global.two")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasValue("two");
+  }
 
-    PropertyDto second = findById(properties, 2);
-    assertThat(second.getKey()).isEqualTo("global.two");
-    assertThat(second.getValue()).isEqualTo("two");
+  @Test
+  @UseDataProvider("allValuesForSelect")
+  public void selectGlobalProperties_supports_all_values(String dbValue, String expected) throws SQLException {
+    insertProperty("global.one", dbValue, null, null);
+
+    List<PropertyDto> dtos = underTest.selectGlobalProperties();
+    assertThat(dtos)
+      .hasSize(1);
+    assertThatDto(dtos.iterator().next())
+      .hasKey("global.one")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasValue(expected);
   }
 
   @Test
-  public void selectGlobalProperty() {
+  public void selectGlobalProperty() throws SQLException {
     // global
-    insertProperty(1, "global.one", "one", null, null);
-    insertProperty(2, "global.two", "two", null, null);
+    insertProperty("global.one", "one", null, null);
+    insertProperty("global.two", "two", null, null);
     // project
-    insertProperty(3, "project.one", "one", 10, null);
+    insertProperty("project.one", "one", 10L, null);
     // user
-    insertProperty(4, "user.one", "one", null, 100);
+    insertProperty("user.one", "one", null, 100L);
 
-    PropertyDto prop = underTest.selectGlobalProperty("global.one");
-    assertThat(prop).isNotNull();
-    assertThat(prop.getValue()).isEqualTo("one");
+    assertThatDto(underTest.selectGlobalProperty("global.one"))
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasValue("one");
 
     assertThat(underTest.selectGlobalProperty("project.one")).isNull();
     assertThat(underTest.selectGlobalProperty("user.one")).isNull();
@@ -191,39 +239,80 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void selectProjectProperties() {
-    insertProject("A", 10);
+  @UseDataProvider("allValuesForSelect")
+  public void selectGlobalProperty_supports_all_values(String dbValue, String expected) throws SQLException {
+    insertProperty("global.one", dbValue, null, null);
+
+    assertThatDto(underTest.selectGlobalProperty("global.one"))
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasValue(expected);
+  }
+
+  @Test
+  public void selectProjectProperties() throws SQLException {
+    ComponentDto projectDto = insertProject("A");
+    long projectId = projectDto.getId();
     // global
-    insertProperty(1, "global.one", "one", null, null);
-    insertProperty(2, "global.two", "two", null, null);
+    insertProperty("global.one", "one", null, null);
+    insertProperty("global.two", "two", null, null);
     // project
-    insertProperty(3, "project.one", "one", 10, null);
-    insertProperty(4, "project.two", "two", 10, null);
+    long id3 = insertProperty("project.one", "Pone", projectId, null);
+    long id4 = insertProperty("project.two", "Ptwo", projectId, null);
+
+    List<PropertyDto> dtos = underTest.selectProjectProperties(projectDto.key());
+    assertThat(dtos)
+      .hasSize(2);
+    assertThatDto(findById(dtos, id3))
+      .hasKey("project.one")
+      .hasResourceId(projectId)
+      .hasValue("Pone");
+    assertThatDto(findById(dtos, id4))
+      .hasKey("project.two")
+      .hasResourceId(projectId)
+      .hasValue("Ptwo");
+  }
 
-    List<PropertyDto> properties = underTest.selectProjectProperties("project10");
-    assertThat(properties)
-      .hasSize(2)
-      .extracting("key", "value")
-      .containsOnly(tuple("project.one", "one"), tuple("project.two", "two"));
+  @Test
+  @UseDataProvider("allValuesForSelect")
+  public void selectProjectProperties_supports_all_values(String dbValue, String expected) throws SQLException {
+    ComponentDto projectDto = insertProject("A");
+    insertProperty("project.one", dbValue, projectDto.getId(), null);
+
+    List<PropertyDto> dtos = underTest.selectProjectProperties(projectDto.key());
+    assertThat(dtos).hasSize(1);
+    assertThatDto(dtos.iterator().next())
+      .hasKey("project.one")
+      .hasResourceId(projectDto.getId())
+      .hasValue(expected);
+  }
+
+  @DataProvider
+  public static Object[][] allValuesForSelect() {
+    return new Object[][] {
+      {null, ""},
+      {"", ""},
+      {"some value", "some value"},
+      {VALUE_SIZE_4000, VALUE_SIZE_4000},
+      {VALUE_SIZE_4001, VALUE_SIZE_4001}
+    };
   }
 
   @Test
-  public void selectProjectProperty() {
-    insertProject("A", 10);
-    // global
-    insertProperty(1, "global.one", "one", null, null);
-    insertProperty(2, "global.two", "two", null, null);
-    // project
-    insertProperty(3, "project.one", "one", 10, null);
+  public void selectProjectProperty() throws SQLException {
+    insertProperty("project.one", "one", 10L, null);
 
     PropertyDto property = underTest.selectProjectProperty(10L, "project.one");
 
-    assertThat(property.getKey()).isEqualTo("project.one");
-    assertThat(property.getValue()).isEqualTo("one");
+    assertThatDto(property)
+      .hasKey("project.one")
+      .hasResourceId(10L)
+      .hasNoUserId()
+      .hasValue("one");
   }
 
   @Test
-  public void select_module_properties_tree() {
+  public void selectEnabledDescendantModuleProperties() {
     dbTester.prepareDbUnit(getClass(), "select_module_properties_tree.xml");
 
     List<PropertyDto> properties = underTest.selectEnabledDescendantModuleProperties("ABCD", dbTester.getSession());
@@ -243,8 +332,38 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void select_by_query() {
-    dbTester.prepareDbUnit(getClass(), "select_by_query.xml");
+  @UseDataProvider("allValuesForSelect")
+  public void selectEnabledDescendantModuleProperties_supports_all_values(String dbValue, String expected) throws SQLException {
+    String projectUuid = "A";
+    ComponentDto project = ComponentTesting.newProjectDto(projectUuid);
+    dbClient.componentDao().insert(session, project);
+    long projectId = project.getId();
+    insertProperty("project.one", dbValue, projectId, null);
+
+    List<PropertyDto> dtos = underTest.selectEnabledDescendantModuleProperties(projectUuid, dbTester.getSession());
+    assertThat(dtos)
+      .hasSize(1);
+    assertThatDto(dtos.iterator().next())
+      .hasKey("project.one")
+      .hasResourceId(projectId)
+      .hasNoUserId()
+      .hasValue(expected);
+  }
+
+  @Test
+  public void select_by_query() throws SQLException {
+    // global
+    insertProperty("global.one", "one", null, null);
+    insertProperty("global.two", "two", null, null);
+    // struts
+    insertProperty("struts.one", "one", 10L, null);
+    // commons
+    insertProperty("commonslang.one", "one", 11L, null);
+    // user
+    insertProperty("user.one", "one", null, 100L);
+    insertProperty("user.two", "two", 10L, 100L);
+    // other
+    insertProperty("other.one", "one", 12L, null);
 
     List<PropertyDto> results = underTest.selectByQuery(PropertyQuery.builder().setKey("user.two").setComponentId(10L).setUserId(100).build(), dbTester.getSession());
     assertThat(results).hasSize(1);
@@ -257,24 +376,27 @@ public class PropertiesDaoTest {
 
   @Test
   public void select_global_properties_by_keys() throws Exception {
-    ComponentDto project = ComponentTesting.newProjectDto();
-    dbClient.componentDao().insert(session, project);
-    UserDto user = UserTesting.newUserDto();
-    dbClient.userDao().insert(session, user);
+    insertProject("A");
+    long userId = insertUser("B");
 
     String key = "key";
     String anotherKey = "anotherKey";
-    insertProperties(
-      newGlobalPropertyDto().setKey(key),
-      newComponentPropertyDto(project).setKey(key),
-      newUserPropertyDto(user).setKey(key),
-      newGlobalPropertyDto().setKey(anotherKey));
-
-    assertThat(underTest.selectGlobalPropertiesByKeys(session, newHashSet(key))).extracting("key").containsOnly(key);
-    assertThat(underTest.selectGlobalPropertiesByKeys(session, newHashSet(key, anotherKey))).extracting("key").containsOnly(key, anotherKey);
-    assertThat(underTest.selectGlobalPropertiesByKeys(session, newHashSet(key, anotherKey, "unknown"))).extracting("key").containsOnly(key, anotherKey);
-
-    assertThat(underTest.selectGlobalPropertiesByKeys(session, newHashSet("unknown"))).isEmpty();
+    insertProperty(key, "value", null, null);
+    insertProperty(key, "value", 10L, null);
+    insertProperty(key, "value", null, userId);
+    insertProperty(anotherKey, "value", null, null);
+
+    assertThat(underTest.selectGlobalPropertiesByKeys(session, newHashSet(key)))
+      .extracting("key")
+      .containsOnly(key);
+    assertThat(underTest.selectGlobalPropertiesByKeys(session, newHashSet(key, anotherKey)))
+      .extracting("key")
+      .containsOnly(key, anotherKey);
+    assertThat(underTest.selectGlobalPropertiesByKeys(session, newHashSet(key, anotherKey, "unknown")))
+      .extracting("key")
+      .containsOnly(key, anotherKey);
+    assertThat(underTest.selectGlobalPropertiesByKeys(session, newHashSet("unknown")))
+      .isEmpty();
   }
 
   @Test
@@ -337,99 +459,527 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void setProperty_update() {
-    dbTester.prepareDbUnit(getClass(), "update.xml");
+  public void saveProperty_inserts_global_properties_when_they_do_not_exist_in_db() {
+    when(system2.now()).thenReturn(DATE_1, DATE_2, DATE_3, DATE_4, DATE_5);
+
+    underTest.saveProperty(new PropertyDto().setKey("global.null").setValue(null));
+    underTest.saveProperty(new PropertyDto().setKey("global.empty").setValue(""));
+    underTest.saveProperty(new PropertyDto().setKey("global.text").setValue("some text"));
+    underTest.saveProperty(new PropertyDto().setKey("global.4000").setValue(VALUE_SIZE_4000));
+    underTest.saveProperty(new PropertyDto().setKey("global.clob").setValue(VALUE_SIZE_4001));
+
+    assertThatPropertiesRow("global.null")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .isEmpty()
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow("global.empty")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .isEmpty()
+      .hasCreatedAt(DATE_2);
+    assertThatPropertiesRow("global.text")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue("some text")
+      .hasCreatedAt(DATE_3);
+    assertThatPropertiesRow("global.4000")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue(VALUE_SIZE_4000)
+      .hasCreatedAt(DATE_4);
+    assertThatPropertiesRow("global.clob")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasClobValue(VALUE_SIZE_4001)
+      .hasCreatedAt(DATE_5);
+  }
 
-    underTest.insertProperty(new PropertyDto().setKey("global.key").setValue("new_global"));
-    underTest.insertProperty(new PropertyDto().setKey("project.key").setResourceId(10L).setValue("new_project"));
-    underTest.insertProperty(new PropertyDto().setKey("user.key").setUserId(100L).setValue("new_user"));
-    underTest.insertProperty(new PropertyDto().setKey("null.value").setValue(null));
+  @Test
+  public void saveProperty_inserts_component_properties_when_they_do_not_exist_in_db() {
+    when(system2.now()).thenReturn(DATE_1, DATE_2, DATE_3, DATE_4, DATE_5);
+
+    long resourceId = 12;
+    underTest.saveProperty(new PropertyDto().setKey("component.null").setResourceId(resourceId).setValue(null));
+    underTest.saveProperty(new PropertyDto().setKey("component.empty").setResourceId(resourceId).setValue(""));
+    underTest.saveProperty(new PropertyDto().setKey("component.text").setResourceId(resourceId).setValue("some text"));
+    underTest.saveProperty(new PropertyDto().setKey("component.4000").setResourceId(resourceId).setValue(VALUE_SIZE_4000));
+    underTest.saveProperty(new PropertyDto().setKey("component.clob").setResourceId(resourceId).setValue(VALUE_SIZE_4001));
+
+    assertThatPropertiesRow("component.null")
+      .hasResourceId(resourceId)
+      .hasNoUserId()
+      .isEmpty()
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow("component.empty")
+      .hasResourceId(resourceId)
+      .hasNoUserId()
+      .isEmpty()
+      .hasCreatedAt(DATE_2);
+    assertThatPropertiesRow("component.text")
+      .hasResourceId(resourceId)
+      .hasNoUserId()
+      .hasTextValue("some text")
+      .hasCreatedAt(DATE_3);
+    assertThatPropertiesRow("component.4000")
+      .hasResourceId(resourceId)
+      .hasNoUserId()
+      .hasTextValue(VALUE_SIZE_4000)
+      .hasCreatedAt(DATE_4);
+    assertThatPropertiesRow("component.clob")
+      .hasResourceId(resourceId)
+      .hasNoUserId()
+      .hasClobValue(VALUE_SIZE_4001)
+      .hasCreatedAt(DATE_5);
+  }
 
-    dbTester.assertDbUnit(getClass(), "update-result.xml", "properties");
+  @Test
+  public void saveProperty_inserts_user_properties_when_they_do_not_exist_in_db() {
+    when(system2.now()).thenReturn(DATE_1, DATE_2, DATE_3, DATE_4, DATE_5);
+
+    long userId = 100;
+    underTest.saveProperty(new PropertyDto().setKey("user.null").setUserId(userId).setValue(null));
+    underTest.saveProperty(new PropertyDto().setKey("user.empty").setUserId(userId).setValue(""));
+    underTest.saveProperty(new PropertyDto().setKey("user.text").setUserId(userId).setValue("some text"));
+    underTest.saveProperty(new PropertyDto().setKey("user.4000").setUserId(userId).setValue(VALUE_SIZE_4000));
+    underTest.saveProperty(new PropertyDto().setKey("user.clob").setUserId(userId).setValue(VALUE_SIZE_4001));
+
+    assertThatPropertiesRow("user.null")
+      .hasNoResourceId()
+      .hasUserId(userId)
+      .isEmpty()
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow("user.empty")
+      .hasNoResourceId()
+      .hasUserId(userId)
+      .isEmpty()
+      .hasCreatedAt(DATE_2);
+    assertThatPropertiesRow("user.text")
+      .hasNoResourceId()
+      .hasUserId(userId)
+      .hasTextValue("some text")
+      .hasCreatedAt(DATE_3);
+    assertThatPropertiesRow("user.4000")
+      .hasNoResourceId()
+      .hasUserId(userId)
+      .hasTextValue(VALUE_SIZE_4000)
+      .hasCreatedAt(DATE_4);
+    assertThatPropertiesRow("user.clob")
+      .hasNoResourceId()
+      .hasUserId(userId)
+      .hasClobValue(VALUE_SIZE_4001)
+      .hasCreatedAt(DATE_5);
   }
 
   @Test
-  public void setProperty_insert() {
-    dbTester.prepareDbUnit(getClass(), "insert.xml");
+  @UseDataProvider("valueUpdatesDataProvider")
+  public void saveProperty_deletes_then_inserts_global_properties_when_they_exist_in_db(@Nullable String oldValue, @Nullable String newValue) throws SQLException {
+    long id = insertProperty("global", oldValue, null, null, DATE_1);
+    when(system2.now()).thenReturn(DATE_4);
+
+    underTest.saveProperty(new PropertyDto().setKey("global").setValue(newValue));
+
+    assertThatPropertiesRow(id)
+      .doesNotExist();
+
+    PropertiesRowAssert propertiesRowAssert = assertThatPropertiesRow("global")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasCreatedAt(DATE_4);
+    if (newValue == null || newValue.isEmpty()) {
+      propertiesRowAssert.isEmpty();
+    } else if (newValue.length() > 4000) {
+      propertiesRowAssert.hasClobValue(newValue);
+    } else {
+      propertiesRowAssert.hasTextValue(newValue);
+    }
+  }
+
+  @Test
+  @UseDataProvider("valueUpdatesDataProvider")
+  public void saveProperty_deletes_then_inserts_component_properties_when_they_exist_in_db(@Nullable String oldValue, @Nullable String newValue) throws SQLException {
+    long resourceId = 999L;
+    long id = insertProperty("global", oldValue, resourceId, null, DATE_1);
+    when(system2.now()).thenReturn(DATE_4);
+
+    underTest.saveProperty(new PropertyDto().setKey("global").setResourceId(resourceId).setValue(newValue));
+
+    assertThatPropertiesRow(id)
+      .doesNotExist();
+
+    PropertiesRowAssert propertiesRowAssert = assertThatPropertiesRow("global")
+      .hasResourceId(resourceId)
+      .hasNoUserId()
+      .hasCreatedAt(DATE_4);
+    if (newValue == null || newValue.isEmpty()) {
+      propertiesRowAssert.isEmpty();
+    } else if (newValue.length() > 4000) {
+      propertiesRowAssert.hasClobValue(newValue);
+    } else {
+      propertiesRowAssert.hasTextValue(newValue);
+    }
+  }
 
-    underTest.insertProperty(new PropertyDto().setKey("global.key").setValue("new_global"));
-    underTest.insertProperty(new PropertyDto().setKey("project.key").setResourceId(10L).setValue("new_project"));
-    underTest.insertProperty(new PropertyDto().setKey("user.key").setUserId(100L).setValue("new_user"));
+  @Test
+  @UseDataProvider("valueUpdatesDataProvider")
+  public void saveProperty_deletes_then_inserts_user_properties_when_they_exist_in_db(@Nullable String oldValue, @Nullable String newValue) throws SQLException {
+    long userId = 90L;
+    long id = insertProperty("global", oldValue, null, userId, DATE_1);
+    when(system2.now()).thenReturn(DATE_4);
+
+    underTest.saveProperty(new PropertyDto().setKey("global").setUserId(userId).setValue(newValue));
+
+    assertThatPropertiesRow(id)
+      .doesNotExist();
+
+    PropertiesRowAssert propertiesRowAssert = assertThatPropertiesRow("global")
+      .hasNoResourceId()
+      .hasUserId(userId)
+      .hasCreatedAt(DATE_4);
+    if (newValue == null || newValue.isEmpty()) {
+      propertiesRowAssert.isEmpty();
+    } else if (newValue.length() > 4000) {
+      propertiesRowAssert.hasClobValue(newValue);
+    } else {
+      propertiesRowAssert.hasTextValue(newValue);
+    }
+  }
 
-    dbTester.assertDbUnit(getClass(), "insert-result.xml", "properties");
+  @DataProvider
+  public static Object[][] valueUpdatesDataProvider() {
+    return new Object[][] {
+      {null, null},
+      {null, ""},
+      {null, "some value"},
+      {null, VALUE_SIZE_4000},
+      {null, VALUE_SIZE_4001},
+      {"", null},
+      {"", ""},
+      {"", "some value"},
+      {"", VALUE_SIZE_4000},
+      {"", VALUE_SIZE_4001},
+      {"a value", null},
+      {"a value", ""},
+      {"a value", "a value"},
+      {"a value", "some value"},
+      {"a value", VALUE_SIZE_4000},
+      {"a value", VALUE_SIZE_4001},
+      {VALUE_SIZE_4000, null},
+      {VALUE_SIZE_4000, ""},
+      {VALUE_SIZE_4000, "a value"},
+      {VALUE_SIZE_4000, VALUE_SIZE_4000},
+      {VALUE_SIZE_4000, VALUE_SIZE_4000.substring(1) + "a"},
+      {VALUE_SIZE_4000, VALUE_SIZE_4001},
+      {VALUE_SIZE_4001, null},
+      {VALUE_SIZE_4001, ""},
+      {VALUE_SIZE_4001, "a value"},
+      {VALUE_SIZE_4001, VALUE_SIZE_4000},
+      {VALUE_SIZE_4001, VALUE_SIZE_4001},
+      {VALUE_SIZE_4001, VALUE_SIZE_4001 + "dfsdfs"},
+    };
   }
 
   @Test
-  public void delete_property_by_id() {
-    dbTester.prepareDbUnit(getClass(), "delete.xml");
+  @UseDataProvider("possibleValuesProvider")
+  public void deleteById(String value) throws SQLException {
+    long id1 = insertProperty("global.key", value, null, null);
+    long id2 = insertProperty("component.key", value, 10L, null);
+    long id3 = insertProperty("user.key", value, null, 100L);
+
+    underTest.deleteById(dbTester.getSession(), id1);
+    dbTester.getSession().commit();
 
-    underTest.deleteById(dbTester.getSession(), 1L);
+    assertThatPropertiesRow(id1)
+      .doesNotExist();
+    assertThatPropertiesRow(id2)
+      .hasKey("component.key");
+    assertThatPropertiesRow(id3)
+      .hasKey("user.key");
+
+    underTest.deleteById(dbTester.getSession(), id2);
+    dbTester.getSession().commit();
+
+    assertThatPropertiesRow(id2)
+      .doesNotExist();
+    assertThatPropertiesRow(id3)
+      .hasKey("user.key");
+
+    underTest.deleteById(dbTester.getSession(), id3);
     dbTester.getSession().commit();
 
-    dbTester.assertDbUnit(getClass(), "delete-result.xml", "properties");
+    assertThatPropertiesRow(id3)
+      .doesNotExist();
   }
 
-  @Test
-  public void delete_project_property() {
-    dbTester.prepareDbUnit(getClass(), "delete_project_property.xml");
+  @DataProvider
+  public static Object[][] possibleValuesProvider() {
+    return new Object[][] {
+      {null},
+      {""},
+      {"some value"},
+      {VALUE_SIZE_4000},
+      {VALUE_SIZE_4001}
+    };
+  }
 
-    underTest.deleteProjectProperty("struts.one", 10L);
+  @Test
+  public void deleteById_does_not_fail_if_row_with_specified_id_does_not_exist() {
+    underTest.deleteById(dbTester.getSession(), 12L);
+    dbTester.getSession().commit();
+  }
 
-    dbTester.assertDbUnit(getClass(), "delete_project_property-result.xml", "properties");
+  @Test
+  public void delete_project_property() throws SQLException {
+    long projectId1 = insertProject("A").getId();
+    long projectId2 = insertProject("B").getId();
+    long projectId3 = insertProject("C").getId();
+    long id1 = insertProperty("global.one", "one", null, null);
+    long id2 = insertProperty("global.two", "two", null, null);
+    long id3 = insertProperty("struts.one", "one", projectId1, null);
+    long id4 = insertProperty("commonslang.one", "one", projectId2, null);
+    long id5 = insertProperty("user.one", "one", null, 100L);
+    long id6 = insertProperty("user.two", "two", null, 100L);
+    long id7 = insertProperty("other.one", "one", projectId3, null);
+
+    underTest.deleteProjectProperty("struts.one", projectId1);
+
+    assertThatPropertiesRow(id1)
+      .hasKey("global.one")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue("one");
+    assertThatPropertiesRow(id2)
+      .hasKey("global.two")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue("two");
+    assertThatPropertiesRow(id3)
+      .doesNotExist();
+    assertThatPropertiesRow(id4)
+      .hasKey("commonslang.one")
+      .hasResourceId(projectId2)
+      .hasNoUserId()
+      .hasTextValue("one");
+    assertThatPropertiesRow(id5)
+      .hasKey("user.one")
+      .hasNoResourceId()
+      .hasUserId(100)
+      .hasTextValue("one");
+    assertThatPropertiesRow(id6)
+      .hasKey("user.two")
+      .hasNoResourceId()
+      .hasUserId(100)
+      .hasTextValue("two");
+    assertThatPropertiesRow(id7)
+      .hasKey("other.one")
+      .hasResourceId(projectId3)
+      .hasNoUserId()
+      .hasTextValue("one");
   }
 
   @Test
-  public void delete_project_properties() {
-    dbTester.prepareDbUnit(getClass(), "delete_project_properties.xml");
+  public void delete_project_properties() throws SQLException {
+    long id1 = insertProperty("sonar.profile.java", "Sonar Way", 1L, null);
+    long id2 = insertProperty("sonar.profile.java", "Sonar Way", 2L, null);
+
+    long id3 = insertProperty("sonar.profile.java", "Sonar Way", null, null);
+
+    long id4 = insertProperty("sonar.profile.js", "Sonar Way", 1L, null);
+    long id5 = insertProperty("sonar.profile.js", "Sonar Way", 2L, null);
+    long id6 = insertProperty("sonar.profile.js", "Sonar Way", null, null);
 
     underTest.deleteProjectProperties("sonar.profile.java", "Sonar Way");
 
-    dbTester.assertDbUnit(getClass(), "delete_project_properties-result.xml", "properties");
+    assertThatPropertiesRow(id1)
+      .doesNotExist();
+    assertThatPropertiesRow(id2)
+      .doesNotExist();
+    assertThatPropertiesRow(id3)
+      .hasKey("sonar.profile.java")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue("Sonar Way");
+    assertThatPropertiesRow(id4)
+      .hasKey("sonar.profile.js")
+      .hasResourceId(1)
+      .hasNoUserId()
+      .hasTextValue("Sonar Way");
+    assertThatPropertiesRow(id5)
+      .hasKey("sonar.profile.js")
+      .hasResourceId(2)
+      .hasNoUserId()
+      .hasTextValue("Sonar Way");
+    assertThatPropertiesRow(id6)
+      .hasKey("sonar.profile.js")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue("Sonar Way");
   }
 
   @Test
-  public void deleteGlobalProperty() {
-    dbTester.prepareDbUnit(getClass(), "deleteGlobalProperty.xml");
+  public void deleteGlobalProperty() throws SQLException {
+    // global
+    long id1 = insertProperty("global.key", "new_global", null, null);
+    long id2 = insertProperty("to_be_deleted", "xxx", null, null);
+    // project - do not delete this project property that has the same key
+    long id3 = insertProperty("to_be_deleted", "new_project", 10L, null);
+    // user
+    long id4 = insertProperty("user.key", "new_user", null, 100L);
 
     underTest.deleteGlobalProperty("to_be_deleted");
 
-    dbTester.assertDbUnit(getClass(), "deleteGlobalProperty-result.xml", "properties");
+    assertThatPropertiesRow(id1)
+      .hasKey("global.key")
+      .hasNoUserId()
+      .hasNoResourceId()
+      .hasTextValue("new_global");
+    assertThatPropertiesRow(id2)
+      .doesNotExist();
+    assertThatPropertiesRow("to_be_deleted", null, null)
+      .doesNotExist();
+    assertThatPropertiesRow(id3)
+      .hasKey("to_be_deleted")
+      .hasResourceId(10)
+      .hasNoUserId()
+      .hasTextValue("new_project");
+    assertThatPropertiesRow(id4)
+      .hasKey("user.key")
+      .hasNoResourceId()
+      .hasUserId(100)
+      .hasTextValue("new_user");
+
   }
 
   @Test
-  public void insertGlobalProperties() {
-    dbTester.prepareDbUnit(getClass(), "insertGlobalProperties.xml");
-
-    underTest.insertGlobalProperties(ImmutableMap.of("to_be_inserted", "inserted"));
-
-    dbTester.assertDbUnitTable(getClass(), "insertGlobalProperties-result.xml", "properties", "prop_key", "text_value", "resource_id", "user_id");
+  public void saveGlobalProperties_insert_property_if_does_not_exist_in_db() {
+    when(system2.now()).thenReturn(DATE_1, DATE_2, DATE_3, DATE_4, DATE_5);
+
+    underTest.saveGlobalProperties(mapOf(
+      "null_value_property", null,
+      "empty_value_property", "",
+      "text_value_property", "dfdsfsd",
+      "4000_char_value_property", VALUE_SIZE_4000,
+      "clob_value_property", VALUE_SIZE_4001));
+
+    assertThatPropertiesRow("null_value_property")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .isEmpty()
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow("empty_value_property")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .isEmpty()
+      .hasCreatedAt(DATE_2);
+    assertThatPropertiesRow("text_value_property")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue("dfdsfsd")
+      .hasCreatedAt(DATE_3);
+    assertThatPropertiesRow("4000_char_value_property")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue(VALUE_SIZE_4000)
+      .hasCreatedAt(DATE_4);
+    assertThatPropertiesRow("clob_value_property")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasClobValue(VALUE_SIZE_4001)
+      .hasCreatedAt(DATE_5);
   }
 
   @Test
-  public void updateGlobalProperties() {
-    dbTester.prepareDbUnit(getClass(), "updateGlobalProperties.xml");
+  public void saveGlobalProperties_delete_and_insert_new_value_when_property_exists_in_db() throws SQLException {
+    long id = insertProperty("to_be_updated", "old_value", null, null, DATE_1);
+    when(system2.now()).thenReturn(DATE_3);
 
-    underTest.insertGlobalProperties(ImmutableMap.of("to_be_updated", "updated"));
+    underTest.saveGlobalProperties(ImmutableMap.of("to_be_updated", "new value"));
 
-    dbTester.assertDbUnitTable(getClass(), "updateGlobalProperties-result.xml", "properties", "prop_key", "text_value", "resource_id", "user_id");
-  }
+    assertThatPropertiesRow(id)
+      .doesNotExist();
 
-  @Test
-  public void renamePropertyKey() {
-    dbTester.prepareDbUnit(getClass(), "renamePropertyKey.xml");
+    assertThatPropertiesRow("to_be_updated")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue("new value")
+      .hasCreatedAt(DATE_3);
+  }
 
-    underTest.renamePropertyKey("sonar.license.secured", "sonar.license");
+  private static Map<String, String> mapOf(String... values) {
+    // use LinkedHashMap to keep order of array
+    Map<String, String> res = new LinkedHashMap<>(values.length / 2);
+    Iterator<String> iterator = Arrays.asList(values).iterator();
+    while (iterator.hasNext()) {
+      res.put(iterator.next(), iterator.next());
+    }
+    return res;
+  }
 
-    dbTester.assertDbUnitTable(getClass(), "renamePropertyKey-result.xml", "properties", "prop_key", "text_value", "resource_id", "user_id");
+  @Test
+  public void renamePropertyKey_updates_global_component_and_user_properties() throws SQLException {
+    long id1 = insertProperty("foo", "bar", null, null, DATE_1);
+    long id2 = insertProperty("old_name", "doc1", null, null, DATE_1);
+    long id3 = insertProperty("old_name", "doc2", 15L, null, DATE_1);
+    long id4 = insertProperty("old_name", "doc3", 16L, null, DATE_1);
+    long id5 = insertProperty("old_name", "doc4", null, 100L, DATE_1);
+    long id6 = insertProperty("old_name", "doc5", null, 101L, DATE_1);
+
+    underTest.renamePropertyKey("old_name", "new_name");
+
+    assertThatPropertiesRow(id1)
+      .hasKey("foo")
+      .hasNoUserId()
+      .hasNoResourceId()
+      .hasTextValue("bar")
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow(id2)
+      .hasKey("new_name")
+      .hasNoResourceId()
+      .hasNoUserId()
+      .hasTextValue("doc1")
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow(id3)
+      .hasKey("new_name")
+      .hasResourceId(15)
+      .hasNoUserId()
+      .hasTextValue("doc2")
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow(id4)
+      .hasKey("new_name")
+      .hasResourceId(16)
+      .hasNoUserId()
+      .hasTextValue("doc3")
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow(id5)
+      .hasKey("new_name")
+      .hasNoResourceId()
+      .hasUserId(100)
+      .hasTextValue("doc4")
+      .hasCreatedAt(DATE_1);
+    assertThatPropertiesRow(id6)
+      .hasKey("new_name")
+      .hasNoResourceId()
+      .hasUserId(101)
+      .hasTextValue("doc5")
+      .hasCreatedAt(DATE_1);
   }
 
   @Test
-  public void should_not_rename_if_same_key() {
-    dbTester.prepareDbUnit(getClass(), "should_not_rename_if_same_key.xml");
+  public void rename_to_same_key_has_no_effect() throws SQLException {
+    long now = 1_890_999L;
+    long id = insertProperty("foo", "bar", null, null, now);
+
+    assertThatPropertiesRow(id)
+      .hasCreatedAt(now);
 
     underTest.renamePropertyKey("foo", "foo");
 
-    dbTester.assertDbUnitTable(getClass(), "should_not_rename_if_same_key-result.xml", "properties", "prop_key", "text_value", "resource_id", "user_id");
+    assertThatPropertiesRow(id)
+      .hasKey("foo")
+      .hasNoUserId()
+      .hasNoResourceId()
+      .hasTextValue("bar")
+      .hasCreatedAt(now);
   }
 
   @Test
@@ -444,7 +994,7 @@ public class PropertiesDaoTest {
     underTest.renamePropertyKey(null, "foo");
   }
 
-  private PropertyDto findById(List<PropertyDto> properties, int id) {
+  private PropertyDto findById(List<PropertyDto> properties, long id) {
     for (PropertyDto property : properties) {
       if (property.getId() == id) {
         return property;
@@ -455,35 +1005,61 @@ public class PropertiesDaoTest {
 
   private void insertProperties(PropertyDto... properties) {
     for (PropertyDto propertyDto : properties) {
-      underTest.insertProperty(session, propertyDto);
+      underTest.saveProperty(session, propertyDto);
     }
     session.commit();
   }
 
-  private void insertProperty(int id, String key, String value, @Nullable Integer resourceId, @Nullable Integer userId) {
-    dbTester.executeInsert("PROPERTIES",
-      "ID", valueOf(id),
-      "prop_key", key,
-      "text_value", value,
-      "resource_id", resourceId == null ? null : valueOf(resourceId),
-      "user_id", userId == null ? null : valueOf(userId));
-    dbTester.commit();
+  private long insertProperty(String key, @Nullable String value, @Nullable Long resourceId, @Nullable Long userId, long createdAt) throws SQLException {
+    when(system2.now()).thenReturn(createdAt);
+    return insertProperty(key, value, resourceId, userId);
   }
 
-  private void insertProject(String uuid, int id) {
-    dbTester.executeInsert("PROJECTS",
-      "uuid", uuid,
-      "uuid_path", "NOT_USED",
-      "root_uuid", uuid,
-      "kee", "project" + id,
-      "id", valueOf(id));
-    dbTester.commit();
+  private long insertProperty(String key, @Nullable String value, @Nullable Long resourceId, @Nullable Long userId) throws SQLException {
+    DbSession session = dbTester.getSession();
+    PropertyDto dto = new PropertyDto().setKey(key)
+      .setResourceId(resourceId == null ? null : resourceId.longValue())
+      .setUserId(userId == null ? null : userId)
+      .setValue(value);
+    dbTester.getDbClient().propertiesDao().saveProperty(session, dto);
+    session.commit();
+
+    return (long) dbTester.selectFirst(session, "select id as \"id\" from properties" +
+      " where prop_key='" + key + "'" +
+      " and user_id" + (userId == null ? " is null" : "='" + userId + "'") +
+      " and resource_id" + (resourceId == null ? " is null" : "='" + resourceId + "'")).get("id");
   }
 
-  private void insertUser(int id) {
-    dbTester.executeInsert("USERS",
-      "id", valueOf(id),
-      "login", "user" + id);
+  private ComponentDto insertProject(String uuid) {
+    String key = "project" + uuid;
+    ComponentDto project = ComponentTesting.newProjectDto(uuid).setKey(key);
+    dbClient.componentDao().insert(session, project);
     dbTester.commit();
+    return project;
   }
+
+  private long insertUser(String login) {
+    UserDto dto = new UserDto().setLogin(login);
+    DbSession session = dbTester.getSession();
+    dbClient.userDao().insert(session, dto);
+    session.commit();
+    return dto.getId();
+  }
+
+  private static PropertyDtoAssert assertThatDto(@Nullable PropertyDto dto) {
+    return new PropertyDtoAssert(dto);
+  }
+
+  private PropertiesRowAssert assertThatPropertiesRow(String key, @Nullable Integer userId, @Nullable Integer componentId) {
+    return new PropertiesRowAssert(dbTester, key, userId, componentId);
+  }
+
+  private PropertiesRowAssert assertThatPropertiesRow(String key) {
+    return new PropertiesRowAssert(dbTester, key);
+  }
+
+  private PropertiesRowAssert assertThatPropertiesRow(long id) {
+    return new PropertiesRowAssert(dbTester, id);
+  }
+
 }
diff --git a/sonar-db/src/test/java/org/sonar/db/property/PropertiesRow.java b/sonar-db/src/test/java/org/sonar/db/property/PropertiesRow.java
new file mode 100644 (file)
index 0000000..e677c16
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.property;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+final class PropertiesRow {
+  private final String key;
+  private final Long userId;
+  private final Long resourceId;
+  private final Boolean empty;
+  private final String textValue;
+  private final String clobValue;
+  private final Long createdAt;
+
+  public PropertiesRow(String key, @Nullable Long userId, @Nullable Long resourceId,
+    @Nullable Boolean empty, @Nullable String textValue, @Nullable String clobValue,
+    @Nullable Long createdAt) {
+    this.key = key;
+    this.userId = userId;
+    this.resourceId = resourceId;
+    this.empty = empty;
+    this.textValue = textValue;
+    this.clobValue = clobValue;
+    this.createdAt = createdAt;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public Long getUserId() {
+    return userId;
+  }
+
+  public Long getResourceId() {
+    return resourceId;
+  }
+
+  @CheckForNull
+  public Boolean getEmpty() {
+    return empty;
+  }
+
+  @CheckForNull
+  public String getTextValue() {
+    return textValue;
+  }
+
+  @CheckForNull
+  public String getClobValue() {
+    return clobValue;
+  }
+
+  @CheckForNull
+  public Long getCreatedAt() {
+    return createdAt;
+  }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/property/PropertiesRowAssert.java b/sonar-db/src/test/java/org/sonar/db/property/PropertiesRowAssert.java
new file mode 100644 (file)
index 0000000..d648383
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.property;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.assertj.core.api.AbstractAssert;
+import org.sonar.db.DbTester;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static java.util.Objects.requireNonNull;
+
+final class PropertiesRowAssert extends AbstractAssert<PropertiesRowAssert, PropertiesRow> {
+
+  PropertiesRowAssert(DbTester dbTester, String propertyKey, @Nullable Integer userId, @Nullable Integer componentId) {
+    super(
+      asInternalProperty(
+        dbTester,
+        () -> " where prop_key='" + propertyKey + "'" +
+          " and user_id" + (userId == null ? " is null" : "='" + userId + "'") +
+          " and resource_id" + (componentId == null ? " is null" : "='" + componentId + "'")),
+      PropertiesRowAssert.class);
+  }
+
+  PropertiesRowAssert(DbTester dbTester, String key) {
+    super(asInternalProperty(dbTester, () -> " where prop_key='" + key + "'"), PropertiesRowAssert.class);
+  }
+
+  PropertiesRowAssert(DbTester dbTester, long id) {
+    super(asInternalProperty(dbTester, () -> " where id=" + id), PropertiesRowAssert.class);
+  }
+
+  @CheckForNull
+  private static PropertiesRow asInternalProperty(DbTester dbTester, Supplier<String> whereClauseSupplier) {
+    String whereClause = whereClauseSupplier.get();
+    List<Map<String, Object>> rows = dbTester.select(
+      "select" +
+        " prop_key as \"key\", user_id as \"userId\", resource_id as \"resourceId\", is_empty as \"isEmpty\", text_value as \"textValue\", clob_value as \"clobValue\", created_at as \"createdAt\""
+        +
+        " from properties" +
+        whereClause);
+    checkState(rows.size() < 2, "More than one property found for where clause \"" + whereClause + "\"");
+    if (rows.isEmpty()) {
+      return null;
+    } else {
+      Map<String, Object> row = rows.iterator().next();
+      return new PropertiesRow(
+        (String) row.get("key"),
+        (Long) row.get("userId"),
+        (Long) row.get("resourceId"),
+        toBoolean(row.get("isEmpty")),
+        (String) row.get("textValue"),
+        (String) row.get("clobValue"),
+        (Long) row.get("createdAt"));
+    }
+  }
+
+  private static Boolean toBoolean(Object flag) {
+    if (flag instanceof Boolean) {
+      return (Boolean) flag;
+    }
+    if (flag instanceof Long) {
+      Long longBoolean = (Long) flag;
+      return longBoolean.equals(1L);
+    }
+    throw new IllegalArgumentException("Unsupported object type returned for column \"isEmpty\": " + flag.getClass());
+  }
+
+  public void doesNotExist() {
+    isNull();
+  }
+
+  public PropertiesRowAssert hasKey(String expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getKey(), expected)) {
+      failWithMessage("Expected PropertiesRow to have column PROP_KEY to be <%s> but was <%s>", expected, actual.getKey());
+    }
+
+    return this;
+  }
+
+  public PropertiesRowAssert hasNoUserId() {
+    isNotNull();
+
+    if (actual.getUserId() != null) {
+      failWithMessage("Expected PropertiesRow to have column USER_ID to be null but was <%s>", actual.getUserId());
+    }
+
+    return this;
+  }
+
+  public PropertiesRowAssert hasUserId(long expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getUserId(), expected)) {
+      failWithMessage("Expected PropertiesRow to have column USER_ID to be <%s> but was <%s>", true, actual.getUserId());
+    }
+
+    return this;
+  }
+
+  public PropertiesRowAssert hasNoResourceId() {
+    isNotNull();
+
+    if (actual.getResourceId() != null) {
+      failWithMessage("Expected PropertiesRow to have column RESOURCE_ID to be null but was <%s>", actual.getResourceId());
+    }
+
+    return this;
+  }
+
+  public PropertiesRowAssert hasResourceId(long expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getResourceId(), expected)) {
+      failWithMessage("Expected PropertiesRow to have column RESOURCE_ID to be <%s> but was <%s>", true, actual.getResourceId());
+    }
+
+    return this;
+  }
+
+  public PropertiesRowAssert isEmpty() {
+    isNotNull();
+
+    if (!Objects.equals(actual.getEmpty(), TRUE)) {
+      failWithMessage("Expected PropertiesRow to have column IS_EMPTY to be <%s> but was <%s>", true, actual.getEmpty());
+    }
+    if (actual.getTextValue() != null) {
+      failWithMessage("Expected PropertiesRow to have column TEXT_VALUE to be null but was <%s>", actual.getTextValue());
+    }
+    if (actual.getClobValue() != null) {
+      failWithMessage("Expected PropertiesRow to have column CLOB_VALUE to be null but was <%s>", actual.getClobValue());
+    }
+
+    return this;
+  }
+
+  public PropertiesRowAssert hasTextValue(String expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getTextValue(), requireNonNull(expected))) {
+      failWithMessage("Expected PropertiesRow to have column TEXT_VALUE to be <%s> but was <%s>", expected, actual.getTextValue());
+    }
+    if (actual.getClobValue() != null) {
+      failWithMessage("Expected PropertiesRow to have column CLOB_VALUE to be null but was <%s>", actual.getClobValue());
+    }
+    if (!Objects.equals(actual.getEmpty(), FALSE)) {
+      failWithMessage("Expected PropertiesRow to have column IS_EMPTY to be <%s> but was <%s>", false, actual.getEmpty());
+    }
+
+    return this;
+  }
+
+  public PropertiesRowAssert hasClobValue(String expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getClobValue(), requireNonNull(expected))) {
+      failWithMessage("Expected PropertiesRow to have column CLOB_VALUE to be <%s> but was <%s>", true, actual.getClobValue());
+    }
+    if (actual.getTextValue() != null) {
+      failWithMessage("Expected PropertiesRow to have column TEXT_VALUE to be null but was <%s>", actual.getTextValue());
+    }
+    if (!Objects.equals(actual.getEmpty(), FALSE)) {
+      failWithMessage("Expected PropertiesRow to have column IS_EMPTY to be <%s> but was <%s>", false, actual.getEmpty());
+    }
+
+    return this;
+  }
+
+  public PropertiesRowAssert hasCreatedAt(long expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getCreatedAt(), expected)) {
+      failWithMessage("Expected PropertiesRow to have column CREATED_AT to be <%s> but was <%s>", expected, actual.getCreatedAt());
+    }
+
+    return this;
+  }
+}
index bbef4a057f3030b7d9b525621b475078e9168d91..d43e8d08a2452b38d86e577c85cc56d348b2b809 100644 (file)
@@ -46,7 +46,7 @@ public class PropertyDbTester {
   }
 
   public PropertyDto insertProperty(PropertyDto property) {
-    dbClient.propertiesDao().insertProperty(dbSession, property);
+    dbClient.propertiesDao().saveProperty(dbSession, property);
     db.commit();
 
     return property;
@@ -58,7 +58,7 @@ public class PropertyDbTester {
 
   public void insertProperties(List<PropertyDto> properties) {
     for (PropertyDto propertyDto : properties) {
-      dbClient.propertiesDao().insertProperty(dbSession, propertyDto);
+      dbClient.propertiesDao().saveProperty(dbSession, propertyDto);
     }
     dbSession.commit();
   }
diff --git a/sonar-db/src/test/java/org/sonar/db/property/PropertyDtoAssert.java b/sonar-db/src/test/java/org/sonar/db/property/PropertyDtoAssert.java
new file mode 100644 (file)
index 0000000..93f94e9
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.property;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.assertj.core.api.AbstractAssert;
+
+import static java.util.Objects.requireNonNull;
+
+public class PropertyDtoAssert extends AbstractAssert<PropertyDtoAssert, PropertyDto> {
+  protected PropertyDtoAssert(@Nullable PropertyDto actual) {
+    super(actual, PropertyDtoAssert.class);
+  }
+
+  public PropertyDtoAssert hasKey(String expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getKey(), expected)) {
+      failWithMessage("Expected PropertyDto to have key to be <%s> but was <%s>", expected, actual.getKey());
+    }
+
+    return this;
+  }
+
+  public PropertyDtoAssert hasNoUserId() {
+    isNotNull();
+
+    if (actual.getUserId() != null) {
+      failWithMessage("Expected PropertyDto to have userId to be null but was <%s>", actual.getUserId());
+    }
+
+    return this;
+  }
+
+  public PropertyDtoAssert hasUserId(long expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getUserId(), expected)) {
+      failWithMessage("Expected PropertyDto to have userId to be <%s> but was <%s>", true, actual.getUserId());
+    }
+
+    return this;
+  }
+
+  public PropertyDtoAssert hasNoResourceId() {
+    isNotNull();
+
+    if (actual.getResourceId() != null) {
+      failWithMessage("Expected PropertyDto to have resourceId to be null but was <%s>", actual.getResourceId());
+    }
+
+    return this;
+  }
+
+  public PropertyDtoAssert hasResourceId(long expected) {
+    isNotNull();
+
+    if (!Objects.equals(actual.getResourceId(), expected)) {
+      failWithMessage("Expected PropertyDto to have resourceId to be <%s> but was <%s>", true, actual.getResourceId());
+    }
+
+    return this;
+  }
+
+  public PropertyDtoAssert hasValue(String expected) {
+    requireNonNull(expected);
+    isNotNull();
+
+    if (!Objects.equals(actual.getValue(), expected)) {
+      failWithMessage("Expected PropertyDto to have value to be <%s> but was <%s>", true, actual.getValue());
+    }
+
+    return this;
+  }
+}
index 5673fe776a3c7190cdd444e2ff4517effae0aa62..220620323e033537251a3cf1e7ba91770a8437ac 100644 (file)
@@ -109,7 +109,7 @@ public class ProjectQgateAssociationDaoTest {
   }
 
   private void associateProjectToQualityGate(long componentId, long qualityGateId) {
-    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto()
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
       .setKey("sonar.qualitygate")
       .setResourceId(componentId)
       .setValue(String.valueOf(qualityGateId)));
index aa2458da8aa9be561b54dbd4f74dec0dc380f620..6e5a916033c940dfecd40b2625440d5b1de23dde 100644 (file)
@@ -525,13 +525,13 @@ public class UserDaoTest {
 
   private PropertyDto insertProperty(UserDto user) {
     PropertyDto dto = new PropertyDto().setKey(randomAlphanumeric(100)).setUserId(user.getId());
-    dbClient.propertiesDao().insertProperty(session, dto);
+    dbClient.propertiesDao().saveProperty(session, dto);
     return dto;
   }
 
   private PropertyDto insertProperty(String key, String value, long componentId) {
     PropertyDto dto = new PropertyDto().setKey(key).setValue(value).setResourceId(componentId);
-    dbClient.propertiesDao().insertProperty(session, dto);
+    dbClient.propertiesDao().saveProperty(session, dto);
     return dto;
   }
 
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete-result.xml
deleted file mode 100644 (file)
index ef4ec2a..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <!--<properties id="1" prop_key="global.key" text_value="global" resource_id="[null]" user_id="[null]"/>-->
-
-  <!-- project -->
-  <properties id="2" prop_key="project.key" text_value="project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="3" prop_key="user.key" text_value="user" resource_id="[null]" user_id="100"/>
-
-  <!-- null value -->
-  <properties id="4" prop_key="null.value" text_value="not null" resource_id="[null]" user_id="[null]"/>
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete.xml
deleted file mode 100644 (file)
index 5229a2b..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="global.key" text_value="global" resource_id="[null]" user_id="[null]"/>
-
-  <!-- project -->
-  <properties id="2" prop_key="project.key" text_value="project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="3" prop_key="user.key" text_value="user" resource_id="[null]" user_id="100"/>
-
-  <!-- null value -->
-  <properties id="4" prop_key="null.value" text_value="not null" resource_id="[null]" user_id="[null]"/>
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties-result.xml
deleted file mode 100644 (file)
index ba4619f..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <!-- <properties id="1" prop_key="to_be_deleted" text_value="new_global" resource_id="[null]" user_id="[null]"/> -->
-  <properties id="2" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
-
-  <!-- project -->
-  <!--  <properties id="3" prop_key="to_be_deleted" text_value="new_project" resource_id="10" user_id="[null]"/> -->
-  <properties id="4" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <!-- <properties id="5" prop_key="to_be_deleted" text_value="new_user" resource_id="[null]" user_id="100"/> -->
-  <properties id="6" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties.xml
deleted file mode 100644 (file)
index 0953b0e..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="to_be_deleted" text_value="new_global" resource_id="[null]" user_id="[null]"/>
-  <properties id="2" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
-
-  <!-- project -->
-  <properties id="3" prop_key="to_be_deleted" text_value="new_project" resource_id="10" user_id="[null]"/>
-  <properties id="4" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="5" prop_key="to_be_deleted" text_value="new_user" resource_id="[null]" user_id="100"/>
-  <properties id="6" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties-result.xml
deleted file mode 100644 (file)
index a5cfed3..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <!--<properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>-->
-
-  <!-- project -->
-  <properties id="2" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="3" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties.xml
deleted file mode 100644 (file)
index 3e5eb87..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
-
-  <!-- project -->
-  <properties id="2" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="3" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty-result.xml
deleted file mode 100644 (file)
index 0428139..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
-  <!--<properties id="2" prop_key="to_be_deleted" text_value="xxx" resource_id="[null]" user_id="[null]"/>-->
-
-  <!-- project -->
-  <properties id="3" prop_key="to_be_deleted" text_value="new_project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="4" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty.xml
deleted file mode 100644 (file)
index aaf0fd6..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
-  <properties id="2" prop_key="to_be_deleted" text_value="xxx" resource_id="[null]" user_id="[null]"/>
-
-  <!-- project - do not delete this project property that has the same key -->
-  <properties id="3" prop_key="to_be_deleted" text_value="new_project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="4" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties-result.xml
deleted file mode 100644 (file)
index 97b8b6f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<dataset>
-
-  <!--<properties id="1" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="1" user_id="[null]"/>-->
-  <!--<properties id="2" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="2" user_id="[null]"/>-->
-
-  <properties id="3" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
-
-  <properties id="4" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="1" user_id="[null]"/>
-  <properties id="5" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
-  <properties id="6" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties.xml
deleted file mode 100644 (file)
index 4e07f27..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<dataset>
-
-  <properties id="1" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="1" user_id="[null]"/>
-  <properties id="2" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
-
-  <properties id="3" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
-
-  <properties id="4" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="1" user_id="[null]"/>
-  <properties id="5" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
-  <properties id="6" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
-
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property-result.xml
deleted file mode 100644 (file)
index 0305f9f..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1"
-              prop_key="global.one"
-              text_value="one"
-              resource_id="[null]"
-              user_id="[null]"/>
-  <properties id="2"
-              prop_key="global.two"
-              text_value="two"
-              resource_id="[null]"
-              user_id="[null]"/>
-
-  <!-- struts -->
-  <!--<properties id="3" prop_key="struts.one" text_value="one" resource_id="10" user_id="[null]"/>-->
-
-  <!-- commons -->
-  <properties id="4"
-              prop_key="commonslang.one"
-              text_value="two"
-              resource_id="11"
-              user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="5"
-              prop_key="user.one"
-              text_value="one"
-              resource_id="[null]"
-              user_id="100"/>
-  <properties id="6"
-              prop_key="user.two"
-              text_value="two"
-              resource_id="10"
-              user_id="100"/>
-
-  <properties id="7"
-              prop_key="commonslang.one"
-              text_value="one"
-              resource_id="12"
-              user_id="[null]"/>
-
-  <projects uuid="A"
-            uuid_path="NOT_USED"
-            kee="org.struts:struts"
-            id="10"/>
-  <projects uuid="B"
-            uuid_path="NOT_USED"
-            kee="org.apache:commons-lang"
-            id="11"/>
-  <projects uuid="C"
-            uuid_path="NOT_USED"
-            kee="other"
-            id="12"/>
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property.xml
deleted file mode 100644 (file)
index 4c53260..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1"
-              prop_key="global.one"
-              text_value="one"
-              resource_id="[null]"
-              user_id="[null]"/>
-  <properties id="2"
-              prop_key="global.two"
-              text_value="two"
-              resource_id="[null]"
-              user_id="[null]"/>
-
-  <!-- struts -->
-  <properties id="3"
-              prop_key="struts.one"
-              text_value="one"
-              resource_id="10"
-              user_id="[null]"/>
-
-  <!-- commons -->
-  <properties id="4"
-              prop_key="commonslang.one"
-              text_value="two"
-              resource_id="11"
-              user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="5"
-              prop_key="user.one"
-              text_value="one"
-              resource_id="[null]"
-              user_id="100"/>
-  <properties id="6"
-              prop_key="user.two"
-              text_value="two"
-              resource_id="10"
-              user_id="100"/>
-
-  <properties id="7"
-              prop_key="commonslang.one"
-              text_value="one"
-              resource_id="12"
-              user_id="[null]"/>
-
-  <projects uuid="A"
-            uuid_path="NOT_USED"
-            root_uuid="A"
-            kee="org.struts:struts"
-            id="10"/>
-  <projects uuid="B"
-            uuid_path="NOT_USED"
-            root_uuid="B"
-            kee="org.apache:commons-lang"
-            id="11"/>
-  <projects uuid="C"
-            uuid_path="NOT_USED"
-            root_uuid="C"
-            kee="other"
-            id="12"/>
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert-result.xml
deleted file mode 100644 (file)
index 3e5eb87..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
-
-  <!-- project -->
-  <properties id="2" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="3" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert.xml
deleted file mode 100644 (file)
index 5ed00ba..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<dataset></dataset>
\ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties-result.xml
deleted file mode 100644 (file)
index 5594180..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<dataset>
-
-  <properties prop_key="to_be_inserted" text_value="inserted" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
\ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties.xml
deleted file mode 100644 (file)
index 871dedc..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<dataset>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey-result.xml
deleted file mode 100644 (file)
index 0877b00..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<dataset>
-
-  <properties id="1" prop_key="foo" text_value="bar" resource_id="[null]" user_id="[null]"/>
-  <properties id="2" prop_key="sonar.license" text_value="the license" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey.xml
deleted file mode 100644 (file)
index 6ab761e..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<dataset>
-
-  <properties id="1" prop_key="foo" text_value="bar" resource_id="[null]" user_id="[null]"/>
-  <properties id="2" prop_key="sonar.license.secured" text_value="the license" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectPropertiesByResourceId.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectPropertiesByResourceId.xml
deleted file mode 100644 (file)
index d420c9d..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1"
-              prop_key="global.one"
-              text_value="one"
-              resource_id="[null]"
-              user_id="[null]"/>
-  <properties id="2"
-              prop_key="global.two"
-              text_value="two"
-              resource_id="[null]"
-              user_id="[null]"/>
-
-  <!-- struts -->
-  <properties id="3"
-              prop_key="struts.one"
-              text_value="one"
-              resource_id="10"
-              user_id="[null]"/>
-
-  <!-- commons -->
-  <properties id="4"
-              prop_key="commonslang.one"
-              text_value="two"
-              resource_id="11"
-              user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="5"
-              prop_key="user.one"
-              text_value="one"
-              resource_id="[null]"
-              user_id="100"/>
-  <properties id="6"
-              prop_key="user.two"
-              text_value="two"
-              resource_id="10"
-              user_id="[null]"/>
-
-  <properties id="7"
-              prop_key="commonslang.one"
-              text_value="one"
-              resource_id="12"
-              user_id="[null]"/>
-
-  <projects uuid="A"
-            uuid_path="NOT_USED"
-            kee="org.struts:struts"
-            id="10"/>
-  <projects uuid="B"
-            uuid_path="NOT_USED"
-            kee="org.apache:commons-lang"
-            id="11"/>
-  <projects uuid="C"
-            uuid_path="NOT_USED"
-            kee="other"
-            id="12"/>
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_by_query.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_by_query.xml
deleted file mode 100644 (file)
index 2873d69..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="global.one" text_value="one" resource_id="[null]" user_id="[null]"/>
-  <properties id="2" prop_key="global.two" text_value="two" resource_id="[null]" user_id="[null]"/>
-
-  <!-- struts -->
-  <properties id="3" prop_key="struts.one" text_value="one" resource_id="10" user_id="[null]"/>
-
-  <!-- commons -->
-  <properties id="4" prop_key="commonslang.one" text_value="two" resource_id="11" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="5" prop_key="user.one" text_value="one" resource_id="[null]" user_id="100"/>
-  <properties id="6" prop_key="user.two" text_value="two" resource_id="10" user_id="100"/>
-
-  <properties id="7" prop_key="commonslang.one" text_value="one" resource_id="12" user_id="[null]"/>
-
-</dataset>
index 9c6b865eaa455747782bc7876c8d18f1ca2360cc..8b837110e6bb9310798ab83f51e3db344d84756d 100644 (file)
@@ -3,52 +3,68 @@
   <!-- global -->
   <properties id="1"
               prop_key="global.one"
-              text_value="one"
               resource_id="[null]"
-              user_id="[null]"/>
+              user_id="[null]"
+              is_empty="[false]"
+              text_value="one"
+              created_at="1555000"/>
   <properties id="2"
               prop_key="global.two"
-              text_value="two"
               resource_id="[null]"
-              user_id="[null]"/>
+              user_id="[null]"
+              is_empty="[false]"
+              text_value="two"
+              created_at="1555000"/>
 
   <!-- org.struts:struts -->
   <properties id="3"
               prop_key="struts.one"
-              text_value="one"
               resource_id="1"
-              user_id="[null]"/>
+              user_id="[null]"
+              is_empty="[false]"
+              text_value="one"
+              created_at="1555000"/>
 
   <!-- org.struts:struts-core -->
   <properties id="4"
               prop_key="core.one"
-              text_value="one"
               resource_id="2"
-              user_id="[null]"/>
+              user_id="[null]"
+              is_empty="[false]"
+              text_value="one"
+              created_at="1555000"/>
   <properties id="7"
               prop_key="core.two"
-              text_value="two"
               resource_id="2"
-              user_id="[null]"/>
+              user_id="[null]"
+              is_empty="[false]"
+              text_value="two"
+              created_at="1555000"/>
 
   <!-- org.struts:struts-data -->
   <properties id="8"
               prop_key="data.one"
-              text_value="one"
               resource_id="3"
-              user_id="[null]"/>
+              user_id="[null]"
+              is_empty="[false]"
+              text_value="one"
+              created_at="1555000"/>
 
   <!-- user -->
   <properties id="5"
               prop_key="user.one"
-              text_value="one"
               resource_id="[null]"
-              user_id="100"/>
+              user_id="100"
+              is_empty="[false]"
+              text_value="one"
+              created_at="1555000"/>
   <properties id="6"
               prop_key="user.two"
-              text_value="two"
               resource_id="1"
-              user_id="102"/>
+              user_id="102"
+              is_empty="[false]"
+              text_value="two"
+              created_at="1555000"/>
 
 
   <!-- root project -->
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key-result.xml
deleted file mode 100644 (file)
index b4fc11a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<dataset>
-
-  <properties id="1" prop_key="foo" text_value="bar" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key.xml
deleted file mode 100644 (file)
index b4fc11a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<dataset>
-
-  <properties id="1" prop_key="foo" text_value="bar" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update-result.xml
deleted file mode 100644 (file)
index c326859..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
-
-  <!-- project -->
-  <properties id="2" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="3" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
-
-  <!-- null value -->
-  <properties id="4" prop_key="null.value" text_value="[null]" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update.xml
deleted file mode 100644 (file)
index 5229a2b..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<dataset>
-
-  <!-- global -->
-  <properties id="1" prop_key="global.key" text_value="global" resource_id="[null]" user_id="[null]"/>
-
-  <!-- project -->
-  <properties id="2" prop_key="project.key" text_value="project" resource_id="10" user_id="[null]"/>
-
-  <!-- user -->
-  <properties id="3" prop_key="user.key" text_value="user" resource_id="[null]" user_id="100"/>
-
-  <!-- null value -->
-  <properties id="4" prop_key="null.value" text_value="not null" resource_id="[null]" user_id="[null]"/>
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties-result.xml
deleted file mode 100644 (file)
index 12033fd..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<dataset>
-
-  <properties prop_key="to_be_updated" text_value="updated" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties.xml
deleted file mode 100644 (file)
index 4616072..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<dataset>
-
-  <properties id="1" prop_key="to_be_updated" text_value="old value" resource_id="[null]" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue-result.xml
deleted file mode 100644 (file)
index 194761a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<dataset>
-
-  <properties id="1" prop_key="sonar.profile.java" text_value="Default" resource_id="[null]" user_id="[null]"/>
-  <properties id="2" prop_key="sonar.profile.java" text_value="Default" resource_id="1" user_id="[null]"/>
-
-  <properties id="3" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
-  <properties id="4" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
-
-</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue.xml
deleted file mode 100644 (file)
index 5567744..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<dataset>
-
-  <properties id="1" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
-  <properties id="2" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="1" user_id="[null]"/>
-
-  <properties id="3" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
-  <properties id="4" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
-
-</dataset>
index e37eb2643eab26e4fa2598971b03806c7a1e6370..431aafa0198d8862592c7b25ee77c803cd9a2f0c 100644 (file)
   <properties id="1"
               prop_key="sonar.qualitygate"
               resource_id="[null]"
-              text_value="43"/>
+              is_empty="[false]"
+              text_value="43"
+              created_at="1555000"/>
   <properties id="2"
               prop_key="sonar.qualitygate"
               resource_id="1"
-              text_value="42"/>
+              is_empty="[false]"
+              text_value="42"
+              created_at="1555000"/>
   <properties id="3"
               prop_key="sonar.qualitygate"
               resource_id="2"
-              text_value="42"/>
+              is_empty="[false]"
+              text_value="42"
+              created_at="1555000"/>
   <properties id="4"
               prop_key="sonar.qualitygate"
               resource_id="3"
-              text_value="42"/>
+              is_empty="[false]"
+              text_value="42"
+              created_at="1555000"/>
 
 </dataset>