我正在使用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)
之前的第一部分将在单个查询中发出,并查找OAuth2Info
和OAuth2InfoParam
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
答案 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)
}
}
}