Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/postgresql/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Postgresql 如何调用存储过程并在Slick中获取返回值(使用Scala)_Postgresql_Scala_Stored Procedures_Playframework_Slick - Fatal编程技术网

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

Postgresql 如何调用存储过程并在Slick中获取返回值(使用Scala),postgresql,scala,stored-procedures,playframework,slick,Postgresql,Scala,Stored Procedures,Playframework,Slick,我试图从Slick 3.0(在Play框架中)调用一个存储过程。我已经一遍又一遍地阅读了文档,但不幸的是,从未显示调用存储过程 看起来很简单的是,导致了一条典型的模糊Scala错误消息: val f = Try { val call: DBIO[Int] = sqlu"?=call app_glimpulse_invitation_pkg.n_send_invitation(${i.token}, ${i.recipientAccountId.getOrElse(None)}, ${i.

我试图从Slick 3.0(在Play框架中)调用一个存储过程。我已经一遍又一遍地阅读了文档,但不幸的是,从未显示调用存储过程

看起来很简单的是,导致了一条典型的模糊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]                                   ^

如果我使用纯硬编码的call语句(删除所有
${I.xyz}
引用,我可以将其编译…但是,我会收到一个运行时错误,报告
更新语句不应返回结果集。

这导致我将语句更改为常规的
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]                                              ^
我确实在会话中找到了一个
prepareCall
(在浏览光滑的API时),但同样地,没有关于如何使用这个东西的文档


非常感谢您的任何建议。这对我来说是一个巨大的障碍,因为我们真的需要接到一个对Postgres存储过程的工作呼叫。谢谢。

好吧,在对相互冲突的文档进行了大量研究和审查之后,我找到了答案。不幸的是,这不是我想要的:

用于返回完整表或存储的 过程请使用普通SQL查询。返回 当前不支持多个结果集

总之,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参数的
列表
。我必须编写一个单独的
executeQuery()
函数来处理返回
结果集的过程。(不过差别很小:您只需编写一个循环来获取
结果集。下一步
,然后将其全部填充到
列表
或任何您想要的其他结构中)

下面是大范围的AJDBC映射
execute()
函数:

