diff --git a/backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java b/backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java index 13eec70..593dda9 100644 --- a/backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java +++ b/backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java @@ -90,8 +90,12 @@ private From join(From root, String[] path) { @SuppressWarnings("rawtypes") private From join(From source, String attributeName) { - Optional existingJoin = source.getJoins().stream().filter(join->join.getAttribute().getName().equals(attributeName)).map(join->(Join)join).findFirst(); - return existingJoin.orElseGet(()->source.join(attributeName, currentJoinType)); + Optional existingJoin = source.getJoins().stream() + .map(join -> (Join) join) + .filter(join -> join.getAttribute().getName().equals(attributeName)) + .filter(join -> join.getJoinType() == currentJoinType) + .findFirst(); + return existingJoin.orElseGet(() -> source.join(attributeName, currentJoinType)); } private static Class boxed(Class type) { diff --git a/backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java b/backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java index 1d1cb54..d624c5e 100644 --- a/backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java +++ b/backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java @@ -168,6 +168,25 @@ void testFilterWithOrConstraintPartialMatch() { assertEquals(6, dao.count(pf)); } + @Test + void testIsNullConstraintAfterOrDoesNotMatchAbsentCity() { + // Without the OR, city.population IS NULL correctly returns 0: + // persistedPerson has no city, so the INNER JOIN on city excludes them. + PersonFilter baseline = new PersonFilter(); + baseline.addConstraint(ConstraintBuilder.of("id").equal(persistedPerson.getId())); + baseline.addConstraint(ConstraintBuilder.of("city", "population").isNull()); + assertEquals(0, dao.count(baseline)); + + // Adding an OR that includes persistedPerson should not change the result: + // the IS NULL constraint still requires city to exist, regardless of the OR. + PersonFilter withOr = new PersonFilter(); + withOr.addConstraint( + ConstraintBuilder.of("city", "id").equal(cities.get(0).getId()) + .or(ConstraintBuilder.of("id").equal(persistedPerson.getId()))); + withOr.addConstraint(ConstraintBuilder.of("city", "population").isNull()); + assertEquals(0, dao.count(withOr)); + } + @Test @Disabled void testDelete() {