3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.platform.db.migration.version.v108;
22 import com.google.gson.Gson;
23 import java.sql.SQLException;
24 import java.util.HashMap;
25 import java.util.List;
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;
40 import static java.lang.String.format;
41 import static java.nio.charset.StandardCharsets.UTF_8;
43 public abstract class AbstractMigrateLiveMeasuresToMeasures extends DataChange {
44 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMigrateLiveMeasuresToMeasures.class);
46 private static final Set<String> TEXT_VALUE_TYPES = Set.of("STRING", "LEVEL", "DATA", "DISTRIB");
47 private static final Gson GSON = new Gson();
49 private static final String SELECT_QUERY = """
50 SELECT lm.component_uuid,
57 INNER JOIN metrics m ON m.uuid = lm.metric_uuid
58 WHERE lm.project_uuid = ?
59 ORDER BY lm.component_uuid
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 ( ?, ?, ?, ?, ?, ?)
67 private final String tableName;
68 private final String item;
69 private final System2 system2;
71 protected AbstractMigrateLiveMeasuresToMeasures(Database db, System2 system2, String tableName, String item) {
73 this.system2 = system2;
74 this.tableName = tableName;
78 private String getSelectUuidQuery() {
82 WHERE measures_migrated = ?
86 private String getCountQuery() {
93 private String getUpdateFlagQuery() {
96 SET measures_migrated = ?
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));
107 Long total = context.prepareSelect(getCountQuery())
108 .get(row -> row.getLong(1));
110 LOGGER.info("Starting the migration of {} {}s (total number of {}s: {})", uuids.size(), item, item, total);
113 for (String uuid : uuids) {
114 migrateItem(uuid, context);
117 if (migrated % 100 == 0) {
118 LOGGER.info("{} {}s migrated", migrated, item);
123 private void migrateItem(String uuid, Context context) throws SQLException {
124 LOGGER.debug("Migrating {} {}...", item, uuid);
126 Map<String, Object> measureValues = new HashMap<>();
127 AtomicReference<String> componentUuid = new AtomicReference<>(null);
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);
141 LOGGER.debug("Starting processing of component {}...", rowComponentUuid);
142 componentUuid.set(rowComponentUuid);
143 measureValues.clear();
144 readMeasureValue(row, measureValues);
146 readMeasureValue(row, measureValues);
150 // insert the last component
151 if (!measureValues.isEmpty()) {
152 try (Upsert measureInsert = context.prepareUpsert(INSERT_QUERY)) {
153 preparePersistMeasure(uuid, measureInsert, componentUuid, measureValues);
160 LOGGER.debug("Flagging migration done for {} {}...", item, uuid);
162 try (Upsert flagUpdate = context.prepareUpsert(getUpdateFlagQuery())) {
170 LOGGER.debug("Migration finished for {} {}", item, uuid);
173 private void preparePersistMeasure(String uuid, Upsert update, AtomicReference<String> componentUuid, Map<String, Object> measureValues) throws SQLException {
174 LOGGER.debug("Persisting measures for component {}...", componentUuid.get());
175 String jsonValue = GSON.toJson(measureValues);
177 long jsonHash = MurmurHash3.hash128(jsonValue.getBytes(UTF_8))[0];
179 update.setString(1, componentUuid.get());
180 update.setString(2, uuid);
181 update.setString(3, jsonValue);
182 update.setLong(4, jsonHash);
183 update.setLong(5, system2.now());
184 update.setLong(6, system2.now());
187 private static void readMeasureValue(Select.Row row, Map<String, Object> measureValues) throws SQLException {
188 String metricName = row.getString(2);
189 String valueType = row.getString(3);
190 Double numericValue = row.getDouble(4);
191 String textValue = row.getString(5);
192 byte[] data = row.getBytes(6);
194 Object metricValue = getMetricValue(data, textValue, valueType, numericValue);
195 if (metricValue != null) {
196 measureValues.put(metricName, metricValue);
200 private static Object getMetricValue(@Nullable byte[] data, @Nullable String textValue, String valueType, Double numericValue) {
201 return TEXT_VALUE_TYPES.contains(valueType) ? getTextValue(data, textValue) : numericValue;
204 private static String getTextValue(@Nullable byte[] data, @Nullable String textValue) {
205 return data != null ? new String(data, UTF_8) : textValue;