From 79f8b5e17443967811d78499ca42cc88f7156370 Mon Sep 17 00:00:00 2001 From: "Dziurdza, Beniamin" Date: Sat, 11 May 2019 16:41:27 +0200 Subject: [PATCH 1/4] notComplement() --- api/src/org/jinq/orm/stream/JinqStream.java | 2 +- .../jinq/orm/stream/NonQueryJinqStream.java | 4 +- .../hibernate/HibernateQueryComposer.java | 9 +++-- .../jinq/hibernate/JPAJinqStreamWrapper.java | 12 ++++++ .../jinq/hibernate/QueryJPAJinqStream.java | 9 ++++- .../test/org/jinq/hibernate/JinqJPATest.java | 1 + jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java | 35 ++++++++++++++++- .../org/jinq/jpa/JPAJinqStreamWrapper.java | 16 ++++++++ .../main/org/jinq/jpa/JPAQueryComposer.java | 8 +++- .../main/org/jinq/jpa/QueryJPAJinqStream.java | 9 ++++- .../org/jinq/jpa/transform/NotTransform.java | 38 +++++++++++++++++++ jinq-jpa/test/org/jinq/jpa/JinqJPATest.java | 21 +++++++++- 12 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 jinq-jpa/main/org/jinq/jpa/transform/NotTransform.java diff --git a/api/src/org/jinq/orm/stream/JinqStream.java b/api/src/org/jinq/orm/stream/JinqStream.java index 0390240e..29deae2e 100644 --- a/api/src/org/jinq/orm/stream/JinqStream.java +++ b/api/src/org/jinq/orm/stream/JinqStream.java @@ -758,5 +758,5 @@ public static JinqStream from(Collection collection) public static JinqStream of(U value) { return new NonQueryJinqStream<>(Stream.of(value)); - } + } } diff --git a/api/src/org/jinq/orm/stream/NonQueryJinqStream.java b/api/src/org/jinq/orm/stream/NonQueryJinqStream.java index 8ad3877e..e61ff4d4 100644 --- a/api/src/org/jinq/orm/stream/NonQueryJinqStream.java +++ b/api/src/org/jinq/orm/stream/NonQueryJinqStream.java @@ -37,7 +37,7 @@ public NonQueryJinqStream(Stream wrapped) public NonQueryJinqStream(Stream wrapped, InQueryStreamSource inQueryStreamSource) { super(wrapped); - this.inQueryStreamSource = inQueryStreamSource; + this.inQueryStreamSource = inQueryStreamSource; } NonQueryJinqStream() @@ -689,5 +689,5 @@ protected void generateNext() public JinqStream setHint(String name, Object value) { return this; - } + } } diff --git a/jinq-hibernate-legacy/main/org/jinq/hibernate/HibernateQueryComposer.java b/jinq-hibernate-legacy/main/org/jinq/hibernate/HibernateQueryComposer.java index 6a4fe56d..b2ae7fc8 100644 --- a/jinq-hibernate-legacy/main/org/jinq/hibernate/HibernateQueryComposer.java +++ b/jinq-hibernate-legacy/main/org/jinq/hibernate/HibernateQueryComposer.java @@ -9,9 +9,6 @@ import java.util.Optional; import java.util.function.Consumer; - - - import org.hibernate.Query; import org.hibernate.Session; import org.jinq.jpa.JPAJinqStream; @@ -39,6 +36,7 @@ import org.jinq.jpa.transform.LimitSkipTransform; import org.jinq.jpa.transform.MetamodelUtil; import org.jinq.jpa.transform.MultiAggregateTransform; +import org.jinq.jpa.transform.NotTransform; import org.jinq.jpa.transform.OuterJoinOnTransform; import org.jinq.jpa.transform.OuterJoinTransform; import org.jinq.jpa.transform.QueryTransformException; @@ -601,6 +599,11 @@ public QueryComposer andIntersect(JPAJinqStream otherSet) return applyTransformWithTwoQueryMerge(new SetOperationEmulationTransform(getConfig(), SetOperationEmulationTransform.SetOperationType.AND_INTERSECT), otherSet); } + public QueryComposer notComplement() + { + return applyTransformWithLambda(new NotTransform(getConfig())); + } + @Override public Long count() { diff --git a/jinq-hibernate-legacy/main/org/jinq/hibernate/JPAJinqStreamWrapper.java b/jinq-hibernate-legacy/main/org/jinq/hibernate/JPAJinqStreamWrapper.java index ccc6aa4a..adc4544c 100644 --- a/jinq-hibernate-legacy/main/org/jinq/hibernate/JPAJinqStreamWrapper.java +++ b/jinq-hibernate-legacy/main/org/jinq/hibernate/JPAJinqStreamWrapper.java @@ -1,5 +1,6 @@ package org.jinq.hibernate; +import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; @@ -11,6 +12,7 @@ import org.jinq.jpa.JPAJinqStream; import org.jinq.orm.stream.JinqStream; import org.jinq.orm.stream.LazyWrappedStream; +import org.jinq.orm.stream.NonQueryJinqStream; import org.jinq.tuples.Pair; import org.jinq.tuples.Tuple3; import org.jinq.tuples.Tuple4; @@ -435,4 +437,14 @@ public JPAJinqStream andIntersect(JPAJinqStream otherSet) Set saved = collect(Collectors.toSet()); return wrap(JinqStream.from(otherSet.filter(el -> saved.contains(el)).collect(Collectors.toSet()))); } + + @Override + public JPAJinqStream notComplement() { + //Set all = ? + //Set saved = wrapped.collect(Collectors.toSet()); + //return wrap( JinqStream.from(all).where( el -> !saved.contains(el)) ); + + throw new UnsupportedOperationException("operation not supported for this stream"); + } + } diff --git a/jinq-hibernate-legacy/main/org/jinq/hibernate/QueryJPAJinqStream.java b/jinq-hibernate-legacy/main/org/jinq/hibernate/QueryJPAJinqStream.java index ce90cafb..4f7a01e2 100644 --- a/jinq-hibernate-legacy/main/org/jinq/hibernate/QueryJPAJinqStream.java +++ b/jinq-hibernate-legacy/main/org/jinq/hibernate/QueryJPAJinqStream.java @@ -96,7 +96,13 @@ public JPAJinqStream andIntersect(JPAJinqStream otherSet) if (newComposer != null) return makeQueryStream(newComposer, inQueryStreamSource); return new JPAJinqStreamWrapper<>(this).andIntersect(otherSet); } - + @Override + public JPAJinqStream notComplement() + { + QueryComposer newComposer = jpaComposer.notComplement(); + if (newComposer != null) return makeQueryStream(newComposer, inQueryStreamSource); + return new JPAJinqStreamWrapper<>(this).notComplement(); + } // Wrapped versions of old API @@ -315,4 +321,5 @@ public JPAJinqStream setHint(String name, Object value) { return wrap(super.setHint(name, value)); } + } diff --git a/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java b/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java index 6e73a073..0df0b665 100644 --- a/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java +++ b/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java @@ -591,4 +591,5 @@ public void testAndIntersect() assertEquals("Bob", customers.get(0)); assertEquals("Dave", customers.get(1)); } + } diff --git a/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java b/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java index 26600d3e..b308e024 100644 --- a/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java +++ b/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java @@ -90,8 +90,23 @@ public interface JPAJinqStream extends JinqStream * @return a new stream with the contents of the two streams INTERSECTed together */ public JPAJinqStream andIntersect(JPAJinqStream otherSet); - - + + /** + * Emulates a complement of stream by negation of the query + * using a NOT operation. JPA does not support NOT operations, so + * Jinq must emulate that behavior using NOTs. It also provides a mechanism + * for Jinq to let people create NOT expressions programmatically and to specify + * complex expressions exactly without relying on the Jinq translation algorithm. + *

+ * Since Universe of collection-based NonQueryJinqStream is not well defined, + * notComplement() is not supported for such streams. + * + * @return a new stream being a COMPLEMENT of this stream, + * if this is actually QueryJPAJinqStream and its complement can be succesfully translated + * @throws UnsupportedOperationException otherwise + */ + public JPAJinqStream notComplement(); + // Variants of the existing JinqStream API that return a JPAJinqStream instead // of a JinqStream. @@ -193,4 +208,20 @@ public > JPAJinqStream sortedDescendingBy( public JPAJinqStream distinct(); public JPAJinqStream setHint(String name, Object value); + + /** + * Easy way to get a JinqStream from a collection. + */ + public static JPAJinqStream from(Collection collection) + { + return new JPAJinqStreamWrapper<>(new NonQueryJinqStream(collection.stream())); + } + + /** + * Creates a JinqStream containing a single object. + */ + public static JPAJinqStream of(U value, Stream universe) + { + return new JPAJinqStreamWrapper<>(new NonQueryJinqStream<>(Stream.of(value))); + } } diff --git a/jinq-jpa/main/org/jinq/jpa/JPAJinqStreamWrapper.java b/jinq-jpa/main/org/jinq/jpa/JPAJinqStreamWrapper.java index 8f8f0cb9..342e1fcc 100644 --- a/jinq-jpa/main/org/jinq/jpa/JPAJinqStreamWrapper.java +++ b/jinq-jpa/main/org/jinq/jpa/JPAJinqStreamWrapper.java @@ -1,15 +1,22 @@ package org.jinq.jpa; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jinq.orm.stream.JinqStream; import org.jinq.orm.stream.LazyWrappedStream; +import org.jinq.orm.stream.NonQueryJinqStream; +import org.jinq.orm.stream.QueryJinqStream; import org.jinq.tuples.Pair; import org.jinq.tuples.Tuple3; import org.jinq.tuples.Tuple4; @@ -435,4 +442,13 @@ public JPAJinqStream andIntersect(JPAJinqStream otherSet) Set saved = collect(Collectors.toSet()); return wrap(JinqStream.from(otherSet.filter(el -> saved.contains(el)).collect(Collectors.toSet()))); } + + @Override + public JPAJinqStream notComplement() { + //Set all = ? + //Set saved = wrapped.collect(Collectors.toSet()); + //return wrap( JinqStream.from(all).where( el -> !saved.contains(el)) ); + + throw new UnsupportedOperationException("operation not supported for this stream"); + } } diff --git a/jinq-jpa/main/org/jinq/jpa/JPAQueryComposer.java b/jinq-jpa/main/org/jinq/jpa/JPAQueryComposer.java index 0354b61f..3b9efe68 100644 --- a/jinq-jpa/main/org/jinq/jpa/JPAQueryComposer.java +++ b/jinq-jpa/main/org/jinq/jpa/JPAQueryComposer.java @@ -36,6 +36,7 @@ import org.jinq.jpa.transform.LimitSkipTransform; import org.jinq.jpa.transform.MetamodelUtil; import org.jinq.jpa.transform.MultiAggregateTransform; +import org.jinq.jpa.transform.NotTransform; import org.jinq.jpa.transform.SetOperationEmulationTransform; import org.jinq.jpa.transform.OuterJoinOnTransform; import org.jinq.jpa.transform.OuterJoinTransform; @@ -589,7 +590,12 @@ public QueryComposer andIntersect(JPAJinqStream otherSet) { return applyTransformWithTwoQueryMerge(new SetOperationEmulationTransform(getConfig(), SetOperationEmulationTransform.SetOperationType.AND_INTERSECT), otherSet); } - + + public QueryComposer notComplement() + { + return applyTransformWithLambda(new NotTransform(getConfig())); + } + @Override public Long count() { diff --git a/jinq-jpa/main/org/jinq/jpa/QueryJPAJinqStream.java b/jinq-jpa/main/org/jinq/jpa/QueryJPAJinqStream.java index f8cdc826..1ea6fa68 100644 --- a/jinq-jpa/main/org/jinq/jpa/QueryJPAJinqStream.java +++ b/jinq-jpa/main/org/jinq/jpa/QueryJPAJinqStream.java @@ -96,7 +96,14 @@ public JPAJinqStream andIntersect(JPAJinqStream otherSet) return new JPAJinqStreamWrapper<>(this).andIntersect(otherSet); } - + @Override + public JPAJinqStream notComplement() + { + QueryComposer newComposer = jpaComposer.notComplement(); + if (newComposer != null) return makeQueryStream(newComposer, inQueryStreamSource); + return new JPAJinqStreamWrapper<>(this).notComplement(); + } + // Wrapped versions of old API @Override diff --git a/jinq-jpa/main/org/jinq/jpa/transform/NotTransform.java b/jinq-jpa/main/org/jinq/jpa/transform/NotTransform.java new file mode 100644 index 00000000..5031fa0d --- /dev/null +++ b/jinq-jpa/main/org/jinq/jpa/transform/NotTransform.java @@ -0,0 +1,38 @@ +package org.jinq.jpa.transform; + +import org.jinq.jpa.jpqlquery.BinaryExpression; +import org.jinq.jpa.jpqlquery.ConstantExpression; +import org.jinq.jpa.jpqlquery.JPQLQuery; +import org.jinq.jpa.jpqlquery.SelectFromWhere; +import org.jinq.jpa.jpqlquery.UnaryExpression; + + +public class NotTransform extends JPQLNoLambdaQueryTransform +{ + public NotTransform(JPQLQueryTransformConfiguration config) + { + super(config); + } + + @Override + public JPQLQuery apply(JPQLQuery query, SymbExArgumentHandler parentArgumentScope) throws QueryTransformException + { + if (query.isSelectFromWhere()) + { + SelectFromWhere sfw = (SelectFromWhere)query; + SelectFromWhere toReturn = (SelectFromWhere)sfw.shallowCopy(); + if (toReturn.where != null) toReturn.where = UnaryExpression.prefix("NOT", toReturn.where); + else toReturn.where = new BinaryExpression("=", new ConstantExpression("0"), new ConstantExpression("1")); + + return toReturn; + } + throw new QueryTransformException("Existing query cannot be transformed further"); + } + + @Override + public String getTransformationTypeCachingTag() + { + return NotTransform.class.getName(); + } + +} diff --git a/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java b/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java index 21cd73ab..4de71fdd 100644 --- a/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java +++ b/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java @@ -7,10 +7,12 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.persistence.Query; @@ -20,8 +22,10 @@ import org.jinq.jpa.test.entities.Lineorder; import org.jinq.jpa.test.entities.Sale; import org.jinq.jpa.test.entities.Supplier; +import org.jinq.orm.stream.InQueryStreamSource; import org.jinq.orm.stream.JinqStream; import org.jinq.orm.stream.JinqStream.Where; +import org.jinq.orm.stream.NonQueryJinqStream; import org.jinq.tuples.Pair; import org.junit.Assert; import org.junit.Test; @@ -662,6 +666,21 @@ public void testOrUnionAndIntersectEmptyWhere() } + @Test(expected = UnsupportedOperationException.class) + public void testNotComplement() + { + JPAJinqStream base = streams.streamAll(em, Customer.class); + JPAJinqStream Bob = base.where( c -> c.getName().equals("Bob") ); + JPAJinqStream notBob = Bob.notComplement(); + List notBobList = notBob.toList(); + + assertEquals("SELECT A FROM Customer A WHERE NOT A.name = 'Bob'", query); + + JinqStream BobStream = new NonQueryJinqStream<>(base); + JPAJinqStream notBobStream = (new JPAJinqStreamWrapper<>(BobStream)).notComplement(); + Assert.fail(); + } + @Test public void testAndIntersect() { @@ -676,6 +695,6 @@ public void testAndIntersect() assertEquals(2, customers.size()); assertEquals("Bob", customers.get(0)); assertEquals("Dave", customers.get(1)); - } + } } From 0d476da921ffb1bb12178e95e553ce84e29e8d9d Mon Sep 17 00:00:00 2001 From: "Dziurdza, Beniamin" Date: Sat, 11 May 2019 17:03:43 +0200 Subject: [PATCH 2/4] notComplement() --- .../test/org/jinq/hibernate/JinqJPATest.java | 4 ---- jinq-jpa/test/org/jinq/jpa/JinqJPATest.java | 1 - 2 files changed, 5 deletions(-) diff --git a/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java b/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java index 0df0b665..8082cccc 100644 --- a/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java +++ b/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java @@ -12,8 +12,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import javax.persistence.Query; - import org.jinq.hibernate.test.entities.Customer; import org.jinq.hibernate.test.entities.Item; import org.jinq.hibernate.test.entities.Lineorder; @@ -22,10 +20,8 @@ import org.jinq.jpa.JPAJinqStream; import org.jinq.jpa.JPQL; import org.jinq.jpa.jpqlquery.JPQLQuery; -import org.jinq.orm.stream.InQueryStreamSource; import org.jinq.orm.stream.JinqStream; import org.jinq.orm.stream.JinqStream.Where; -import org.jinq.orm.stream.QueryJinqStream; import org.jinq.tuples.Pair; import org.junit.Assert; import org.junit.Test; diff --git a/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java b/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java index 4de71fdd..818dd35c 100644 --- a/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java +++ b/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java @@ -22,7 +22,6 @@ import org.jinq.jpa.test.entities.Lineorder; import org.jinq.jpa.test.entities.Sale; import org.jinq.jpa.test.entities.Supplier; -import org.jinq.orm.stream.InQueryStreamSource; import org.jinq.orm.stream.JinqStream; import org.jinq.orm.stream.JinqStream.Where; import org.jinq.orm.stream.NonQueryJinqStream; From 8c4a0fe1d60f4fd55bd2d826a3e3e906b57f99b0 Mon Sep 17 00:00:00 2001 From: "Dziurdza, Beniamin" Date: Sun, 12 May 2019 17:01:33 +0200 Subject: [PATCH 3/4] missing imports --- jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java b/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java index b308e024..badf4d33 100644 --- a/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java +++ b/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java @@ -1,6 +1,9 @@ package org.jinq.jpa; +import java.util.Collection; +import java.util.stream.Stream; import org.jinq.orm.stream.JinqStream; +import org.jinq.orm.stream.NonQueryJinqStream; import org.jinq.tuples.Pair; import org.jinq.tuples.Tuple3; import org.jinq.tuples.Tuple4; From 4725796e8c4579ab51e6f6b700293ed921fd702a Mon Sep 17 00:00:00 2001 From: "Dziurdza, Beniamin" Date: Tue, 21 May 2019 16:32:14 +0200 Subject: [PATCH 4/4] difference (andNotDifference) JPA and legacy hibernate --- .../hibernate/HibernateQueryComposer.java | 7 ++ .../jinq/hibernate/JPAJinqStreamWrapper.java | 24 ++++-- .../jinq/hibernate/QueryJPAJinqStream.java | 8 ++ .../test/org/jinq/hibernate/JinqJPATest.java | 74 +++++++++++++++++++ jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java | 6 ++ .../org/jinq/jpa/JPAJinqStreamWrapper.java | 26 +++++-- .../main/org/jinq/jpa/JPAQueryComposer.java | 5 ++ .../main/org/jinq/jpa/QueryJPAJinqStream.java | 9 +++ .../SetOperationEmulationTransform.java | 33 +++++++-- jinq-jpa/test/org/jinq/jpa/JinqJPATest.java | 56 ++++++++++++++ 10 files changed, 225 insertions(+), 23 deletions(-) diff --git a/jinq-hibernate-legacy/main/org/jinq/hibernate/HibernateQueryComposer.java b/jinq-hibernate-legacy/main/org/jinq/hibernate/HibernateQueryComposer.java index b2ae7fc8..786b3d05 100644 --- a/jinq-hibernate-legacy/main/org/jinq/hibernate/HibernateQueryComposer.java +++ b/jinq-hibernate-legacy/main/org/jinq/hibernate/HibernateQueryComposer.java @@ -603,6 +603,13 @@ public QueryComposer notComplement() { return applyTransformWithLambda(new NotTransform(getConfig())); } + + + public QueryComposer difference(JPAJinqStream otherSet) + { + return applyTransformWithTwoQueryMerge(new SetOperationEmulationTransform(getConfig(), SetOperationEmulationTransform.SetOperationType.DIFFERENCE), otherSet); + } + @Override public Long count() diff --git a/jinq-hibernate-legacy/main/org/jinq/hibernate/JPAJinqStreamWrapper.java b/jinq-hibernate-legacy/main/org/jinq/hibernate/JPAJinqStreamWrapper.java index adc4544c..8b8acdde 100644 --- a/jinq-hibernate-legacy/main/org/jinq/hibernate/JPAJinqStreamWrapper.java +++ b/jinq-hibernate-legacy/main/org/jinq/hibernate/JPAJinqStreamWrapper.java @@ -438,13 +438,23 @@ public JPAJinqStream andIntersect(JPAJinqStream otherSet) return wrap(JinqStream.from(otherSet.filter(el -> saved.contains(el)).collect(Collectors.toSet()))); } - @Override - public JPAJinqStream notComplement() { - //Set all = ? - //Set saved = wrapped.collect(Collectors.toSet()); - //return wrap( JinqStream.from(all).where( el -> !saved.contains(el)) ); + @Override + public JPAJinqStream notComplement() + { + //Set all = ? + //Set saved = wrapped.collect(Collectors.toSet()); + //return wrap( JinqStream.from(all).where( el -> !saved.contains(el)) ); - throw new UnsupportedOperationException("operation not supported for this stream"); - } + throw new UnsupportedOperationException("operation not supported for this stream"); + } + + @Override + public JPAJinqStream andNotDifference(JPAJinqStream otherSet) + { + Set saved = collect(Collectors.toSet()); + Set other = otherSet.collect(Collectors.toSet()); + saved.removeAll(other); + return wrap( JinqStream.from( saved ) ); + } } diff --git a/jinq-hibernate-legacy/main/org/jinq/hibernate/QueryJPAJinqStream.java b/jinq-hibernate-legacy/main/org/jinq/hibernate/QueryJPAJinqStream.java index 4f7a01e2..ffb8a21a 100644 --- a/jinq-hibernate-legacy/main/org/jinq/hibernate/QueryJPAJinqStream.java +++ b/jinq-hibernate-legacy/main/org/jinq/hibernate/QueryJPAJinqStream.java @@ -103,6 +103,14 @@ public JPAJinqStream notComplement() if (newComposer != null) return makeQueryStream(newComposer, inQueryStreamSource); return new JPAJinqStreamWrapper<>(this).notComplement(); } + + @Override + public JPAJinqStream andNotDifference(JPAJinqStream otherSet) + { + QueryComposer newComposer = jpaComposer.difference(otherSet); + if (newComposer != null) return makeQueryStream(newComposer, inQueryStreamSource); + return new JPAJinqStreamWrapper<>(this).andNotDifference(otherSet); + } // Wrapped versions of old API diff --git a/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java b/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java index 8082cccc..d3e49828 100644 --- a/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java +++ b/jinq-hibernate-legacy/test/org/jinq/hibernate/JinqJPATest.java @@ -22,8 +22,10 @@ import org.jinq.jpa.jpqlquery.JPQLQuery; import org.jinq.orm.stream.JinqStream; import org.jinq.orm.stream.JinqStream.Where; +import org.jinq.orm.stream.NonQueryJinqStream; import org.jinq.tuples.Pair; import org.junit.Assert; +import static org.junit.Assert.assertEquals; import org.junit.Test; public class JinqJPATest extends JinqJPATestBase @@ -571,6 +573,78 @@ public void testOrUnionParameters() assertEquals("SELECT A FROM org.jinq.hibernate.test.entities.Customer A WHERE A.name = :param0 OR A.name = :param1", query); assertEquals(2, customers.size()); } + + @Test(expected = UnsupportedOperationException.class) + public void testNotComplement() + { + JPAJinqStream base = streams.streamAll(em, Customer.class); + JPAJinqStream Bob = base.where( c -> c.getName().equals("Bob") ); + JPAJinqStream notBob = Bob.notComplement(); + List notBobList = notBob.toList(); + + assertEquals("SELECT A FROM Customer A WHERE NOT A.name = 'Bob'", query); + + JinqStream BobStream = new NonQueryJinqStream<>(base); + JPAJinqStream notBobStream = (new JPAJinqStreamWrapper<>(BobStream)).notComplement(); + Assert.fail(); + } + + @Test + public void testNotComplementQueries() + { + JPAJinqStream base = streams.streamAll(em, Customer.class); + JPAJinqStream Dave = base.where(c -> c.getName().equals("Dave")); + JPAJinqStream allCustomers = base; + + JPAJinqStream notDave = Dave.notComplement(); + List notDaveList = notDave.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT A.name = 'Dave'", query); + + JPAJinqStream notAll = allCustomers.notComplement(); + List notAllList = notAll.toList(); + assertEquals("SELECT A FROM Customer A WHERE 0 = 1", query); + + JPAJinqStream notNotAll = notAll.notComplement(); + List notNotAllList = notNotAll.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT 0 = 1", query); + + JPAJinqStream q = notNotAll.andIntersect(notDave); + List qList = q.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT 0 = 1 AND NOT A.name = 'Dave'", query); + + JPAJinqStream notQ = q.notComplement(); + List notQList = notQ.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT (NOT 0 = 1 AND NOT A.name = 'Dave')", query); + } + + + @Test + public void testDifference() + { + JPAJinqStream base = streams.streamAll(em, Customer.class); + JPAJinqStream Dave = base.where(c -> c.getName().equals("Dave")); + String bobName = "Bob"; + JPAJinqStream Bob = base.where(c -> c.getName().equals(bobName)); + JPAJinqStream allCustomers = base; + + JPAJinqStream allDiffDave = allCustomers.andNotDifference(Dave); + List allDiffDaveList = allDiffDave.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT A.name = 'Dave'", query); + + JPAJinqStream BobDiffDave = Bob.andNotDifference(Dave); + List BobDiffDaveResult = BobDiffDave.toList(); + assertEquals("SELECT A FROM Customer A WHERE A.name = :param0 AND NOT A.name = 'Dave'", query); + + JPAJinqStream allDiffAll = allCustomers.andNotDifference(allCustomers); + List allDiffAllList = allDiffAll.toList(); + assertEquals("SELECT A FROM Customer A WHERE 0 = 1", query); + + JPAJinqStream DaveDiffAll = Dave.andNotDifference(allCustomers); + List DaveDiffAllList = DaveDiffAll.toList(); + assertEquals("SELECT A FROM Customer A WHERE 0 = 1", query); + + } + @Test public void testAndIntersect() diff --git a/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java b/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java index badf4d33..d4b4675c 100644 --- a/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java +++ b/jinq-jpa/main/org/jinq/jpa/JPAJinqStream.java @@ -107,9 +107,15 @@ public interface JPAJinqStream extends JinqStream * @return a new stream being a COMPLEMENT of this stream, * if this is actually QueryJPAJinqStream and its complement can be succesfully translated * @throws UnsupportedOperationException otherwise + * + * @see difference(JPAJinqStream otherSet); */ public JPAJinqStream notComplement(); + + // andNotDifference of sets + public JPAJinqStream andNotDifference(JPAJinqStream otherSet); + // Variants of the existing JinqStream API that return a JPAJinqStream instead // of a JinqStream. diff --git a/jinq-jpa/main/org/jinq/jpa/JPAJinqStreamWrapper.java b/jinq-jpa/main/org/jinq/jpa/JPAJinqStreamWrapper.java index 342e1fcc..99bf467d 100644 --- a/jinq-jpa/main/org/jinq/jpa/JPAJinqStreamWrapper.java +++ b/jinq-jpa/main/org/jinq/jpa/JPAJinqStreamWrapper.java @@ -443,12 +443,22 @@ public JPAJinqStream andIntersect(JPAJinqStream otherSet) return wrap(JinqStream.from(otherSet.filter(el -> saved.contains(el)).collect(Collectors.toSet()))); } - @Override - public JPAJinqStream notComplement() { - //Set all = ? - //Set saved = wrapped.collect(Collectors.toSet()); - //return wrap( JinqStream.from(all).where( el -> !saved.contains(el)) ); - - throw new UnsupportedOperationException("operation not supported for this stream"); - } + @Override + public JPAJinqStream notComplement() + { + //Set all = ? + //Set saved = wrapped.collect(Collectors.toSet()); + //return wrap( JinqStream.from(all).where( el -> !saved.contains(el)) ); + + throw new UnsupportedOperationException("operation not supported for this stream"); + } + + @Override + public JPAJinqStream andNotDifference(JPAJinqStream otherSet) + { + Set saved = collect(Collectors.toSet()); + Set other = otherSet.collect(Collectors.toSet()); + saved.removeAll(other); + return wrap( JinqStream.from( saved ) ); + } } diff --git a/jinq-jpa/main/org/jinq/jpa/JPAQueryComposer.java b/jinq-jpa/main/org/jinq/jpa/JPAQueryComposer.java index 3b9efe68..dd08bb44 100644 --- a/jinq-jpa/main/org/jinq/jpa/JPAQueryComposer.java +++ b/jinq-jpa/main/org/jinq/jpa/JPAQueryComposer.java @@ -595,6 +595,11 @@ public QueryComposer notComplement() { return applyTransformWithLambda(new NotTransform(getConfig())); } + + public QueryComposer difference(JPAJinqStream otherSet) + { + return applyTransformWithTwoQueryMerge(new SetOperationEmulationTransform(getConfig(), SetOperationEmulationTransform.SetOperationType.DIFFERENCE), otherSet); + } @Override public Long count() diff --git a/jinq-jpa/main/org/jinq/jpa/QueryJPAJinqStream.java b/jinq-jpa/main/org/jinq/jpa/QueryJPAJinqStream.java index 1ea6fa68..0066fc37 100644 --- a/jinq-jpa/main/org/jinq/jpa/QueryJPAJinqStream.java +++ b/jinq-jpa/main/org/jinq/jpa/QueryJPAJinqStream.java @@ -104,6 +104,15 @@ public JPAJinqStream notComplement() return new JPAJinqStreamWrapper<>(this).notComplement(); } + @Override + public JPAJinqStream andNotDifference(JPAJinqStream otherSet) + { + QueryComposer newComposer = jpaComposer.difference(otherSet); + if (newComposer != null) return makeQueryStream(newComposer, inQueryStreamSource); + return new JPAJinqStreamWrapper<>(this).andNotDifference(otherSet); + } + + // Wrapped versions of old API @Override diff --git a/jinq-jpa/main/org/jinq/jpa/transform/SetOperationEmulationTransform.java b/jinq-jpa/main/org/jinq/jpa/transform/SetOperationEmulationTransform.java index 861fc579..91ce7cc8 100644 --- a/jinq-jpa/main/org/jinq/jpa/transform/SetOperationEmulationTransform.java +++ b/jinq-jpa/main/org/jinq/jpa/transform/SetOperationEmulationTransform.java @@ -1,16 +1,19 @@ package org.jinq.jpa.transform; import org.jinq.jpa.jpqlquery.BinaryExpression; +import org.jinq.jpa.jpqlquery.ConstantExpression; import org.jinq.jpa.jpqlquery.Expression; import org.jinq.jpa.jpqlquery.JPQLQuery; import org.jinq.jpa.jpqlquery.OffsetLambdaIndexInExpressionsVisitor; import org.jinq.jpa.jpqlquery.SelectFromWhere; +import org.jinq.jpa.jpqlquery.UnaryExpression; public class SetOperationEmulationTransform extends JPQLTwoQueryMergeQueryTransform { public enum SetOperationType { - OR_UNION("OR"), AND_INTERSECT("AND"); + OR_UNION("OR"), AND_INTERSECT("AND"), + DIFFERENCE("AND_NOT"); // do not use "AND_NOT" pseudo op; actually, this is composition of two: binary AND with unary NOT and as such results in less brackets SetOperationType(String op) { this.op = op; @@ -57,8 +60,12 @@ public JPQLQuery apply(JPQLQuery query1, JPQLQuery query2, in // merged.froms.add(from); if (merged.where == null && sfw2.where == null) { - // Nothing to do + if (type == SetOperationType.DIFFERENCE) { + merged.where = new BinaryExpression("=", new ConstantExpression("0"), new ConstantExpression("1")); + return (SelectFromWhere)merged; + } } + if (merged.where == null || sfw2.where == null) { Expression offsetWhere = merged.where; @@ -66,21 +73,31 @@ public JPQLQuery apply(JPQLQuery query1, JPQLQuery query2, in { offsetWhere = sfw2.where.copy(); offsetWhere.visit(new OffsetLambdaIndexInExpressionsVisitor(lambdaOffset)); + + if (type == SetOperationType.DIFFERENCE) { + merged.where = UnaryExpression.prefix("NOT", offsetWhere); + return (SelectFromWhere) merged; + } } + switch (type) { - case AND_INTERSECT: - merged.where = offsetWhere; - break; - case OR_UNION: - merged.where = null; + case DIFFERENCE: + merged.where = new BinaryExpression("=", new ConstantExpression("0"), new ConstantExpression("1")); + return (SelectFromWhere)merged; + case AND_INTERSECT: + merged.where = offsetWhere; + break; + case OR_UNION: + merged.where = null; } } else { Expression offsetWhere = sfw2.where.copy(); offsetWhere.visit(new OffsetLambdaIndexInExpressionsVisitor(lambdaOffset)); - merged.where = new BinaryExpression(type.op, merged.where, offsetWhere); + merged.where = type != SetOperationType.DIFFERENCE ? new BinaryExpression(type.op, merged.where, offsetWhere) + : new BinaryExpression(SetOperationType.AND_INTERSECT.op, merged.where, UnaryExpression.prefix("NOT", offsetWhere)); } return (SelectFromWhere)merged; } diff --git a/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java b/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java index 818dd35c..92b98916 100644 --- a/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java +++ b/jinq-jpa/test/org/jinq/jpa/JinqJPATest.java @@ -679,7 +679,63 @@ public void testNotComplement() JPAJinqStream notBobStream = (new JPAJinqStreamWrapper<>(BobStream)).notComplement(); Assert.fail(); } + + @Test + public void testNotComplementQueries() + { + JPAJinqStream base = streams.streamAll(em, Customer.class); + JPAJinqStream Dave = base.where(c -> c.getName().equals("Dave")); + JPAJinqStream allCustomers = base; + + JPAJinqStream notDave = Dave.notComplement(); + List notDaveList = notDave.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT A.name = 'Dave'", query); + + JPAJinqStream notAll = allCustomers.notComplement(); + List notAllList = notAll.toList(); + assertEquals("SELECT A FROM Customer A WHERE 0 = 1", query); + + JPAJinqStream notNotAll = notAll.notComplement(); + List notNotAllList = notNotAll.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT 0 = 1", query); + + JPAJinqStream q = notNotAll.andIntersect(notDave); + List qList = q.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT 0 = 1 AND NOT A.name = 'Dave'", query); + + JPAJinqStream notQ = q.notComplement(); + List notQList = notQ.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT (NOT 0 = 1 AND NOT A.name = 'Dave')", query); + } + + + @Test + public void testDifference() + { + JPAJinqStream base = streams.streamAll(em, Customer.class); + JPAJinqStream Dave = base.where(c -> c.getName().equals("Dave")); + String bobName = "Bob"; + JPAJinqStream Bob = base.where(c -> c.getName().equals(bobName)); + JPAJinqStream allCustomers = base; + + JPAJinqStream allDiffDave = allCustomers.andNotDifference(Dave); + List allDiffDaveList = allDiffDave.toList(); + assertEquals("SELECT A FROM Customer A WHERE NOT A.name = 'Dave'", query); + JPAJinqStream BobDiffDave = Bob.andNotDifference(Dave); + List BobDiffDaveResult = BobDiffDave.toList(); + assertEquals("SELECT A FROM Customer A WHERE A.name = :param0 AND NOT A.name = 'Dave'", query); + + JPAJinqStream allDiffAll = allCustomers.andNotDifference(allCustomers); + List allDiffAllList = allDiffAll.toList(); + assertEquals("SELECT A FROM Customer A WHERE 0 = 1", query); + + JPAJinqStream DaveDiffAll = Dave.andNotDifference(allCustomers); + List DaveDiffAllList = DaveDiffAll.toList(); + assertEquals("SELECT A FROM Customer A WHERE 0 = 1", query); + + } + @Test public void testAndIntersect() {