1

Coming from How to get max(id) of table = Rep[Option[Long]] for subsequent insert, without calling db.run in between

I have yet another problem with my code:

def add(languageCode: String,
  typeId: Long,
  properties: Seq[Property]): Unit = {
    val dbAction = (
        for{
            nodeId <- (nodes.all returning nodes.all.map(_.id)) += Node(typeId)
            language <- (languages.all filter (_.code === languageCode)).result.head
            _ <- DBIO.seq(properties.map
            {
            property =>
                val id = property.id
                val name = property.key
                val value = property.value
                if(id == 0) {
                  val currentPropId: FixedSqlAction[Option[Long], h2Profile.api.NoStream, Effect.Read] = this.properties.all.map(_.id).max.result
                  val propertyId = (this.properties.all returning this.properties.all.map(_.id)) += Property(language.id.get, currentPropId + 1, name)
                  nodeProperties.all += NodeProperty(nodeId, 2, value)
                } else {
                  nodeProperties.all += NodeProperty(nodeId, id, value)
                }
            }: _*)
        } yield()).transactionally

        db.run(dbAction)
}

1) The basic idea is, that if that for each property which has id of 0, there are two inserts to be called. One to actually create that new property in some table, the other to also add the value of that property to another table.

That's in the:

if(id == 0) {
    val currentPropId: Rep[Option[Long]] = this.properties.all.map(_.id).max
    // FIXME get rid of hardcoded languageId
    val DUMMY_LANGUAGE_ID = 1
    val propertyId = (this.properties.all returning this.properties.all.map(_.id)) += Property(DUMMY_LANGUAGE_ID, currentPropId + 1, name)
    nodeProperties.all += NodeProperty(nodeId, 2, value)
}

part.

Now, if that is not the case, only the value of the property needs to be inserted to the correct table, that's what the else part is for.

The problem I face is that for the case where the id == 0 nothing gets inserted.

I also tried it like this:

if(id == 0) {
    val nextPropId: Rep[Option[Long]] = this.properties.all.map(_.id).max
    // FIXME get rid of hardcoded languageId
    val propertyId = (this.properties.all returning this.properties.all.map(_.id)) += Property(1, 10, name)
    val nodeProperty = nodeProperties.all += NodeProperty(nodeId, 2, value)
    propertyId andThen nodeProperty
}

but to no avail.

The else part works just fine. What am I missing here?

edit:

Because I got asked.

val propertyId = (this.properties.all returning this.properties.all.map(_.id)) += Property(language.id.get, 10, name)
val nodeProperty = nodeProperties.all += NodeProperty(nodeId, 2, value)

log.info(s"Statements: ${propertyId.statements}")
log.info(s"${nodeProperty.statements}")

gives me:

Statements: Vector(insert into "properties" ("language_id","property_id","name") values (?,?,?)) Vector(insert into "node_property" ("node_id","property_id","value") values (?,?,?))

Which looks just fine.

I would love to log also the content of dbAction itself but as it's a DBIOAction I did not find a way to do so in a meaningful manner.

If I set my logging to trace I get:

22:18:16:016 - [debug] s.c.QueryCompilerBenchmark - ------------------- Phase: Time ---------
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark -       assignUniqueSymbols:    0.456428 ms
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark -                inferTypes:    0.089135 ms
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark -            insertCompiler:    0.292443 ms
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark -                   codeGen:    0.534494 ms
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark -                     TOTAL:    1.372500 ms
22:18:16:016 - [debug] s.b.B.action - #5: Rollback

With no idea why the last line says Rollback as everything before (quite a lot more) seems okay to me.

edit2:

Looks like I finally have it with:

val dbAction = for {
      nodeId <- (nodes.all returning nodes.all.map(_.id)) += Node(typeId)
      languageId <- (languages.all filter (_.code === languageCode)).map(_.id).result.head
      _ <- DBIO.seq(properties.values.map
      {
        property =>
          val id = property.id
          val name = property.key
          val value = property.value

          if(id == 0) {
            for {
              currentPropId <- this.properties.all.map(_.id).max.result
              propertyId <- (this.properties.all returning this.properties.all.map(_.id)) += Property(languageId, currentPropId.get + 1, name)
              _ <- this.nodeProperties.all += NodeProperty(nodeId, propertyId, value)
            } yield ()
          } else {
            this.nodeProperties.all += NodeProperty(nodeId, id, value)
          }
      }: _*)
    } yield ()
  • I might be wrong, but I think in the `id == 0` case, you're also only returning one insert action which will end up in the `DBIO.seq`. But that does not explain why nothing is inserted though... – LiMuBei Nov 08 '17 at 15:51
  • Well, I add one for each property, but yes, it's the same as the `else` because I chain it with `andThen` (in the newest try) –  Nov 08 '17 at 19:43
  • Have you tried printing the statements that Slick is generating? – Radu Gancea Nov 12 '17 at 10:47
  • @Radu as it's a `DBIOAction` I am not sure how to: https://stackoverflow.com/questions/33124899/slick-3-1-printing-sql-from-dbioaction-insert-statements (for the individual `FixedSqlAction` the `insert`-statements look correct!) –  Nov 12 '17 at 20:59

1 Answers1

0

It looks like the final answer is to restructure a bit:

 val dbAction = for {
      nodeId <- (nodes.all returning nodes.all.map(_.id)) += Node(typeId)
      languageId <- (languages.all filter (_.code === languageCode)).map(_.id).result.head
      _ <- DBIO.seq(properties.values.map
      {
        property =>
          val id = property.id
          val name = property.key
          val value = property.value

          if(id == 0) {
            for {
              currentPropId <- this.properties.all.map(_.id).max.result
              propertyId <- (this.properties.all returning this.properties.all.map(_.id)) += Property(languageId, currentPropId.get + 1, name)
              _ <- this.nodeProperties.all += NodeProperty(nodeId, propertyId, value)
            } yield ()
          } else {
            this.nodeProperties.all += NodeProperty(nodeId, id, value)
          }
      }: _*)
    } yield ()

I would love to see other solutions, but that's the one I found after another long trial and error session. I am still unsure why I couldn't get the andThen approach to work.

And I am wondering what happens to the transactionally if something happens in the inner for comprehension. I guess it wouldn't work according to my interpretation of documentation - http://slick.lightbend.com/doc/3.2.1/dbio.html#transactions-and-pinned-sessions

Nested transactionally actions simply execute inside the existing transaction without additional savepoints.