光滑:我有一个简单的joinLeft去kaboom ...为什么?

时间:2019-05-29 07:21:37

标签: scala runtime-error slick

我正在使用Slick 3.3.0来构建应用程序,并且具有以下简单(我相信)用例,其中有一个Auth2InfoRow并且依赖于Auth2InfoParamRow,这些是Slick mapped case classes对应于模型:

package com.mohiva.play.silhouette.impl.providers

case class OAuth2Info(
  accessToken: String,
  tokenType: Option[String] = None,
  expiresIn: Option[Int] = None,
  refreshToken: Option[String] = None,
  params: Option[Map[String, String]] = None) extends AuthInfo

本质上,查询使用Silhouette的LoginInfo查找 master OAuth2Info,包括其params,后者驻留在另一个OAuth2InfoParam详细信息表。

import com.mohiva.play.silhouette.api.{ LoginInfo => ExtLoginInfo }
import com.mohiva.play.silhouette.impl.providers.{ OAuth2Info => ExtOAuth2Info }

/**
 * Returns the matching Silhouette [[ExtOAuth2Info]] used for social
 * (e.g. the Facebook) authentication provider given a Silhouette [[ExtLoginInfo]].
 * The [[ExtLoginInfo]] is looked up using the `providerId` and `providerKey` and
 * then the result's `userId` used as look up key.
 *
 * @param extLoginInfo The linked Silhouette login info instance.
 * @return the matching Silhouette [[ExtOAuth2Info]] used for social.
 */
def find(extLoginInfo: ExtLoginInfo): Future[Option[ExtOAuth2Info]] = {
  val action = (for {
    loginInfo <- LoginInfo if loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey
    (oauth2Info, oauth2InfoParam) <- OAuth2Info.filter(_.userId === loginInfo.userId).joinLeft(OAuth2InfoParam).on(_.userId === _.userId)
  } yield (oauth2Info, oauth2InfoParam)).result
  db.run(action).map {
    case results => {
      val params = results.map(_._2).map {
        case Some(param) => Some(param.key -> param.value)
        case _ => None.asInstanceOf[Option[(String, String)]]
      }.filterNot(_.isEmpty).map(_.get) match {
        case seq if (seq.nonEmpty) => Some(seq.toMap)
        case _ => None
      }

      results.headOption.map {
        case (oauth2Info, _) => oauth2Info.toExt(params)
      }
    }
  }
}

要对其进行细分,db.run(action)之前的第一部分将在单个查询中发出,并查找OAuth2InfoOAuth2InfoParam s,如果未找到后面的行,则它应该是(oauth2Info, None)

db.run(action)之后的第二部分通过从第一个元素中收集 master 然后是与可能参数对应的 details 来重构OAuth2Info OAuth2InfoParam

这就是我得到的:

