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;
"data_field",
"user_login",
"log_type",
- "created_at"
+ "created_at",
+ "profile_key"
};
private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from activities ";
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<String, String> 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);
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())
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<String, Object> dbMap = db.selectFirst("select log_type as \"type\", log_action as \"action\", log_message as \"msg\", data_field as \"data\" from activities");
+ Map<String, Object> 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<ActivityDoc> docs = es.getDocuments("activities", "activity", ActivityDoc.class);
assertThat(docs).hasSize(1);
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<String, String>) 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();
--- /dev/null
+#
+# 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
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
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;
AddLastUsedColumnToRulesProfiles.class,
PopulateLastUsedColumnOfRulesProfiles.class,
AddProfileKeyToActivities.class,
+ PopulateProfileKeyOfActivities.class,
// SNAPSHOTS.UUID
AddUuidColumnToSnapshots.class,
--- /dev/null
+/*
+ * 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<String, String> 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;
+ }
+}
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;
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);
}
}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+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)
+);