def execute(connection: Connection, procedure: GPProcedure, ps: GPProcedureParameterSet) = {
    val cs = connection.prepareCall(procedure.toString)
    var index = 0

    for (parameter <- ps.parameters) {
        index = index + 1
        parameter match {
            // Handle any IN (or INOUT) types: If the optional value is None, set it to NULL, otherwise, map it according to
            // the actual object value and type encoding:
            case p: GPOut => cs.registerOutParameter(index, p.parameterType)
            case GPIn(None, t) => cs.setNull(index, t)
            case GPIn(v: Some[_], Types.NUMERIC | Types.DECIMAL) => cs.setBigDecimal(index, v.get.asInstanceOf[java.math.BigDecimal])
            case GPIn(v: Some[_], Types.BIGINT) => cs.setLong(index, v.get.asInstanceOf[Long])
            case GPIn(v: Some[_], Types.INTEGER) => cs.setInt(index, v.get.asInstanceOf[Int])
            case GPIn(v: Some[_], Types.VARCHAR | Types.LONGVARCHAR) => cs.setString(index, v.get.asInstanceOf[String])
            case GPIn(v: Some[_], Types.CHAR) => cs.setString(index, v.get.asInstanceOf[String].head.toString)
            case GPInOut(None, t) => cs.setNull(index, t)

            // Now handle all of the OUT (or INOUT) parameters, these we just need to set the return value type:
            case GPInOut(v: Some[_], Types.NUMERIC) => {
                cs.setBigDecimal(index, v.get.asInstanceOf[java.math.BigDecimal]); cs.registerOutParameter(index, Types.NUMERIC)
            }
            case GPInOut(v: Some[_], Types.DECIMAL) => {
                cs.setBigDecimal(index, v.get.asInstanceOf[java.math.BigDecimal]); cs.registerOutParameter(index, Types.DECIMAL)
            }
            case GPInOut(v: Some[_], Types.BIGINT) => {
                cs.setLong(index, v.get.asInstanceOf[Long]); cs.registerOutParameter(index, Types.BIGINT)
            }
            case GPInOut(v: Some[_], Types.INTEGER) => {
                cs.setInt(index, v.get.asInstanceOf[Int]); cs.registerOutParameter(index, Types.INTEGER)
            }
            case GPInOut(v: Some[_], Types.VARCHAR) => {
                cs.setString(index, v.get.asInstanceOf[String]); cs.registerOutParameter(index, Types.VARCHAR)
            }
            case GPInOut(v: Some[_], Types.LONGVARCHAR) => {
                cs.setString(index, v.get.asInstanceOf[String]); cs.registerOutParameter(index, Types.LONGVARCHAR)
            }
            case GPInOut(v: Some[_], Types.CHAR) => {
                cs.setString(index, v.get.asInstanceOf[String].head.toString); cs.registerOutParameter(index, Types.CHAR)
            }
            case _ => { Logger(s"Failed to match GPProcedureParameter in executeFunction (IN): index $index (${parameter.toString})") }
        }
    }

    cs.execute()

    // Now, step through each of the parameters, and get the corresponding result from the execute statement. If there is
    // no result for the specified column (index), we'll basically end up getting a "nothing" back, which we strip out.

    index = 0

    val results: List[Any] = for (parameter <- ps.parameters) yield {
        index = index + 1
        parameter match {
            case GPOut(Types.NUMERIC) | GPOut(Types.DECIMAL) => cs.getBigDecimal(index)
            case GPOut(Types.BIGINT) => cs.getLong(index)
            case GPOut(Types.INTEGER) => cs.getInt(index)
            case GPOut(Types.VARCHAR | Types.LONGVARCHAR | Types.CHAR) => cs.getString(index)
            case GPInOut(v: Some[_], Types.NUMERIC | Types.DECIMAL) => cs.getInt(index)
            case GPInOut(v: Some[_], Types.BIGINT) => cs.getLong(index)
            case GPInOut(v: Some[_], Types.INTEGER) => cs.getInt(index)
            case GPInOut(v: Some[_], Types.VARCHAR | Types.LONGVARCHAR | Types.CHAR) => cs.getString(index)
            case _ => {
                Logger(s"Failed to match GPProcedureParameter in executeFunction (OUT): index $index (${parameter.toString})")
            }
        }
    }

    cs.close()

    // Return the function return parameters (there should always be one, the caller will get a List with as many return
    // parameters as we receive):

    results.filter(_ != (()))
}
def execute(连接:连接,过程:GPProcedure,ps:GPProcedureParameterSet)={
val cs=连接.prepareCall(过程.toString)
var指数=0
for(参数cs.registerOutParameter(索引,p.parameterType)
案例GPIn(无,t)=>cs.setNull(索引,t)
案例GPIn(v:Some[|]、Types.NUMERIC | Types.DECIMAL)=>cs.setBigDecimal(index、v.get.asInstanceOf[java.math.BigDecimal])
case-GPIn(v:Some[_],Types.BIGINT)=>cs.setLong(index,v.get.asInstanceOf[Long])
case-GPIn(v:Some[_],Types.INTEGER)=>cs.setInt(index,v.get.asInstanceOf[Int])
案例GPIn(v:Some[|],Types.VARCHAR | Types.LONGVARCHAR)=>cs.setString(index,v.get.asInstanceOf[String])
case-GPIn(v:Some[\u]、Types.CHAR)=>cs.setString(index,v.get.asInstanceOf[String].head.toString)
案例GPInOut(无,t)=>cs.setNull(索引,t)
//现在处理所有OUT(或INOUT)参数,我们只需要设置返回值类型:
大小写GPInOut(v:Some[\u]、Types.NUMERIC)=>{
cs.setBigDecimal(index,v.get.asInstanceOf[java.math.BigDecimal]);cs.registerOutParameter(index,Types.NUMERIC)
}
大小写GPInOut(v:Some[\ux],Types.DECIMAL)=>{
cs.setBigDecimal(index,v.get.asInstanceOf[java.math.BigDecimal]);cs.registerOutParameter(index,Types.DECIMAL)
}
大小写GPInOut(v:Some[_],Types.BIGINT)=>{
cs.setLong(index,v.get.asInstanceOf[Long]);cs.registerOutParameter(index,Types.BIGINT)
}
大小写GPInOut(v:Some[\u],Types.INTEGER)=>{
cs.setInt(index,v.get.asInstanceOf[Int]);cs.registerOutParameter(index,Types.INTEGER)
}
case-GPInOut(v:Some[_],Types.VARCHAR)=>{
cs.setString(index,v.get.asInstanceOf[String]);cs.registerOutParameter(index,Types.VARCHAR)
}
case-GPInOut(v:Some[_],Types.LONGVARCHAR)=>{
cs.setString(index,v.get.asInstanceOf[String]);cs.registerOutParameter(index,Types.LONGVARCHAR)
}
大小写GPInOut(v:Some[\ux],Types.CHAR)=>{
cs.setString(index,v.get.asInstanceOf[String].head.toString);cs.registerOutParameter(index,Types.CHAR)
}
案例{Logger(s“未能匹配executeFunction(in):index$index(${parameter.toString})”中的GPProcedureParameter}
}
}
cs.execute()
//现在,逐步检查每个参数,并从execute语句中获得相应的结果
//对于指定的列(索引)没有结果,我们基本上会得到一个“nothing”返回,我们去掉它。
索引=0
val结果:List[Any]=for(参数cs.getBigDecimal(索引)
案例GPOut(Types.BIGINT)