如何在Slick中调用存储过程并获取返回值(使用Scala)

时间:2015-05-17 19:21:15

标签: postgresql scala stored-procedures playframework slick

我正在尝试从Slick 3.0(在Play Framework中)调用存储过程。我一直在翻阅文档,但遗憾的是plain SQL docs at Typesafe从未显示调用存储过程。

看起来非常简单的是导致一个典型的Scala错误消息:

val f = Try {
    val call: DBIO[Int] = sqlu"?=call app_glimpulse_invitation_pkg.n_send_invitation(${i.token}, ${i.recipientAccountId.getOrElse(None)}, ${i.email}, ${i.phoneNumber}, ${requestType})"

    val result: Future[Int] = db.run(call)

    val r = Await.result(result, Duration.Inf) // should only return one; use .seq.count(_.id != null)) to validate
    val z = result.value.get.get // should return the stored procedure return value...?
}

以上代码导致此编译器错误:

[error] /Users/zbeckman/Projects/Glimpulse/Server-2/project/glimpulse-server/app/controllers/GPInviteService/GPInviteService.scala:120: could not find implicit value for parameter e: slick.jdbc.SetParameter[Product with Serializable]
[error]             val call: DBIO[Int] = sqlu"?=call app_glimpulse_invitation_pkg.n_send_invitation(${i.token}, ${i.recipientAccountId.getOrElse(None)}, ${i.email}, ${i.phoneNumber}, ${requestType})"
[error]                                   ^

如果我使用纯硬编码的调用语句(删除所有${i.xyz}引用,我可以让它编译......但是,我得到一个运行时错误报告Update statements should not return a result set.

这导致我将声明更改为常规sql电话:

val call: DBIO[Seq[(Int)]] = sql"call app_glimpulse_invitation_pkg.n_send_invitation('xyz', 1000, 1, 'me@here.com', NULL, 'I', ${out})".as[(Int)]
val result: Future[Int] = db.run(call)

但这也无处可去,产生编译错误:

[error] /Users/zbeckman/Projects/Glimpulse/Server-2/project/glimpulse-server/app/controllers/GPInviteService/GPInviteService.scala:126: type mismatch;
[error]  found   : slick.driver.PostgresDriver.api.DBIO[Seq[Int]]
[error]     (which expands to)  slick.dbio.DBIOAction[Seq[Int],slick.dbio.NoStream,slick.dbio.Effect.All]
[error]  required: slick.dbio.DBIOAction[Int,slick.dbio.NoStream,Nothing]
[error]             val result: Future[Int] = db.run(call)
[error]                                              ^

我确实在(在浏览Slick API时)找到了会话中的prepareCall,但是再次......没有关于如何使用此内容的文档。

非常感谢任何和所有建议。这对我来说已经成为一个巨大的障碍,因为我们真的需要打电话给我们的Postgres存储过程。谢谢。

1 个答案:

答案 0 :(得分:4)

好吧,经过对相互冲突的文档的大量研究和审查,我找到了答案。不幸的是,它不是我想要的那个:

  

对于返回完整表或存储的数据库函数   程序请使用Plain SQL Queries。返回的存储过程   目前不支持多个结果集。

底线,Slick不支持开箱即用的存储功能或程序,因此我们必须自己编写。

答案是通过抓取会话对象从Slick中删除,然后使用标准JDBC来管理过程调用。对于那些熟悉JDBC的人来说,这不是一种快乐......但是,幸运的是,使用Scala我们可以通过模式匹配做一些相当不错的技巧,使工作变得更容易。

我的第一步是整理一个干净的外部API。这就是它最终看起来像:

val db = Database.forDataSource(DB.getDataSource)
var response: Option[GPInviteResponse] = None

db.withSession {
    implicit session => {
        val parameters = GPProcedureParameterSet(
            GPOut(Types.INTEGER) ::
            GPIn(Option(i.token), Types.VARCHAR) ::
            GPIn(recipientAccountId, Types.INTEGER) ::
            GPIn(Option(contactType), Types.INTEGER) ::
            GPIn(contactValue, Types.VARCHAR) ::
            GPIn(None, Types.INTEGER) :: 
            GPIn(Option(requestType), Types.CHAR) ::
            GPOut(Types.INTEGER) ::  
            Nil
        )

        val result = execute(session.conn, GPProcedure.SendInvitation, parameters)
        val rc = result.head.asInstanceOf[Int]

        Logger(s"FUNC return code: $rc")
        response = rc match {
            case 0 => Option(GPInviteResponse(true, None, None))
            case _ => Option(GPInviteResponse(false, None, Option(GPError.errorForCode(rc))))
        }
    }
}

db.close()

这是一个快速演练:我创建了一个简单的容器来模拟存储过程调用。 GPProcedureParameterSet可以包含GPIn,GPOut或GPInOut实例的列表。其中每个都将值映射到JDBC类型。容器看起来像这样:

case class GPOut(parameterType: Int) extends GPProcedureParameter
object GPOut

case class GPIn(value: Option[Any], parameterType: Int) extends GPProcedureParameter
object GPIn

case class GPInOut(value: Option[Any], parameterType: Int) extends GPProcedureParameter
object GPInOut

case class GPProcedureParameterSet(parameters: List[GPProcedureParameter])
object GPProcedureParameterSet

object GPProcedure extends Enumeration {
    type GPProcedure = Value
    val SendInvitation = Value("{?=call app_glimpulse_invitation_pkg.n_send_invitation(?, ?, ?, ?, ?, ?, ?)}")
}

为了完整性,我将包括GPProcedure枚举,以便您可以将它们放在一起。

所有这些都交给我的execute()函数。它很大而且很讨厌,闻起来像老式的JDBC,而且我确信我会改进Scala。我昨晚凌晨3点完成了这件事......但它确实有效,而且效果非常好。请注意,此特定execute()函数返回包含所有OUT参数的List ...我必须编写单独的executeQuery()函数来处理返回{的过程{1}}。 (但差别很小:你只需编写一个抓取resultSet的循环,然后将其全部填入resultSet.next或其他任何你想要的结构中。

这是一个令人讨厌的Scala< - > JDBC映射List函数:

execute()