]> source.dussan.org Git - sonarqube.git/blob
307d57507106f620fb231f414fc7a01cb96b10d0
[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.SQLException;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.concurrent.atomic.AtomicReference;
29 import javax.annotation.Nullable;
30 import org.apache.commons.codec.digest.MurmurHash3;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import org.sonar.api.utils.System2;
34 import org.sonar.db.Database;
35 import org.sonar.server.platform.db.migration.step.DataChange;
36 import org.sonar.server.platform.db.migration.step.MassUpdate;
37 import org.sonar.server.platform.db.migration.step.Select;
38 import org.sonar.server.platform.db.migration.step.Upsert;
39
40 import static java.lang.String.format;
41 import static java.nio.charset.StandardCharsets.UTF_8;
42
43 public abstract class AbstractMigrateLiveMeasuresToMeasures extends DataChange {
44   private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMigrateLiveMeasuresToMeasures.class);
45
46   private static final Set<String> TEXT_VALUE_TYPES = Set.of("STRING", "LEVEL", "DATA", "DISTRIB");
47   private static final Gson GSON = new Gson();
48
49   private static final String SELECT_QUERY = """
50     SELECT lm.component_uuid,
51       m.name,
52       m.val_type,
53       lm.value,
54       lm.text_value,
55       lm.measure_data
56     FROM live_measures lm
57     INNER JOIN metrics m ON m.uuid = lm.metric_uuid
58     WHERE lm.project_uuid = ?
59     ORDER BY lm.component_uuid
60     """;
61
62   private static final String INSERT_QUERY = """
63     insert into measures (component_uuid, branch_uuid, json_value, json_value_hash, created_at, updated_at)
64     values ( ?, ?, ?, ?, ?, ?)
65     """;
66
67   private final String tableName;
68   private final String item;
69   private final System2 system2;
70
71   protected AbstractMigrateLiveMeasuresToMeasures(Database db, System2 system2, String tableName, String item) {
72     super(db);
73     this.system2 = system2;
74     this.tableName = tableName;
75     this.item = item;
76   }
77
78   private String getSelectUuidQuery() {
79     return format("""
80     SELECT uuid
81     FROM %s
82     WHERE measures_migrated = ?
83     """, tableName);
84   }
85
86   private String getCountQuery() {
87     return format("""
88     SELECT count(uuid)
89     FROM %s
90     """, tableName);
91   }
92
93   private String getUpdateFlagQuery() {
94     return format("""
95     UPDATE %s
96     SET measures_migrated = ?
97     WHERE uuid = ?
98     """, tableName);
99   }
100
101   @Override
102   protected void execute(Context context) throws SQLException {
103     List<String> uuids = context.prepareSelect(getSelectUuidQuery())
104       .setBoolean(1, false)
105       .list(row -> row.getString(1));
106
107     Long total = context.prepareSelect(getCountQuery())
108       .get(row -> row.getLong(1));
109
110     LOGGER.info("Starting the migration of {} {}s (total number of {}s: {})", uuids.size(), item, item, total);
111     int migrated = 0;
112
113     for (String uuid : uuids) {
114       migrateItem(uuid, context);
115
116       migrated++;
117       if (migrated % 100 == 0) {
118         LOGGER.info("{} {}s migrated", migrated, item);
119       }
120     }
121   }
122
123   private void migrateItem(String uuid, Context context) throws SQLException {
124     LOGGER.debug("Migrating {} {}...", item, uuid);
125
126     Map<String, Object> measureValues = new HashMap<>();
127     AtomicReference<String> componentUuid = new AtomicReference<>(null);
128
129     MassUpdate massUpdate = context.prepareMassUpdate();
130     massUpdate.select(SELECT_QUERY).setString(1, uuid);
131     massUpdate.update(INSERT_QUERY);
132     massUpdate.execute((row, update) -> {
133       boolean shouldUpdate = false;
134       String rowComponentUuid = row.getString(1);
135       if (componentUuid.get() == null || !rowComponentUuid.equals(componentUuid.get())) {
136         if (!measureValues.isEmpty()) {
137           preparePersistMeasure(uuid, update, componentUuid, measureValues);
138           shouldUpdate = true;
139         }
140
141         LOGGER.debug("Starting processing of component {}...", rowComponentUuid);
142         componentUuid.set(rowComponentUuid);
143         measureValues.clear();
144         readMeasureValue(row, measureValues);
145       } else {
146         readMeasureValue(row, measureValues);
147       }
148       return shouldUpdate;
149     });
150     // insert the last component
151     if (!measureValues.isEmpty()) {
152       Upsert measureInsert = context.prepareUpsert(INSERT_QUERY);
153       preparePersistMeasure(uuid, measureInsert, componentUuid, measureValues);
154       measureInsert
155         .execute()
156         .commit();
157     }
158
159     LOGGER.debug("Flagging migration done for {} {}...", item, uuid);
160
161     context.prepareUpsert(getUpdateFlagQuery())
162       .setBoolean(1, true)
163       .setString(2, uuid)
164       .execute()
165       .commit();
166
167     LOGGER.debug("Migration finished for {} {}", item, uuid);
168   }
169
170   private void preparePersistMeasure(String uuid, Upsert update, AtomicReference<String> componentUuid, Map<String, Object> measureValues) throws SQLException {
171     LOGGER.debug("Persisting measures for component {}...", componentUuid.get());
172     String jsonValue = GSON.toJson(measureValues);
173
174     long jsonHash = MurmurHash3.hash128(jsonValue.getBytes(UTF_8))[0];
175
176     update.setString(1, componentUuid.get());
177     update.setString(2, uuid);
178     update.setString(3, jsonValue);
179     update.setLong(4, jsonHash);
180     update.setLong(5, system2.now());
181     update.setLong(6, system2.now());
182   }
183
184   private static void readMeasureValue(Select.Row row, Map<String, Object> measureValues) throws SQLException {
185     String metricName = row.getString(2);
186     String valueType = row.getString(3);
187     Double numericValue = row.getDouble(4);
188     String textValue = row.getString(5);
189     byte[] data = row.getBytes(6);
190
191     Object metricValue = getMetricValue(data, textValue, valueType, numericValue);
192     if (metricValue != null) {
193       measureValues.put(metricName, metricValue);
194     }
195   }
196
197   private static Object getMetricValue(@Nullable byte[] data, @Nullable String textValue, String valueType, Double numericValue) {
198     return TEXT_VALUE_TYPES.contains(valueType) ? getTextValue(data, textValue) : numericValue;
199   }
200
201   private static String getTextValue(@Nullable byte[] data, @Nullable String textValue) {
202     return data != null ? new String(data, UTF_8) : textValue;
203   }
204 }