Nun, nach viel Recherche und Überprüfung der widersprüchlichen Dokumentation, fand ich die Antwort. Leider war es nicht das, was ich suchte:
For database functions that return complete tables or stored procedures please use Plain SQL Queries. Stored procedures that return multiple result sets are currently not supported.
Unterm Strich Slick nicht gespeicherte Funktionen oder Verfahren aus der Box unterstützt, so müssen wir unsere eigenen schreiben.
Die Antwort besteht darin, Slick aus dem Speicher zu entfernen, indem Sie das Sitzungsobjekt abgreifen und dann Standard-JDBC verwenden, um den Prozeduraufruf zu verwalten. Für diejenigen von euch, die mit JDBC vertraut sind, ist das keine Freude ... aber glücklicherweise können wir mit Scala einige schöne Tricks mit Pattern Matching machen, die die Arbeit erleichtern.
Der erste Schritt für mich bestand darin, eine saubere externe API zusammenzustellen. Dies ist, was es am Ende aussehen wie:
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()
Hier ist ein kurzer Durchmarsch: Ich habe einen einfachen Container erstellt einen gespeicherte Prozedur-Aufruf zu modellieren. Das GPProcedureParameterSet kann eine Liste von GPIn-, GPOut- oder GPInOut-Instanzen enthalten. Jeder von diesen bildet einen Wert für einen JDBC-Typ ab. Der Behälter sieht wie folgt aus:
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(?, ?, ?, ?, ?, ?, ?)}")
}
Der Vollständigkeit Ich schließe die GPProcedure Aufzählung, so dass Sie sie alle zusammen setzen können.
All dies wird an meine execute()
Funktion übergeben. Es ist groß und eklig, riecht nach altmodischem JDBC und ich bin mir sicher, dass ich die Scala ein bisschen verbessern werde. Ich habe das letzte Mal um 3 Uhr morgens fertiggestellt ... aber es funktioniert, und es funktioniert wirklich gut. Beachten Sie, dass diese spezielle Funktion execute()
eine List
zurückgibt, die alle OUT-Parameter enthält ...Ich muss eine separate executeQuery()
Funktion schreiben, um eine Prozedur zu behandeln, die resultSet
zurückgibt. (Der Unterschied ist jedoch trivial: Sie schreiben einfach eine Schleife, die eine resultSet.next
schnappt und alles in eine List
oder irgendeine andere Struktur stopft, die Sie möchten).
Hier ist der große böse 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(_ != (()))
}
Sie Anorm in einem solchen Fall verwenden können. – cchantep
Keine Notwendigkeit, "Anruf" zu verwenden. Verwenden Sie einfach eine einfache 'Select app_glimpulse_invitation_pkg.n_send_invitation (...)' –
Gute Frage. Klarer Punkt, beginnt Ihr erster Aufruf wirklich mit '? =' - das sieht für mich nicht nach gültigem SQL aus ... –