Skip to content

Commit

Permalink
Move JdbcType into the cake and abstract it to RelationalProfile
Browse files Browse the repository at this point in the history
  • Loading branch information
szeiger committed Nov 28, 2013
1 parent a8608e2 commit 006d8a7
Show file tree
Hide file tree
Showing 20 changed files with 239 additions and 218 deletions.
Expand Up @@ -4,7 +4,7 @@ import org.junit.Assert._

import com.typesafe.slick.testkit.util.{JdbcTestDB, TestkitTest}

class MapperTest extends TestkitTest[JdbcTestDB] {
class JdbcMapperTest extends TestkitTest[JdbcTestDB] {
import tdb.profile.simple._

override val reuseInstance = true
Expand Down Expand Up @@ -95,66 +95,6 @@ class MapperTest extends TestkitTest[JdbcTestDB] {
)
}

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

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
}
)

class T(tag: Tag) extends Table[(Int, Bool, Option[Bool])](tag, "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)
}
val ts = TableQuery[T]

ts.ddl.create
ts.map(t => (t.b, t.c)).insertAll((False, None), (True, Some(True)))
assertEquals(ts.list.toSet, Set((1, False, None), (2, True, Some(True))))
assertEquals(ts.where(_.b === (True:Bool)).list.toSet, Set((2, True, Some(True))))
assertEquals(ts.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
}
)

class T(tag: Tag) extends Table[(Int, Bool, Option[Bool])](tag, "t3") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def b = column[Bool]("b")
def c = column[Option[Bool]]("c")
def * = (id, b, c)
}
val ts = TableQuery[T]

ts.ddl.create
ts.map(t => (t.b, t.c)).insertAll((False, None), (True, Some(True)))
assertEquals(ts.list.toSet, Set((1, False, None), (2, True, Some(True))))
assertEquals(ts.where(_.b === (True:Bool)).list.toSet, Set((2, True, Some(True))))
assertEquals(ts.where(_.b === (False:Bool)).list.toSet, Set((1, False, None)))
}

def testWideMappedEntity {
case class Part(i1: Int, i2: Int, i3: Int, i4: Int, i5: Int, i6: Int)
case class Whole(id: Int, p1: Part, p2: Part, p3: Part, p4: Part)
Expand Down
@@ -0,0 +1,71 @@
package com.typesafe.slick.testkit.tests

import org.junit.Assert._

import com.typesafe.slick.testkit.util.{RelationalTestDB, TestkitTest}

class RelationalMapperTest extends TestkitTest[RelationalTestDB] {
import tdb.profile.simple._

override val reuseInstance = true

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

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
}
)

class T(tag: Tag) extends Table[(Int, Bool, Option[Bool])](tag, "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)
}
val ts = TableQuery[T]

ts.ddl.create
ts.map(t => (t.b, t.c)) ++= Seq((False, None), (True, Some(True)))
assertEquals(ts.run.toSet, Set((1, False, None), (2, True, Some(True))))
assertEquals(ts.filter(_.b === (True:Bool)).run.toSet, Set((2, True, Some(True))))
assertEquals(ts.filter(_.b === (False:Bool)).run.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
}
)

class T(tag: Tag) extends Table[(Int, Bool, Option[Bool])](tag, "t3") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def b = column[Bool]("b")
def c = column[Option[Bool]]("c")
def * = (id, b, c)
}
val ts = TableQuery[T]

