You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ossie.apache.org by jb...@apache.org on 2026/10/16 09:11:08 UTC

(ossie) branch main updated: fix(salesforce): rebuild each relationship's join criteria independently (#593)

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ossie.git


The following commit(s) were added to refs/heads/main by this push:
     new 464f3968 fix(salesforce): rebuild each relationship's join criteria independently (#593)
464f3968 is described below

commit 464f3968a91150ab2dec091ab2b4c62c3ea598e9
Author: RyutoYoda❄️ <98...@users.noreply.github.com>
AuthorDate: Fri Oct 16 18:11:00 2026 +0900

    fix(salesforce): rebuild each relationship's join criteria independently (#593)
---
 .../converter/RelationshipMappingHandler.java      | 14 ++--
 .../apache/ossie/MetricExportIntegrationTest.java  |  9 +-
 .../ossie/OssieToSalesforceConverterTest.java      | 97 ++++++++++++++++++++++
 3 files changed, 110 insertions(+), 10 deletions(-)

diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java
index 08ce6fd1..07c2c829 100644
--- a/converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java
+++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java
@@ -94,10 +94,12 @@ public class RelationshipMappingHandler implements PipelineStep {
 
         outputData.putAll(mappedData);
 
-        // Apply manual mappings (criteria)
+        // Apply manual mappings (criteria). The Salesforce relationships were mapped from
+        // the filtered list, so criteria have to be rebuilt from that same list: pairing
+        // them with the unfiltered one shifts every relationship that follows a dropped one.
         List<Object> sfRelationships = getList(outputData, SEMANTIC_RELATIONSHIPS);
         if (sfRelationships != null) {
-            reconstructCriteria(ossieRelationships, sfRelationships);
+            reconstructCriteria(validRelationships, sfRelationships);
         }
 
         customExtensionHandler.restoreCustomExtensionsAtLevel(outputData, sourceData, Level.RELATIONSHIPS);
@@ -216,19 +218,21 @@ public class RelationshipMappingHandler implements PipelineStep {
             Object fromColumnsObj = ossieRel.get(FROM_COLUMNS);
             Object toColumnsObj = ossieRel.get(TO_COLUMNS);
 
+            // Each relationship is rebuilt on its own: a relationship whose join columns
+            // cannot be paired gets no criteria, and the ones that follow still get theirs.
             if (fromColumnsObj == null || toColumnsObj == null) {
-                return;
+                continue;
             }
 
             List<Object> fromColumns = asList(fromColumnsObj);
             List<Object> toColumns = asList(toColumnsObj);
 
             if (fromColumns.isEmpty() || toColumns.isEmpty()) {
-                return;
+                continue;
             }
 
             if (fromColumns.size() != toColumns.size()) {
-                return;
+                continue;
             }
 
             List<Map<String, Object>> criteriaArray = new ArrayList<>();
diff --git a/converters/salesforce/src/test/java/org/apache/ossie/MetricExportIntegrationTest.java b/converters/salesforce/src/test/java/org/apache/ossie/MetricExportIntegrationTest.java
index 4c496d99..418acef4 100644
--- a/converters/salesforce/src/test/java/org/apache/ossie/MetricExportIntegrationTest.java
+++ b/converters/salesforce/src/test/java/org/apache/ossie/MetricExportIntegrationTest.java
@@ -346,7 +346,7 @@ class MetricExportIntegrationTest {
     }
 
     @Test
-    void metricCannotUseJoinCriteriaCorruptedByInheritedRelationshipFiltering() throws Exception {
+    void metricKeepsItsJoinCriteriaWhenAnEarlierRelationshipIsFiltered() throws Exception {
         Map<String, Object> source = model("sales", List.of(metric("combined", "ANSI_SQL",
                 "SUM(orders.profit) + SUM(returns.profit)")));
         Map<String, Object> returns = new LinkedHashMap<>(items(source, "datasets").get(0));
@@ -358,12 +358,11 @@ class MetricExportIntegrationTest {
         source.put("relationships", List.of(valid));
         assertTrue(measurements(convertOne(source)).get(0).get("expression").toString().contains("[returns].[profit]"));
 
+        // A relationship ahead of it is dropped for naming a column that does not exist. The
+        // surviving relationship keeps its own join columns, so the metric resolves unchanged.
         source.put("relationships", List.of(Map.of("name", "removed_first", "from", "orders", "to", "returns",
                 "from_columns", List.of("missing"), "to_columns", List.of("customer_id")), valid));
-        var error = assertThrows(ConversionException.class, () -> convertOne(source));
-        assertTrue(error.getMessage().contains("combined"), error.getMessage());
-        assertTrue(error.getMessage().contains("orders_returns"), error.getMessage());
-        assertTrue(error.getMessage().contains("join key correspondence"), error.getMessage());
+        assertTrue(measurements(convertOne(source)).get(0).get("expression").toString().contains("[returns].[profit]"));
     }
 
     private Map<String, Object> convertOne(Map<String, Object> model) throws IOException {
diff --git a/converters/salesforce/src/test/java/org/apache/ossie/OssieToSalesforceConverterTest.java b/converters/salesforce/src/test/java/org/apache/ossie/OssieToSalesforceConverterTest.java
index e9938834..90a19dc3 100644
--- a/converters/salesforce/src/test/java/org/apache/ossie/OssieToSalesforceConverterTest.java
+++ b/converters/salesforce/src/test/java/org/apache/ossie/OssieToSalesforceConverterTest.java
@@ -251,6 +251,103 @@ class OssieToSalesforceConverterTest {
         assertTrue(hasValidOrdersProducts, "Orders_Products should be included");
     }
 
+    /**
+     * Builds a model whose first relationship is dropped by the validator, because its
+     * join column is not a field of the entity it names, while the second one is valid.
+     * Export has to carry the second relationship's own endpoints and columns across.
+     */
+    private static String ossieModelWithAFilteredRelationshipFirst() {
+        return "version: 0.2.0.dev0\n"
+                + "name: Imported_Model\n"
+                + "datasets:\n"
+                + dataset("Customers", "customer_id")
+                + dataset("Orders", "product_id")
+                + dataset("Products", "product_id")
+                + "relationships:\n"
+                + relationship("Customers_Orders", "Customers", "Orders", "missing_column")
+                + relationship("Orders_Products", "Orders", "Products", "product_id");
+    }
+
+    private static String dataset(String name, String... columns) {
+        StringBuilder yaml = new StringBuilder("- name: " + name + "\n"
+                + "  source: " + name + "__dll\n"
+                + "  fields:\n");
+        for (String column : columns) {
+            yaml.append("  - name: ").append(column).append("\n")
+                    .append("    datatype: String\n")
+                    .append("    dimension: {}\n")
+                    .append("    expression:\n")
+                    .append("      dialects:\n")
+                    .append("      - dialect: ANSI_SQL\n")
+                    .append("        expression: ").append(column).append("\n");
+        }
+        return yaml.toString();
+    }
+
+    private static String relationship(String name, String from, String to, String column) {
+        return "- name: " + name + "\n"
+                + "  from: " + from + "\n"
+                + "  to: " + to + "\n"
+                + "  from_columns: [" + column + "]\n"
+                + "  to_columns: [" + column + "]\n";
+    }
+
+    @Test
+    void testRelationshipAfterAFilteredOneKeepsItsOwnEndpointsAndCriteria() throws Exception {
+        List<String> results = converter.convert(ossieModelWithAFilteredRelationshipFirst());
+        Map<String, Object> sfModel = jsonMapper.readValue(results.get(0), new TypeReference<Map<String, Object>>() {});
+
+        List<Map<String, Object>> relationships = (List<Map<String, Object>>) sfModel.get("semanticRelationships");
+        assertNotNull(relationships);
+        assertEquals(1, relationships.size(), "Only the valid relationship should be exported");
+
+        Map<String, Object> exported = relationships.get(0);
+        assertEquals("Orders_Products", exported.get("apiName"));
+        assertEquals("Orders", exported.get("leftSemanticDefinitionApiName"));
+        assertEquals("Products", exported.get("rightSemanticDefinitionApiName"));
+
+        List<Map<String, Object>> criteria = (List<Map<String, Object>>) exported.get("criteria");
+        assertNotNull(criteria);
+        assertEquals(1, criteria.size());
+        assertEquals("product_id", criteria.get(0).get("leftSemanticFieldApiName"));
+        assertEquals("product_id", criteria.get(0).get("rightSemanticFieldApiName"));
+    }
+
+    @Test
+    void testRelationshipWithUnpairableJoinColumnsLeavesTheNextOneIntact() throws Exception {
+        // The first relationship names two join columns on one side and one on the other, so
+        // there is no criterion to build for it. The second one still gets its own criteria.
+        String model = "version: 0.2.0.dev0\n"
+                + "name: Imported_Model\n"
+                + "datasets:\n"
+                + dataset("Customers", "customer_id", "tenant_id")
+                + dataset("Orders", "customer_id", "product_id")
+                + dataset("Products", "product_id")
+                + "relationships:\n"
+                + "- name: Customers_Orders\n"
+                + "  from: Customers\n"
+                + "  to: Orders\n"
+                + "  from_columns: [customer_id, tenant_id]\n"
+                + "  to_columns: [customer_id]\n"
+                + relationship("Orders_Products", "Orders", "Products", "product_id");
+
+        List<String> results = converter.convert(model);
+        Map<String, Object> sfModel = jsonMapper.readValue(results.get(0), new TypeReference<Map<String, Object>>() {});
+
+        List<Map<String, Object>> relationships = (List<Map<String, Object>>) sfModel.get("semanticRelationships");
+        assertNotNull(relationships);
+        Map<String, Object> exported = relationships.stream()
+                .filter(relationship -> "Orders_Products".equals(relationship.get("apiName")))
+                .findFirst()
+                .orElseThrow();
+
+        List<Map<String, Object>> criteria = (List<Map<String, Object>>) exported.get("criteria");
+        assertNotNull(criteria, "The relationship after an unpairable one keeps its criteria");
+        assertEquals(1, criteria.size());
+        assertEquals("product_id", criteria.get(0).get("leftSemanticFieldApiName"));
+        assertEquals("product_id", criteria.get(0).get("rightSemanticFieldApiName"));
+    }
+
     @Test
     void testAModelWhoseRelationshipsAreAllFilteredExportsNone() throws Exception {
         // Dropping the last valid relationship used to leave the relationship mappings