Skip to content

Commit

Permalink
Added Scala support for LEFT OUTER JOIN...ON queries.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ming committed Apr 23, 2016
1 parent 6abd693 commit 1bf4e95
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.jinq.jpa.transform;

import org.jinq.jpa.jpqlquery.RowReader;
import org.jinq.jpa.jpqlquery.ScalaTupleRowReader;

public class ScalaOuterJoinOnTransform extends OuterJoinOnTransform
{
public ScalaOuterJoinOnTransform(JPQLQueryTransformConfiguration config)
{
super(config, true, false);
}

@Override
protected <U> RowReader<U> createPairReader(RowReader<?> a, RowReader<?> b)
{
return ScalaTupleRowReader.createReaderForTuple(ScalaTupleRowReader.TUPLE2_CLASS, a, b);
}

@Override
public String getTransformationTypeCachingTag()
{
return ScalaOuterJoinOnTransform.class.getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.jinq.orm.stream.scala.JinqIterator
import org.jinq.orm.stream.scala.NonQueryJinqIterator
import _root_.scala.collection.GenTraversableOnce
import org.jinq.jpa.transform.JoinFetchTransform
import org.jinq.jpa.transform.ScalaOuterJoinOnTransform

class JinqJPAScalaIterator[T](_query: JPAQueryComposer[T], _inQueryStreamSource: InQueryStreamSource) extends JinqIterator[T] {
val GENERIC_TRANSLATION_FAIL_MESSAGE = "Could not translate Scala code to a query";
Expand Down Expand Up @@ -98,6 +99,12 @@ class JinqJPAScalaIterator[T](_query: JPAQueryComposer[T], _inQueryStreamSource:
asNonQuery().leftOuterJoin(fn)
}

override def leftOuterJoin[U](join: (T, InQueryStreamSource) => JinqIterator[U], on: (T, U) => Boolean) : JinqIterator[Tuple2[T, U]] = {
val newComposer: JPAQueryComposer[(T, U)] = queryComposer.applyTransformWithTwoLambdas(new ScalaOuterJoinOnTransform(queryComposer.getConfig()), join, on);
if (newComposer != null) return new JinqJPAScalaIterator(newComposer, inQueryStreamSource);
asNonQuery().leftOuterJoin(join, on)
}

override def joinFetch[U](fn: (T) => JinqIterator[U]): JinqIterator[T] = {
val newComposer: JPAQueryComposer[T] = queryComposer.applyTransformWithLambda(new JoinFetchTransform(queryComposer.getConfig()).setIsExpectingStream(true).setIsOuterJoinFetch(false), fn);
if (newComposer != null) return new JinqJPAScalaIterator(newComposer, inQueryStreamSource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,44 @@ trait JinqIterator[T] extends Iterator[T] {
*/
def leftOuterJoin[U](fn: (T) => JinqIterator[U]): JinqIterator[Tuple2[T, U]]

/**
* Pairs up each entry of the stream with a stream of related elements. Uses
* a left outer join during the pairing up process, so even if an element is
* not joined with anything, a pair will still be created in the output
* stream consisting of the element paired with null. This version also passes
* an {@link InQueryStreamSource} to the join function so that the function
* can join elements with unrelated streams of entities from a database
* and an ON clause can be specified that will determine which elements from
* the two streams will be joined together.
*
* <pre>
* {@code JinqStream<Country> stream = ...;
* JinqStream<Pair<Country, Mountain>> result =
* stream.leftOuterJoin(
* (c, source) -> source.stream(Mountain.class),
* (country, mountain) -> country.getName().equals(mountain.getCountry()));
* }
* </pre>
*
* @see #leftOuterJoin(Join)
* @param join
* function applied to the elements of the stream. When passed an
* element from the stream, the function should return a stream of
* values that should be paired up with that stream element. The
* function must use a JPA association or navigational link as the
* base for the stream returned. Both singular or plural
* associations are allowed.
* @param on
* this is a comparison function that returns true if the elements
* from the two streams should be joined together. It is similar to
* a standard where() clause except that the elements from the two
* streams are passed in as separate parameters for convenience
* (as opposed to being passed in as a pair)
* @return a new stream with the paired up elements
*/
def leftOuterJoin[U](join: (T, InQueryStreamSource) => JinqIterator[U], on: (T, U) => Boolean) : JinqIterator[Tuple2[T, U]]


/**
* When executing the query, the items referred to be the plural
* association will be fetched as well. The stream itself will
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ class NonQueryJinqIterator[T](_wrapped: Iterator[T], _inQueryStreamSource: InQue
}))
}

override def leftOuterJoin[U](join: (T, InQueryStreamSource) => JinqIterator[U], on: (T, U) => Boolean) : JinqIterator[Tuple2[T, U]] = {
wrap(flatMap((left: T) => {
val joined = join(left, inQueryStreamSource).filter(right => on(left, right)).toBuffer
if (joined.isEmpty)
List((left, null.asInstanceOf[U]))
else
joined.map(right => (left, right))
}))
}

override def joinFetch[U](fn: (T) => JinqIterator[U]): JinqIterator[T] = {
this;
}
Expand Down
47 changes: 47 additions & 0 deletions jinq-jpa-scala/src/test/scala/org/jinq/jpa/JinqJPAScalaTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,53 @@ class JinqJPAScalaTest extends JinqJPAScalaTestBase {
Assert.assertEquals("Conglomerate", results(0)._2.getName());
Assert.assertEquals("HW Supplier", results(1)._2.getName());
}

@Test
def testOuterJoinOn() {
var results = streamAll(em, classOf[Item])
.leftOuterJoin(
(i, source) => source.stream(classOf[Supplier]),
(item, supplier : Supplier) => item.getName().substring(0, 1) == supplier.getName().substring(0, 1))
.toList;

Assert.assertEquals("SELECT A, B FROM Item A LEFT OUTER JOIN Supplier B ON SUBSTRING(A.name, 0 + 1, 1 - 0) IS NOT NULL AND SUBSTRING(A.name, 0 + 1, 1 - 0) = SUBSTRING(B.name, 0 + 1, 1 - 0) OR SUBSTRING(A.name, 0 + 1, 1 - 0) IS NULL AND SUBSTRING(B.name, 0 + 1, 1 - 0) IS NULL", query);
results = results.sortBy(c1 => c1._1.getName());
Assert.assertEquals(5, results.length);
Assert.assertEquals("Lawnmowers", results(0)._1.getName());
Assert.assertNull(results(0)._2);
Assert.assertEquals("Talent", results(2)._1.getName());
Assert.assertEquals("Talent Agency", results(2)._2.getName());
}

@Test
def testOuterJoinOnTrueAndNavigationalLinks() {
var results = streamAll(em, classOf[Item])
.leftOuterJoin(
(i, source) => i.getSuppliers,
(item, supplier : Supplier) => true)
.toList;
Assert.assertEquals("SELECT A, B FROM Item A LEFT OUTER JOIN A.suppliers B", query);
Assert.assertEquals(6, results.length);
}

@Test
def testOuterJoinOnWithParametersAndIndirect() {
val m = "Screws";
var results = streamAll(em, classOf[Item])
.select(i => i.getName())
.leftOuterJoin(
(i, source) => source.stream(classOf[Supplier]),
(item, supplier : Supplier) => item == m)
.toList;
Assert.assertEquals("SELECT A.name, B FROM Item A LEFT OUTER JOIN Supplier B ON A.name IS NOT NULL AND A.name = :param0 OR A.name IS NULL AND :param1 IS NULL", query);
results = results.sortBy(c1 => c1._1);
Assert.assertEquals(7, results.length);
Assert.assertEquals("Lawnmowers", results(0)._1);
Assert.assertNull(results(0)._2);
Assert.assertEquals("Screws", results(1)._1);
Assert.assertEquals("Screws", results(2)._1);
Assert.assertEquals("Screws", results(3)._1);
}

// @Test
// def testStreamPages()
Expand Down

0 comments on commit 1bf4e95

Please sign in to comment.