2009-08-21 10 views
31

Estoy intentando usar la sustitución de parámetros con SQLite within Python para una cláusula IN. Aquí está un ejemplo de funcionamiento completo que demuestra:Sustitución de parámetros para una cláusula SQLite "IN"

import sqlite3 

c = sqlite3.connect(":memory:") 
c.execute('CREATE TABLE distro (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)') 

for name in 'Ubuntu Fedora Puppy DSL SuSE'.split(): 
    c.execute('INSERT INTO distro (name) VALUES (?)', [ name ]) 

desired_ids = ["1", "2", "5", "47"] 
result_set = c.execute('SELECT * FROM distro WHERE id IN (%s)' % (", ".join(desired_ids)),()) 
for result in result_set: 
    print result 

Se imprime:

(1, u'Ubuntu') (2, u'Fedora') (5, u'SuSE')

Como los documentos afirman que "[N] o no se debe montar su consulta mediante operaciones de cadenas de Python, ya que hacerlo es inseguro, hace que su programa sea vulnerable a un ataque de inyección SQL, "Estoy esperando utilizar la sustitución de parámetros.

cuando intento:

result_set = c.execute('SELECT * FROM distro WHERE id IN (?)', [ (", ".join(desired_ids)) ]) 

Obtengo un conjunto de resultados vacío, y cuando intento:

result_set = c.execute('SELECT * FROM distro WHERE id IN (?)', [ desired_ids ]) 

me sale:

InterfaceError: Error binding parameter 0 - probably unsupported type.

Aunque espero que cualquier respuesta para este problema simplificado funcionará, me gustaría señalar que la consulta real que deseo realizar se encuentra en una subconsulta doblemente anidada. A saber:

UPDATE dir_x_user SET user_revision = user_attempted_revision 
WHERE user_id IN 
    (SELECT user_id FROM 
     (SELECT user_id, MAX(revision) FROM users WHERE obfuscated_name IN 
      ("Argl883", "Manf496", "Mook657") GROUP BY user_id 
     ) 
    ) 
+0

Gracias por todas las respuestas. Tuve mucho sentido cuando finalmente vi que solo necesitaba un signo de interrogación para cada parámetro que estoy sustituyendo. –

Respuesta

47

sí es necesario el número correcto de ? s, pero que no representan un riesgo de inyección SQL:

>>> result_set = c.execute('SELECT * FROM distro WHERE id IN (%s)' % 
          ','.join('?'*len(desired_ids)), desired_ids) 
>>> print result_set.fetchall() 
[(1, u'Ubuntu'), (2, u'Fedora'), (5, u'SuSE')] 
+0

+1 para la "mejor" solución para generar la cadena de lista de marcadores de posición :-) –

+0

¿Hay alguna manera fácil de hacer que esto use parámetros nombrados? Algo como ': id1: id2: id3' etc. Estoy usando esto en el contexto de una consulta más grande con algunos otros parámetros nombrados. – User

+2

Estoy llegando a esto varios años después, pero también necesitaba parámetros con nombre. Acabo de hacer esto: 'query =" SELECT * FROM my_table WHERE my_param =: my_param AND id IN ({}) ". Format (',' .join (': {}'. Format (i) for i in rango (len (desired_ids)))); params = {'my_param': 'foo'}; params.update ({str (i): id para i, id en enumerate (desired_ids)}); result = cursor.execute (query, params) ' El módulo sqlite3 está perfectamente satisfecho con cosas como': 0', ': 1',': 2' como parámetros de sustitución de cadena. (El desbordamiento de pila realmente mata el formato del código en los comentarios; lo siento, es tan difícil de leer). – geekofalltrades

10

Actualización: esto funciona:

import sqlite3 

c = sqlite3.connect(":memory:") 
c.execute('CREATE TABLE distro (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)') 

for name in 'Ubuntu Fedora Puppy DSL SuSE'.split(): 
    c.execute('INSERT INTO distro (name) VALUES (?)', (name,)) 

desired_ids = ["1", "2", "5", "47"] 
result_set = c.execute('SELECT * FROM distro WHERE id IN (%s)' % ("?," * len(desired_ids))[:-1], desired_ids) 
for result in result_set: 
    print result 

El problema es que es necesario tener uno? para cada elemento en la lista de entrada.

La instrucción ("?," * len(desired_ids))[:-1] hace una secuencia repetitiva de "?,", Luego corta la última coma. de modo que haya un signo de interrogación para cada elemento en desired_ids.

+0

Esa fue una gran explicación. Gracias. –

2

siempre termino haciendo algo como esto:

query = 'SELECT * FROM distro WHERE id IN (%s)' % ','.join('?' for i in desired_ids) 
c.execute(query, desired_ids) 

No hay riesgo de inyección porque no está colocando cadenas de desired_ids en la consulta directamente.

+0

Los valores que utilizaré en la cláusula IN en realidad provienen de un archivo exportado desde otro sistema. Espero que el riesgo de inyección sea minúsculo, pero nunca se sabe cuándo aparecerá Bobby Tables. –

+0

El riesgo de Injecton es 0 porque lo único que está programando en su consulta es un montón de signos de interrogación. Todo lo que un atacante hipotético puede hacer es controlar la cantidad de signos de interrogación, eso no es un vector de ataque. Los datos reales suministrados externamente pasan por el? mecanismo de paso de parámetros como de costumbre. –

+0

Ya veo. Sí, tiene usted razón. –

0

En caso de que sqlite tenga un problema con la longitud de la solicitud sql, la cantidad indefinida de signos de interrogación puede ser una especie de forma de entender las cosas.

18

De acuerdo con http://www.sqlite.org/limits.html (elemento 9), SQLite no puede (de forma predeterminada) manejar más de 999 parámetros para una consulta, por lo que las soluciones aquí (generar la lista de marcadores de posición necesaria) fallarán si tiene miles de elementos que estás buscando IN. Si ese es el caso, vas a tener que dividir la lista, recorrer las partes y unir los resultados tú mismo.

Si no necesita miles de elementos en su cláusula IN, entonces la solución de Alex es la forma de hacerlo (y parece ser la forma en que Django lo hace).

+0

Es bueno saberlo. Gracias. Incluso puedo tener que revivir mi código. –

Cuestiones relacionadas