Я использую 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