Е, след много проучвания и преглед на противоречива документация, намерих отговора. За съжаление не беше този, който търсих:
В крайна сметка, 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(_ != (()))
}