2012-07-03 14 views
7

Como dice el documento Android, los parámetros selectionArgs del método rawQuery se analizan como cadenas.SQLite rawQuery selectionArgs and Integers Fields

SQLiteDatabase.rawQuery (String sql, String [] selectionArgs)

selectionArgs: Usted puede incluir s en la cláusula WHERE en la consulta, que será sustituido por los valores de selectionArgs?. Los valores se vincularán como cadenas.

Pero hoy tuve un problema que me llevó gran parte de mi día. Imagine la siguiente consulta:

SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= 15 

COLUMN_A es INTEGER. La tabla tiene aproximadamente 10 filas que atiende a ese criterio. Al ejecutar la consulta en un editor de base de datos, el resultado siempre fue correcto, pero, en el teléfono inteligente, la instrucción siempre devuelve ninguna fila.

Después de algún tiempo, un cambió la consulta a:

SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= '15' 

y el editor devuelve ninguna fila, al igual que Android. Por lo tanto, cambiar la consulta a:

SELECT * FROM TABLE_A WHERE CAST(IFNULL(COLUMN_A, 0) as INTEGER) >= '15' 

ha solucionado el problema. Otra prueba que se realizó fue:

SELECT * FROM TABLE_A WHERE COLUMN_A >= '15' 

también, devolvió el resultado correcto.

Esto parece ser un problema que involucra la forma en que Android vincula los parámetros a la consulta (como cadenas) con la cláusula IFNULL.

Entonces, ¿alguien sabe por qué sucedió esto? ¿Hay alguna sugerencia para resolver esto sin usar CAST en la consulta?

+0

Tuve el problema similar y pasé una hora tratando de encontrar un error en mi aplicación. Entonces he encontrado tu publicación. Por lo tanto, es genial que lo hayas escrito aquí. Otra cosa es que nunca entenderé la filosofía de Android y por qué este CAST es obligatorio aquí ... – user2707175

Respuesta

9

La razón por la que ates sus valores a la consulta en el primer lugar es, que desea evitar que un SQL-injection attack.

Básicamente, envíe su consulta (incluyendo marcadores de posición) a su base de datos y diga "Mi próxima consulta será de esta forma y ninguna otra!". Cuando un atacante luego inyecta una cadena de consulta diferente (a través de un campo de formulario, por ejemplo) la base de datos dice "¡Oye, esa no es la consulta que dijiste que enviarías!" y te arroja un error.

Desde comandos (que se pueden inyectar en su consulta real como se muestra en el artículo enlazado) son cadenas, la cadena de tipo de datos es la más "peligrosa" uno. Si un usuario intenta inyectar un código en su campo que solo debe tomar números e intenta convertir/analizar la entrada a un número entero (antes de poner el valor en su consulta), obtendrá una excepción de inmediato. Con una cadena, no hay tal clase de seguridad. Por lo tanto, tienen que escapar, probablemente. Ese podría ser la razón por la que los valores de vinculación se interpretan todos como cadenas.

Lo anterior es bongus!No molesta más si los argumentos que unes son cadenas o enteros, todos son igual de peligrosos. Además, la verificación previa de los valores en el código en un gran número de código repetitivo, es propenso a errores y no flexible.


Para impedir que la aplicación SQL-inyecciones y también acelerar múltiples bases de datos de escritura operaciones (usando el mismo comando pero con diferentes valores), se debe utilizar "declaraciones preparadas". La clase correcta para escribiendo en la base de datos en el SDK de Android es SQLiteStatement.

Para crear una declaración preparada, se utiliza el compileStatement()-method de su SQLiteDatabase -objeto y enlazar los valores correspondientes (que serán reemplazadas con las ? -marks en su consulta) utilizando el correcto bindXX() -method (heredado de SQLiteProgram):

