您的位置:首页 > 编程语言 > PHP开发

通过ContentProvider实现groupby查询数据(转)及ICS后的修改(原)

2012-05-09 16:40 232 查看
很多人想在Contentprovider的query方法中使用groupby查询,奈何SDK里面不提供这样的功能,于是有牛人从黑客的注入式破解中得到灵感,做法如下:
比如要用实现这么一个sql语句:

SELECT _id, number, date, duration, type, name, numbertype,

numberlabel, COUNT(*) FROM calls GROUP BY number,type,date/8640000

ORDER BY date DESC

这个如果在一般的SQL编译工具里都能正常运行,在ContentRosolver中有些不一样。

用ContentRosolver中的query这么写:

private static String CALLS_COUNT = "calls_count";

static final String[] CALL_LOG_PROJECTION = new String[] {

Calls._ID,

Calls.NUMBER,

Calls.DATE,

Calls.DURATION,

Calls.TYPE,

Calls.CACHED_NAME,

Calls.CACHED_NUMBER_TYPE,

Calls.CACHED_NUMBER_LABEL,

"COUNT(*) AS " + CALLS_COUNT

};

String selection = "0==0) GROUP BY ("+

Calls.NUMBER+"),("+

Calls.TYPE+"),("+

Calls.DATE+"/86400000";

rosolver.query(QUERY_TOKEN, null, Calls.CONTENT_URI,

CALL_LOG_PROJECTION, selection, null, Calls.DEFAULT_SORT_ORDER);

注意事项:

1 关键字“COUNT, AS, GROUP BY”的大小写

2 COUNT(*) 后需要跟AS ***

3 Android会将query中的参数整合成一条sql语句,其中会将selection的字符串自动加一个括号,形成 “WHERE

(*******)”的形式,所以要特别注意selection中有括号的情况

4 GROUP BY后面的字段应该加括号,用逗号隔开。

转化为sql语句正确的形式应该如下:

SELECT _id, number, date, duration, type, name, numbertype,

numberlabel, COUNT(*) FROM calls WHERE (0==0) GROUP BY

(number),(type),(date/8640000) ORDER BY date DESC

=================分割线=======================
可惜!在ICS里面,这个方法又不适用了!(为什么是又。。。)
经过跟踪源码,ICS中的对应的Provider(比如CalllogProvider)和Framework中的SQLiteQueryBuilder类,已经将这个漏洞补上
首先增加了一个参数mStrict,在CalllogProvider的query方法中被设置为true
并在SQLiteQueryBuilder中进行了检测:

private String[] computeProjection(String[] projectionIn) {
if (projectionIn != null && projectionIn.length > 0) {
if (mProjectionMap != null) {
String[] projection = new String[projectionIn.length];
int length = projectionIn.length;

for (int i = 0; i < length; i++) {
String userColumn = projectionIn[i];
String column = mProjectionMap.get(userColumn);

if (column != null) {
projection[i] = column;
continue;
}

if (!mStrict &&
( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
/* A column alias already exist */
projection[i] = userColumn;
continue;
}

throw new IllegalArgumentException("Invalid column "
+ projectionIn[i]);
}
return projection;
} else {
return projectionIn;
}
} else if (mProjectionMap != null) {
// Return all columns in projection map.
Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
String[] projection = new String[entrySet.size()];
Iterator<Entry<String, String>> entryIter = entrySet.iterator();
int i = 0;

while (entryIter.hasNext()) {
Entry<String, String> entry = entryIter.next();

// Don't include the _count column when people ask for no projection.
if (entry.getKey().equals(BaseColumns._COUNT)) {
continue;
}
projection[i++] = entry.getValue();
}
return projection;
}
return null;
}
从标红前后代码可以看见,自己定义的projection中的column,直接就被抛异常了

就算采用了什么猥琐的方法,将mStrict设为了false,seleciton还是会被在接下来的拼接过程中被加上括号弥补掉可能被inject的可能。
简单看了下,这个修改,没有在ICS的变更说明中有描述,毕竟对于google来说只是改了个漏洞
如果有同学的app是用了这样的方法,要在ICS下兼容估计要改改了。

回到问题本身,如何在ContentProvider中实现groupby,如果,这个ContentProvider是自己实现或者可以修改系统的provider,是可以做到的,SQLiteQueryBuilder类提供了带groupby的query接口:

public Cursor query(SQLiteDatabase db, String[] projectionIn,
String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder, String limit)
如果,是使用系统的ContentProvider如CalllogProvider、ContactsProvider,现在据我所知,是没有直接查询的办法了。代替的办法是使用MatrixCursor类,先查询到所有数据,然后遍历Cursor,自己处理拼接一个MatrixCursor使用,这个需要注意的是MatrixCursor的拼接,效率偏低,当数据量较大时,会很耗时,需要做好算法优化和异步处理。

参考文档:
/article/4262860.html

http://www.eoeandroid.com/thread-31662-1-1.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