Skip to content

Commit

Permalink
Merge branch 'master' into topic/backend-refactoring
Browse files Browse the repository at this point in the history
Conflicts:
	slick-testkit/src/main/scala/com/typesafe/slick/testkit/tests/MapperTest.scala
	src/main/scala/scala/slick/driver/AccessDriver.scala
	src/main/scala/scala/slick/lifted/ExtensionMethods.scala
	src/main/scala/scala/slick/lifted/TypeMapper.scala
  • Loading branch information
szeiger committed Oct 16, 2012
2 parents 92f191f + cdfaf32 commit 49cb16d
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 14 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ The following database systems are directly supported for type-safe queries:
- PostgreSQL
- SQLite

Support for DB2 and Oracle is scheduled to be available for production use by
[Typesafe subscribers](http://www.typesafe.com/products/typesafe-subscription)
(free for evaluation and development) along with release 1.0 of Slick.

Accessing other database systems is possible, with a reduced feature set.

See <https://github.com/slick/slick/wiki> for more information.
The [manual and scaladocs](http://slick.typesafe.com/docs/) for Slick can be
found on the [Slick web site](http://slick.typesafe.com/).
There is some older documentation (which may still apply to some extent to
Slick) in the [ScalaQuery Wiki](https://github.com/szeiger/scala-query/wiki).

Licensing conditions (BSD-style) can be found in LICENSE.txt.
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,15 @@ class MapperTest(val tdb: TestDB) extends TestkitTest {
User(None, "Lenny", "Leonard")
)

val lastNames = Set("Bouvier", "Ferdinand")
assertEquals(1, Query(Users).where(_.last inSet lastNames).list.size)

val updateQ = Users.where(_.id === 2.bind).map(_.forInsert)
println("Update: "+updateQ.updateStatement)
updateQ.update(User(None, "Marge", "Simpson"))

assertTrue(Query(Users.where(_.id === 1).exists).first)

Users.where(_.id between(1, 2)).foreach(println)
println("ID 3 -> " + Users.findByID.first(3))

Expand Down Expand Up @@ -82,20 +87,72 @@ class MapperTest(val tdb: TestDB) extends TestkitTest {
case object True extends Bool
case object False extends Bool

implicit val boolType = MappedColumnType.base[Bool, Int](
b => if(b == True) 1 else 0,
i => if(i == 1) True else False)
implicit val boolTypeMapper = MappedColumnType.base[Bool, Int](
{ b =>
assertNotNull(b)
if(b == True) 1 else 0
}, { i =>
assertNotNull(i)
if(i == 1) True else False
}
)

object T extends Table[(Int, Bool, Option[Bool])]("t2") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def b = column[Bool]("b")
def c = column[Option[Bool]]("c")
def * = id ~ b ~ c
}

T.ddl.create
(T.b ~ T.c).insertAll((False, None), (True, Some(True)))
assertEquals(Query(T).list.toSet, Set((1, False, None), (2, True, Some(True))))
assertEquals(T.where(_.b === (True:Bool)).list.toSet, Set((2, True, Some(True))))
assertEquals(T.where(_.b === (False:Bool)).list.toSet, Set((1, False, None)))
}

def testMappedRefType {
sealed trait Bool
case object True extends Bool
case object False extends Bool

implicit val boolTypeMapper = MappedColumnType.base[Bool, String](
{ b =>
assertNotNull(b)
if(b == True) "y" else "n"
}, { i =>
assertNotNull(i)
if(i == "y") True else False
}
)

object T extends Table[(Int, Bool)]("t2") {
object T extends Table[(Int, Bool, Option[Bool])]("t3") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def b = column[Bool]("b")
def * = id ~ b
def c = column[Option[Bool]]("c")
def * = id ~ b ~ c
}

T.ddl.create
T.b.insertAll(False, True)
assertEquals(Query(T).list.toSet, Set((1, False), (2, True)))
assertEquals(T.where(_.b === (True:Bool)).list.toSet, Set((2, True)))
assertEquals(T.where(_.b === (False:Bool)).list.toSet, Set((1, False)))
(T.b ~ T.c).insertAll((False, None), (True, Some(True)))
assertEquals(Query(T).list.toSet, Set((1, False, None), (2, True, Some(True))))
assertEquals(T.where(_.b === (True:Bool)).list.toSet, Set((2, True, Some(True))))
assertEquals(T.where(_.b === (False:Bool)).list.toSet, Set((1, False, None)))
}

/*
def testGetOr {
object T extends Table[Option[Int]]("t4") {
def year = column[Option[Int]]("YEAR")
def * = year
}
T.ddl.create
T.insertAll(Some(2000), None)
val q = T.map(t => (t.year.getOr(2000), (t.year.getOr(2000)-0)))
println(q.selectStatement)
q.foreach(println)
}
*/
}
3 changes: 2 additions & 1 deletion src/main/scala/scala/slick/ast/Library.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ object Library {
val Avg = new SqlAggregateFunction("avg")
val Sum = new SqlAggregateFunction("sum")
val Count = new SqlAggregateFunction("count")
val Exists = new SqlAggregateFunction("exists")
val CountAll = new AggregateFunction("count(*)")
val CountDistinct = new AggregateFunction("count distinct")

val Exists = new SqlFunction("exists")

val Cast = new FunctionSymbol("Cast")
val IfNull = new JdbcFunction("ifnull")

Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/scala/slick/ast/Node.scala
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ final case class TableRefExpansion(marker: Symbol, ref: Node, columns: Node) ext
}

final case class Select(in: Node, field: Symbol) extends UnaryNode with SimpleRefNode {
if(in.isInstanceOf[TableNode])
throw new SlickException("Select(TableNode, \""+field+"\") found. This is "+
"typically caused by an attempt to use a \"raw\" table object directly "+
"in a query without introducing it through a generator.")
def child = in
override def nodeChildNames = Seq("in")
protected[this] def nodeRebuild(child: Node) = copy(in = child)
Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/scala/slick/compiler/Inline.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ class Inline(unique: Boolean = true, paths: Boolean = true, from: Boolean = true
val toInline = globalCounts.iterator.filter { case (a, i) =>
all ||
(unique && i == 1) ||
(paths && Path.unapply(globals(a)).isDefined)
(paths && Path.unapply(globals(a)).isDefined) ||
/* This happens when referencing a raw TableNode from multiple Selects.
We inline it here to make the subsequent Select(TableNode, _) fails */
globals(a).isInstanceOf[TableNode]
}.map(_._1).toSet
logger.debug("symbols to inline everywhere: "+toInline)
val inlined = new HashSet[Symbol]
Expand Down
8 changes: 8 additions & 0 deletions src/main/scala/scala/slick/compiler/QueryCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ class QueryCompiler(val phases: Vector[Phase]) extends Logging {
else phases.patch(i+1, Seq(p), 0)
})

/** Return a new compiler with the new phase added directly before another
* phase (or a different implementation of the same phase name). */
def addBefore(p: Phase, before: Phase) = new QueryCompiler({
val i = phases.indexWhere(_.name == before.name)
if(i == -1) throw new SlickException("Following phase "+before.name+" not found")
else phases.patch(i, Seq(p), 0)
})

/** Return a new compiler without the given phase (or a different
* implementation of the same phase name. */
def - (p: Phase) = new QueryCompiler(phases.filterNot(_.name == p.name))
Expand Down
21 changes: 21 additions & 0 deletions src/main/scala/scala/slick/driver/AccessDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import scala.slick.jdbc.{PositionedParameters, PositionedResult, ResultSetType,
import java.util.UUID
import java.sql.{Blob, Clob, Date, Time, Timestamp, SQLException}
import scala.slick.profile.{SqlProfile, Capability}
import scala.slick.compiler.{QueryCompiler, CompilationState, Phase}

/**
* Slick driver for Microsoft Access via JdbcOdbcDriver.
Expand Down Expand Up @@ -77,6 +78,8 @@ trait AccessDriver extends ExtendedDriver { driver =>

override val Implicit: Implicits = new Implicits
override val simple: SimpleQL = new Implicits with SimpleQL
override val compiler =
QueryCompiler.relational.addBefore(new ExistsToCount, QueryCompiler.relationalPhases.head)

class Implicits extends super.Implicits {
override implicit def queryToQueryInvoker[T, U](q: Query[T, _ <: U]): QueryInvoker[T, U] = new QueryInvoker(q)
Expand Down Expand Up @@ -273,6 +276,24 @@ trait AccessDriver extends ExtendedDriver { driver =>
/* Access goes forward instead of backward after deleting the current row in a mutable result set */
override protected val previousAfterDelete = true
}

/** Query compiler phase that rewrites Exists calls in projections to
* equivalent CountAll > 0 calls which can then be fused into aggregation
* sub-queries in the fuseComprehensions phase. */
class ExistsToCount extends Phase {
val name = "access:existsToCount"
import StaticType._

def apply(n: Node, state: CompilationState) = tr(n, false)

protected def tr(n: Node, inSelect: Boolean): Node = n match {
case b @ Bind(_, _, sel) => b.nodeMapChildren { n => tr(n, n eq sel) }
case f: FilteredQuery => f.nodeMapChildren(tr(_, false))
case a @ Library.Exists(ch) if inSelect =>
Library.>.typed[Boolean](Library.CountAll.typed[Int](tr(ch, true)), LiteralNode(0))
case n => n.nodeMapChildren(ch => tr(ch, inSelect))
}
}
}

object AccessDriver extends AccessDriver
2 changes: 2 additions & 0 deletions src/main/scala/scala/slick/jdbc/JdbcType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ abstract class MappedJdbcType[T, U](implicit tmd: JdbcType[U]) extends JdbcType[
def setValue(v: T, p: PositionedParameters) = tmd.setValue(map(v), p)
def setOption(v: Option[T], p: PositionedParameters) = tmd.setOption(v.map(map _), p)
def nextValue(r: PositionedResult) = comap(tmd.nextValue(r))
override def nextValueOrElse(d: =>T, r: PositionedResult) = { val v = tmd.nextValue(r); if(r.rs.wasNull) d else comap(v) }
override def nextOption(r: PositionedResult): Option[T] = { val v = tmd.nextValue(r); if(r.rs.wasNull) None else Some(comap(v)) }
def updateValue(v: T, r: PositionedResult) = tmd.updateValue(map(v), r)
override def valueToSQLLiteral(value: T) = newValueToSQLLiteral(value).getOrElse(tmd.valueToSQLLiteral(map(value)))
override def nullable = newNullable.getOrElse(tmd.nullable)
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/scala/slick/lifted/ExtensionMethods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ final class ColumnExtensionMethods[B1, P1](val c: Column[P1]) extends AnyVal wit
om(Library.In.column(n, Node(e)))
def notIn[P2, R](e: Query[Column[P2], _])(implicit om: o#arg[B1, P2]#to[Boolean, R]) =
om(Library.Not.column(Library.In.typed[Boolean](n, Node(e))))
def inSet[R : TypedType](seq: Traversable[B1])(implicit om: o#to[Boolean, R]) = om(
def inSet[R](seq: Traversable[B1])(implicit om: o#to[Boolean, R]) = om(
if(seq.isEmpty) ConstColumn(false)
else Library.In.column(n, ProductNode(seq.map{ v => LiteralNode.apply(implicitly[TypedType[R]], v) }.toSeq)))
else Library.In.column(n, ProductNode(seq.map{ v => LiteralNode(implicitly[TypedType[B1]], v) }.toSeq)))
def inSetBind[R](seq: Traversable[B1])(implicit om: o#to[Boolean, R]) = om(
if(seq.isEmpty) ConstColumn(false)
else Library.In.column(n, ProductNode(seq.map(v => BindColumn[B1](v)).toSeq)))
Expand Down

0 comments on commit 49cb16d

Please sign in to comment.