play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[SlickTreeException: Unreachable reference to s2 after resolving monadic joins
| Join Inner : Vector[(@t16<{user_id: Long', provider_id: String', provider_key: String', modified: Option[org.joda.time.DateTime']}>, (t17<{s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}>, t24<{s25: Long', s26: String', s27: String'}>))]
|   left s2: < Table > myappdb.login_info : Vector[@t16<{user_id: Long', provider_id: String', provider_key: String', modified: Option[org.joda.time.DateTime']}>]
|   right s15: Join Left : Vector[(t17<{s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}>, t24<{s25: Long', s26: String', s27: String'}>)]
|     left s28: Bind : Vector[t17<{s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}>]
|       from s30: Filter s31 : Vector[@t32<{access_token: String', expires_in: Option[Int'], modified: Option[org.joda.time.DateTime'], token_type: Option[String'], refresh_token: Option[String'], user_id: Long'}>]
|         from s31: Table myappdb.o_auth2_info : Vector[@t32<{access_token: String', expires_in: Option[Int'], modified: Option[org.joda.time.DateTime'], token_type: Option[String'], refresh_token: Option[String'], user_id: Long'}>]
|         where: Apply Function = : Boolean
|           0: Path s31.user_id : Long'
|           1: < Path > s2.user_id : Long'
|       select: Pure t17 : Vector[t17<{s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}>]
|         value: StructNode : {s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}
|           s18: Path s30.access_token : String'
|           s19: Path s30.expires_in : Option[Int']
|           s20: Path s30.modified : Option[org.joda.time.DateTime']
|           s21: Path s30.token_type : Option[String']
|           s22: Path s30.refresh_token : Option[String']
|           s23: Path s30.user_id : Long'
|     right s29: Bind : Vector[t24<{s25: Long', s26: String', s27: String'}>]
|       from s33: Table myappdb.o_auth2_info_param : Vector[@t34<{user_id: Long', key: String', value: String'}>]
|       select: Pure t24 : Vector[t24<{s25: Long', s26: String', s27: String'}>]
|         value: StructNode : {s25: Long', s26: String', s27: String'}
|           s25: Path s33.user_id : Long'
|           s26: Path s33.key : String'
|           s27: Path s33.value : String'
|     on: Apply Function = : Boolean
|       0: Path s28.s23 : Long'
|       1: Path s29.s25 : Long'
|   on: Apply Function and : Boolean
|     0: Apply Function and : Boolean
|       0: Apply Function = : Boolean
|         0: Path s2.provider_id : String'
|         1: LiteralNode facebook (volatileHint=false) : String'
|       1: Apply Function = : Boolean
|         0: Path s2.provider_key : String'
|         1: LiteralNode 123456789 (volatileHint=false) : String'
|     1: LiteralNode true (volatileHint=false) : Boolean
]]
    at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:351)
    at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:267)
    at play.core.server.AkkaHttpServer$$anonfun$1.applyOrElse(AkkaHttpServer.scala:382)
    at play.core.server.AkkaHttpServer$$anonfun$1.applyOrElse(AkkaHttpServer.scala:380)
    at scala.concurrent.Future.$anonfun$recoverWith$1(Future.scala:417)
    at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
    at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
    at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55)
    at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:92)
    at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
Caused by: slick.SlickTreeException: Unreachable reference to s2 after resolving monadic joins
| Join Inner : Vector[(@t16<{user_id: Long', provider_id: String', provider_key: String', modified: Option[org.joda.time.DateTime']}>, (t17<{s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}>, t24<{s25: Long', s26: String', s27: String'}>))]
|   left s2: < Table > myappdb.login_info : Vector[@t16<{user_id: Long', provider_id: String', provider_key: String', modified: Option[org.joda.time.DateTime']}>]
|   right s15: Join Left : Vector[(t17<{s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}>, t24<{s25: Long', s26: String', s27: String'}>)]
|     left s28: Bind : Vector[t17<{s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}>]
|       from s30: Filter s31 : Vector[@t32<{access_token: String', expires_in: Option[Int'], modified: Option[org.joda.time.DateTime'], token_type: Option[String'], refresh_token: Option[String'], user_id: Long'}>]
|         from s31: Table myappdb.o_auth2_info : Vector[@t32<{access_token: String', expires_in: Option[Int'], modified: Option[org.joda.time.DateTime'], token_type: Option[String'], refresh_token: Option[String'], user_id: Long'}>]
|         where: Apply Function = : Boolean
|           0: Path s31.user_id : Long'
|           1: < Path > s2.user_id : Long'
|       select: Pure t17 : Vector[t17<{s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}>]
|         value: StructNode : {s18: String', s19: Option[Int'], s20: Option[org.joda.time.DateTime'], s21: Option[String'], s22: Option[String'], s23: Long'}
|           s18: Path s30.access_token : String'
|           s19: Path s30.expires_in : Option[Int']
|           s20: Path s30.modified : Option[org.joda.time.DateTime']
|           s21: Path s30.token_type : Option[String']
|           s22: Path s30.refresh_token : Option[String']
|           s23: Path s30.user_id : Long'
|     right s29: Bind : Vector[t24<{s25: Long', s26: String', s27: String'}>]
|       from s33: Table myappdb.o_auth2_info_param : Vector[@t34<{user_id: Long', key: String', value: String'}>]
|       select: Pure t24 : Vector[t24<{s25: Long', s26: String', s27: String'}>]
|         value: StructNode : {s25: Long', s26: String', s27: String'}
|           s25: Path s33.user_id : Long'
|           s26: Path s33.key : String'
|           s27: Path s33.value : String'
|     on: Apply Function = : Boolean
|       0: Path s28.s23 : Long'
|       1: Path s29.s25 : Long'
|   on: Apply Function and : Boolean
|     0: Apply Function and : Boolean
|       0: Apply Function = : Boolean
|         0: Path s2.provider_id : String'
|         1: LiteralNode facebook (volatileHint=false) : String'
|       1: Apply Function = : Boolean
|         0: Path s2.provider_key : String'
|         1: LiteralNode 123456789 (volatileHint=false) : String'
|     1: LiteralNode true (volatileHint=false) : Boolean

    at slick.compiler.VerifySymbols.verifyScoping$1(VerifySymbols.scala:17)
    at slick.compiler.VerifySymbols.$anonfun$apply$6(VerifySymbols.scala:38)
    at slick.compiler.VerifySymbols.$anonfun$apply$6$adapted(VerifySymbols.scala:38)
    at slick.util.ConstArray.foreach(ConstArray.scala:29)
    at slick.ast.Node.childrenForeach(Node.scala:59)
    at slick.ast.Node.childrenForeach$(Node.scala:58)
    at slick.ast.Apply.childrenForeach(Node.scala:546)
    at slick.compiler.VerifySymbols.verifyScoping$1(VerifySymbols.scala:38)
    at slick.compiler.VerifySymbols.$anonfun$apply$5(VerifySymbols.scala:29)
    at slick.compiler.VerifySymbols.$anonfun$apply$5$adapted(VerifySymbols.scala:29)

PS:Slick就像是这个卑鄙的女友,对你很可怕,但是你仍然爱着她:D

1 个答案:

答案 0 :(得分:0)

好吧,感谢@Dmytro Mitin的评论和https://github.com/slick/slick/issues/1316中提出的解决方法,我尝试了几种变体,直到找到了可行的变体,基本上是在第一个枚举器上进行了左联接:

def find(extLoginInfo: ExtLoginInfo): Future[Option[ExtOAuth2Info]] = {
    val action = (for {
        (loginInfo, oauth2InfoParam) <- LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey ===   extLoginInfo.providerKey }.joinLeft(OAuth2InfoParam).on(_.userId === _.userId)
        oauth2Info <- OAuth2Info.filter(_.userId === loginInfo.userId)
    } yield (oauth2Info, oauth2InfoParam)).result
    db.run(action).map {
        case results => {
        val params = results.map(_._2).map {
            case Some(param) => Some(param.key -> param.value)
            case _ => None.asInstanceOf[Option[(String, String)]]
        }.filterNot(_.isEmpty).map(_.get) match {
            case seq if (seq.nonEmpty) => Some(seq.toMap)
            case _ => None
        }

        results.headOption.map {
            case (oauth2Info, _) => oauth2Info.toExt(params)
        }
    }
}