scala anorm

scala - Cláusula "en" en anorm?



(7)

¡Dado en el clavo! Realmente no ha habido más actualizaciones en este hilo, pero parece que todavía es relevante. Por eso, y porque no hay una respuesta, pensé que iba a lanzar la mía para su consideración.

Anorm no soporta las cláusulas ''IN''. Dudo que alguna vez lo hagan. No hay nada que puedas hacer para que funcionen, incluso leí un post donde anorm eliminó esas cláusulas específicamente porque hicieron que Anorm se sintiera ''como un ORM''.

Sin embargo, es bastante fácil envolver el SqlQuery en una clase corta que admita la cláusula IN, y luego convertir esa clase en un SqlQuery cuando sea necesario.

En lugar de pegar el código aquí, porque se alarga un poco, aquí está el enlace a mi blog, donde publiqué el código y cómo usarlo.

En cláusula con Anorm

Básicamente, cuando tienes el código de mi blog, tus declaraciones se ven así:

RichSQL(""" SELECT * FROM users WHERE id IN ({userIds}) """).onList("userIds" -> userIds).toSQL.as(userParser *)(connection)

Parece que no es una forma fácil de usar la cláusula "in" en anorm:

val ids = List("111", "222", "333") val users = SQL("select * from users where id in ({ids})").on(''ids-> ???).as(parser *)

¿Cómo reemplazar el ??? ¿parte?

Lo intenté:

