Skip to content

feat: add OR operator to QuerySpec constraints#108

Draft
javier-godoy wants to merge 2 commits intomasterfrom
querySpec-or
Draft

feat: add OR operator to QuerySpec constraints#108
javier-godoy wants to merge 2 commits intomasterfrom
querySpec-or

Conversation

@javier-godoy
Copy link
Member

@javier-godoy javier-godoy commented Mar 16, 2026

Close #61

Summary by CodeRabbit

  • New Features

    • Added OR-based filtering across the model with a new disjunction construct and convenience method to combine criteria.
    • OR evaluation now handles partial-match relationships so items matching any condition (including across optional relations) are returned.
  • Tests

    • Added tests validating full-match and partial-match OR scenarios.
  • Chores

    • Bumped project version to 1.2.0-SNAPSHOT across modules.

@javier-godoy javier-godoy requested a review from mlopezFC March 16, 2026 18:48
@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Walkthrough

Adds OR (disjunction) support: new DisjunctionConstraint model and factory, convenience or(...) on Constraint, transformer hook in model, JPA implementation producing OR predicates using LEFT joins for disjunction members, tests, and module version bumps.

Changes

Cohort / File(s) Summary
Constraint Model
backend-core-model/src/main/java/com/flowingcode/backendcore/model/Constraint.java, backend-core-model/src/main/java/com/flowingcode/backendcore/model/ConstraintTransformer.java, backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java
Adds DisjunctionConstraint class with of(...) factory; adds default Constraint or(...) convenience on Constraint; extends ConstraintTransformer dispatch with transformDisjunctionConstraint(...) hook (default null).
JPA Transformer Implementation
backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java
Introduces stateful currentJoinType (default INNER); implements transformDisjunctionConstraint(DisjunctionConstraint) which temporarily sets join type to LEFT, transforms sub-constraints to predicates, combines them with criteriaBuilder.or(...), and restores join type.
Tests
backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java
Adds testFilterWithOrConstraint() and testFilterWithOrConstraintPartialMatch() to validate OR/disjunction behavior (including partial-match LEFT JOIN scenarios).
POM version bumps
pom.xml, backend-core-business/pom.xml, backend-core-business-impl/pom.xml, backend-core-business-spring-impl/pom.xml, backend-core-data/pom.xml, backend-core-data-impl/pom.xml, backend-core-model/pom.xml
Bumps project/parent version from 1.1.1-SNAPSHOT to 1.2.0-SNAPSHOT across modules.
Headers / Copyrights
backend-core-data-impl/src/main/java/.../ConstraintTransformerJpaImpl.java, backend-core-model/src/main/java/.../ConstraintTransformer.java, backend-core-model/src/main/java/.../Constraint.java, backend-core-data-impl/src/test/java/.../JpaDaoSupportTest.java
Updated copyright years to include 2026.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add OR operator to QuerySpec constraints' directly and clearly describes the main feature added in the changeset: implementing OR operator support for QuerySpec constraints.
Linked Issues check ✅ Passed The PR successfully implements the requested OR operator feature [#61] by introducing DisjunctionConstraint class, adding transformDisjunctionConstraint() methods, and providing or() convenience methods on the Constraint interface.
Out of Scope Changes check ✅ Passed All code changes directly support the OR operator feature [#61]: constraint model changes, transformer implementations, comprehensive test coverage, and version bumps are all within scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch querySpec-or
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java`:
- Around line 173-175: The stream currently maps child constraints with
this::transform which bypasses the public unsupported-constraint guard and can
produce null entries; instead call the transformer’s public apply(...) for each
child so any ConstraintTransformerException is thrown immediately. Replace the
mapping in ConstraintTransformerJpaImpl where predicates are built (the
c.getConstraints() stream that produces predicates) to invoke apply(constraint)
for each child constraint (e.g., map(constraint -> apply(constraint))) so
unsupported constraints are surfaced rather than allowing nulls into the
Predicate[].
- Around line 171-176: transformDisjunctionConstraint currently maps child
constraints with this::transform (which returns null for unsupported
constraints) and uses path navigation that defaults to JoinType.INNER, causing
nullable associations to be filtered out before OR evaluation; change the
implementation to use apply() instead of transform() so unsupported child
constraints throw, and ensure path navigation invoked during disjunctions uses
left-join-safe handling (use LEFT joins for attribute path resolution when
building predicates for transformDisjunctionConstraint) so expressions like
"city.name = X OR id = Y" will include rows with null city; keep combining the
resulting Predicate[] with criteriaBuilder.or(...) as before.

In
`@backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java`:
- Around line 150-167: The tests testFilterWithOrConstraint and
testFilterWithOrConstraintPartialMatch rely on generated city names being
distinct; make the predicates deterministic by comparing on city.id (e.g.,
ConstraintBuilder.of("city","id").equal(...)) or by using fixed known names
instead of cities.get(...).getName() so the OR branches are unambiguous—update
the ConstraintBuilder calls in both tests (the ones using "city","name") to use
"city","id" (or fixed literals) and adjust expected counts if necessary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65232f81-59a7-4593-92e7-142f997b555d

📥 Commits

Reviewing files that changed from the base of the PR and between 39116c3 and 425f07f.

📒 Files selected for processing (5)
  • backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java
  • backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java
  • backend-core-model/src/main/java/com/flowingcode/backendcore/model/Constraint.java
  • backend-core-model/src/main/java/com/flowingcode/backendcore/model/ConstraintTransformer.java
  • backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java

@javier-godoy javier-godoy marked this pull request as draft March 16, 2026 19:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java (1)

87-90: ⚠️ Potential issue | 🟠 Major

LEFT-join disjunction can still reuse an INNER join and drop nullable rows.

At Line 89, existing joins are matched only by attribute name. If an INNER join was created earlier on the same path, the disjunction at Line 174 (which switches to LEFT) can still reuse that INNER join, so rows with null associations may still be filtered before the OR is evaluated.

Suggested fix
 `@SuppressWarnings`("rawtypes")
 private From<?,?> join(From<?,?> source, String attributeName) {
-	Optional<Join> existingJoin = source.getJoins().stream().filter(join->join.getAttribute().getName().equals(attributeName)).map(join->(Join)join).findFirst();
+	Optional<Join> 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));
 }

Also applies to: 174-181

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java`
around lines 87 - 90, The join reuse logic in join(From<?,?> source, String
attributeName) matches only on attribute name and can return an INNER join when
currentJoinType is LEFT; update the matching so you only reuse an existing Join
if both the attribute name and the Join.getJoinType() equal the
requested/currentJoinType, otherwise create a new join with
source.join(attributeName, currentJoinType); apply the same change to the other
join helper used around the disjunction (the overloaded join method at the
174-181 area) so LEFT joins are never replaced by previously-created INNER
joins.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java`:
- Around line 87-90: The join reuse logic in join(From<?,?> source, String
attributeName) matches only on attribute name and can return an INNER join when
currentJoinType is LEFT; update the matching so you only reuse an existing Join
if both the attribute name and the Join.getJoinType() equal the
requested/currentJoinType, otherwise create a new join with
source.join(attributeName, currentJoinType); apply the same change to the other
join helper used around the disjunction (the overloaded join method at the
174-181 area) so LEFT joins are never replaced by previously-created INNER
joins.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 012bb055-a583-4c99-8b5a-7dee1619ed7a

📥 Commits

Reviewing files that changed from the base of the PR and between a7c4315 and 16a2c21.

📒 Files selected for processing (2)
  • backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java
  • backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java

@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java (1)

39-44: Consider null-safety for constraint elements.

The factory accepts varargs that could contain null elements, which would propagate to the list and potentially cause NPEs during transformation. The first parameter also lacks explicit null validation.

💡 Proposed defensive implementation
 public static DisjunctionConstraint of(Constraint first, Constraint... rest) {
+   Objects.requireNonNull(first, "first constraint must not be null");
   List<Constraint> list = new java.util.ArrayList<>();
   list.add(first);
-  list.addAll(Arrays.asList(rest));
+  for (Constraint c : rest) {
+    list.add(Objects.requireNonNull(c, "constraint must not be null"));
+  }
   return new DisjunctionConstraint(list);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java`
around lines 39 - 44, The factory DisjunctionConstraint.of(Constraint first,
Constraint... rest) should defensively validate inputs: throw a clear exception
(e.g., NullPointerException or IllegalArgumentException) if first is null, and
filter out any null entries from the varargs before constructing the list;
update the method that builds the internal List<Constraint> so it only adds
non-null elements from rest (or throws if an empty/invalid set is produced) and
then passes that sanitized list into the DisjunctionConstraint constructor to
avoid NPEs during later transformation/processing.
backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java (1)

84-89: Mutable state for join type introduced.

The currentJoinType field makes this class not thread-safe. This is acceptable if instances are created per-query (which appears to be the case from constructor usage), but consider adding a class-level Javadoc note about thread-safety expectations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java`
around lines 84 - 89, The class currently holds mutable state in the field
currentJoinType (used by the join(From<?,?>, String) method), making instances
not thread-safe; add a class-level Javadoc on ConstraintTransformerJpaImpl that
clearly states instances are not thread-safe, that currentJoinType is mutable
and that callers must create a new instance per query (referencing the
constructor and the join(...) method), or alternatively remove the mutable field
and pass JoinType as a parameter to join(...) to make instances
thread-safe—include which approach you choose in the Javadoc so callers know
usage expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java`:
- Around line 84-89: The class currently holds mutable state in the field
currentJoinType (used by the join(From<?,?>, String) method), making instances
not thread-safe; add a class-level Javadoc on ConstraintTransformerJpaImpl that
clearly states instances are not thread-safe, that currentJoinType is mutable
and that callers must create a new instance per query (referencing the
constructor and the join(...) method), or alternatively remove the mutable field
and pass JoinType as a parameter to join(...) to make instances
thread-safe—include which approach you choose in the Javadoc so callers know
usage expectations.

In
`@backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java`:
- Around line 39-44: The factory DisjunctionConstraint.of(Constraint first,
Constraint... rest) should defensively validate inputs: throw a clear exception
(e.g., NullPointerException or IllegalArgumentException) if first is null, and
filter out any null entries from the varargs before constructing the list;
update the method that builds the internal List<Constraint> so it only adds
non-null elements from rest (or throws if an empty/invalid set is produced) and
then passes that sanitized list into the DisjunctionConstraint constructor to
avoid NPEs during later transformation/processing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 37b60e1c-ddd8-4860-ac1c-ec7009d5140b

📥 Commits

Reviewing files that changed from the base of the PR and between 16a2c21 and 7a5834f.

📒 Files selected for processing (12)
  • backend-core-business-impl/pom.xml
  • backend-core-business-spring-impl/pom.xml
  • backend-core-business/pom.xml
  • backend-core-data-impl/pom.xml
  • backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java
  • backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java
  • backend-core-data/pom.xml
  • backend-core-model/pom.xml
  • backend-core-model/src/main/java/com/flowingcode/backendcore/model/Constraint.java
  • backend-core-model/src/main/java/com/flowingcode/backendcore/model/ConstraintTransformer.java
  • backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java
  • pom.xml
🚧 Files skipped from review as they are similar to previous changes (8)
  • pom.xml
  • backend-core-data/pom.xml
  • backend-core-business/pom.xml
  • backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java
  • backend-core-model/src/main/java/com/flowingcode/backendcore/model/Constraint.java
  • backend-core-model/pom.xml
  • backend-core-business-spring-impl/pom.xml
  • backend-core-data-impl/pom.xml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add OR operator to QuerySpec constraints

1 participant