ts.ddl.create
ts.map(t => (t.b, t.c)) ++= Seq((False, None), (True, Some(True)))
assertEquals(ts.run.toSet, Set((1, False, None), (2, True, Some(True))))
assertEquals(ts.where(_.b === (True:Bool)).run.toSet, Set((2, True, Some(True))))
assertEquals(ts.where(_.b === (False:Bool)).run.toSet, Set((1, False, None)))
}
}
Expand Up @@ -27,7 +27,8 @@ object Testkit {
classOf[tk.IterateeTest] ::
classOf[tk.JoinTest] ::
classOf[tk.MainTest] ::
classOf[tk.MapperTest] ::
classOf[tk.JdbcMapperTest] ::
classOf[tk.RelationalMapperTest] ::
classOf[tk.RelationalMiscTest] ::
classOf[tk.JdbcMiscTest] ::
classOf[tk.MutateTest] ::
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/scala/slick/driver/AccessDriver.scala
Expand Up @@ -4,7 +4,7 @@ import scala.language.implicitConversions
import scala.slick.SlickException
import scala.slick.ast._
import scala.slick.compiler.{QueryCompiler, CompilerState, Phase}
import scala.slick.jdbc.{PositionedParameters, PositionedResult, ResultSetType, JdbcType}
import scala.slick.jdbc.{PositionedParameters, PositionedResult, ResultSetType}
import scala.slick.lifted._
import scala.slick.profile.{RelationalProfile, SqlProfile, Capability}
import scala.slick.util.MacroSupport.macroSupportInterpolation
Expand Down
1 change: 0 additions & 1 deletion src/main/scala/scala/slick/driver/DerbyDriver.scala
Expand Up @@ -3,7 +3,6 @@ package scala.slick.driver
import scala.slick.SlickException
import scala.slick.lifted._
import scala.slick.ast._
import scala.slick.jdbc.JdbcType
import scala.slick.util.MacroSupport.macroSupportInterpolation
import scala.slick.profile.{RelationalProfile, SqlProfile, Capability}
import scala.slick.compiler.{Phase, QueryCompiler, CompilerState}
Expand Down
1 change: 0 additions & 1 deletion src/main/scala/scala/slick/driver/H2Driver.scala
Expand Up @@ -2,7 +2,6 @@ package scala.slick.driver

import scala.slick.lifted._
import scala.slick.ast._
import scala.slick.jdbc.JdbcType
import scala.slick.util.MacroSupport.macroSupportInterpolation
import scala.slick.profile.{SqlProfile, Capability}
import scala.slick.compiler.CompilerState
Expand Down
15 changes: 5 additions & 10 deletions src/main/scala/scala/slick/driver/JdbcProfile.scala
Expand Up @@ -4,24 +4,25 @@ import scala.language.implicitConversions
import scala.slick.ast.{Node, TypedType, BaseTypedType}
import scala.slick.compiler.{Phase, QueryCompiler}
import scala.slick.lifted._
import scala.slick.jdbc.{MappedJdbcType, JdbcMappingCompilerComponent, JdbcType, MutatingUnitInvoker, JdbcBackend}
import scala.slick.jdbc.{JdbcMappingCompilerComponent, MutatingUnitInvoker, JdbcBackend}
import scala.slick.profile.{SqlDriver, SqlProfile, Capability}
import scala.slick.SlickException

/**
* A profile for accessing SQL databases via JDBC.
*/
trait JdbcProfile extends SqlProfile with JdbcTableComponent
with JdbcInvokerComponent with JdbcExecutorComponent { driver: JdbcDriver =>
with JdbcInvokerComponent with JdbcExecutorComponent with JdbcTypesComponent { driver: JdbcDriver =>

type Backend = JdbcBackend
val backend: Backend = JdbcBackend
val compiler = QueryCompiler.relational
val Implicit: Implicits = new Implicits {}
val simple: SimpleQL = new SimpleQL {}
val simple: SimpleQL with Implicits = new SimpleQL with Implicits {}
type ColumnType[T] = JdbcType[T]
type BaseColumnType[T] = JdbcType[T] with BaseTypedType[T]
val columnTypes = new JdbcTypes
lazy val MappedColumnType = MappedJdbcType

override protected def computeCapabilities = super.computeCapabilities ++ JdbcProfile.capabilities.all

Expand Down Expand Up @@ -51,11 +52,6 @@ trait JdbcProfile extends SqlProfile with JdbcTableComponent
implicit def productQueryToUpdateInvoker[T](q: Query[_ <: ColumnBase[T], T]): UpdateInvoker[T] =
createUpdateInvoker(updateCompiler.run(q.toNode).tree, ())
}

trait SimpleQL extends super.SimpleQL with Implicits {
type MappedColumnType[T, U] = MappedJdbcType[T, U]
val MappedColumnType = MappedJdbcType
}
}

object JdbcProfile {
Expand All @@ -80,8 +76,7 @@ object JdbcProfile {
trait JdbcDriver extends SqlDriver
with JdbcProfile
with JdbcStatementBuilderComponent
with JdbcMappingCompilerComponent
with JdbcTypesComponent { driver =>
with JdbcMappingCompilerComponent { driver =>

override val profile: JdbcProfile = this

Expand Down
110 changes: 107 additions & 3 deletions src/main/scala/scala/slick/driver/JdbcTypesComponent.scala
Expand Up @@ -3,13 +3,111 @@ package scala.slick.driver
import java.sql.{Blob, Clob, Date, Time, Timestamp}
import java.util.UUID
import scala.slick.SlickException
import scala.slick.ast.{ScalaBaseType, OptionType, NumericTypedType, BaseTypedType, Type}
import scala.slick.jdbc.{PositionedParameters, PositionedResult, JdbcType}
import scala.slick.ast._
import scala.slick.jdbc.{PositionedParameters, PositionedResult}
import scala.slick.profile.RelationalTypesComponent
import scala.reflect.ClassTag

trait JdbcTypesComponent extends RelationalTypesComponent { driver: JdbcDriver =>

/** A JdbcType object represents a Scala type that can be
* used as a column type in the database. Implicit JdbcTypes
* for the standard types of a profile are provided by the drivers. */
trait JdbcType[T] extends TypedType[T] { self =>
/** The constant from java.sql.Types that is used for setting parameters
* of the type to NULL. */
def sqlType: Int
/** The default name for the SQL type that is used for column declarations. */
def sqlTypeName: String
/** Set a parameter of the type. */
def setValue(v: T, p: PositionedParameters): Unit
/** Set an Option parameter of the type. */
def setOption(v: Option[T], p: PositionedParameters): Unit
/** Get a result column of the type. */
def nextValue(r: PositionedResult): T
/** Update a column of the type in a mutable result set. */
def updateValue(v: T, r: PositionedResult): Unit
def nextValueOrElse(d: =>T, r: PositionedResult) = { val v = nextValue(r); if(r.rs.wasNull) d else v }
def nextOption(r: PositionedResult): Option[T] = { val v = nextValue(r); if(r.rs.wasNull) None else Some(v) }
def updateOption(v: Option[T], r: PositionedResult): Unit = v match {
case Some(s) => updateValue(s, r)
case None => r.updateNull()
}

/** Convert a value to a SQL literal.
* This should throw a `SlickException` if `hasLiteralForm` is false. */
def valueToSQLLiteral(value: T): String

def nullable = false

/** Indicates whether values of this type have a literal representation in
* SQL statements.
* This must return false if `valueToSQLLiteral` throws a SlickException.
* QueryBuilder (and driver-specific subclasses thereof) uses this method
* to treat LiteralNodes as volatile (i.e. using bind variables) as needed. */
def hasLiteralForm: Boolean

override def optionType: OptionTypedType[T] with JdbcType[Option[T]] = new OptionTypedType[T] with JdbcType[Option[T]] {
val elementType = self
def sqlType = self.sqlType
override def sqlTypeName = self.sqlTypeName
def scalaType = new ScalaOptionType[T](self.scalaType)
def setValue(v: Option[T], p: PositionedParameters) = self.setOption(v, p)
def setOption(v: Option[Option[T]], p: PositionedParameters) = self.setOption(v.getOrElse(None), p)
def nextValue(r: PositionedResult) = self.nextOption(r)
def updateValue(v: Option[T], r: PositionedResult) = self.updateOption(v, r)
override def valueToSQLLiteral(value: Option[T]): String = value.map(self.valueToSQLLiteral).getOrElse("null")
override def nullable = true
override def toString = s"Option[$self]"
def hasLiteralForm = self.hasLiteralForm
def mapChildren(f: Type => Type): OptionTypedType[T] with JdbcType[Option[T]] = {
val e2 = f(elementType)
if(e2 eq elementType) this
else e2.asInstanceOf[JdbcType[T]].optionType
}
}

override def toString = {
def cln = getClass.getName
val pos = cln.lastIndexOf("$JdbcTypes$")
val s = if(pos >= 0) cln.substring(pos+11) else cln
val s2 = if(s.endsWith("JdbcType")) s.substring(0, s.length-8) else s
s2 + "/" + sqlTypeName
}
}

abstract class MappedJdbcType[T, U](implicit tmd: JdbcType[U], tag: ClassTag[T]) extends JdbcType[T] {
def map(t: T): U
def comap(u: U): T

def newSqlType: Option[Int] = None
def newSqlTypeName: Option[String] = None
def newValueToSQLLiteral(value: T): Option[String] = None
def newNullable: Option[Boolean] = None
def newHasLiteralForm: Option[Boolean] = None

def sqlType = newSqlType.getOrElse(tmd.sqlType)
override def sqlTypeName = newSqlTypeName.getOrElse(tmd.sqlTypeName)
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)
def hasLiteralForm = newHasLiteralForm.getOrElse(tmd.hasLiteralForm)
def scalaType = ScalaBaseType[T]
}

object MappedJdbcType extends MappedColumnTypeFactory {
def base[T : ClassTag, U : BaseColumnType](tmap: T => U, tcomap: U => T): BaseColumnType[T] =
new MappedJdbcType[T, U] with BaseTypedType[T] {
def map(t: T) = tmap(t)
def comap(u: U) = tcomap(u)
}
}

type TypeInfo = JdbcType[Any /* it's really _ but we'd have to cast it to Any anyway */]

def typeInfoFor(t: Type): TypeInfo = ((t match {
Expand All @@ -32,7 +130,7 @@ trait JdbcTypesComponent extends RelationalTypesComponent { driver: JdbcDriver =
def defaultSqlTypeName(tmd: JdbcType[_]): String = tmd.sqlType match {
case java.sql.Types.VARCHAR => "VARCHAR(254)"
case java.sql.Types.DECIMAL => "DECIMAL(21,2)"
case t => JdbcType.typeNames.getOrElse(t,
case t => JdbcTypesComponent.typeNames.getOrElse(t,
throw new SlickException("No SQL type name found in java.sql.Types for code "+t))
}

Expand Down Expand Up @@ -280,3 +378,9 @@ trait JdbcTypesComponent extends RelationalTypesComponent { driver: JdbcDriver =
implicit def bigDecimalColumnType = columnTypes.bigDecimalJdbcType
}
}

object JdbcTypesComponent {
private[slick] lazy val typeNames = Map() ++
(for(f <- classOf[java.sql.Types].getFields)
yield f.get(null).asInstanceOf[Int] -> f.getName)
}
2 changes: 1 addition & 1 deletion src/main/scala/scala/slick/driver/PostgresDriver.scala
Expand Up @@ -2,7 +2,7 @@ package scala.slick.driver

import java.util.UUID
import scala.slick.lifted._
import scala.slick.jdbc.{PositionedParameters, PositionedResult, JdbcType}
import scala.slick.jdbc.{PositionedParameters, PositionedResult}
import scala.slick.ast.{SequenceNode, Library, FieldSymbol, Node}
import scala.slick.util.MacroSupport.macroSupportInterpolation
import scala.slick.compiler.CompilerState
Expand Down

0 comments on commit 006d8a7

Please sign in to comment.