Skip to content
Permalink
Browse files

Add test for outer joins and fix capability declarations.

We also support it directly in QueryInterpreter for MemoryDriver. The
test reveals that the drivers for Access, Derby, H2 and MySQL declared
the (already existing) joinFull capability but do not actually support
full outer joins.

Test in JoinTest.testJoin. Fixes issue #466.
  • Loading branch information
szeiger committed Mar 25, 2014
1 parent b3aee9a commit 76e638cb7a445fcd8cc75c290ff852cc8f38f17b
@@ -82,6 +82,15 @@ class JoinTest extends TestkitTest[RelationalTestDB] {
q4.run.foreach(x => println(" "+x))
assertEquals(List((1,0), (2,1), (3,2), (4,3), (5,2)), q4.map(p => (p._1, p._2)).run)
}

ifCap(rcap.joinFull) {
val q4 = (for {
(c,p) <- categories outerJoin posts on (_.id is _.category)
} yield (p.id.?.getOrElse(0), c.id.?.getOrElse(0), c.name.?.getOrElse(""), p.title.?.getOrElse(""))).sortBy(_._1)
println("Full outer join")
q4.run.foreach(x => println(" "+x))
assertEquals(Vector((0,4), (1,0), (2,1), (3,2), (4,3), (5,2)), q4.map(p => (p._1, p._2)).run)
}
}

def testZip = ifCap(rcap.zip) {
@@ -58,6 +58,8 @@ import java.sql.{Blob, Clob, Date, Time, Timestamp, SQLException}
* Row numbers (required by <code>zip</code> and
* <code>zipWithIndex</code>) are not supported. Trying to generate SQL
* code which uses this feature throws a SlickException.</li>
* <li>[[scala.slick.profile.RelationalProfile.capabilities.joinFull]]:
* Full outer joins are not supported by Access.</li>
* </ul>
*
* @author szeiger
@@ -81,6 +83,7 @@ trait AccessDriver extends JdbcDriver { driver =>
- RelationalProfile.capabilities.typeLong
- RelationalProfile.capabilities.zip
- JdbcProfile.capabilities.createModel
- RelationalProfile.capabilities.joinFull
)

def integralTypes = Set(
@@ -43,6 +43,8 @@ import scala.slick.jdbc.Invoker
* support them with the standard SQL:2003 syntax (see
* <a href="http://wiki.apache.org/db-derby/OLAPRowNumber" target="_parent"
* >http://wiki.apache.org/db-derby/OLAPRowNumber</a>).</li>
* <li>[[scala.slick.profile.RelationalProfile.capabilities.joinFull]]:
* Full outer joins are not supported by Derby.</li>
* </ul>
*
* @author szeiger
@@ -57,6 +59,7 @@ trait DerbyDriver extends JdbcDriver { driver =>
// Cycling is broken in Derby. It cycles to the start value instead of min or max
- SqlProfile.capabilities.sequenceCycle
- RelationalProfile.capabilities.zip
- RelationalProfile.capabilities.joinFull
)

override def getTables: Invoker[MTable] = MTable.getTables(None, None, None, Some(Seq("TABLE")))
@@ -3,7 +3,7 @@ package scala.slick.driver
import scala.slick.lifted._
import scala.slick.ast._
import scala.slick.util.MacroSupport.macroSupportInterpolation
import scala.slick.profile.{SqlProfile, Capability}
import scala.slick.profile.{RelationalProfile, SqlProfile, Capability}
import scala.slick.compiler.CompilerState

/**
@@ -20,6 +20,8 @@ import scala.slick.compiler.CompilerState
* <li>[[scala.slick.driver.JdbcProfile.capabilities.returnInsertOther]]:
* When returning columns from an INSERT operation, only a single column
* may be specified which must be the table's AutoInc column.</li>
* <li>[[scala.slick.profile.RelationalProfile.capabilities.joinFull]]:
* Full outer joins are not supported by H2.</li>
* </ul>
*
* @author szeiger
@@ -31,6 +33,7 @@ trait H2Driver extends JdbcDriver { driver =>
- SqlProfile.capabilities.sequenceMax
- SqlProfile.capabilities.sequenceCycle
- JdbcProfile.capabilities.returnInsertOther
- RelationalProfile.capabilities.joinFull
)

override def createQueryBuilder(n: Node, state: CompilerState): QueryBuilder = new QueryBuilder(n, state)
@@ -7,7 +7,7 @@ import scala.slick.ast.Util._
import scala.slick.ast.TypeUtil._
import scala.slick.ast.ExtraUtil._
import scala.slick.util.MacroSupport.macroSupportInterpolation
import scala.slick.profile.{SqlProfile, Capability}
import scala.slick.profile.{RelationalProfile, SqlProfile, Capability}
import scala.slick.compiler.CompilerState

/**
@@ -22,6 +22,8 @@ import scala.slick.compiler.CompilerState
* may be specified which must be the table's AutoInc column.</li>
* <li>[[scala.slick.profile.SqlProfile.capabilities.sequenceLimited]]:
* Non-cyclic sequence may not have an upper limit.</li>
* <li>[[scala.slick.profile.RelationalProfile.capabilities.joinFull]]:
* Full outer joins are not supported by MySQL.</li>
* </ul>
*
* Sequences are supported through an emulation which requires the schema to
@@ -36,6 +38,7 @@ trait MySQLDriver extends JdbcDriver { driver =>
override protected def computeCapabilities: Set[Capability] = (super.computeCapabilities
- JdbcProfile.capabilities.returnInsertOther
- SqlProfile.capabilities.sequenceLimited
- RelationalProfile.capabilities.joinFull
)

override val columnTypes = new JdbcTypes
@@ -112,6 +112,30 @@ class QueryInterpreter(db: HeapBackend#Database, params: Any) extends Logging {
scope.remove(leftGen)
scope.remove(rightGen)
res
case Join(leftGen, rightGen, left, right, JoinType.Outer, by) =>
val leftJoinRes = run(left).asInstanceOf[Coll].flatMap { l =>
scope(leftGen) = l
val inner = run(right).asInstanceOf[Coll].filter { r =>
scope(rightGen) = r
asBoolean(run(by))
}.map { r =>
new ProductValue(Vector(l, r))
}
if(inner.headOption.isEmpty) Vector(new ProductValue(Vector(l, createNullRow(right.nodeType.asCollectionType.elementType))))
else inner
}
scope.remove(leftGen)
scope.remove(rightGen)
val emptyRightRes = run(right).asInstanceOf[Coll].filter { r =>
scope(rightGen) = r
run(left).asInstanceOf[Coll].find { l =>
scope(leftGen) = l
asBoolean(run(by))
}.isEmpty
}.map { r => new ProductValue(Vector(createNullRow(left.nodeType.asCollectionType.elementType), r)) }
scope.remove(leftGen)
scope.remove(rightGen)
leftJoinRes ++ emptyRightRes
case Filter(gen, from, where) =>
val res = run(from).asInstanceOf[Coll].filter { v =>
scope(gen) = v

0 comments on commit 76e638c

Please sign in to comment.
You can’t perform that action at this time.