Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slick - Update full object or more than 22 columns

I've a table user_permissions which has 46 permission columns along with id and created_date. This table has a corresponding UserPermissions class:

class UserPermission(val id: Long,
  val createdDate: Option[Timestamp],
  val permission1: Boolean,
  val permission2: Boolean,
  ...
  val permission46: Boolean)

and slick mapping table

class UserPermissions(tag: Tag) extends Table[UserPermission](tag, "users_permissions") {
  def * = (
    id ::
    createdDate ::
    permission1 ::
    permission2 ::
    ...
    permission46 ::
    HNil).shaped <> (
    { case x => UserPermission(
         x(0), x(1), x(2), ... x(47))
    },
    {
       UserPermission.unapply _
    }
  }
  ... <columns defined here>
)

Now I want to update UserPermission set which is identified by id. The function that I've is:

object UserPermissions {
  val userPermissions = TableQuery[UserPermissions]

  def update(userPermission: UserPermission)(implicit session: Session) = {
    userPermissions.filter(_.id === userPermission.id.get).update(userPermission)
  }
}

This is not working and throwing Exception:

play.api.Application$$anon$1: Execution exception[[SQLServerException: Cannot update identity column 'id'.]]

which makes sense as the SQL generated by Slick is:

update "users_permissions" set "id" = ?, "created_date" = ?, ...

Problem 1 So my first problem is that I'm unable to update a full UserPermission object with slick. If I've a solution to this problem then it would be great.


Since I'm unable to update full object then I thought to yield the columns I want to update then fire an update query. The code looks like this:

def update(obj: UserPermission)(implicit session: Session) = {
    val query = for {
      p <- userPermissions
      if p.id === obj.id.get
    } yield (p.permission1, p.permission2, ... p.permission46)
    query.update(obj.permission1, obj.permission2, ... obj.permission46)
}

Problem 2 Now slick is not updating 46 columns in query.update() function. It can handle only 22 columns at one time. How can I update my UserPermissions object?

One bad solution I can think is to update 22 first time, then 22 second, then 2 in third query. It'll be 3 db update queries which I don't want.

Any solutions to my problem?


Dependencies are:


scalaVersion := "2.11.4"

"com.typesafe.play" %% "play-slick" % "0.8.1"
"com.typesafe.slick" %% "slick-extensions" % "2.1.0"

like image 270
Sunil Kumar Avatar asked Jul 09 '15 14:07

Sunil Kumar


2 Answers

Stefan Zeiger, the Slick lead, said we couldn't. He suggested however that we have nested projections over the flat 22+ columns table:

// 2 classes for the nested structure
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)

// Note that it's a Table[Int] -- we only map the primary key in *
object T extends Table[Int]("t_wide") {
  def id = column[Int]("id", O.PrimaryKey)
  def p1i1 = column[Int]("p1i1")
  def p1i2 = column[Int]("p1i2")
  def p1i3 = column[Int]("p1i3")
  def p1i4 = column[Int]("p1i4")
  def p1i5 = column[Int]("p1i5")
  def p1i6 = column[Int]("p1i6")
  def p2i1 = column[Int]("p2i1")
  def p2i2 = column[Int]("p2i2")
  def p2i3 = column[Int]("p2i3")
  def p2i4 = column[Int]("p2i4")
  def p2i5 = column[Int]("p2i5")
  def p2i6 = column[Int]("p2i6")
  def p3i1 = column[Int]("p3i1")
  def p3i2 = column[Int]("p3i2")
  def p3i3 = column[Int]("p3i3")
  def p3i4 = column[Int]("p3i4")
  def p3i5 = column[Int]("p3i5")
  def p3i6 = column[Int]("p3i6")
  def p4i1 = column[Int]("p4i1")
  def p4i2 = column[Int]("p4i2")
  def p4i3 = column[Int]("p4i3")
  def p4i4 = column[Int]("p4i4")
  def p4i5 = column[Int]("p4i5")
  def p4i6 = column[Int]("p4i6")
  // This is just the default projection -- It doesn't have to contain all columns
  def * = id
  // Instead, we use nested tuples for a full projection:
  def all = (
    id,
    (p1i1, p1i2, p1i3, p1i4, p1i5, p1i6),
    (p2i1, p2i2, p2i3, p2i4, p2i5, p2i6),
    (p3i1, p3i2, p3i3, p3i4, p3i5, p3i6),
    (p4i1, p4i2, p4i3, p4i4, p4i5, p4i6)
  )
  // And override create_* to get the DDL for all columns.
  // Yeah, this is ugly. It used to be much simpler in ScalaQuery.
  // We can add a helper method to simplify it.
  override def create_* =
    all.shaped.packedNode.collect {
      case Select(Ref(IntrinsicSymbol(in)), f: FieldSymbol) if in == this => f
    }.toSeq.distinct
}

T.ddl.create
// Insert into T.all. The extra ".shaped" call is needed because we cannot
// get the types in an implicit conversion due to SI-3346
T.all.shaped.insert(
  0,
  (11, 12, 13, 14, 15, 16),
  (21, 22, 23, 24, 25, 26),
  (31, 32, 33, 34, 35, 36),
  (41, 42, 43, 44, 45, 46)
)

// Get the nested tuples in a query
val q1 = T.map(_.all)
println(q1.first)

// Map the result to the case classes
val i2 = q1.mapResult { case (id, p1, p2, p3, p4) =>
  Whole(id, Part.tupled.apply(p1), Part.tupled.apply(p2), Part.tupled.apply(p3), Part.tupled.apply(p4))
}
println(i2.first)

which is now a test at Slick including one for version 3. As for updating:

val oData = Whole(0,
  Part(11, 12, 13, 14, 15, 16),
  Part(21, 22, 23, 24, 25, 26),
  Part(31, 32, 33, 34, 35, 36),
  Part(41, 42, 43, 44, 45, 46)
)
val oData2 = Whole(10,
  Part(111, 12, 13, 14, 15, 16),
  Part(121, 22, 23, 24, 25, 26),
  Part(131, 32, 33, 34, 35, 36),
  Part(141, 42, 43, 44, 45, 46)
)

ts.ddl.create

ts.insert(oData)
assertEquals(oData, ts.first)

ts.filter(_.p1i2 === 12).update(oData2)
assertEquals(oData2, ts.first)

The nested objects with Slick's projections can be flattened for the single object that you bring in, or take away with.

like image 65
bjfletcher Avatar answered Oct 18 '22 17:10

bjfletcher


Some suggestions for problem 2:

  • Could you use Slick 3.0? This version seems to have a solution
  • Could you change the layout of the database in such a way permissions are rows instead of columns? This seems more extensible anyway
like image 1
Pim Verkerk Avatar answered Oct 18 '22 17:10

Pim Verkerk