2012-03-02 3 views
23

Wydaje ma łatwego sposobu korzystania „w” klauzuli w anorm:Klauzula "w" w anormie?

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

Jak wymienić ??? udział?

Próbowałem:

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

Ale żaden działa.

widzę w dyskusji na dokładnie ten sam problem: https://groups.google.com/d/topic/play-framework/qls6dhhdayc/discussion, autor ma złożoną rozwiązanie:

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

To jest zbyt skomplikowane.

Czy istnieje łatwiejszy sposób? A może gra powinna coś ułatwić?

Odpowiedz

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

To rozwiązanie jest skutecznie takie samo, jak sugerowane w pytaniu, z jednym wyjątkiem, że jest podatne na iniekcję SQL, ponieważ wklejasz parametry bezpośrednio do ciągu zapytania (bez ucieczki!), Zamiast używać wiązania parametry. Nie warto, aby zaoszczędzić 14 znaków! –

+1

Otwarta luka w zabezpieczeniach nie jest warta 14 znaków ?! –

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

Byłoby bardziej idiomatycznie napisać część 'reduceLeft' jako po prostu' ids.mkString (",") '. I zgodnie z drugą odpowiedzią, jest to podatne na SQL Injection ze względu na umieszczanie parametrów bezpośrednio w ciągu zapytania. –

0

Miałem ten sam problem w ostatnim czasie. Niestety, nie wydaje się, aby istniała droga bez interpolacji ciągów i w ten sposób podatna na iniekcję SQL.

Co skończyło się robi kinda odkażania go poprzez przekształcenie go do listy wskazówki i tył:

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

Nailed it! Naprawdę nie było żadnych aktualizacji w tym wątku, ale wydaje się, że nadal jest to istotne. Z tego powodu, a ponieważ nie ma odpowiedzi, pomyślałem, że wrzucę moją do rozważenia.

Anorm nie obsługuje klauzul "IN". Wątpię, że kiedykolwiek będą. Nic nie możesz zrobić, aby działały, nawet czytałem post, w którym anorm wyraźnie wyłączył te klauzule, ponieważ sprawili, że Anorm czuje się "jak ORM".

Dość łatwo jest jednak umieścić SqlQuery w krótkiej klasie obsługującej klauzulę IN, a następnie przekonwertować tę klasę na SqlQuery w razie potrzeby.

Zamiast wklejać tutaj kod, ponieważ trwa to trochę długo, oto link do mojego bloga, na którym opublikowałem kod i jak go używać.

In clause with Anorm

Zasadniczo, jeśli masz kod z mojego bloga, Twoje wypowiedzi wyglądać następująco:

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

Może to zbyt późno, ale tutaj jest to wskazówka dla użyciu niestandardowego interpolacji ciąg, który również działa do rozwiązania problemu klauzuli IN.

Zaimplementowałem klasę pomocniczą do zdefiniowania interpolacji ciągów znaków. Możesz to zobaczyć poniżej i możesz po prostu skopiować i wkleić, ale najpierw zobaczmy, jak możesz z niego korzystać.

Zamiast napisać coś

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

można po prostu napisać:

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

Więc za pomocą interpolacji ciąg jest bardziej zwięzły i łatwiejsze do odczytania.

, aw przypadku korzystania z klauzuli, można napisać:

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

Albo dla przykładu:

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

Aby uzyskać więcej informacji na temat interpolacji smyczkowy, zaznacz to link

Kod tej niejawnej klasy jest następujący:

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:_*) 
    } 
    } 

} 
1

Jest prawdopodobnie późno, ale dodaję to dla innych szukających tego samego. Można użyć niektórych wbudowanych funkcji bazy danych, aby temu zaradzić. Jest to jedna z zalet Anorm na ponad ORM. Na przykład, jeśli korzystasz z PostgreSQL, możesz przekazać listę jako tablicę i anulować tablicę w zapytaniu:

Zakładam, że id są liczbami całkowitymi.

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

Wykonane zapytanie będzie

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

która jest równa

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

Anorm obsługuje teraz taki przypadek (i więcej) od 2.3: "Using multi-value parameter"

Powrót do początkowego przykład daje:

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

Można również użyć opcji "SQL" select * od użytkowników, których id ($ ids) ". As (parser. *)' – cchantep