on(''ids -> ids) on(''ids -> ids.mkString("''","'',''","''")) on(''ids -> ids.mkString("'',''")

Pero ninguno funciona.

Veo en la discusión exactamente el mismo problema: https://groups.google.com/d/topic/play-framework/qls6dhhdayc/discussion , el autor tiene una solución compleja:

val params = List(1, 2, 3) val paramsList = for ( i <- 0 until params.size ) yield ("userId" + i) // ---> results in List("userId0", "userId1", "userId2") User.find("id in ({%s})" // produces "id in ({userId0},{userId1},{userId2})" .format(paramsList.mkString("},{")) // produces Map("userId0" -> 1, "userId1" -> 2, ...) .on(paramsList.zip(params)) .list()

Esto es demasiado complicado.

¿Hay alguna forma más fácil? ¿O debería el juego proporcionar algo para hacerlo más fácil?


Anorm ahora admite este caso (y más) desde la versión 2.3: "Uso de un parámetro de múltiples valores"

De vuelta al ejemplo inicial que da:

val ids = Seq("111", "222", "333") val users = SQL("select * from users where id in ({ids})").on(''ids-> ids).as(parser *)


Probablemente sea tarde, pero agrego esto para otros que buscan lo mismo. Podrías usar algunas características integradas de la base de datos para superar esto. Esta es una de las ventajas que Anorm tiene sobre los ORM. Por ejemplo, si está utilizando PostgreSQL, podría pasar su lista como una matriz y anular la matriz en su consulta:

Asumo que los identificadores son enteros.

val ids = List(1, 2, 3) val idsPgArray = "{%s}".format(ids.mkString(",")) //Outputs {1, 2, 3} val users = SQL( """select * from users where id in (select unnest({idsPgArray}::integer[]))""" ).on(''ids-> ???).as(parser *)

La consulta ejecutada será

select * from users where id in (select unnest(''{1, 2, 3}''::integer[]))

que es igual a

select * from users where id in (1, 2, 3)


Tal vez sea demasiado tarde, pero aquí hay una sugerencia para usar la interpolación de cadenas personalizada que también funciona para resolver el problema de la cláusula IN.

He implementado una clase de ayuda para definir una interpolación de cadena. Puede verlo a continuación, y simplemente puede copiar y pegar, pero primero veamos cómo puede usarlo.

En lugar de escribir algo

SQL("select * from car where brand = {brand} and color = {color} and year = {year} order by name").on("brand" -> brand, "color" -> color, "year" -> year).as(Car.simple *)

Usted puede simplemente escribir:

SQL"select * from car where brand = $brand and color = $color and year = $year order by name".as(Car.simple *)

Así que usar la interpolación de cadenas es más conciso y fácil de leer.

Y para el caso de usar la cláusula IN, puede escribir:

val carIds = List(1, 3, 5) SQLin"select * from car where id in ($carIds)".as(Car.simple *)

O para su ejemplo:

val ids = List("111", "222", "333") val users = SQLin"select * from users where id in ($ids)".as(parser *)

Para obtener más información sobre la interpolación de cadenas, consulte este link

El código para esta clase implícita es el siguiente:

package utils object AnormHelpers { def wild (str: String) = "%" + str + "%" implicit class AnormHelper (val sc: StringContext) extends AnyVal { // SQL raw -> it simply create an anorm.Sql using string interpolation def SQLr (args: Any*) = { // Matches every argument to an arbitrary name -> ("p0", value0), ("p1", value1), ... val params = args.zipWithIndex.map(p => ("p"+p._2, p._1)) // Regenerates the original query substituting each argument by its name with the brackets -> "select * from user where id = {p0}" val query = (sc.parts zip params).map{ case (s, p) => s + "{"+p._1+"}" }.mkString("") + sc.parts.last // Creates the anorm.Sql anorm.SQL(query).on( params.map(p => (p._1, anorm.toParameterValue(p._2))) :_*) } // SQL -> similar to SQLr but trimming any string value def SQL (args: Any*) = { val params = args.zipWithIndex.map { case (arg: String, index) => ("p"+index, arg.trim.replaceAll("//s{2,}", " ")) case (arg, index) => ("p"+index, arg) } val query = (sc.parts zip params).map { case (s, p) => s + "{"+ p._1 + "}" }.mkString("") + sc.parts.last anorm.SQL(query).on( params.map(p => (p._1, anorm.toParameterValue(p._2))) :_*) } // SQL in clause -> similar to SQL but expanding Seq[Any] values separated by commas def SQLin (args: Any*) = { // Matches every argument to an arbitrary name -> ("p0", value0), ("p1", value1), ... val params = args.zipWithIndex.map { case (arg: String, index) => ("p"+index, arg.trim.replaceAll("//s{2,}", " ")) case (arg, index) => ("p"+index, arg) } // Expands the Seq[Any] values with their names -> ("p0", v0), ("p1_0", v1_item0), ("p1_1", v1_item1), ... val onParams = params.flatMap { case (name, values: Seq[Any]) => values.zipWithIndex.map(v => (name+"_"+v._2, anorm.toParameterValue(v._1))) case (name, value) => List((name, anorm.toParameterValue(value))) } // Regenerates the original query substituting each argument by its name expanding Seq[Any] values separated by commas val query = (sc.parts zip params).map { case (s, (name, values: Seq[Any])) => s + values.indices.map(name+"_"+_).mkString("{", "},{", "}") case (s, (name, value)) => s + "{"+name+"}" }.mkString("") + sc.parts.last // Creates the anorm.Sql anorm.SQL(query).on(onParams:_*) } } }


Tuve el mismo problema recientemente. Desafortunadamente, no parece haber una manera sin utilizar la interpolación de cadenas y, por lo tanto, vulnerable a la inyección de SQL.

Lo que terminé haciendo fue sanearlo al transformarlo en una lista de ints y volver:

val input = "1,2,3,4,5" // here there will be an exception if someone is trying to sql-inject you val list = (_ids.split(",") map Integer.parseInt).toList // re-create the "in" string SQL("select * from foo where foo.id in (%s)" format list.mkString(","))


User.find("id in (%s)" .format(params.map("''%s''".format(_)).mkString(",") ) .list()


val ids = List("111", "222", "333") val users = SQL("select * from users where id in (" + ids.reduceLeft((acc, s) => acc + "," + s) + ")").as(parser *)