Slick: у меня есть простой joinLeft, собирающийся kaboom ... почему? - PullRequest
0 голосов
/ 29 мая 2019

Я использую Slick 3.3.0 для создания приложения и имею следующий простой (я полагаю) вариант использования, где есть Auth2InfoRow и он зависит Auth2InfoParamRow это Классы отображения Slick Mapped которые соответствуют модели:

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 detail table.

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) выдает в одном запросе и ищет OAuth2Info s и OAuth2InfoParam s иесли более поздние строки не найдены, тогда это должно быть (oauth2Info, None).

Вторая часть после db.run(action) восстанавливает OAuth2Info, собирая master из первого элемента, а затем детали , соответствующие возможным параметрам 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: Слик - это как подлая подруга, которая ужасно относится к тебено ты все равно любишь ее: D

1 Ответ

0 голосов
/ 29 мая 2019

Хорошо, благодаря комментариям @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)
        }
    }
}
...