]> source.dussan.org Git - sonarqube.git/blob
5ca1074bd787f043f39e35b83671fef963f3061e
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.platform.db.migration.version.v108;
21
22 import com.google.gson.Gson;
23 import java.sql.Connection;
24 import java.sql.SQLException;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.concurrent.atomic.AtomicReference;
30 import javax.annotation.Nullable;
31 import org.apache.commons.codec.digest.MurmurHash3;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.sonar.api.utils.System2;
35 import org.sonar.db.Database;
36 import org.sonar.db.DatabaseUtils;
37 import org.sonar.server.platform.db.migration.step.DataChange;
38 import org.sonar.server.platform.db.migration.step.MassUpdate;
39 import org.sonar.server.platform.db.migration.step.Select;
40 import org.sonar.server.platform.db.migration.step.Upsert;
41
42 import static java.lang.String.format;
43 import static java.nio.charset.StandardCharsets.UTF_8;
44
45 public abstract class AbstractMigrateLiveMeasuresToMeasures extends DataChange {
46   private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMigrateLiveMeasuresToMeasures.class);
47
48   private static final Set<String> TEXT_VALUE_TYPES = Set.of("STRING", "LEVEL", "DATA", "DISTRIB");
49   private static final Gson GSON = new Gson();
50
51   private static final String SELECT_QUERY = """
52     SELECT lm.component_uuid,
53       m.name,
54       m.val_type,
55       lm.value,
56       lm.text_value,
57       lm.measure_data
58     FROM live_measures lm
59     INNER JOIN metrics m ON m.uuid = lm.metric_uuid
60     WHERE lm.project_uuid = ?
61     ORDER BY lm.component_uuid
62     """;
63
64   private static final String INSERT_QUERY = """
65     insert into measures (component_uuid, branch_uuid, json_value, json_value_hash, created_at, updated_at)
66     values ( ?, ?, ?, ?, ?, ?)
67     """;
68
69   private final String tableName;
70   private final String item;
71   private final System2 system2;
72
73   protected AbstractMigrateLiveMeasuresToMeasures(Database db, System2 system2, String tableName, String item) {
74     super(db);
75     this.system2 = system2;
76     this.tableName = tableName;
77     this.item = item;
78   }
79
80   private String getSelectUuidQuery() {
81     return format("""
82       SELECT uuid
83       FROM %s
84       WHERE measures_migrated = ?
85       """, tableName);
86   }
87
88   private String getCountQuery() {
89     return format("""
90       SELECT count(uuid)
91       FROM %s
92       """, tableName);
93   }
94
95   private String getUpdateFlagQuery() {
96     return format("""
97       UPDATE %s
98       SET measures_migrated = ?
99       WHERE uuid = ?
100       """, tableName);
101   }
102
103   @Override
104   protected void execute(Context context) throws SQLException {
105     try (Connection c = getDatabase().getDataSource().getConnection()) {
106       // the table is later deleted, this check ensures the migration re-entrance
107       if (!DatabaseUtils.tableExists("live_measures", c)) {
108         return;
109       }
110     }
111
112     List<String> uuids = context.prepareSelect(getSelectUuidQuery())
113       .setBoolean(1, false)
114       .list(row -> row.getString(1));
115
116     Long total = context.prepareSelect(getCountQuery())
117       .get(row -> row.getLong(1));
118
119     LOGGER.info("Starting the migration of {} {}s (total number of {}s: {})", uuids.size(), item, item, total);
120     int migrated = 0;
121
122     for (String uuid : uuids) {
123       try {
124         migrateItem(uuid, context);
125       } catch (Exception e) {
126         LOGGER.error(format("Migration of %s %s failed", item, uuid));
127         throw e;
128       }
129
130       migrated++;
131       if (migrated % 100 == 0) {
132         LOGGER.info("{} {}s migrated", migrated, item);
133       }
134     }
135   }
136
137   private void migrateItem(String uuid, Context context) throws SQLException {
138     LOGGER.debug("Migrating {} {}...", item, uuid);
139
140     Map<String, Object> measureValues = new HashMap<>();
141     AtomicReference<String> componentUuid = new AtomicReference<>(null);
142
143     MassUpdate massUpdate = context.prepareMassUpdate();
144     massUpdate.select(SELECT_QUERY).setString(1, uuid);
145     massUpdate.update(INSERT_QUERY);
146     massUpdate.execute((row, update) -> {
147       boolean shouldUpdate = false;
148       String rowComponentUuid = row.getString(1);
149       if (componentUuid.get() == null || !rowComponentUuid.equals(componentUuid.get())) {
150         if (!measureValues.isEmpty()) {
151           preparePersistMeasure(uuid, update, componentUuid, measureValues);
152           shouldUpdate = true;
153         }
154
155         LOGGER.debug("Starting processing of component {}...", rowComponentUuid);
156         componentUuid.set(rowComponentUuid);
157         measureValues.clear();
158         readMeasureValue(row, measureValues);
159       } else {
160         readMeasureValue(row, measureValues);
161       }
162       return shouldUpdate;
163     });
164     // insert the last component
165     if (!measureValues.isEmpty()) {
166       try (Upsert measureInsert = context.prepareUpsert(INSERT_QUERY)) {
167         preparePersistMeasure(uuid, measureInsert, componentUuid, measureValues);
168         measureInsert
169           .execute()
170           .commit();
171       }
172     }
173
174     LOGGER.debug("Flagging migration done for {} {}...", item, uuid);
175
176     try (Upsert flagUpdate = context.prepareUpsert(getUpdateFlagQuery())) {
177       flagUpdate
178         .setBoolean(1, true)
179         .setString(2, uuid)
180         .execute()
181         .commit();
182     }
183
184     LOGGER.debug("Migration finished for {} {}", item, uuid);
185   }
186
187   private void preparePersistMeasure(String uuid, Upsert update, AtomicReference<String> componentUuid, Map<String, Object> measureValues) throws SQLException {
188     LOGGER.debug("Persisting measures for component {}...", componentUuid.get());
189     String jsonValue = GSON.toJson(measureValues);
190
191     long jsonHash = MurmurHash3.hash128(jsonValue.getBytes(UTF_8))[0];
192
193     update.setString(1, componentUuid.get());
194     update.setString(2, uuid);
195     update.setString(3, jsonValue);
196     update.setLong(4, jsonHash);
197     update.setLong(5, system2.now());
198     update.setLong(6, system2.now());
199   }
200
201   private static void readMeasureValue(Select.Row row, Map<String, Object> measureValues) throws SQLException {
202     String metricName = row.getString(2);
203     String valueType = row.getString(3);
204     Double numericValue = row.getDouble(4);
205     String textValue = row.getString(5);
206     byte[] data = row.getBytes(6);
207
208     Object metricValue = getMetricValue(data, textValue, valueType, numericValue);
209     if (metricValue != null
210       && !measuresPlannedForDeletion(metricName)) {
211       measureValues.put(metricName, metricValue);
212     }
213   }
214
215   private static boolean measuresPlannedForDeletion(String metricName) {
216     return DeleteSoftwareQualityRatingFromProjectMeasures.SOFTWARE_QUALITY_METRICS_TO_DELETE.contains(metricName);
217   }
218
219   private static Object getMetricValue(@Nullable byte[] data, @Nullable String textValue, String valueType, Double numericValue) {
220     return TEXT_VALUE_TYPES.contains(valueType) ? getTextValue(data, textValue) : numericValue;
221   }
222
223   private static String getTextValue(@Nullable byte[] data, @Nullable String textValue) {
224     return data != null ? new String(data, UTF_8) : textValue;
225   }
226 }