Sujit Sujit - 3 months ago 127
Scala Question

How to fix Slick Exception of single AutoInc column to be returned from an INSERT

I tried to implement the akka-http rest example provided at
https://github.com/ArchDev/akka-http-rest
but I'm stuck with the

[ERROR] [08/28/2016 10:35:34.091] [default-akka.actor.default-dispatcher-8] [akka.actor.ActorSystemImpl(default)] Error during processing of request HttpRequest(HttpMethod(POST),http://127.0.0.1:9000/v1/auth/signIn,List(Host: 127.0.0.1:9000, Connection: keep-alive, Cache-Control: no-cache, Origin: chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop, User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36, Postman-Token: ec513598-b8bc-87ca-8eba-743b214fe1fa, Accept: */*, Accept-Encoding: gzip, deflate, Accept-Language: en-US, en;q=0.8, Timeout-Access: <function1>),HttpEntity.Strict(application/json, {
"login": "admin",
"password": "admin"
}),HttpProtocol(HTTP/1.1))
slick.SlickException: This DBMS allows only a single AutoInc column to be returned from an INSERT
at slick.driver.JdbcStatementBuilderComponent$JdbcCompiledInsert.buildReturnColumns(JdbcStatementBuilderComponent.scala:69)
at slick.driver.JdbcActionComponent$ReturningInsertActionComposerImpl.x$12$lzycompute(JdbcActionComponent.scala:633)
at slick.driver.JdbcActionComponent$ReturningInsertActionComposerImpl.x$12(JdbcActionComponent.scala:633)
at slick.driver.JdbcActionComponent$ReturningInsertActionComposerImpl.keyColumns$lzycompute(JdbcActionComponent.scala:633)
at slick.driver.JdbcActionComponent$ReturningInsertActionComposerImpl.keyColumns(JdbcActionComponent.scala:633)
at slick.driver.JdbcActionComponent$ReturningInsertActionComposerImpl.preparedInsert(JdbcActionComponent.scala:636)
at slick.driver.JdbcActionComponent$InsertActionComposerImpl$SingleInsertAction.run(JdbcActionComponent.scala:504)
at slick.driver.JdbcActionComponent$SimpleJdbcDriverAction.run(JdbcActionComponent.scala:32)
at slick.driver.JdbcActionComponent$SimpleJdbcDriverAction.run(JdbcActionComponent.scala:29)
at slick.backend.DatabaseComponent$DatabaseDef$$anon$2.liftedTree1$1(DatabaseComponent.scala:237)
at slick.backend.DatabaseComponent$DatabaseDef$$anon$2.run(DatabaseComponent.scala:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)


Here is the Scala Code:
Signup API:

path("signUp") {
pathEndOrSingleSlash {
post {
entity(as[UserEntity]) { userEntity =>
complete(Created -> signUp(userEntity).map(_.asJson))
}
}
}
}


UserEntityTable.scala

package oc.api.models.db

/**
* Created by sujit on 8/27/16.
*/
import oc.api.models.UserEntity
import oc.api.utils.DatabaseService
trait UserEntityTable {
protected val databaseService: DatabaseService
import databaseService.driver.api._

class Users(tag: Tag) extends Table[UserEntity](tag, "users") {
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def username = column[String]("username")
def password = column[String]("password")

def * = (id, username, password) <> ((UserEntity.apply _).tupled, UserEntity.unapply)
}

protected val users = TableQuery[Users]
}


UserEntity.scala

package oc.api.models

/**
* Created by sujit on 8/27/16.
*/
case class UserEntity(id: Option[Long] = None, username: String, password: String) {
require(!username.isEmpty, "username.empty")
require(!password.isEmpty, "password.empty")
}

case class UserEntityUpdate(username: Option[String] = None, password: Option[String] = None) {
def merge(user: UserEntity): UserEntity = {
UserEntity(user.id, username.getOrElse(user.username), password.getOrElse(user.password))
}
}


AuthService.scala

package oc.api.services

import oc.api.models.{TokenEntity, UserEntity}
import oc.api.models.db.TokenEntityTable
import oc.api.utils.DatabaseService

import scala.concurrent.{ExecutionContext, Future}

/**
* Created by sujit on 8/27/16.
*/
class AuthService(val databaseService: DatabaseService)(usersService: UsersService)(implicit executionContext: ExecutionContext) extends TokenEntityTable {

import databaseService._
import databaseService.driver.api._

def signUp(newUser: UserEntity): Future[TokenEntity] = {
usersService.createUser(newUser).flatMap(user => createToken(user))
}

def authenticate(token: String): Future[Option[UserEntity]] =
db.run((for {
token <- tokens.filter(_.token === token)
user <- users.filter(_.id === token.userId)
} yield user).result.headOption)

def createToken(user: UserEntity): Future[TokenEntity] = db.run(tokens returning tokens += TokenEntity(userId = user.id))

}


Since I'm new to Scala and Slick, can anyway provide the information why this exception is occurring even though I've defined
O.AutoInc
in Model

Answer

I assumed your TokenEntity looks something like this where id is auto incremented and token is automatically created:

case class UserEntity(id: Option[Long] = None, userId: Long, token: String)

Now you are asking your DBMS to return multiple columns (id, userId and token) in your insert action which is not supported. Change

def createToken(user: UserEntity): Future[TokenEntity] = db.run(tokens returning tokens += TokenEntity(userId = user.id))

to

def createToken(user: UserEntity): Future[TokenEntity] = db.run(
  tokens returning tokens.map(_.userId) into((token, id) => token.copy(id = Some(id)) += TokenEntity(userId = user.id))
)

Here you specify column id to be returned on insert. The following into method merges token and the generated key. See Inserting for a more detailed explanation.