Skip to content

Commit fa93fad

Browse files
committed
Polishing.
Introduce factory methods to flatten predicates. Also, avoid list copies. See #4195
1 parent ef98596 commit fa93fad

File tree

2 files changed

+57
-70
lines changed

2 files changed

+57
-70
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java

Lines changed: 49 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -431,19 +431,7 @@ public Predicate neq(Expression value) {
431431
}
432432

433433
public static @Nullable Predicate or(List<Predicate> intermediate) {
434-
435-
Predicate predicate = null;
436-
437-
for (Predicate other : intermediate) {
438-
439-
if (predicate == null) {
440-
predicate = other;
441-
} else {
442-
predicate = predicate.or(other);
443-
}
444-
}
445-
446-
return predicate;
434+
return intermediate.isEmpty() ? null : OrPredicate.of(intermediate);
447435
}
448436

449437
/**
@@ -712,23 +700,7 @@ public interface Predicate extends Renderable {
712700
@Contract("_ -> new")
713701
@CheckReturnValue
714702
default Predicate or(Predicate other) {
715-
716-
// Flatten OR composition: Or([a,b]).or(c) -> Or([a,b,c])
717-
List<Predicate> parts = new ArrayList<>();
718-
719-
if (this instanceof OrPredicate op) {
720-
parts.addAll(op.parts());
721-
} else {
722-
parts.add(this);
723-
}
724-
725-
if (other instanceof OrPredicate op) {
726-
parts.addAll(op.parts());
727-
} else {
728-
parts.add(other);
729-
}
730-
731-
return new OrPredicate(parts);
703+
return OrPredicate.of(this, other);
732704
}
733705

734706
/**
@@ -740,26 +712,7 @@ default Predicate or(Predicate other) {
740712
@Contract("_ -> new")
741713
@CheckReturnValue
742714
default Predicate and(Predicate other) {
743-
744-
// don't like the structuring of this and the nest() thing
745-
//
746-
// Flatten AND composition to avoid deep nesting:
747-
// And([a,b]).and(c) -> And([a,b,c])
748-
List<Predicate> parts = new ArrayList<>();
749-
750-
if (this instanceof AndPredicate ap) {
751-
parts.addAll(ap.parts());
752-
} else {
753-
parts.add(this);
754-
}
755-
756-
if (other instanceof AndPredicate ap) {
757-
parts.addAll(ap.parts());
758-
} else {
759-
parts.add(other);
760-
}
761-
762-
return new AndPredicate(parts);
715+
return AndPredicate.of(this, other);
763716
}
764717

765718
/**
@@ -1566,20 +1519,40 @@ private static boolean hasParenthesis(String str) {
15661519
record AndPredicate(List<Predicate> parts) implements Predicate {
15671520

15681521
AndPredicate {
1569-
15701522
Assert.notEmpty(parts, "Predicate parts must not be empty");
1523+
}
15711524

1572-
// Flatten nested AND predicates to keep the internal representation normalized.
1573-
List<Predicate> flattened = new ArrayList<>(parts.size());
1574-
for (Predicate p : parts) {
1575-
if (p instanceof AndPredicate ap) {
1576-
flattened.addAll(ap.parts());
1525+
public static AndPredicate of(List<Predicate> predicates) {
1526+
1527+
if (predicates.size() == 1) {
1528+
1529+
Predicate predicate = predicates.get(0);
1530+
if (predicate instanceof AndPredicate ap) {
1531+
return ap;
1532+
}
1533+
1534+
return new AndPredicate(Collections.singletonList(predicate));
1535+
}
1536+
1537+
List<Predicate> flattened = new ArrayList<>(predicates.size());
1538+
for (Predicate p : predicates) {
1539+
if (p instanceof AndPredicate op) {
1540+
flattened.addAll(op.parts());
15771541
} else {
15781542
flattened.add(p);
15791543
}
15801544
}
15811545

1582-
parts = List.copyOf(flattened);
1546+
return new AndPredicate(flattened);
1547+
}
1548+
1549+
public static AndPredicate of(Predicate predicate, Predicate other) {
1550+
return of(List.of(predicate, other));
1551+
}
1552+
1553+
@Override
1554+
public Predicate and(Predicate other) {
1555+
return of(this, other);
15831556
}
15841557

15851558
@Override
@@ -1598,20 +1571,29 @@ public String toString() {
15981571
record OrPredicate(List<Predicate> parts) implements Predicate {
15991572

16001573
OrPredicate {
1601-
16021574
Assert.notEmpty(parts, "Predicate parts must not be empty");
1575+
}
1576+
1577+
public static OrPredicate of(List<Predicate> predicates) {
16031578

1604-
// Flatten nested OR predicates to keep the internal representation normalized.
1605-
List<Predicate> flattened = new ArrayList<>(parts.size());
1606-
for (Predicate p : parts) {
1579+
if (predicates.size() == 1) {
1580+
return new OrPredicate(Collections.singletonList(predicates.get(0)));
1581+
}
1582+
1583+
List<Predicate> flattened = new ArrayList<>(predicates.size());
1584+
for (Predicate p : predicates) {
16071585
if (p instanceof OrPredicate op) {
16081586
flattened.addAll(op.parts());
16091587
} else {
16101588
flattened.add(p);
16111589
}
16121590
}
16131591

1614-
parts = List.copyOf(flattened);
1592+
return new OrPredicate(flattened);
1593+
}
1594+
1595+
public static OrPredicate of(Predicate predicate, Predicate other) {
1596+
return of(List.of(predicate, other));
16151597
}
16161598

16171599
@Override
@@ -1621,6 +1603,11 @@ public String render(RenderContext context) {
16211603
.collect(Collectors.joining(" OR "));
16221604
}
16231605

1606+
@Override
1607+
public Predicate or(Predicate other) {
1608+
return of(this, other);
1609+
}
1610+
16241611
@Override
16251612
public String toString() {
16261613
return render(RenderContext.EMPTY);

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilderUnitTests.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ void inPredicateWithNestedExpression() {
181181
.isEqualTo("o.country IN (SELECT c.code FROM Country c WHERE c.active = true)");
182182
}
183183

184-
@Test
184+
@Test // GH-4195
185185
void chainedAndRendersWithoutExtraParentheses() {
186186

187187
Entity entity = entity(LineItem.class);
@@ -196,12 +196,14 @@ void chainedAndRendersWithoutExtraParentheses() {
196196
Predicate b = where(bPath).eq(literal("ex40"));
197197
Predicate c = where(aPath).isNotNull();
198198

199-
String fragment = a.and(b).and(c).render(ctx(entity));
199+
AndPredicate and = (AndPredicate) a.and(b).and(c);
200+
String fragment = and.render(ctx(entity));
200201

202+
assertThat(and.parts()).hasSize(3);
201203
assertThat(fragment).isEqualTo("p.name = 'ex30' AND p_0.name = 'ex40' AND p.name IS NOT NULL");
202204
}
203205

204-
@Test
206+
@Test // GH-4195
205207
void chainedOrRendersWithoutExtraParentheses() {
206208

207209
Entity entity = entity(Order.class);
@@ -211,8 +213,10 @@ void chainedOrRendersWithoutExtraParentheses() {
211213
Predicate b = where(id).eq(literal("2"));
212214
Predicate c = where(id).eq(literal("3"));
213215

214-
String fragment = a.or(b).or(c).render(ctx(entity));
216+
OrPredicate or = (OrPredicate) a.or(b).or(c);
217+
String fragment = or.render(ctx(entity));
215218

219+
assertThat(or.parts()).hasSize(3);
216220
assertThat(fragment).isEqualTo("o.id = '1' OR o.id = '2' OR o.id = '3'");
217221
}
218222

@@ -369,10 +373,6 @@ static AbstractStringAssert<?> assertThatRendered(Renderable renderable) {
369373
return contextual(RenderContext.EMPTY).assertThat(renderable);
370374
}
371375

372-
static AbstractStringAssert<?> assertThat(String actual) {
373-
return Assertions.assertThat(actual);
374-
}
375-
376376
record ContextualAssert(RenderContext context) {
377377

378378
public AbstractStringAssert<?> assertThat(Renderable renderable) {

0 commit comments

Comments
 (0)