From da6dbbc8e24d9a2150b5f06a6c300c541a3df1cb Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Fri, 24 Jun 2016 17:21:51 +0200 Subject: [PATCH] SONAR-7824 Populate DB column ACTIVITIES.PROFILE_KEY --- .../index/ActivityResultSetIterator.java | 8 +- .../qualityprofile/ActiveRuleChange.java | 1 - .../server/activity/ActivityServiceTest.java | 12 ++- .../index/ActivityResultSetIteratorTest.java | 3 +- ...1260_populate_profile_key_of_activities.rb | 28 ++++++ .../org/sonar/db/version/DatabaseVersion.java | 2 +- .../sonar/db/version/MigrationStepModule.java | 2 + .../v60/PopulateProfileKeyOfActivities.java | 63 ++++++++++++ .../org/sonar/db/version/rows-h2.sql | 1 + .../db/version/MigrationStepModuleTest.java | 2 +- .../PopulateProfileKeyOfActivitiesTest.java | 97 +++++++++++++++++++ .../activities.sql | 11 +++ 12 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1260_populate_profile_key_of_activities.rb create mode 100644 sonar-db/src/main/java/org/sonar/db/version/v60/PopulateProfileKeyOfActivities.java create mode 100644 sonar-db/src/test/java/org/sonar/db/version/v60/PopulateProfileKeyOfActivitiesTest.java create mode 100644 sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateProfileKeyOfActivitiesTest/activities.sql diff --git a/server/sonar-server/src/main/java/org/sonar/server/activity/index/ActivityResultSetIterator.java b/server/sonar-server/src/main/java/org/sonar/server/activity/index/ActivityResultSetIterator.java index c9409491add..2440075e8dd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/activity/index/ActivityResultSetIterator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/activity/index/ActivityResultSetIterator.java @@ -27,6 +27,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Date; +import java.util.Map; import org.apache.commons.lang.StringUtils; import org.elasticsearch.action.update.UpdateRequest; import org.sonar.api.utils.KeyValueFormat; @@ -50,7 +51,8 @@ class ActivityResultSetIterator extends ResultSetIterator { "data_field", "user_login", "log_type", - "created_at" + "created_at", + "profile_key" }; private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from activities "; @@ -86,7 +88,9 @@ class ActivityResultSetIterator extends ResultSetIterator { writer.prop(ActivityIndexDefinition.FIELD_KEY, key); writer.prop(ActivityIndexDefinition.FIELD_ACTION, rs.getString(2)); writer.prop(ActivityIndexDefinition.FIELD_MESSAGE, rs.getString(3)); - writer.name(ActivityIndexDefinition.FIELD_DETAILS).valueObject(KeyValueFormat.parse(rs.getString(4))); + Map details = KeyValueFormat.parse(rs.getString(4)); + details.put("profileKey", rs.getString(8)); + writer.name(ActivityIndexDefinition.FIELD_DETAILS).valueObject(details); writer.prop(ActivityIndexDefinition.FIELD_LOGIN, rs.getString(5)); writer.prop(ActivityIndexDefinition.FIELD_TYPE, rs.getString(6)); Date createdAt = rs.getTimestamp(7); diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ActiveRuleChange.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ActiveRuleChange.java index 87350de9d01..9621ce0fc26 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ActiveRuleChange.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ActiveRuleChange.java @@ -95,7 +95,6 @@ public class ActiveRuleChange { activity.setProfileKey(getKey().qProfile()); activity.setData("key", getKey().toString()); activity.setData("ruleKey", getKey().ruleKey().toString()); - activity.setData("profileKey", getKey().qProfile()); parameters.entrySet().stream() .filter(param -> !param.getKey().isEmpty()) diff --git a/server/sonar-server/src/test/java/org/sonar/server/activity/ActivityServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/activity/ActivityServiceTest.java index 69618927959..7f5eccc0a01 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/activity/ActivityServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/activity/ActivityServiceTest.java @@ -71,15 +71,21 @@ public class ActivityServiceTest { activity.setAction("THE_ACTION"); activity.setMessage("THE_MSG"); activity.setData("foo", "bar"); - activity.setData("profileKey", "PROFILE_KEY"); activity.setProfileKey("PROFILE_KEY"); service.save(activity); - Map dbMap = db.selectFirst("select log_type as \"type\", log_action as \"action\", log_message as \"msg\", data_field as \"data\" from activities"); + Map dbMap = db.selectFirst("select " + + " log_type as \"type\", " + + " log_action as \"action\", " + + " log_message as \"msg\", " + + " data_field as \"data\", " + + " profile_key as \"profileKey\" " + + "from activities"); assertThat(dbMap).containsEntry("type", "ANALYSIS_REPORT"); assertThat(dbMap).containsEntry("action", "THE_ACTION"); assertThat(dbMap).containsEntry("msg", "THE_MSG"); - assertThat(dbMap.get("data")).isEqualTo("foo=bar;profileKey=PROFILE_KEY"); + assertThat(dbMap).containsEntry("profileKey", "PROFILE_KEY"); + assertThat(dbMap.get("data")).isEqualTo("foo=bar"); List docs = es.getDocuments("activities", "activity", ActivityDoc.class); assertThat(docs).hasSize(1); diff --git a/server/sonar-server/src/test/java/org/sonar/server/activity/index/ActivityResultSetIteratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/activity/index/ActivityResultSetIteratorTest.java index 7a20cd28de2..52713dc282b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/activity/index/ActivityResultSetIteratorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/activity/index/ActivityResultSetIteratorTest.java @@ -49,7 +49,8 @@ public class ActivityResultSetIteratorTest { assertThat(doc.get(ActivityIndexDefinition.FIELD_KEY)).isEqualTo("UUID1"); assertThat(doc.get(ActivityIndexDefinition.FIELD_ACTION)).isEqualTo("THE_ACTION"); assertThat(doc.get(ActivityIndexDefinition.FIELD_MESSAGE)).isEqualTo("THE_MSG"); - assertThat((Map) doc.get(ActivityIndexDefinition.FIELD_DETAILS)).containsOnly(MapEntry.entry("foo", "bar")); + assertThat((Map) doc.get(ActivityIndexDefinition.FIELD_DETAILS)) + .containsOnly(MapEntry.entry("foo", "bar"), MapEntry.entry("profileKey", "PROFILE_KEY")); assertThat(doc.get(ActivityIndexDefinition.FIELD_LOGIN)).isEqualTo("THE_AUTHOR"); assertThat(it.hasNext()).isTrue(); diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1260_populate_profile_key_of_activities.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1260_populate_profile_key_of_activities.rb new file mode 100644 index 00000000000..bd92cfafe2c --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1260_populate_profile_key_of_activities.rb @@ -0,0 +1,28 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2014 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube 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. +# +# SonarQube 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. +# +# +# SonarQube 6.0 +# SONAR-7794 +# +class PopulateProfileKeyOfActivities < ActiveRecord::Migration + def self.up + execute_java_migration('org.sonar.db.version.v60.PopulateProfileKeyOfActivities') + end +end diff --git a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java index ec5a26be020..7279a6207be 100644 --- a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java +++ b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java @@ -30,7 +30,7 @@ import org.sonar.db.MyBatis; public class DatabaseVersion { - public static final int LAST_VERSION = 1_259; + public static final int LAST_VERSION = 1_260; /** * The minimum supported version which can be upgraded. Lower diff --git a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java index b8c9c9e3eb4..fb1ec165eb1 100644 --- a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java +++ b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java @@ -128,6 +128,7 @@ import org.sonar.db.version.v60.PopulateComponentUuidColumnsOfSnapshots; import org.sonar.db.version.v60.PopulateComponentUuidOfDuplicationsIndex; import org.sonar.db.version.v60.PopulateComponentUuidOfMeasures; import org.sonar.db.version.v60.PopulateLastUsedColumnOfRulesProfiles; +import org.sonar.db.version.v60.PopulateProfileKeyOfActivities; import org.sonar.db.version.v60.PopulateUuidColumnOnSnapshots; import org.sonar.db.version.v60.PopulateUuidColumnsOfProjects; import org.sonar.db.version.v60.PopulateUuidColumnsOfResourceIndex; @@ -243,6 +244,7 @@ public class MigrationStepModule extends Module { AddLastUsedColumnToRulesProfiles.class, PopulateLastUsedColumnOfRulesProfiles.class, AddProfileKeyToActivities.class, + PopulateProfileKeyOfActivities.class, // SNAPSHOTS.UUID AddUuidColumnToSnapshots.class, diff --git a/sonar-db/src/main/java/org/sonar/db/version/v60/PopulateProfileKeyOfActivities.java b/sonar-db/src/main/java/org/sonar/db/version/v60/PopulateProfileKeyOfActivities.java new file mode 100644 index 00000000000..fa9f0877115 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/version/v60/PopulateProfileKeyOfActivities.java @@ -0,0 +1,63 @@ +/* + * 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.version.v60; + +import java.sql.SQLException; +import java.util.Map; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.db.Database; +import org.sonar.db.version.BaseDataChange; +import org.sonar.db.version.MassUpdate; +import org.sonar.db.version.Select; +import org.sonar.db.version.SqlStatement; + +import static com.google.common.base.Preconditions.checkState; +import static org.apache.commons.lang.StringUtils.isNotBlank; + +public class PopulateProfileKeyOfActivities extends BaseDataChange { + + public PopulateProfileKeyOfActivities(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("select id, data_field from activities where profile_key is null"); + massUpdate.update("update activities set profile_key=?, data_field=? where id=?"); + massUpdate.rowPluralName("activities"); + massUpdate.execute(PopulateProfileKeyOfActivities::handle); + } + + private static boolean handle(Select.Row row, SqlStatement update) throws SQLException { + int id = row.getInt(1); + String data = row.getString(2); + Map fields = KeyValueFormat.parse(data); + String profileKey = fields.remove("profileKey"); + checkState(isNotBlank(profileKey), "No profile key found in db row of activities.data_field", id); + + update.setString(1, profileKey); + update.setString(2, KeyValueFormat.format(fields)); + update.setInt(3, id); + + return true; + } +} diff --git a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql index 207cb3d1b81..de83b31f714 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql +++ b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql @@ -465,6 +465,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1255'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1256'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1257'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1258'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1259'); INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '1418215735482', '1418215735482'); ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; diff --git a/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java b/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java index 00e8e84fecb..d5522ed08da 100644 --- a/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java +++ b/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java @@ -29,6 +29,6 @@ public class MigrationStepModuleTest { public void verify_count_of_added_MigrationStep_types() { ComponentContainer container = new ComponentContainer(); new MigrationStepModule().configure(container); - assertThat(container.size()).isEqualTo(115); + assertThat(container.size()).isEqualTo(116); } } diff --git a/sonar-db/src/test/java/org/sonar/db/version/v60/PopulateProfileKeyOfActivitiesTest.java b/sonar-db/src/test/java/org/sonar/db/version/v60/PopulateProfileKeyOfActivitiesTest.java new file mode 100644 index 00000000000..4bad1469671 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/version/v60/PopulateProfileKeyOfActivitiesTest.java @@ -0,0 +1,97 @@ +/* + * 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.version.v60; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PopulateProfileKeyOfActivitiesTest { + private static final String ACTIVITIES_TABLE = "activities"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public DbTester db = DbTester.createForSchema(System2.INSTANCE, PopulateProfileKeyOfActivitiesTest.class, "activities.sql"); + + PopulateProfileKeyOfActivities underTest = new PopulateProfileKeyOfActivities(db.database()); + + @Test + public void migration_has_no_effect_on_empty_tables() throws SQLException { + underTest.execute(); + + assertThat(db.countRowsOfTable(ACTIVITIES_TABLE)).isEqualTo(0); + } + + @Test + public void migration_update_activities_profile_key() throws SQLException { + insertActivity("first-profile-key"); + insertActivity("first-profile-key"); + insertActivity("first-profile-key"); + insertActivity("second-profile-key"); + insertActivity("third-profile-key"); + + underTest.execute(); + + assertCountActivitiesWithProfile("first-profile-key", 3); + assertCountActivitiesWithProfile("second-profile-key", 1); + assertCountActivitiesWithProfile("third-profile-key", 1); + } + + @Test + public void migration_is_reentrant() throws SQLException { + insertActivity("profile-key"); + underTest.execute(); + assertCountActivitiesWithProfile("profile-key", 1); + + underTest.execute(); + assertCountActivitiesWithProfile("profile-key", 1); + } + + @Test + public void fail_if_activity_data_is_not_well_formatted() throws SQLException { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Error during processing of row: "); + + db.executeInsert(ACTIVITIES_TABLE, "data_field", "key=fakeKey"); + + underTest.execute(); + } + + private void assertCountActivitiesWithProfile(String profileKey, int expectedNumberOfActivities) { + assertThat(countActivitiesWithProfile(profileKey)).isEqualTo(expectedNumberOfActivities); + } + + private int countActivitiesWithProfile(String qualityProfileKey) { + // profile key is removed from data_field + return db.countSql(String.format("select count(1) from activities where profile_key='%s' and data_field='key=fakeKey'", qualityProfileKey)); + } + + private void insertActivity(String profileKey) { + db.executeInsert(ACTIVITIES_TABLE, + "data_field", "key=fakeKey;profileKey=" + profileKey); + } +} diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateProfileKeyOfActivitiesTest/activities.sql b/sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateProfileKeyOfActivitiesTest/activities.sql new file mode 100644 index 00000000000..fead19a9ef2 --- /dev/null +++ b/sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateProfileKeyOfActivitiesTest/activities.sql @@ -0,0 +1,11 @@ +CREATE TABLE "ACTIVITIES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "LOG_KEY" VARCHAR(250), + "PROFILE_KEY" VARCHAR(255), + "CREATED_AT" TIMESTAMP, + "USER_LOGIN" VARCHAR(255), + "LOG_TYPE" VARCHAR(250), + "LOG_ACTION" VARCHAR(250), + "LOG_MESSAGE" VARCHAR(250), + "DATA_FIELD" CLOB(2147483647) +); -- 2.39.5