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 via a new disjunction constraint and a convenience or(...) helper; OR evaluation uses left-join semantics for partial-match relations.
  • Tests

    • Added tests validating full-match and partial-match OR scenarios with expected counts.
  • 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 type and factory, Constraint.or(...) convenience, transformer hook and JPA implementation combining sub-predicates with OR using LEFT joins, tests for OR behavior, and version bumps across Maven modules.

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.of(...), Constraint.or(...) convenience method, and a transformDisjunctionConstraint(...) hook branch in ConstraintTransformer.
JPA Transformer Implementation
backend-core-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.java
Introduces stateful currentJoinType (default INNER), uses it for joins, and implements transformDisjunctionConstraint to evaluate sub-constraints with temporary LEFT joins and combine predicates using OR.
Tests
backend-core-data-impl/src/test/java/com/flowingcode/backendcore/dao/jpa/JpaDaoSupportTest.java
Adds testFilterWithOrConstraint() and testFilterWithOrConstraintPartialMatch() to verify OR/disjunction behavior and LEFT JOIN partial matches.
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
Updates file header year ranges to include 2026 and adds JavaDoc describing non-thread-safety/usage in the JPA transformer.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 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 title 'feat: add OR operator to QuerySpec constraints' accurately describes the main change—introducing OR operator functionality to constraint handling in QuerySpec.
Linked Issues check ✅ Passed The implementation fulfills the requirement to add OR operator support: DisjunctionConstraint class provides the mechanism, Constraint.or() method enables fluent API usage, and ConstraintTransformerJpaImpl handles OR logic with LEFT JOINs.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the OR operator feature. Version bumps and copyright updates are necessary maintenance aligned with feature release, and test additions validate the new functionality.

✏️ 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

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

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 (1)
backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java (1)

33-46: Consider making the constraint list immutable.

The @Getter on constraints exposes the internal mutable ArrayList, allowing callers to modify it after construction. For a value object representing a logical OR, immutability is preferred.