SQLiteDatabase db = dbHelper.getWritableDatabase(); 
SQLiteStatement stmt = db.compileStatement("INSERT INTO SomeTable (name, age) values (?,?)"); 
// Careful! The index begins at 1, not 0 !! 
stmt.bindString(1, "Jon"); 
stmt.bindLong(2, 48L); 
stmt.execute(); 
// Also important! Clean up after yourself. 
stmt.close(); 

Ejemplo tomado de esta pregunta más: How do I use prepared statements in SQlite in Android?


Lamentablemente, SQLiteStatement no tiene una sobrecarga que devuelve Cursor, por lo que no puede usarlo para SELECT -statements. Para aquellos, se puede utilizar el rawQuery(String, String[]) -method de SQLiteDatabase:

int number = getNumberFromUser(); 
String[] arguments = new String[]{String.valueOf(number)}; 
db.rawQuery("SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= ?", arguments); 

Tenga en cuenta que la rawQuery() -method toma una cadena de matriz para los valores de los argumentos. En realidad, esto no molesta, a SQLite no le importa el tipo. Siempre que las representaciones de cadena sean iguales a las que esperaría en una consulta SQL, está bien.

+1

Hola @lukas. Gracias a su respuesta, pero, como se dice en los documentos, las declaraciones preparadas no se pueden usar para devolver los cursores. – regisxp

+0

@regisxp No sé por qué no lo vi.Actualicé mi respuesta. Echale un vistazo. –

+0

¿Por qué Android arroja números enteros a 'String'? No puede inyectar consultas SQL con números, ¿verdad? Desearía que los parámetros 'selectionArguments' fueran una matriz de' Object' y que la estructura mantendría enteros como enteros y protegería 'String' de las inyecciones. –

1

Tengo exactamente el mismo problema, supongo. Lo que está tratando de mencionar es que no puede hacer cálculos reales con respecto a la base de datos sqlite. Descubrí que el problema es más grande que el que mencionas. La base de datos SQLite no parece entender ningún tipo de campo con respecto al valor del campo. Lo que significa que si está intentando insertar un valor de cadena en un campo INTEGER escriba que la inserción no se quejará.

Así que el problema es aún mayor, como puede ver. Aunque he visto que si tiene una columna que solo tiene enteros y hace una instrucción where como: donde id = 1 sin '', entonces hay un conjunto de datos de resultados. Así que podría preguntarle si está seguro de que esta afirmación no funciona: "SELECCIONAR * FROM TABLE_A WHERE IFNULL (COLUMN_A, 0)> = 15". Pero donde id> = '15' funciona porque toma la representación de cadena de id que son 2 caracteres unicode reales (!!!) e intenta hacer un operador> = a '15' que SÍ aplica.

La primera vez que me encontré con estos problemas me sorprendió y he decidido crear dinámicamente el SQL sin parámetros de enlace y ejecutarlo como una cadena completa. Sé que no es la mejor manera de acceder a la base de datos, sin parámetros vinculantes, pero es una solución buena porque las razones de seguridad no son tan importantes, aunque su base de datos está segura y sus métodos de acceso son privados para su Aplicación . Si el "intruso" tiene la capacidad de acceder a la base de datos por un teléfono raíz, puede simplemente ponerlo en un SQLITE Studio y listo.

+0

Hi @ 10s. Gracias por tu comentario. La 'primera versión' de mi aplicación utilizó la misma técnica que dijiste, reemplazando los parámetros manualmente. Decidí cambiar debido a algunas alertas que recibí del sistema operativo, pidiéndome usar argumentos en consultas para mejorar el caché de sentencias de SQLite. ¿Recibiste estos consejos también? – regisxp

+0

No, afortunadamente o no, aún no me he encontrado con ese tipo de alertas y estoy trabajando en una aplicación comercial que requiere una gran cantidad de cambios manuales de parámetros de SQL con consultas y enmiendas muy "pesadas", subconsultas, etc. Así que supongo han probado al máximo las capacidades de SQLite en Android. ¿Pueden publicar estas alertas para darme una pista? – 10s

+0

Confirmación? ¿discusión? – 10s