Skip to content

Commit

Permalink
Add the ability to perform result set updates to the compiled mappings.
Browse files Browse the repository at this point in the history
This allows us to remove any dependency on Linearizers from
QueryInvoker.
  • Loading branch information
szeiger committed Feb 6, 2013
1 parent f0a28ba commit 12c746e
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 39 deletions.
4 changes: 2 additions & 2 deletions src/main/scala/scala/slick/driver/AccessDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ trait AccessDriver extends JdbcDriver { driver =>
QueryCompiler.relational.addBefore(new ExistsToCount, QueryCompiler.relationalPhases.head)

class Implicits extends super.Implicits {
override implicit def queryToQueryInvoker[T, U](q: Query[T, _ <: U]): QueryInvoker[U] = new QueryInvoker(newSelectStatementCompiler.run(Node(q)).tree, q)
override implicit def queryToQueryInvoker[T, U](q: Query[T, _ <: U]): QueryInvoker[U] = new QueryInvoker(newSelectStatementCompiler.run(Node(q)).tree)
}

val retryCount = 10
Expand Down Expand Up @@ -292,7 +292,7 @@ trait AccessDriver extends JdbcDriver { driver =>
override val uuidJdbcType = new UUIDJdbcType with Retry[UUID]
}

class QueryInvoker[R](tree: Node, linearizer: ValueLinearizer[_]) extends super.QueryInvoker[R](tree, linearizer) {
class QueryInvoker[R](tree: Node) extends super.QueryInvoker[R](tree) {
/* Using Auto or ForwardOnly causes a NPE in the JdbcOdbcDriver */
override protected val mutateType: ResultSetType = ResultSetType.ScrollInsensitive
/* Access goes forward instead of backward after deleting the current row in a mutable result set */
Expand Down
10 changes: 5 additions & 5 deletions src/main/scala/scala/slick/driver/JdbcInvokerComponent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import scala.slick.SlickException
import scala.slick.ast.{CompiledStatement, ResultSetMapping, Node}
import scala.slick.compiler.CodeGen
import scala.slick.lifted.{DDL, Query, Shape, ShapedValue}
import scala.slick.jdbc.{PositionedResultReader, UnitInvoker, UnitInvokerMixin, MutatingStatementInvoker, MutatingUnitInvoker, ResultSetInvoker, PositionedParameters, PositionedResult}
import scala.slick.jdbc.{CompiledMapping, UnitInvoker, UnitInvokerMixin, MutatingStatementInvoker, MutatingUnitInvoker, ResultSetInvoker, PositionedParameters, PositionedResult}
import scala.slick.util.{SQLBuilder, ValueLinearizer, RecordLinearizer}

trait JdbcInvokerComponent { driver: JdbcDriver =>
Expand All @@ -16,18 +16,18 @@ trait JdbcInvokerComponent { driver: JdbcDriver =>
def createMappedKeysInsertInvoker[U, RU, R](unpackable: ShapedValue[_, U], keys: ShapedValue[_, RU], tr: (U, RU) => R) = new MappedKeysInsertInvoker(unpackable, keys, tr)

/** Invoker for executing queries. */
class QueryInvoker[R](protected val tree: Node, protected val linearizer: ValueLinearizer[_])
class QueryInvoker[R](protected val tree: Node)
extends MutatingStatementInvoker[Unit, R] with UnitInvokerMixin[R] with MutatingUnitInvoker[R] {

val ResultSetMapping(_,
CompiledStatement(_, sres: SQLBuilder.Result, _),
PositionedResultReader(_, read, _)) = tree
CompiledMapping(converter, _)) = tree

override protected val delegate = this
protected def getStatement = sres.sql
protected def setParam(param: Unit, st: PreparedStatement): Unit = sres.setter(new PositionedParameters(st), null)
protected def extractValue(rs: PositionedResult): R = read(rs).asInstanceOf[R]
protected def updateRowValues(rs: PositionedResult, value: R) = linearizer.narrowedLinearizer.asInstanceOf[RecordLinearizer[R]].updateResult(driver, rs, value)
protected def extractValue(pr: PositionedResult): R = converter.read(pr).asInstanceOf[R]
protected def updateRowValues(pr: PositionedResult, value: R) = converter.update(value, pr)
def invoker: this.type = this
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/scala/slick/driver/JdbcProfile.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package scala.slick.driver

import scala.language.implicitConversions
import scala.slick.ast.{CompiledStatement, ClientSideOp, Node, TypedType, BaseTypedType}
import scala.slick.ast.{Node, TypedType, BaseTypedType}
import scala.slick.compiler.{CompilerState, CodeGen, QueryCompiler}
import scala.slick.lifted._
import scala.slick.jdbc.{JdbcCodeGen, JdbcBackend, JdbcType, MappedJdbcType}
Expand Down Expand Up @@ -43,7 +43,7 @@ trait JdbcProfile extends SqlProfile with JdbcTableComponent
implicit def tableToQuery[T <: AbstractTable[_]](t: T) = Query[T, NothingContainer#TableNothing, T](t)(Shape.tableShape)
implicit def columnToOrdered[T](c: Column[T]): ColumnOrdered[T] = c.asc
implicit def ddlToDDLInvoker(d: DDL): DDLInvoker = new DDLInvoker(d)
implicit def queryToQueryInvoker[T, U](q: Query[T, _ <: U]): QueryInvoker[U] = new QueryInvoker(newSelectStatementCompiler.run(Node(q)).tree, q)
implicit def queryToQueryInvoker[T, U](q: Query[T, _ <: U]): QueryInvoker[U] = new QueryInvoker(newSelectStatementCompiler.run(Node(q)).tree)
implicit def queryToDeleteInvoker(q: Query[_ <: Table[_], _]): DeleteInvoker = new DeleteInvoker(deleteStatementCompiler.run(Node(q)).tree)
implicit def columnBaseToInsertInvoker[T](c: ColumnBase[T]) = createCountingInsertInvoker(ShapedValue.createShapedValue(c))
implicit def shapedValueToInsertInvoker[T, U](u: ShapedValue[T, U]) = createCountingInsertInvoker(u)
Expand Down
85 changes: 55 additions & 30 deletions src/main/scala/scala/slick/jdbc/JdbcCodeGen.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package scala.slick.jdbc

import scala.slick.compiler.{CompilerState, CodeGen}
import scala.slick.ast.{GetOrElse, OptionApply, TypeMapping, UnaryNode, ProductNode, Path, TypedNode, Type, CompiledStatement, ClientSideOp, Node}
import scala.slick.ast.{NullaryNode, GetOrElse, OptionApply, TypeMapping, UnaryNode, ProductNode, Path, TypedNode, Type, CompiledStatement, ClientSideOp, Node}
import scala.slick.driver.JdbcDriver
import scala.slick.util.{TupleSupport, SQLBuilder}
import scala.slick.SlickException
Expand All @@ -15,46 +15,31 @@ class JdbcCodeGen[Driver <: JdbcDriver](val driver: Driver)(f: Driver#QueryBuild
ClientSideOp.mapResultSetMapping(node, keepType = true) { rsm =>
val sbr = f(driver.createQueryBuilder(rsm.from, state))
val nfrom = CompiledStatement(sbr.sql, sbr, rsm.from.nodeType)
val nmap = compileMapping(rsm.map)
val nmap = CompiledMapping(compileMapping(rsm.map), rsm.map.nodeType)
rsm.copy(from = nfrom, map = nmap).nodeTyped(rsm.nodeType)
}

/* TODO: JdbcTypeReader/Writer isn't the right interface -- it assumes that
* all columns will be read in order. We should not limit it in this way. */
def compileMapping(n: Node): PositionedResultReader = n match {
case p @ Path(_) =>
// TODO: Extract the correct index instead of assuming sequential access
val ti = driver.typeInfoFor(n.nodeType)
PositionedResultReader(n, { pr =>
ti.nextValueOrElse(
if(ti.nullable) ti.zero else throw new SlickException("Read NULL value for ResultSet column "+p),
pr)
}, n.nodeType)
def compileMapping(n: Node): ResultConverter = n match {
case Path(_) =>
new ColumnResultConverter(driver.typeInfoFor(n.nodeType), n)
case OptionApply(Path(_)) =>
// TODO: Extract the correct index instead of assuming sequential access
PositionedResultReader(n, driver.typeInfoFor(n.nodeType).nextValue _, n.nodeType)
new OptionApplyColumnResultConverter(driver.typeInfoFor(n.nodeType))
case ProductNode(ch) =>
val readers: IndexedSeq[PositionedResult => Any] =
ch.map(n => compileMapping(n).read)(collection.breakOut)
PositionedResultReader(n, { pr =>
TupleSupport.buildTuple(readers.map(_.apply(pr)))
}, n.nodeType)
new ProductResultConverter(ch.map(n => compileMapping(n))(collection.breakOut))
case GetOrElse(ch, default) =>
val chreader = compileMapping(ch).read
PositionedResultReader(n, { pr => chreader(pr).asInstanceOf[Option[Any]].getOrElse(default()) }, n.nodeType)
case TypeMapping(ch, _, _, toMapped) =>
val chreader = compileMapping(ch).read
PositionedResultReader(n, { pr => toMapped(chreader(pr)) }, n.nodeType)
new GetOrElseResultConverter(compileMapping(ch), default)
case TypeMapping(ch, _, toBase, toMapped) =>
new TypeMappingResultConverter(compileMapping(ch), toBase, toMapped)
case n =>
throw new SlickException("Unexpected node in ResultSetMapping: "+n)
}
}

/** A node that wraps a function for reading a row from a PositionedResult */
final case class PositionedResultReader(child: Node, read: PositionedResult => Any, tpe: Type) extends UnaryNode with TypedNode {
type Self = PositionedResultReader
def nodeRebuild(ch: Node) = copy(child = ch)
override def toString = "PositionedResultReader"
/** A node that wraps a ResultConverter */
final case class CompiledMapping(converter: ResultConverter, tpe: Type) extends NullaryNode with TypedNode {
type Self = CompiledMapping
def nodeRebuild = copy()
override def toString = "CompiledMapping"
}

/** A node that wraps the execution of an SQL statement */
Expand All @@ -63,3 +48,43 @@ final case class ExecuteStatement(child: Node, call: Any => PositionedResult, tp
def nodeRebuild(ch: Node) = copy(child = ch)
override def toString = "PositionedResultReader"
}

trait ResultConverter {
/* TODO: PositionedResult isn't the right interface -- it assumes that
* all columns will be read and updated in order. We should not limit it in
* this way. */
def read(pr: PositionedResult): Any
def update(value: Any, pr: PositionedResult): Unit
}

final class ColumnResultConverter(ti: JdbcType[Any], path: Node) extends ResultConverter {
def read(pr: PositionedResult) = ti.nextValueOrElse(
if(ti.nullable) ti.zero
else throw new SlickException("Read NULL value for ResultSet column "+path),
pr
)
def update(value: Any, pr: PositionedResult) = ti.updateValue(value, pr)
}

final class OptionApplyColumnResultConverter(ti: JdbcType[Any]) extends ResultConverter {
def read(pr: PositionedResult) = ti.nextValue(pr)
def update(value: Any, pr: PositionedResult) = ti.updateValue(value, pr)
}

final class ProductResultConverter(children: IndexedSeq[ResultConverter]) extends ResultConverter {
def read(pr: PositionedResult) = TupleSupport.buildTuple(children.map(_.read(pr)))
def update(value: Any, pr: PositionedResult) =
children.iterator.zip(value.asInstanceOf[Product].productIterator).foreach { case (ch, v) =>
ch.update(v, pr)
}
}

final class GetOrElseResultConverter(child: ResultConverter, default: () => Any) extends ResultConverter {
def read(pr: PositionedResult) = child.read(pr).asInstanceOf[Option[Any]].getOrElse(default())
def update(value: Any, pr: PositionedResult) = child.update(Some(value), pr)
}

final class TypeMappingResultConverter(child: ResultConverter, toBase: Any => Any, toMapped: Any => Any) extends ResultConverter {
def read(pr: PositionedResult) = toMapped(child.read(pr))
def update(value: Any, pr: PositionedResult) = child.update(toBase(value), pr)
}

0 comments on commit 12c746e

Please sign in to comment.