¿Cómo se ejecuta una actualización de la base de datos de parches/parciales en Scala Slick?
(3)
¿Por qué no hacer coincidir el patrón antes de construir la consulta de actualización?
def patchPerson(name: Option[String], age: Option[Int]) = {
val query = people.filter(_.name === "M Odersky")
(name, age) match {
case (Some(name), Some(age)) =>
query.map(p => (p.name, p.age)).update(name, age)
case (Some(name), None) =>
query.map(p => p.name).update(name)
case (None, Some(age)) =>
query.map(p => p.age).update(age)
}
}
Nos gustaría ejecutar un parche / UPDATE
parcial con Slick (3.0.0) para que solo modifiquemos algunos de los campos en un registro. Exactamente qué campos se actualizarán exactamente solo se conocerán en tiempo de ejecución.
Por ejemplo, para una solicitud REST PATCH
.
Actualmente ejecutamos un SELECT
primero para obtener el registro original, luego ejecutamos una UPDATE
pero sería mejor hacerlo en una sola instrucción SQL.
Algo como esto:
def patchPerson(name: Option[String], age: Option[Int]) = {
people.filter(_.name === "M Odersky")
.map(p =>
(name, age) match {
case (Some(_), Some(_)) => (p.name, p.age)
case (Some(_), None) => (p.name)
case (None , Some(_)) => (p.age)
}
)
.update(
(name, age) match {
case (Some(_), Some(_)) => (name.get, age.get)
case (Some(_), None) => (name.get)
case (None , Some(_)) => (age.get)
}
)
}
(Por favor ignore el código feo aquí)
Lo anterior no se compila con el siguiente mensaje de error:
No se ha encontrado una Forma coincidente. Slick no sabe cómo mapear los tipos dados. Causas posibles: T en la Tabla [T] no coincide con su * proyección. O utiliza un tipo no compatible en una consulta (por ejemplo, lista de Scala). Nivel requerido: slick.lifted.FlatShapeLevel Tipo de fuente: Objeto Tipo sin empacar: T Tipo de paquete: G
Y:
No hay suficientes argumentos para el mapa del método: (forma implícita: slick.lifted.Shape [_ <: slick.lifted.FlatShapeLevel, Object, T, G]) slick.lifted.Query [G, T, Seq]. Valor del parámetro no especificado forma.
Supongo que esto se debe a que Slick espera que la longitud y el tipo de la tupla coincidan con los resultados de las funciones de filter
y update
.
Hemos intentado usar la clase de lista heterogénea Slick pero esto también parece esperar que la longitud y los tipos coincidan.
¿Hay alguna forma de escribir esto en Slick para que podamos actualizar un número arbitrario de campos en un registro con una llamada a la base de datos?
Mi mejor conjetura sería ejecutar una consulta simple de SQL
Incluso si la consulta SQL tiene 2 partes, el sistema de administración de base de datos relacional (postgresql, mysql, etc.) puede ajustar la consulta bajo los capítulos.
No estoy seguro si en este caso Slick puede optimizar, pero en varios casos también optimiza las consultas por sí mismo.
Actualización típica:
def updateRecord(id: Long, field1: Int) = {
db.withSession {
self.filter(_.id === id).map(_.field1).update(field1)
}
}
Hacer su tipo de actualización requeriría un poco más de lógica como lo hizo usted. No piense que es posible simplificar si solo sabe en tiempo de ejecución qué campos cambiar. Pero puede forzar la actualización, usando el valor existente para el campo en el registro como un respaldo (puede llevar a más actualizaciones en la base de datos de lo que debería)
def updateRecord(id: Long, field1: Option[Int], field2: Option[Int]) = {
db.withSession {
self.filter(_.id === id).map(_.field1, _.field2).update(field1.getOrElse(existingValue1), field2.getOrElse(existingValue2))
}
}
Ya tienes las respuestas escritas por @ pedrorijo91 y @thirstycow, pero intentaré explicar por qué esto no funciona.
No he usado Slick 3 pero supongo que es porque la función de mapa no devuelve un tipo coherente para que se ejecute la actualización. Como experimento mental, si cortas tu llamada en el mapa, ¿cuál crees que será el tipo?
val partialQuery:??? = people.filter(_.name === "M Odersky")
.map(p =>
(name, age) match {
case (Some(_), Some(_)) => (p.name, p.age)
case (Some(_), None) => (p.name)
case (None , Some(_)) => (p.age)
}
);
val fullQuery:??? = partialQuery.update {
(name, age) match {
case (Some(_), Some(_)) => (name.get, age.get)
case (Some(_), None) => (name.get)
case (None , Some(_)) => (age.get)
}
}
El emparejador devuelve diferentes "formas" en el momento de la compilación, y las suposiciones vuelven a cualquier tipo.