Slick:我有一个简单的 joinLeft going kaboom ... 为什么?
Slick: I have a simple joinLeft going kaboom ... why?
我正在使用 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
,包括位于另一个 [=18] 中的 params
=] 详情 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)
之后的第二部分通过从第一个元素收集 master 然后 details[= 重建一个 OAuth2Info
43=]对应可能的参数OAuth2InfoParam
.
这是我得到的:
play.api.http.HttpErrorHandlerExceptions$$anon: 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.applyOrElse(AkkaHttpServer.scala:382)
at play.core.server.AkkaHttpServer$$anonfun.applyOrElse(AkkaHttpServer.scala:380)
at scala.concurrent.Future.$anonfun$recoverWith(Future.scala:417)
at scala.concurrent.impl.Promise.$anonfun$transformWith(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(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(VerifySymbols.scala:17)
at slick.compiler.VerifySymbols.$anonfun$apply(VerifySymbols.scala:38)
at slick.compiler.VerifySymbols.$anonfun$apply$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(VerifySymbols.scala:38)
at slick.compiler.VerifySymbols.$anonfun$apply(VerifySymbols.scala:29)
at slick.compiler.VerifySymbols.$anonfun$apply$adapted(VerifySymbols.scala:29)
PS: Slick 就像这个对你很坏但你还是爱她的坏女朋友:D
好的,感谢@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)
}
}
}
我正在使用 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
,包括位于另一个 [=18] 中的 params
=] 详情 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)
之后的第二部分通过从第一个元素收集 master 然后 details[= 重建一个 OAuth2Info
43=]对应可能的参数OAuth2InfoParam
.
这是我得到的:
play.api.http.HttpErrorHandlerExceptions$$anon: 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.applyOrElse(AkkaHttpServer.scala:382)
at play.core.server.AkkaHttpServer$$anonfun.applyOrElse(AkkaHttpServer.scala:380)
at scala.concurrent.Future.$anonfun$recoverWith(Future.scala:417)
at scala.concurrent.impl.Promise.$anonfun$transformWith(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(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(VerifySymbols.scala:17)
at slick.compiler.VerifySymbols.$anonfun$apply(VerifySymbols.scala:38)
at slick.compiler.VerifySymbols.$anonfun$apply$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(VerifySymbols.scala:38)
at slick.compiler.VerifySymbols.$anonfun$apply(VerifySymbols.scala:29)
at slick.compiler.VerifySymbols.$anonfun$apply$adapted(VerifySymbols.scala:29)
PS: Slick 就像这个对你很坏但你还是爱她的坏女朋友:D
好的,感谢@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)
}
}
}