【问题标题】:SQLite rawQuery selectionArgs and Integers FieldsSQLite rawQuery selectionArgs 和 Integers 字段
【发布时间】:2012-07-03 23:07:34
【问题描述】:

正如Android文档所说,rawQuery方法的selectionArgs参数被解析为字符串。

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

selectionArgs:您可以在查询的 where 子句中包含 ?s, 它将被 selectionArgs 中的值替换。 这些值将绑定为字符串。

但是今天,我遇到了一个问题,这个问题占据了我大部分时间。想象以下查询:

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

COLUMN_A 是整数。该表有大约 10 行符合该标准。在数据库编辑器上运行查询,结果总是正确的,但在智能手机上,语句总是不返回任何行。

一段时间后,a 将查询更改为:

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

并且编辑器没有返回任何行,就像 Android 一样。因此,将查询更改为:

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

解决了这个问题。另一项测试是:

SELECT * FROM TABLE_A WHERE COLUMN_A >= '15'

同样,返回了正确的结果。

这似乎是一个问题,涉及 Android 使用 IFNULL 子句将参数绑定到查询(作为字符串)的方式。

那么,有人知道为什么会这样吗?有什么建议可以在不使用 CAST 的情况下解决这个问题吗?

【问题讨论】:

  • 我遇到了类似的问题,我花了一个小时试图在我的应用程序中找到错误。然后我找到了你的帖子。因此,您在这里写它真的很棒。另一件事是我永远不会理解 Android 哲学以及为什么这个 CAST 在这里是必须的.​​.....

标签: android sqlite


【解决方案1】:

您将值绑定到查询的原因是为了防止SQL-injection attack

基本上,您将查询(包括占位符)发送到您的数据库并说“我的下一个查询将采用这种形式,而不是其他形式!”。当攻击者随后注入不同的查询字符串(例如,通过表单字段)时,数据库会显示“嘿,这不是您说要发送的查询!”并向你抛出一个错误。

由于命令(如链接文章中所示,可以注入到您的实际查询中)是字符串,因此字符串数据类型是更“危险”的一种。 如果用户尝试将一些代码注入到您的字段中,该代码应该只接受数字,并且您尝试将输入转换/解析为整数(在将值放入查询之前),您将立即收到异常。使用字符串,就没有这种安全性。因此,他们可能必须逃脱。 这可能是绑定值都被解释为字符串的原因。

以上是虚假!无论您绑定的参数是字符串还是整数,它们都同样危险。此外,在代码中预先检查您的值会导致大量样板代码,容易出错且不灵活!


为了防止您的应用程序遭受 SQL 注入并加快多个数据库编写操作(使用具有不同值的相同查询),您应该使用“准备好的语句”。在 Android SDK 中写入到数据库的正确类是SQLiteStatement

要创建准备好的语句,请使用 SQLiteDatabase 对象的 compileStatement()-method 并使用正确的 bindXX()- 绑定相应的值(在查询中将替换为 ? 标记)方法(继承自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();

示例取自这个较早的问题:How do I use prepared statements in SQlite in Android?


遗憾的是,SQLiteStatement 没有返回Cursor 的重载,因此您不能将它用于SELECT 语句。对于这些,你可以使用SQLiteDatabaserawQuery(String, String[])-方法:

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

请注意,rawQuery()-方法采用字符串数组作为参数值。这其实没关系,SQLite 会自动转换为正确的类型。只要字符串表示等于您在 SQL 查询中所期望的,就可以了。

【讨论】:

  • 嗨@lukas。感谢您的回复,但正如文档中所说,准备好的语句不能用于返回游标。
  • @regisxp 我不知道为什么我没有看到。我更新了我的答案。看看吧。
  • 为什么 Android 将整数转换为 String?你不能用数字注入 SQL 查询,对吧?我希望selectionArguments 参数是Object 的数组,并且框架将整数保留为整数,并保护String 免受注入。
  • Android sqlite 需要改变这个,参数应该是像 JDBC 这样的 Object。字符串不适用于stackoverflow.com/questions/50310213/…
  • @Sunnyday 不管你是否使用bindLong(),这都应该可以工作,就像上面的例子一样。您可能还想使用Room,这是适用于 Android SQLite 的新官方 ORM。
【解决方案2】:

我猜我有完全相同的问题。您要提及的是您无法对 sqlite 数据库进行实际计算。我发现问题比你提到的更大。 SQLite 数据库似乎不了解有关字段值的任何字段类型。这意味着如果您尝试在 INTEGER 字段类型中插入字符串值,则插入不会抱怨。

如您所见,问题更大。虽然我已经看到,如果你有一列只有整数,并且你做了一个 where 语句,比如: where id = 1 without ' ' 那么就有一个结果数据集。所以我可能会问你是否确定这个语句不起作用:“SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= 15”。但是 where id >= '15' 确实有效,因为它采用 id 的字符串表示形式,它是实际的 2 个 unicode 字符(!!!),并尝试将运算符 >= 设为适用的 '15'。

我第一次遇到这些问题时让我感到惊讶,我决定动态创建 SQL 而不绑定参数并作为一个完整的字符串执行。我知道在没有绑定参数的情况下,这不是访问数据库的最佳方式,但它是一种解决方案,而且是一个很好的解决方案,因为尽管您的数据库是安全的并且您的访问方法对您的应用程序来说是私有的,但安全原因并不那么重要.如果“入侵者”能够通过 root 电话访问数据库,他只需将其放入 SQLITE Studio 即可。

【讨论】:

  • 嗨@10s。谢谢你的评论。我的应用程序的“第一个版本”使用了您所说的相同技术,手动替换了参数。我决定进行更改,因为我从操作系统收到了一些警报,要求我在查询中使用参数来改进 SQLite 语句缓存。你是否也收到过这样的建议?
  • 不,幸运与否,我还没有遇到这种警报,我正在开发一个业务应用程序,该应用程序需要大量手动替换 SQL 参数和非常“繁重”的查询和连接,子查询等。所以我想我已经在 android 中测试了完整的 SQLite 功能。您能否发布这些警报以便给我一个提示?
猜你喜欢
  • 1970-01-01
  • 2012-05-22
  • 1970-01-01
  • 2011-07-16
  • 1970-01-01
  • 2016-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多