♻️ Proposed fix to return an unmodifiable list
+import java.util.Collections;
...
 `@Getter`
 `@RequiredArgsConstructor`
 `@FieldDefaults`(level = AccessLevel.PRIVATE, makeFinal = true)
 public class DisjunctionConstraint implements Constraint {

-  `@NonNull` List<Constraint> constraints;
+  `@NonNull` List<Constraint> constraints;
+
+  public List<Constraint> getConstraints() {
+    return Collections.unmodifiableList(constraints);
+  }

   public static DisjunctionConstraint of(Constraint first, Constraint... rest) {
     List<Constraint> list = new java.util.ArrayList<>();
     list.add(Objects.requireNonNull(first, "constraint must not be null"));
     for (Constraint c : rest) {
       list.add(Objects.requireNonNull(c, "constraint must not be null"));
     }
-    return new DisjunctionConstraint(list);
+    return new DisjunctionConstraint(Collections.unmodifiableList(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 33 - 46, The constraints List exposed by DisjunctionConstraint
(field constraints and getter generated by `@Getter`) is currently mutable; make
it immutable by wrapping the list used in the constructor/factory (e.g., in
DisjunctionConstraint.of(Constraint first, Constraint... rest)) with an
unmodifiable copy (for example use Collections.unmodifiableList(new
ArrayList<>(list)) or List.copyOf(list)) before constructing the
DisjunctionConstraint so callers cannot modify the internal list after creation.
🤖 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-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java`:
- Around line 33-46: The constraints List exposed by DisjunctionConstraint
(field constraints and getter generated by `@Getter`) is currently mutable; make
it immutable by wrapping the list used in the constructor/factory (e.g., in
DisjunctionConstraint.of(Constraint first, Constraint... rest)) with an
unmodifiable copy (for example use Collections.unmodifiableList(new
ArrayList<>(list)) or List.copyOf(list)) before constructing the
DisjunctionConstraint so callers cannot modify the internal list after creation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6fee4fd-4be0-4f36-aef8-eeeb1e9ed6f2

📥 Commits

Reviewing files that changed from the base of the PR and between 7a5834f and 6d553b4.

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

@javier-godoy
Copy link
Member Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

✅ Actions performed

Full review triggered.

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: 1

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

32-37: Consider restricting constructor visibility (optional).

The Lombok-generated public constructor allows bypassing the factory method's per-element null validation. If you want to enforce the factory pattern:

♻️ Optional: Make constructor private
 `@Getter`
-@RequiredArgsConstructor
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
 `@FieldDefaults`(level = AccessLevel.PRIVATE, makeFinal = true)
 public class DisjunctionConstraint implements Constraint {
🤖 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 32 - 37, The public constructor generated by Lombok
(`@RequiredArgsConstructor`) on DisjunctionConstraint lets callers bypass the
factory null-checks for the constraints field; change the constructor visibility
to private by updating the Lombok annotation to generate a private constructor
(e.g., `@RequiredArgsConstructor`(access = AccessLevel.PRIVATE)) so callers must
use the factory method that enforces per-element null validation for the
List<Constraint> constraints.
🤖 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-model/src/main/java/com/flowingcode/backendcore/model/Constraint.java`:
- Around line 39-47: The or(Constraint first, Constraint... rest) path assumes
rest is non-null and will NPE in prepend(...) for calls like or(x,
(Constraint[]) null); modify prepend(Constraint first, Constraint[] rest) (or
add a null-guard in or()) to treat a null rest as an empty array before creating
the result array and calling System.arraycopy — e.g., normalize rest to an empty
Constraint[] when null so the array creation and copy logic work safely and
subsequent constraint-level null validations can run.

---

Nitpick comments:
In
`@backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java`:
- Around line 32-37: The public constructor generated by Lombok
(`@RequiredArgsConstructor`) on DisjunctionConstraint lets callers bypass the
factory null-checks for the constraints field; change the constructor visibility
to private by updating the Lombok annotation to generate a private constructor
(e.g., `@RequiredArgsConstructor`(access = AccessLevel.PRIVATE)) so callers must
use the factory method that enforces per-element null validation for the
List<Constraint> constraints.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 60535b5a-a58f-4190-8082-a5ec1579447d

📥 Commits

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

📒 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

Comment on lines +39 to +47
default Constraint or(Constraint first, Constraint... rest) {
return DisjunctionConstraint.of(this, prepend(first, rest));
}

private static Constraint[] prepend(Constraint first, Constraint[] rest) {
Constraint[] result = new Constraint[1 + rest.length];
result[0] = first;
System.arraycopy(rest, 0, result, 1, rest.length);
return result;
Copy link

@coderabbitai coderabbitai bot Mar 17, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle explicit null varargs defensively in or(...).

Line 43 assumes rest is non-null. A call like or(x, (Constraint[]) null) throws an NPE in prepend(...) before constraint-level null validation runs.

Suggested fix
 import com.flowingcode.backendcore.model.constraints.DisjunctionConstraint;
 import com.flowingcode.backendcore.model.constraints.NegatedConstraint;
+import java.util.Objects;
@@
   default Constraint or(Constraint first, Constraint... rest) {
-    return DisjunctionConstraint.of(this, prepend(first, rest));
+    return DisjunctionConstraint.of(this, prepend(first, Objects.requireNonNull(rest, "rest must not be null")));
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
default Constraint or(Constraint first, Constraint... rest) {
return DisjunctionConstraint.of(this, prepend(first, rest));
}
private static Constraint[] prepend(Constraint first, Constraint[] rest) {
Constraint[] result = new Constraint[1 + rest.length];
result[0] = first;
System.arraycopy(rest, 0, result, 1, rest.length);
return result;
default Constraint or(Constraint first, Constraint... rest) {
return DisjunctionConstraint.of(this, prepend(first, Objects.requireNonNull(rest, "rest must not be null")));
}
private static Constraint[] prepend(Constraint first, Constraint[] rest) {
Constraint[] result = new Constraint[1 + rest.length];
result[0] = first;
System.arraycopy(rest, 0, result, 1, rest.length);
return result;
🤖 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/Constraint.java`
around lines 39 - 47, The or(Constraint first, Constraint... rest) path assumes
rest is non-null and will NPE in prepend(...) for calls like or(x,
(Constraint[]) null); modify prepend(Constraint first, Constraint[] rest) (or
add a null-guard in or()) to treat a null rest as an empty array before creating
the result array and calling System.arraycopy — e.g., normalize rest to an empty
Constraint[] when null so the array creation and copy logic work safely and
subsequent constraint-level null validations can run.

Copy link
Member Author

Choose a reason for hiding this comment

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

@coderabbitai please check if the comment has been addressed.

Copy link

Choose a reason for hiding this comment

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

@javier-godoy, based on the current file content, the comment has not been addressed yet. The or(Constraint first, Constraint... rest) method still passes rest directly to prepend(...) without any null-guard:

default Constraint or(Constraint first, Constraint... rest) {
    return DisjunctionConstraint.of(this, prepend(first, rest));
}

A call like or(x, (Constraint[]) null) will still throw an NPE inside prepend(...) at rest.length before any constraint-level validation runs. The suggested fix (Objects.requireNonNull(rest, "rest must not be null")) has not been applied.

Copy link
Member Author

Choose a reason for hiding this comment

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

@coderabbitai Note that DisjunctionConstraint.of already checks whether the varargs is null

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd "DisjunctionConstraint.java" --exec cat {}

Length of output: 1759


@javier-godoy, you're right that DisjunctionConstraint.of does include a null check (Objects.requireNonNull(rest, "constraints must not be null")). However, that check is never reached in this code path when rest is null. The call chain is:

  1. or(first, null) → calls prepend(first, null)
  2. In prepend: new Constraint[1 + rest.length]NPE here (no message), before DisjunctionConstraint.of is ever invoked.

So the null guard in DisjunctionConstraint.of doesn't cover this case — prepend fails first. The original concern remains: a caller passing or(x, (Constraint[]) null) gets an NPE with no helpful message, whereas the guard in DisjunctionConstraint.of would give "constraints must not be null".

That said, explicitly casting null to Constraint[] is a very unusual call pattern, and both paths still fail fast with an NPE. If you consider this acceptable behavior, I can close the comment.

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: 1

🤖 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-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java`:
- Around line 39-44: DisjunctionConstraint.of currently assumes the varargs
array 'rest' is non-null and will throw an implicit NPE in the for-loop; add an
explicit null check for the varargs container before iterating (e.g.,
Objects.requireNonNull(rest, "constraints must not be null")) so callers calling
of(first, (Constraint[]) null) get a predictable error, then continue to
validate each element in 'rest' with Objects.requireNonNull as already done.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e688aad-15c0-42c2-b24c-b0b3f2181db7

📥 Commits

Reviewing files that changed from the base of the PR and between 7a5834f and 90692ec.

📒 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
🚧 Files skipped from review as they are similar to previous changes (3)
  • 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-data-impl/src/main/java/com/flowingcode/backendcore/dao/jpa/ConstraintTransformerJpaImpl.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.

♻️ Duplicate comments (1)
backend-core-model/src/main/java/com/flowingcode/backendcore/model/Constraint.java (1)

39-47: ⚠️ Potential issue | 🟡 Minor

Guard the varargs container before prepend(...).

or(x, (Constraint[]) null) still blows up on rest.length inside prepend() before DisjunctionConstraint.of(...) can validate anything. Treat a null varargs container as an empty array here so this path behaves predictably.

Suggested fix
   private static Constraint[] prepend(Constraint first, Constraint[] rest) {
+    rest = rest == null ? new Constraint[0] : rest;
     Constraint[] result = new Constraint[1 + rest.length];
     result[0] = first;
     System.arraycopy(rest, 0, result, 1, rest.length);
     return result;
   }
🤖 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/Constraint.java`
around lines 39 - 47, The prepend(...) helper doesn't guard against a null
varargs array, so calling or(x, (Constraint[]) null) throws on rest.length
before DisjunctionConstraint.of(...) can validate; update the code to treat a
null rest as an empty array (e.g., if rest == null then set rest = new
Constraint[0]) either inside prepend(Constraint first, Constraint[] rest) or by
handling null before calling prepend in or(...), ensuring
DisjunctionConstraint.of(this, prepend(...)) always receives a non-null array.
🤖 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-model/src/main/java/com/flowingcode/backendcore/model/Constraint.java`:
- Around line 39-47: The prepend(...) helper doesn't guard against a null
varargs array, so calling or(x, (Constraint[]) null) throws on rest.length
before DisjunctionConstraint.of(...) can validate; update the code to treat a
null rest as an empty array (e.g., if rest == null then set rest = new
Constraint[0]) either inside prepend(Constraint first, Constraint[] rest) or by
handling null before calling prepend in or(...), ensuring
DisjunctionConstraint.of(this, prepend(...)) always receives a non-null array.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cff07aaf-5473-4d1c-9fe5-2e15f414dba8

📥 Commits

Reviewing files that changed from the base of the PR and between 90692ec and 4c30971.

📒 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
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend-core-model/src/main/java/com/flowingcode/backendcore/model/ConstraintTransformer.java

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