Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
View
8 src/main/scala/scala/slick/driver/AccessDriver.scala
@@ -34,6 +34,8 @@ import java.sql.{Blob, Clob, Date, Time, Timestamp, SQLException}
* <li>Trying to use <code>java.sql.Blob</code> objects causes a NPE in the
* JdbcOdbcDriver. Binary data in the form of <code>Array[Byte]</code> is
* supported.</li>
+ * <li>Returning columns from an INSERT operation is not supported. Trying
+ * to execute such an insert statement throws a SlickException.</li>
* </ul>
*
* @author szeiger
@@ -48,6 +50,7 @@ trait AccessDriver extends ExtendedDriver { driver =>
override val typeMapperDelegates = new TypeMapperDelegates(retryCount)
override def createQueryBuilder(input: QueryBuilderInput): QueryBuilder = new QueryBuilder(input)
+ override def createInsertBuilder(node: Node): InsertBuilder = new InsertBuilder(node)
override def createTableDDLBuilder(table: Table[_]): TableDDLBuilder = new TableDDLBuilder(table)
override def createColumnDDLBuilder(column: FieldSymbol, table: Table[_]): ColumnDDLBuilder = new ColumnDDLBuilder(column)
@@ -135,6 +138,11 @@ trait AccessDriver extends ExtendedDriver { driver =>
override protected def buildFetchOffsetClause(fetch: Option[Long], offset: Option[Long]) = ()
}
+ class InsertBuilder(node: Node) extends super.InsertBuilder(node) {
+ override def buildReturnColumns(node: Node, table: String): IndexedSeq[FieldSymbol] =
+ throw new SlickException("Returning columns from INSERT statements is not supported by Access")
+ }
+
class TableDDLBuilder(table: Table[_]) extends super.TableDDLBuilder(table) {
override protected def addForeignKey(fk: ForeignKey[_ <: TableNode, _], sb: StringBuilder) {
sb append "CONSTRAINT " append quoteIdentifier(fk.name) append " FOREIGN KEY("
View
187 src/main/scala/scala/slick/driver/BasicInvokerComponent.scala
@@ -2,10 +2,11 @@ package scala.slick.driver
import java.sql.{Statement, PreparedStatement}
import scala.slick.SlickException
+import scala.slick.ast.Node
import scala.slick.lifted.{Query, Shape, ShapedValue}
import scala.slick.session.{Session, PositionedParameters, PositionedResult}
import scala.slick.util.RecordLinearizer
-import scala.slick.jdbc.{UnitInvokerMixin, MutatingStatementInvoker, MutatingUnitInvoker}
+import scala.slick.jdbc.{UnitInvoker, UnitInvokerMixin, MutatingStatementInvoker, MutatingUnitInvoker, ResultSetInvoker}
trait BasicInvokerComponent { driver: BasicDriver =>
@@ -22,7 +23,7 @@ trait BasicInvokerComponent { driver: BasicDriver =>
def invoker: this.type = this
}
- /** Pseudo-invoker for runing DELETE calls. */
+ /** Pseudo-invoker for running DELETE calls. */
class DeleteInvoker(query: Query[_, _]) {
protected lazy val built = buildDeleteStatement(query)
@@ -36,68 +37,176 @@ trait BasicInvokerComponent { driver: BasicDriver =>
def deleteInvoker: this.type = this
}
- /** Pseudo-invoker for runing INSERT calls. */
- class InsertInvoker[U](unpackable: ShapedValue[_, U]) {
- lazy val insertStatement = buildInsertStatement(unpackable.value)
- def insertStatementFor[TT](query: Query[TT, U]): String = buildInsertStatement(unpackable.value, query).sql
+ /** Pseudo-invoker for running INSERT calls. */
+ abstract class InsertInvoker[U](unpackable: ShapedValue[_, U]) {
+ protected lazy val builder = createInsertBuilder(Node(unpackable.value))
+
+ type RetOne
+ type RetMany
+
+ protected def retOne(st: Statement, value: U, updateCount: Int): RetOne
+ protected def retMany(values: Seq[U], individual: Seq[RetOne]): RetMany
+ protected def retManyBatch(st: Statement, values: Seq[U], updateCounts: Array[Int]): RetMany
+
+ protected lazy val insertResult = builder.buildInsert
+ lazy val insertStatement = insertResult.sql
+ def insertStatementFor[TT](query: Query[TT, U]): String = builder.buildInsert(query).sql
def insertStatementFor[TT](c: TT)(implicit shape: Shape[TT, U, _]): String = insertStatementFor(Query(c)(shape))
def useBatchUpdates(implicit session: Session) = session.capabilities.supportsBatchUpdates
- /**
- * Insert a single row.
- */
- def insert(value: U)(implicit session: Session): Int = session.withPreparedStatement(insertStatement) { st =>
+ protected def prepared[T](sql: String)(f: PreparedStatement => T)(implicit session: Session) =
+ session.withPreparedStatement(sql)(f)
+
+ /** Insert a single row. */
+ def insert(value: U)(implicit session: Session): RetOne = prepared(insertStatement) { st =>
st.clearParameters()
unpackable.linearizer.narrowedLinearizer.asInstanceOf[RecordLinearizer[U]].setParameter(driver, new PositionedParameters(st), Some(value))
- st.executeUpdate()
+ val count = st.executeUpdate()
+ retOne(st, value, count)
}
- def insertExpr[TT](c: TT)(implicit shape: Shape[TT, U, _], session: Session): Int =
- insert(Query(c)(shape))(session)
-
- /**
- * Insert multiple rows. Uses JDBC's batch update feature if supported by
- * the JDBC driver. Returns Some(rowsAffected), or None if the database
- * returned no row count for some part of the batch. If any part of the
- * batch fails, an exception thrown.
- */
- def insertAll(values: U*)(implicit session: Session): Option[Int] = {
- if(!useBatchUpdates || (values.isInstanceOf[IndexedSeq[_]] && values.length < 2))
- Some( (0 /: values) { _ + insert(_) } )
- else session.withTransaction {
- session.withPreparedStatement(insertStatement) { st =>
+ /** Insert multiple rows. Uses JDBC's batch update feature if supported by
+ * the JDBC driver. Returns Some(rowsAffected), or None if the database
+ * returned no row count for some part of the batch. If any part of the
+ * batch fails, an exception is thrown. */
+ def insertAll(values: U*)(implicit session: Session): RetMany = session.withTransaction {
+ if(!useBatchUpdates || (values.isInstanceOf[IndexedSeq[_]] && values.length < 2)) {
+ retMany(values, values.map(insert))
+ } else {
+ prepared(insertStatement) { st =>
st.clearParameters()
for(value <- values) {
unpackable.linearizer.narrowedLinearizer.asInstanceOf[RecordLinearizer[U]].setParameter(driver, new PositionedParameters(st), Some(value))
st.addBatch()
}
var unknown = false
- var count = 0
- for((res, idx) <- st.executeBatch().zipWithIndex) res match {
- case Statement.SUCCESS_NO_INFO => unknown = true
- case Statement.EXECUTE_FAILED =>
- throw new SlickException("Failed to insert row #" + (idx+1))
- case i => count += i
- }
- if(unknown) None else Some(count)
+ val counts = st.executeBatch()
+ retManyBatch(st, values, counts)
}
}
}
- def insert[TT](query: Query[TT, U])(implicit session: Session): Int = {
- val sbr = buildInsertStatement(unpackable.value, query)
- session.withPreparedStatement(insertStatementFor(query)) { st =>
+ def insertInvoker: this.type = this
+ }
+
+ /** An InsertInvoker that can also insert from another query. */
+ trait FullInsertInvoker[U] { this: InsertInvoker[U] =>
+ type RetQuery
+
+ protected def retQuery(st: Statement, updateCount: Int): RetQuery
+
+ def insertExpr[TT](c: TT)(implicit shape: Shape[TT, U, _], session: Session): RetQuery =
+ insert(Query(c)(shape))(session)
+
+ def insert[TT](query: Query[TT, U])(implicit session: Session): RetQuery = {
+ val sbr = builder.buildInsert(query)
+ prepared(insertStatementFor(query)) { st =>
st.clearParameters()
sbr.setter(new PositionedParameters(st), null)
- st.executeUpdate()
+ val count = st.executeUpdate()
+ retQuery(st, count)
}
}
+ }
- def insertInvoker: this.type = this
+ /** Pseudo-invoker for running INSERT calls and returning affected row counts. */
+ class CountingInsertInvoker[U](unpackable: ShapedValue[_, U])
+ extends InsertInvoker[U](unpackable) with FullInsertInvoker[U] {
+
+ type RetOne = Int
+ type RetMany = Option[Int]
+ type RetQuery = Int
+
+ protected def retOne(st: Statement, value: U, updateCount: Int) = updateCount
+
+ protected def retMany(values: Seq[U], individual: Seq[RetOne]) = Some(individual.sum)
+
+ protected def retManyBatch(st: Statement, values: Seq[U], updateCounts: Array[Int]) = {
+ var unknown = false
+ var count = 0
+ for((res, idx) <- updateCounts.zipWithIndex) res match {
+ case Statement.SUCCESS_NO_INFO => unknown = true
+ case Statement.EXECUTE_FAILED =>
+ throw new SlickException("Failed to insert row #" + (idx+1))
+ case i => count += i
+ }
+ if(unknown) None else Some(count)
+ }
+
+ protected def retQuery(st: Statement, updateCount: Int) = updateCount
+
+ def returning[RT, RU](value: RT)(implicit shape: Shape[RT, RU, _]) =
+ new KeysInsertInvoker[U, RU](unpackable, new ShapedValue[RT, RU](value, shape))
+ }
+
+ /** Base class with common functionality for KeysInsertInvoker and MappedKeysInsertInvoker. */
+ abstract class AbstractKeysInsertInvoker[U, RU](unpackable: ShapedValue[_, U], keys: ShapedValue[_, RU])
+ extends InsertInvoker[U](unpackable) {
+
+ protected def buildKeysResult(st: Statement): UnitInvoker[RU] = {
+ val lin = keys.linearizer.asInstanceOf[RecordLinearizer[RU]]
+ ResultSetInvoker[RU](_ => st.getGeneratedKeys)(pr => lin.getResult(profile, pr))
+ }
+
+ // Returning keys from batch inserts is generally not supported
+ override def useBatchUpdates(implicit session: Session) = false
+
+ protected lazy val keyColumns =
+ builder.buildReturnColumns(keys.packedNode, insertResult.table).map(_.name).toArray
+
+ override protected def prepared[T](sql: String)(f: PreparedStatement => T)(implicit session: Session) =
+ session.withPreparedInsertStatement(sql, keyColumns)(f)
+ }
+
+ /** Pseudo-invoker for running INSERT calls and returning generated keys. */
+ class KeysInsertInvoker[U, RU](unpackable: ShapedValue[_, U], keys: ShapedValue[_, RU])
+ extends AbstractKeysInsertInvoker[U, RU](unpackable, keys) with FullInsertInvoker[U] {
+
+ type RetOne = RU
+ type RetMany = Seq[RU]
+ type RetQuery = RetMany
+
+ protected def retOne(st: Statement, value: U, updateCount: Int) =
+ buildKeysResult(st).first()(null)
+
+ protected def retMany(values: Seq[U], individual: Seq[RetOne]) = individual
+
+ protected def retManyBatch(st: Statement, values: Seq[U], updateCounts: Array[Int]) = {
+ implicit val session: Session = null
+ buildKeysResult(st).to[Vector]
+ }
+
+ protected def retQuery(st: Statement, updateCount: Int) = {
+ implicit val session: Session = null
+ buildKeysResult(st).to[Vector]
+ }
+
+ def into[R](f: (U, RU) => R) = new MappedKeysInsertInvoker[U, RU, R](unpackable, keys, f)
+ }
+
+ /** Pseudo-invoker for running INSERT calls and returning generated keys combined with the values. */
+ class MappedKeysInsertInvoker[U, RU, R](unpackable: ShapedValue[_, U], keys: ShapedValue[_, RU],
+ tr: (U, RU) => R) extends AbstractKeysInsertInvoker[U, RU](unpackable, keys) {
+
+ type RetOne = R
+ type RetMany = Seq[R]
+
+ protected def retOne(st: Statement, value: U, updateCount: Int) = {
+ val ru = buildKeysResult(st).first()(null)
+ tr(value, ru)
+ }
+
+ protected def retMany(values: Seq[U], individual: Seq[RetOne]) = individual
+
+ protected def retManyBatch(st: Statement, values: Seq[U], updateCounts: Array[Int]) = {
+ implicit val session: Session = null
+ val ru = buildKeysResult(st).to[Vector]
+ (values, ru).zipped.map(tr)
+ }
}
- /** Pseudo-invoker for runing UPDATE calls. */
+ /** Pseudo-invoker for running UPDATE calls. */
class UpdateInvoker[T] (query: Query[_, T]) {
protected lazy val built = buildUpdateStatement(query)
View
11 src/main/scala/scala/slick/driver/BasicProfile.scala
@@ -10,6 +10,7 @@ trait BasicProfile extends BasicTableComponent { driver: BasicDriver =>
// Create the different builders -- these methods should be overridden by drivers as needed
def createQueryTemplate[P,R](q: Query[_, R]): BasicQueryTemplate[P,R] = new BasicQueryTemplate[P,R](q, this)
def createQueryBuilder(input: QueryBuilderInput): QueryBuilder = new QueryBuilder(input)
+ def createInsertBuilder(node: Node): InsertBuilder = new InsertBuilder(node)
def createTableDDLBuilder(table: Table[_]): TableDDLBuilder = new TableDDLBuilder(table)
def createColumnDDLBuilder(column: FieldSymbol, table: Table[_]): ColumnDDLBuilder = new ColumnDDLBuilder(column)
def createSequenceDDLBuilder(seq: Sequence[_]): SequenceDDLBuilder = new SequenceDDLBuilder(seq)
@@ -22,8 +23,10 @@ trait BasicProfile extends BasicTableComponent { driver: BasicDriver =>
final def buildSelectStatement(q: Query[_, _]): QueryBuilderResult = createQueryBuilder(q).buildSelect
final def buildUpdateStatement(q: Query[_, _]): QueryBuilderResult = createQueryBuilder(q).buildUpdate
final def buildDeleteStatement(q: Query[_, _]): QueryBuilderResult = createQueryBuilder(q).buildDelete
- final def buildInsertStatement(cb: Any): String = new InsertBuilder(cb).buildInsert
- final def buildInsertStatement(cb: Any, q: Query[_, _]): QueryBuilderResult = new InsertBuilder(cb).buildInsert(q)
+ @deprecated("Use createInsertBuilder.buildInsert", "1.0")
+ final def buildInsertStatement(cb: Any): InsertBuilderResult = createInsertBuilder(Node(cb)).buildInsert
+ @deprecated("Use createInsertBuilder.buildInsert", "1.0")
+ final def buildInsertStatement(cb: Any, q: Query[_, _]): InsertBuilderResult = createInsertBuilder(Node(cb)).buildInsert(q)
final def buildTableDDL(table: Table[_]): DDL = createTableDDLBuilder(table).buildDDL
final def buildSequenceDDL(seq: Sequence[_]): DDL = createSequenceDDLBuilder(seq).buildDDL
@@ -35,8 +38,8 @@ trait BasicProfile extends BasicTableComponent { driver: BasicDriver =>
implicit def columnToOrdered[T](c: Column[T]): ColumnOrdered[T] = c.asc
implicit def queryToQueryInvoker[T, U](q: Query[T, _ <: U]): QueryInvoker[T, U] = new QueryInvoker(q)
implicit def queryToDeleteInvoker(q: Query[_ <: Table[_], _]): DeleteInvoker = new DeleteInvoker(q)
- implicit def columnBaseToInsertInvoker[T](c: ColumnBase[T]) = new InsertInvoker(ShapedValue.createShapedValue(c))
- implicit def shapedValueToInsertInvoker[T, U](u: ShapedValue[T, U]) = new InsertInvoker(u)
+ implicit def columnBaseToInsertInvoker[T](c: ColumnBase[T]) = new CountingInsertInvoker(ShapedValue.createShapedValue(c))
+ implicit def shapedValueToInsertInvoker[T, U](u: ShapedValue[T, U]) = new CountingInsertInvoker(u)
implicit def queryToQueryExecutor[E, U](q: Query[E, U]): QueryExecutor[Seq[U]] = new QueryExecutor[Seq[U]](new QueryBuilderInput(compiler.run(Node(q)), q))
View
74 src/main/scala/scala/slick/driver/BasicStatementBuilderComponent.scala
@@ -5,11 +5,14 @@ import scala.slick.SlickException
import scala.slick.ast._
import scala.slick.util._
import scala.slick.lifted.{Join => _, _}
-import scala.collection.mutable.HashMap
+import scala.collection.mutable.{ArrayBuffer, HashMap}
import scala.slick.compiler.CompilationState
trait BasicStatementBuilderComponent { driver: BasicDriver =>
+ // Immutable config options (to be overridden by subclasses)
+ val supportsArbitraryInsertReturnColumns: Boolean = true
+
abstract class StatementPart
case object SelectPart extends StatementPart
case object FromPart extends StatementPart
@@ -367,57 +370,56 @@ trait BasicStatementBuilderComponent { driver: BasicDriver =>
}
QueryBuilderResult(b.build, input.linearizer)
}
-
- /*TODO
- final protected def appendGroupClause(): Unit = query.typedModifiers[Grouping] match {
- case Nil =>
- case xs => b += " group by "; b.sep(xs, ",")(x => expr(x.by))
- }
- */
}
/** Builder for INSERT statements. */
- class InsertBuilder(val column: Any) {
+ class InsertBuilder(val node: Node) {
- def buildInsert: String = {
- val (table, cols, vals) = buildParts
- "INSERT INTO " + quoteIdentifier(table) + " (" + cols + ") VALUES (" + vals + ")"
+ class PartsResult(val table: String, val fields: IndexedSeq[FieldSymbol]) {
+ def qtable = quoteIdentifier(table)
+ def qcolumns = fields.map(s => quoteIdentifier(s.name)).mkString(",")
+ def qvalues = IndexedSeq.fill(fields.size)("?").mkString(",")
}
- def buildInsert(query: Query[_, _]): QueryBuilderResult = {
- val (table, cols, _) = buildParts
+ def buildInsert: InsertBuilderResult = {
+ val pr = buildParts(node)
+ InsertBuilderResult(pr.table,
+ "INSERT INTO " + pr.qtable + " (" + pr.qcolumns + ") VALUES (" + pr.qvalues + ")")
+ }
+
+ def buildInsert(query: Query[_, _]): InsertBuilderResult = {
+ val pr = buildParts(node)
val qb = driver.createQueryBuilder(query)
- qb.sqlBuilder += "INSERT INTO " += quoteIdentifier(table) += " (" += cols.toString += ") "
- qb.buildSelect()
+ qb.sqlBuilder += "INSERT INTO " += pr.qtable += " (" += pr.qcolumns += ") "
+ val qbr = qb.buildSelect()
+ InsertBuilderResult(pr.table, qbr.sql, qbr.setter)
}
- protected def buildParts: (String, StringBuilder, StringBuilder) = {
- val cols = new StringBuilder
- val vals = new StringBuilder
- var table:String = null
+ def buildReturnColumns(node: Node, table: String): IndexedSeq[FieldSymbol] = {
+ val r = buildParts(node)
+ if(r.table != table)
+ throw new SlickException("Returned key columns must be from same table as inserted columns ("+
+ r.table+" != "+table+")")
+ if(!supportsArbitraryInsertReturnColumns && (r.fields.size > 1 || !r.fields.head.options.contains(ColumnOption.AutoInc)))
+ throw new SlickException("This DBMS allows only a single AutoInc column to be returned from an INSERT")
+ r.fields
+ }
+
+ protected def buildParts(node: Node): PartsResult = {
+ val cols = new ArrayBuffer[FieldSymbol]
+ var table: String = null
def f(c: Any): Unit = c match {
- case p:Projection[_] =>
- for(i <- 0 until p.productArity)
- f(Node(p.productElement(i)))
+ case ProductNode(ch) => ch.foreach(f)
case t:TableNode => f(Node(t.nodeShaped_*.value))
case Select(Ref(IntrinsicSymbol(t: TableNode)), field: FieldSymbol) =>
if(table eq null) table = t.tableName
else if(table != t.tableName) throw new SlickException("Inserts must all be to the same table")
- appendNamedColumn(field, cols, vals)
+ cols += field
case _ => throw new SlickException("Cannot use column "+c+" in INSERT statement")
}
- f(Node(column))
+ f(node)
if(table eq null) throw new SlickException("No table to insert into")
- (table, cols, vals)
- }
-
- protected def appendNamedColumn(n: FieldSymbol, cols: StringBuilder, vals: StringBuilder) {
- if(!cols.isEmpty) {
- cols append ","
- vals append ","
- }
- cols append quoteIdentifier(n.name)
- vals append '?'
+ new PartsResult(table, cols)
}
}
@@ -588,3 +590,5 @@ case class QueryBuilderResult(sbr: SQLBuilder.Result, linearizer: ValueLinearize
def sql = sbr.sql
def setter = sbr.setter
}
+
+case class InsertBuilderResult(table: String, sql: String, setter: SQLBuilder.Setter = SQLBuilder.EmptySetter)
View
3  src/main/scala/scala/slick/driver/DerbyDriver.scala
@@ -26,11 +26,14 @@ import scala.slick.ast._
* support them with the standard SQL:2003 syntax (see
* <a href="http://wiki.apache.org/db-derby/OLAPRowNumber"
* >http://wiki.apache.org/db-derby/OLAPRowNumber</a>).</li>
+ * <li>When returning columns from an INSERT operation, only a single column
+ * may be specified which must be the table's AutoInc column.</li>
* </ul>
*
* @author szeiger
*/
trait DerbyDriver extends ExtendedDriver { driver =>
+ override val supportsArbitraryInsertReturnColumns = false
override val typeMapperDelegates = new TypeMapperDelegates
override def createQueryBuilder(input: QueryBuilderInput): QueryBuilder = new QueryBuilder(input)
View
9 src/main/scala/scala/slick/driver/H2Driver.scala
@@ -4,15 +4,22 @@ import scala.slick.lifted._
import scala.slick.ast._
import scala.slick.ast.Util._
import scala.slick.ast.ExtraUtil._
+import scala.slick.SlickException
/**
* SLICK driver for H2.
*
- * All ExtendedProfile features are supported.
+ * <p>This driver implements the ExtendedProfile with the following
+ * limitations:</p>
+ * <ul>
+ * <li>When returning columns from an INSERT operation, only a single column
+ * may be specified which must be the table's AutoInc column.</li>
+ * </ul>
*
* @author szeiger
*/
trait H2Driver extends ExtendedDriver { driver =>
+ override val supportsArbitraryInsertReturnColumns = false
override def createQueryBuilder(input: QueryBuilderInput): QueryBuilder = new QueryBuilder(input)
View
3  src/main/scala/scala/slick/driver/MySQLDriver.scala
@@ -14,11 +14,14 @@ import scala.slick.ast.ExtraUtil._
* <ul>
* <li><code>Sequence.curr</code> to get the current value of a sequence is
* not supported. Other sequence features are emulated.</li>
+ * <li>When returning columns from an INSERT operation, only a single column
+ * may be specified which must be the table's AutoInc column.</li>
* </ul>
*
* @author szeiger
*/
trait MySQLDriver extends ExtendedDriver { driver =>
+ override val supportsArbitraryInsertReturnColumns = false
override val typeMapperDelegates = new TypeMapperDelegates
View
3  src/main/scala/scala/slick/driver/SQLServerDriver.scala
@@ -15,11 +15,14 @@ import scala.slick.session.PositionedResult
* <ul>
* <li>Sequences are not supported because SQLServer does not have this
* feature.</li>
+ * <li>When returning columns from an INSERT operation, only a single column
+ * may be specified which must be the table's AutoInc column.</li>
* </ul>
*
* @author szeiger
*/
trait SQLServerDriver extends ExtendedDriver { driver =>
+ override val supportsArbitraryInsertReturnColumns = false
override val typeMapperDelegates = new TypeMapperDelegates
override def createQueryBuilder(input: QueryBuilderInput): QueryBuilder = new QueryBuilder(input)
View
3  src/main/scala/scala/slick/driver/SQLiteDriver.scala
@@ -22,9 +22,12 @@ import java.sql.{Timestamp, Time, Date}
* <li>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>When returning columns from an INSERT operation, only a single column
+ * may be specified which must be the table's AutoInc column.</li>
* </ul>
*/
trait SQLiteDriver extends ExtendedDriver { driver =>
+ override val supportsArbitraryInsertReturnColumns = false
override val typeMapperDelegates = new TypeMapperDelegates
override def createQueryBuilder(input: QueryBuilderInput): QueryBuilder = new QueryBuilder(input)
View
12 src/main/scala/scala/slick/session/Session.scala
@@ -33,6 +33,12 @@ trait Session extends java.io.Closeable with Logging { self =>
}
}
+ final def prepareInsertStatement(sql: String,
+ columnNames: Array[String] = new Array[String](0)): PreparedStatement = {
+ logger.debug("Preparing insert statement: "+sql+", returning: "+columnNames.mkString(","))
+ conn.prepareStatement(sql, columnNames)
+ }
+
final def createStatement(defaultType: ResultSetType = ResultSetType.ForwardOnly,
defaultConcurrency: ResultSetConcurrency = ResultSetConcurrency.ReadOnly,
defaultHoldability: ResultSetHoldability = ResultSetHoldability.Default): Statement = {
@@ -55,6 +61,12 @@ trait Session extends java.io.Closeable with Logging { self =>
try f(st) finally st.close()
}
+ final def withPreparedInsertStatement[T](sql: String,
+ columnNames: Array[String] = new Array[String](0))(f: (PreparedStatement => T)): T = {
+ val st = prepareInsertStatement(sql, columnNames)
+ try f(st) finally st.close()
+ }
+
final def withStatement[T](defaultType: ResultSetType = ResultSetType.ForwardOnly,
defaultConcurrency: ResultSetConcurrency = ResultSetConcurrency.ReadOnly,
defaultHoldability: ResultSetHoldability = ResultSetHoldability.Default)(f: (Statement => T)): T = {
View
2  src/main/scala/scala/slick/util/SQLBuilder.scala
@@ -63,6 +63,8 @@ final class SQLBuilder extends SQLBuilder.Segment { self =>
object SQLBuilder {
final type Setter = ((PositionedParameters, Any) => Unit)
+ val EmptySetter: Setter = (_: PositionedParameters, _: Any) => ()
+
final case class Result(sql: String, setter: Setter)
private class CombinedSetter(b: Seq[Setter]) extends Setter {
View
53 src/test/scala/scala/slick/test/lifted/InsertTest.scala
@@ -6,6 +6,7 @@ import scala.slick.lifted._
import scala.slick.session.Database.threadLocalSession
import scala.slick.testutil._
import scala.slick.testutil.TestDB._
+import scala.slick.driver.{AccessDriver, H2Driver}
object InsertTest extends DBTestObject(H2Mem, SQLiteMem, Postgres, MySQL, DerbyMem, HsqldbMem, MSAccess, SQLServer)
@@ -13,17 +14,17 @@ class InsertTest(val tdb: TestDB) extends DBTest {
import tdb.profile.Table
import tdb.profile.Implicit._
- class TestTable(name: String) extends Table[(Int, String)](name) {
- def id = column[Int]("id")
- def name = column[String]("name")
- def * = id ~ name
- }
+ @Test def testSimple(): Unit = db withSession {
- object Src1 extends TestTable("src1")
- object Dst1 extends TestTable("dst1")
- object Dst2 extends TestTable("dst2")
+ class TestTable(name: String) extends Table[(Int, String)](name) {
+ def id = column[Int]("id")
+ def name = column[String]("name")
+ def * = id ~ name
+ }
- @Test def testSimple(): Unit = db withSession {
+ object Src1 extends TestTable("src1")
+ object Dst1 extends TestTable("dst1")
+ object Dst2 extends TestTable("dst2")
(Src1.ddl ++ Dst1.ddl ++ Dst2.ddl).create
@@ -48,4 +49,38 @@ class InsertTest(val tdb: TestDB) extends DBTest {
Dst2.shaped.insertExpr(q4)
assertEquals(Set((1,"A"), (2,"B"), (42,"X"), (43,"Y")), Query(Dst2).list.toSet)
}
+
+ @Test def testReturning(): Unit = db withSession {
+
+ object A extends Table[(Int, String, String)]("a") {
+ def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
+ def s1 = column[String]("s1")
+ def s2 = column[String]("s2")
+ def * = id ~ s1 ~ s2
+ def ins1 = s1 ~ s2 returning id
+ def ins2 = (s1, s2).shaped returning (id, s1)
+ def ins3 = s1 ~ s2 returning id into ((v, i) => (i, v._1, v._2))
+ }
+
+ A.ddl.create
+
+ if(tdb.driver != AccessDriver) {
+ val id1: Int = A.ins1.insert("a", "b")
+ assertEquals(1, id1)
+
+ if(tdb.driver.supportsArbitraryInsertReturnColumns) {
+ val id2: (Int, String) = A.ins2.insert("c", "d")
+ assertEquals((2, "c"), id2)
+ } else {
+ val id2: Int = A.ins1.insert("c", "d")
+ assertEquals(2, id2)
+ }
+
+ val ids3 = A.ins1.insertAll(("e", "f"), ("g", "h"))
+ assertEquals(Seq(3, 4), ids3)
+
+ val id4 = A.ins3.insert("i", "j")
+ assertEquals((5, "i", "j"), id4)
+ }
+ }
}
View
2  src/test/scala/scala/slick/test/lifted/OldTest.scala
@@ -12,7 +12,7 @@ class OldTest(val tdb: TestDB) extends DBTest {
import tdb.profile.Table
import tdb.profile.Implicit._
- @deprecated("Testing deprecated methods Query#sub and Query.orderBy", "0.10.0-M2")
+ @deprecated("Testing deprecated methods Query#sub, Query.orderBy and BasicProfile.buildInsertStatement", "0.10.0-M2")
@Test def test() {
object Users extends Table[(Int, String, String)]("users") {
def id = column[Int]("id")

1 comment on commit 09a65a8

@ijuma

Nice. :)

Please sign in to comment.
Something went wrong with that request. Please try again.