diff --git a/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/DatasetServiceImpl.java b/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/DatasetServiceImpl.java
index cfed11c91..10ae3bad3 100644
--- a/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/DatasetServiceImpl.java
+++ b/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/DatasetServiceImpl.java
@@ -18,6 +18,8 @@
import jakarta.ws.rs.DefaultValue;
import org.hibernate.Hibernate;
+import org.hibernate.ScrollMode;
+import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.query.NativeQuery;
import org.hibernate.type.StandardBasicTypes;
@@ -445,9 +447,8 @@ void calculateLabelValues(int testId, int datasetId, int queryLabelId, boolean i
"Evaluation of label %s failed: '%s' Code:
%s
", row[0], e.getMessage(), jsCode),
(out) -> logMessage(datasetId, PersistentLogDAO.DEBUG, "Output while calculating labels: %s
", out));
- //Create new dataset views from the recently created label values
- em.createNativeQuery("DELETE FROM dataset_view WHERE dataset_id = ?1").setParameter(1, datasetId).executeUpdate();
- em.createNativeQuery("call calc_dataset_view(?1);").setParameter(1, datasetId).executeUpdate();
+ // create new dataset views from the recently created label values
+ calcDatasetViews(datasetId);
createFingerprint(datasetId, testId);
mediator.updateLabels(new Dataset.LabelsUpdatedEvent(testId, datasetId, isRecalculation));
@@ -456,10 +457,48 @@ void calculateLabelValues(int testId, int datasetId, int queryLabelId, boolean i
testId, new Dataset.LabelsUpdatedEvent(testId, datasetId, isRecalculation)));
}
+ @Transactional
+ public void calcDatasetViews(int datasetId) {
+ // TODO(user) move calc_dataset_view into Horreum business logic see https://github.com/hibernate/hibernate-orm/pull/7457
+ em.createNativeQuery("DELETE FROM dataset_view WHERE dataset_id = ?1").setParameter(1, datasetId).executeUpdate();
+ em.createNativeQuery("call calc_dataset_view(?1, NULL);").setParameter(1, datasetId).executeUpdate();
+ }
+
+ @Transactional
+ @SuppressWarnings("unchecked")
+ public void calcDatasetViewsByTestAndView(int testId, int viewId) {
+ // delete all dataset views associated to the provided viewId and testId
+ // for new views it won't delete anything
+ em.createNativeQuery(
+ "DELETE FROM dataset_view WHERE view_id = ?1 AND dataset_id IN (SELECT id FROM dataset WHERE testid = ?2)")
+ .setParameter(1, viewId)
+ .setParameter(2, testId)
+ .executeUpdate();
+
+ // re-create dataset views associated to the provided viewId
+ try (ScrollableResults datasetIds = em
+ .createNativeQuery("SELECT id FROM dataset WHERE testid = ?1")
+ .setParameter(1, testId)
+ .unwrap(NativeQuery.class)
+ .setReadOnly(false)
+ .setFetchSize(100)
+ .scroll(ScrollMode.FORWARD_ONLY)) {
+ while (datasetIds.next()) {
+ int datasetId = datasetIds.get();
+ log.tracef("Recalculate dataset views for view %d and dataset %d", viewId, datasetId);
+ em.createNativeQuery("call calc_dataset_view(?1, ?2);")
+ .setParameter(1, datasetId)
+ .setParameter(2, viewId)
+ .executeUpdate();
+ }
+ }
+ }
+
@Transactional
public void deleteDataset(int datasetId) {
em.createNativeQuery("DELETE FROM label_values WHERE dataset_id = ?1").setParameter(1, datasetId).executeUpdate();
em.createNativeQuery("DELETE FROM dataset_schemas WHERE dataset_id = ?1").setParameter(1, datasetId).executeUpdate();
+ em.createNativeQuery("DELETE FROM dataset_view WHERE dataset_id = ?1").setParameter(1, datasetId).executeUpdate();
em.createNativeQuery("DELETE FROM fingerprint WHERE dataset_id = ?1").setParameter(1, datasetId).executeUpdate();
em.createNativeQuery("DELETE FROM dataset WHERE id = ?1").setParameter(1, datasetId).executeUpdate();
}
diff --git a/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/RunServiceImpl.java b/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/RunServiceImpl.java
index 4d65d03e6..88961da86 100644
--- a/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/RunServiceImpl.java
+++ b/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/RunServiceImpl.java
@@ -1087,9 +1087,8 @@ public Map updateSchema(int id, String path, String schemaUri)
@Transactional
@Override
public List recalculateDatasets(int runId) {
- transform(runId, true);
- return session.createNativeQuery("SELECT id FROM dataset WHERE runid = ? ORDER BY ordinal", Integer.class)
- .setParameter(1, runId).getResultList();
+ log.infof("Transforming run id %d", runId);
+ return transform(runId, true);
}
@RolesAllowed(Roles.ADMIN)
diff --git a/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/TestServiceImpl.java b/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/TestServiceImpl.java
index f2c158124..46eef8d91 100644
--- a/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/TestServiceImpl.java
+++ b/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/TestServiceImpl.java
@@ -669,6 +669,7 @@ public void recalculateDatasets(int testId) {
status.finished++;
status.datasets += newDatasets;
if (status.finished == status.totalRuns) {
+ log.infof("Datasets recalculation for test %d (%s) completed", testId, test.name);
recalculations.remove(testId, status);
}
}
diff --git a/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/UIServiceImpl.java b/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/UIServiceImpl.java
index b3c28f8ac..d8692018e 100644
--- a/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/UIServiceImpl.java
+++ b/horreum-backend/src/main/java/io/hyperfoil/tools/horreum/svc/UIServiceImpl.java
@@ -25,6 +25,9 @@ public class UIServiceImpl implements UIService {
@Inject
TestServiceImpl testService;
+ @Inject
+ DatasetServiceImpl datasetService;
+
@Override
@RolesAllowed("tester")
@WithRoles
@@ -64,6 +67,9 @@ private View doUpdate(TestDAO test, ViewDAO view) {
test.views.add(view);
test.persist();
em.flush();
+
+ // update datasets views
+ datasetService.calcDatasetViewsByTestAndView(test.id, view.id);
return ViewMapper.from(view);
}
@@ -80,6 +86,8 @@ public void deleteView(int testId, int viewId) {
if (!test.views.removeIf(v -> v.id == viewId)) {
throw ServiceException.badRequest("Test does not contain this view!");
}
+ // remove dataset views records linked to this view
+ em.createNativeQuery("DELETE FROM dataset_view WHERE view_id = ?1").setParameter(1, viewId).executeUpdate();
// the orphan removal doesn't work for some reason, we need to remove if manually
ViewDAO.deleteById(viewId);
test.persist();
diff --git a/horreum-backend/src/main/resources/db/changeLog.xml b/horreum-backend/src/main/resources/db/changeLog.xml
index ae409e624..59532b782 100644
--- a/horreum-backend/src/main/resources/db/changeLog.xml
+++ b/horreum-backend/src/main/resources/db/changeLog.xml
@@ -4688,4 +4688,43 @@
$$ LANGUAGE plpgsql;
+
+ ANY
+
+ -- drop triggers
+ DROP TRIGGER IF EXISTS dsv_after_delete ON viewcomponent;
+ DROP TRIGGER IF EXISTS dsv_after_update ON viewcomponent;
+ DROP TRIGGER IF EXISTS recalc_dataset_view ON view_recalc_queue;
+ DROP TRIGGER IF EXISTS dsv_after_insert ON label_values;
+
+ -- drop functions
+ DROP FUNCTION dsv_after_vc_delete_func;
+ DROP FUNCTION dsv_after_vc_update_func;
+ DROP FUNCTION recalc_dataset_view;
+ DROP FUNCTION dsv_after_lv_insert_func;
+
+ -- drop view_recalc_queue table as not needed anymore
+ DROP TABLE view_recalc_queue;
+
+ -- still need db procedure until https://hibernate.atlassian.net/browse/HHH-17314 is fixed in quarkus
+ -- viewId can be NULL
+ CREATE OR REPLACE PROCEDURE calc_dataset_view(datasetId bigint, viewId bigint DEFAULT NULL) AS $$
+ BEGIN
+ WITH view_agg AS (
+ SELECT
+ vc.view_id, vc.id as vcid, array_agg(DISTINCT label.id) as label_ids, jsonb_object_agg(label.name, lv.value) as value FROM dataset_schemas ds
+ JOIN label ON label.schema_id = ds.schema_id
+ JOIN viewcomponent vc ON vc.labels ? label.name
+ JOIN label_values lv ON lv.label_id = label.id AND lv.dataset_id = ds.dataset_id
+ WHERE ds.dataset_id = datasetId AND (viewId IS NULL OR vc.view_id = viewId)
+ AND vc.view_id IN (SELECT view.id FROM view JOIN dataset ON view.test_id = dataset.testid WHERE dataset.id = datasetId)
+ GROUP BY vc.view_id, vcid
+ )
+ INSERT INTO dataset_view (dataset_id, view_id, label_ids, value)
+ SELECT datasetId, view_id, array_agg(DISTINCT label_id), jsonb_object_agg(vcid, value) FROM view_agg, unnest(label_ids) as label_id
+ GROUP BY view_id;
+ END
+ $$ LANGUAGE plpgsql;
+
+
diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/DatasetServiceTest.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/DatasetServiceTest.java
index da0b8d2c5..af6f594e3 100644
--- a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/DatasetServiceTest.java
+++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/DatasetServiceTest.java
@@ -34,6 +34,7 @@
import io.hyperfoil.tools.horreum.entity.data.*;
import io.hyperfoil.tools.horreum.entity.data.ViewComponentDAO;
import io.hyperfoil.tools.horreum.mapper.LabelMapper;
+import io.hyperfoil.tools.horreum.mapper.ViewMapper;
import io.hyperfoil.tools.horreum.server.CloseMe;
import io.hyperfoil.tools.horreum.test.HorreumTestProfile;
import io.hyperfoil.tools.horreum.test.PostgresResource;
@@ -41,6 +42,7 @@
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import io.quarkus.test.oidc.server.OidcWiremockTestResource;
+import io.restassured.response.Response;
@QuarkusTest
@QuarkusTestResource(PostgresResource.class)
@@ -380,13 +382,21 @@ public void testDatasetView() {
assertTrue(ids.contains(labelA));
assertTrue(ids.contains(labelB));
- Util.withTx(tm, () -> {
- try (CloseMe ignored = roleManager.withRoles(Arrays.asList(TESTER_ROLES))) {
- int vcs = em.createNativeQuery("UPDATE viewcomponent SET labels = '[\"a\",\"b\"]'").executeUpdate();
- assertEquals(2, vcs);
- }
- return null;
- });
+ // there must be only one result here!
+ ViewDAO dbView = (ViewDAO) ViewDAO.findAll().list().get(0);
+ assertEquals(2, dbView.components.size());
+
+ View updateView = ViewMapper.from(dbView);
+ ArrayNode vcLabels = (ArrayNode) updateView.components.stream().filter(vc -> vc.labels.size() == 1).findFirst()
+ .orElseThrow().labels;
+ vcLabels.add("b");
+
+ Response response = jsonRequest()
+ .auth()
+ .oauth2(getTesterToken())
+ .body(updateView)
+ .post("/api/ui/view");
+ assertEquals(200, response.getStatusCode());
JsonNode updated = fetchDatasetsByTest(test.id);
JsonNode updatedView = updated.get("datasets").get(0).get("view");