sql >> Database teknologi >  >> RDS >> PostgreSQL

Sådan kalder du en lagret procedure og får returværdi i Slick (ved hjælp af Scala)

Nå, efter megen research og gennemgang af modstridende dokumentation, fandt jeg svaret. Desværre var det ikke den, jeg ledte efter:

Bundlinjen understøtter Slick ikke lagrede funktioner eller procedurer ud af boksen, så vi er nødt til at skrive vores egne.

Svaret er at droppe ned fra Slick ved at gribe sessionsobjektet og derefter bruge standard JDBC til at styre procedurekaldet. For dem af jer, der er bekendt med JDBC, er det ikke en glæde... men heldigvis kan vi med Scala lave nogle ret gode tricks med mønstermatchning, der gør arbejdet lettere.

Det første skridt for mig var at sammensætte en ren ekstern API. Sådan endte det med at se ud:

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()
 

Her er en hurtig gennemgang:Jeg oprettede en simpel beholder til at modellere et lagret procedurekald. GPProcedureParameterSet kan indeholde en liste over GPIn-, GPOut- eller GPInOut-forekomster. Hver af disse knytter en værdi til en JDBC-type. Beholderen ser sådan ud:

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(?, ?, ?, ?, ?, ?, ?)}") }

For fuldstændighedens skyld inkluderer jeg GPProcedure-opregningen, så du kan sætte det hele sammen.

Alt dette bliver overdraget til min execute() fungere. Den er stor og grim, lugter af gammeldags JDBC, og jeg er sikker på, at jeg vil forbedre Scala'en en del. Jeg afsluttede bogstaveligt talt dette kl. 3 i nat... men det virker, og det fungerer rigtig godt. Bemærk, at denne særlige execute() funktion returnerer en List indeholdende alle OUT-parametrene... Jeg bliver nødt til at skrive en separat executeQuery() funktion til at håndtere en procedure, der returnerer et resultSet . (Forskellen er dog triviel:du skriver bare en løkke, der fanger en resultSet.next og læg det hele i en List eller hvilken som helst anden struktur du kunne tænke dig).

Her er den store grimme Scala<->JDBC-mapping execute() funktion:

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. Hvordan tilføjer man 2 datoer i Oracle sp?

  2. Heroku Postgres Fejl:PGError:ERROR:Relationsorganisationer eksisterer ikke (ActiveRecord::StatementInvalid)

  3. Vælg data mellem et dato-/tidsinterval

  4. MySQL 5.7 returnerer alle kolonner i tabellen baseret på en særskilt kolonne