Slick: У меня простое соединение с левой кнопкой мыши... почему?

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

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

По сути, запрос использует LoginInfo Силуэта для поиска владелец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) выдается в одном запросе и ищет OAuth2Infos и OAuth2InfoParams, и если строки более поздних не найдены, то это должно быть (oauth2Info, None).

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

Можете ли вы попробовать переписать монадические соединения с аппликативными? Можете ли вы попробовать переписать для понимания? github.com/slick/slick/issues/1316slick.lightbend.com/doc/3.3.0/queries.html#monadic-соединения "Если монадическое соединение не может быть правильно переведено, во время выполнения произойдет сбой."

Dmytro Mitin 29.05.2019 12:07

@DmytroMitin пишет, что одно из них уже было для меня гимнастическим упражнением, представляя, если мне придется написать другое, чтобы увидеть, работает ли оно: D, не могли бы вы опубликовать ответ на обходной путь, пожалуйста?

SkyWalker 29.05.2019 12:23

Я не могу. Вы не предоставили MCVE.

Dmytro Mitin 29.05.2019 12:32

@DmytroMitin Хорошо подготовлю MCVE спасибо!

SkyWalker 29.05.2019 12:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
423
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Другие вопросы по теме