sql >> データベース >  >> RDS >> PostgreSQL

ストアドプロシージャを呼び出してSlickで戻り値を取得する方法(Scalaを使用)

    まあ、矛盾するドキュメントの多くの調査とレビューの後、私は答えを見つけました。残念ながら、それは私が探していたものではありませんでした:

    結論として、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()に注意してください 関数はListを返します すべてのOUTパラメータが含まれています...別のexecuteQuery()を作成する必要があります resultSetを返すプロシージャを処理する関数 。 (ただし、違いはわずかです。resultSet.nextを取得するループを作成するだけです。 すべてをListに詰め込みます または他の任意の構造)。

    これが大きな厄介なScala<->JDBCマッピング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(_ != (()))
    }
    



    1. フィルタ可能な属性を持つアイテムを保存する正しい方法は?

    2. MySQLカウント頻度

    3. Oracleストアドプロシージャ内の別のユーザーのテーブルへのアクセス

    4. SqlAlchemyモデルクラス定義内の@propertyを介したPythonゲッターとセッター:HOWTO