Android Database(SQLite)参数绑定问题初探

Posted seebigsea

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Database(SQLite)参数绑定问题初探相关的知识,希望对你有一定的参考价值。

不废话,先贴一下测试代码:

    void test001() {
        final String path = "/sdcard/test.db";
        File file = new File(path);
        if(file.exists()) file.delete();
        android.database.sqlite.SQLiteDatabase db = android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(path, null);
        db.execSQL("create table t(_id integer, mid integer, data text);");
        db.execSQL("create view v  as select IFNULL(mid, _id) as _id, data from t;");
        db.execSQL("create view v2 as select mid as _id, data from t;");
        db.execSQL("create view v3 as select mid+0 as _id, data from t;");
        db.execSQL("insert into t values(10001, null, \'dudu\');");
        db.execSQL("insert into t values(10002, 1000, \'bird\');");
        Cursor csr = null;
        
        Log.e(TAG, "------------------------------test001 -----------------------------------");
        
        csr = db.query("v", new String[]{"data"}, "_id=1000", null, null, null, null);
        Log.i(TAG,"#1 csr.getCount() = " + csr.getCount());
        while(csr.moveToNext()) {
            Log.i(TAG, "#1 data = " + csr.getString(0));
        }
        csr.close();
        Log.d(TAG, "-------------------------------------------------------------------------");
        
        csr = db.query("v", new String[]{"data"}, "_id=?", new String[]{"1000"}, null, null, null);
        Log.i(TAG,"#2 csr.getCount() = " + csr.getCount());
        while(csr.moveToNext()) {
            Log.i(TAG, "#2 data = " + csr.getString(0));
        }
        csr.close();
        Log.d(TAG, "-------------------------------------------------------------------------");
        
        csr = db.query("v2", new String[]{"data"}, "_id=1000", null, null, null, null);
        Log.i(TAG,"#3 csr.getCount() = " + csr.getCount());
        while(csr.moveToNext()) {
            Log.i(TAG, "#3 data = " + csr.getString(0));
        }
        csr.close();
        Log.d(TAG, "-------------------------------------------------------------------------");
        
        csr = db.query("v2", new String[]{"data"}, "_id=?", new String[]{"1000"}, null, null, null);
        Log.i(TAG,"#4 csr.getCount() = " + csr.getCount());
        while(csr.moveToNext()) {
            Log.i(TAG, "#4 data = " + csr.getString(0));
        }
        csr.close();
        Log.d(TAG, "-------------------------------------------------------------------------");
        
        csr = db.query("v3", new String[]{"data"}, "_id=1000", null, null, null, null);
        Log.i(TAG,"#5 csr.getCount() = " + csr.getCount());
        while(csr.moveToNext()) {
            Log.i(TAG, "#5 data = " + csr.getString(0));
        }
        csr.close();
        Log.d(TAG, "-------------------------------------------------------------------------");
        
        csr = db.query("v3", new String[]{"data"}, "_id=?", new String[]{"1000"}, null, null, null);
        Log.i(TAG,"#6 csr.getCount() = " + csr.getCount());
        while(csr.moveToNext()) {
            Log.i(TAG, "#6 data = " + csr.getString(0));
        }
        csr.close();
        Log.d(TAG, "-------------------------------------------------------------------------");
        
        db.close();
    }

6个查询的正常的返回结果应该都是 csr.getCount() = 1 , 看一下实际测试结果:

10-14 12:02:09.442: E/JUST(22524): ------------------------------test001 -----------------------------------
10-14 12:02:09.443: I/JUST(22524): #1 csr.getCount() = 1
10-14 12:02:09.443: I/JUST(22524): #1 data = bird
10-14 12:02:09.443: D/JUST(22524): -------------------------------------------------------------------------
10-14 12:02:09.446: I/JUST(22524): #2 csr.getCount() = 0
10-14 12:02:09.447: D/JUST(22524): -------------------------------------------------------------------------
10-14 12:02:09.449: I/JUST(22524): #3 csr.getCount() = 1
10-14 12:02:09.449: I/JUST(22524): #3 data = bird
10-14 12:02:09.449: D/JUST(22524): -------------------------------------------------------------------------
10-14 12:02:09.450: I/JUST(22524): #4 csr.getCount() = 1
10-14 12:02:09.450: I/JUST(22524): #4 data = bird
10-14 12:02:09.450: D/JUST(22524): -------------------------------------------------------------------------
10-14 12:02:09.450: I/JUST(22524): #5 csr.getCount() = 1
10-14 12:02:09.451: I/JUST(22524): #5 data = bird
10-14 12:02:09.451: D/JUST(22524): -------------------------------------------------------------------------
10-14 12:02:09.451: I/JUST(22524): #6 csr.getCount() = 0
10-14 12:02:09.452: D/JUST(22524): -------------------------------------------------------------------------

这个就神奇了!

看一下查询函数的声明:

Cursor android.database.sqlite.SQLiteDatabase.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
注意到 selectionArgs 的说明:
selectionArgs You may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings.

数据库表中列类型是Int的,而Java代码中“粗暴”的全部绑定为String类型了,SQLite有很强大的弱类型支持,这里为什么会出现这个结果呢?

这个需要研究一下绑定参数SQL间的区别了:

 

对比查询计划:
explain select data from v2 where _id=?; 与 explain select data from v where _id=?;
发现Ne指令的P5参数比较可疑:
4 Ne 2 7 1 (BINARY) 54 if r[1]!=r[2] goto 7
6 Ne 2 9 1 51 if r[1]!=r[2] goto 9

 

在sqlite3.c(以目前Android主流还在用的3.9.2为例)中定位到Ne指令相关关键代码:

case OP_Ne:               /* same as TK_NE, jump, in1, in3 */
    ...
    /* Neither operand is NULL.  Do a comparison. */
    affinity = pOp->p5 & SQLITE_AFF_MASK;
    if( affinity>=SQLITE_AFF_NUMERIC ){
      if( (flags1 | flags3)&MEM_Str ){
        if( (flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
          applyNumericAffinity(pIn1,0);
          testcase( flags3!=pIn3->flags ); /* Possible if pIn1==pIn3 */
          flags3 = pIn3->flags;
        }
        if( (flags3 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
          applyNumericAffinity(pIn3,0);
        }
      }
    ...
    res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl);
    ...

到了Ne这里P5已经被改动过了,经过一番有点艰苦的GDB调试定位到:

/*
** Generate code for a comparison operator.
*/
static int codeCompare(
  Parse *pParse,    /* The parsing (and code generating) context */
  Expr *pLeft,      /* The left operand */
  Expr *pRight,     /* The right operand */
  int opcode,       /* The comparison opcode */
  int in1, int in2, /* Register holding operands */
  int dest,         /* Jump here if true.  */
  int jumpIfNull    /* If true, jump if either operand is NULL */
){
  int p5;
  int addr;
  CollSeq *p4;

  p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
  p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);// P5在这里获取
  addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
                           (void*)p4, P4_COLLSEQ);
  sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5);// 注意这里会修改P5的值
  return addr;
}

所以下面重点查看binaryCompareP5的实现:

/*
** Return the P5 value that should be used for a binary comparison
** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2.
*/
static u8 binaryCompareP5(Expr *pExpr1, Expr *pExpr2, int jumpIfNull){
  u8 aff = (char)sqlite3ExprAffinity(pExpr2);
  aff = (u8)sqlite3CompareAffinity(pExpr1, aff) | (u8)jumpIfNull;
  return aff;
}

/*
** Return the \'affinity\' of the expression pExpr if any.
**
** If pExpr is a column, a reference to a column via an \'AS\' alias,
** or a sub-select with a column as the return value, then the 
** affinity of that column is returned. Otherwise, 0x00 is returned,
** indicating no affinity for the expression.
**
** i.e. the WHERE clause expressions in the following statements all
** have an affinity:
**
** CREATE TABLE t1(a);
** SELECT * FROM t1 WHERE a;
** SELECT a AS b FROM t1 WHERE b;
** SELECT * FROM t1 WHERE (select a from t1);
*/
SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){
  int op;
  pExpr = sqlite3ExprSkipCollate(pExpr);
  if( pExpr->flags & EP_Generic ) return 0;
  op = pExpr->op;
  if( op==TK_SELECT ){
    assert( pExpr->flags&EP_xIsSelect );
    return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr);
  }
  if( op==TK_REGISTER ) op = pExpr->op2;
#ifndef SQLITE_OMIT_CAST
  if( op==TK_CAST ){
    assert( !ExprHasProperty(pExpr, EP_IntValue) );
    return sqlite3AffinityType(pExpr->u.zToken, 0);
  }
#endif
  if( (op==TK_AGG_COLUMN || op==TK_COLUMN) && pExpr->pTab ){
    return sqlite3TableColumnAffinity(pExpr->pTab, pExpr->iColumn);
  }
  if( op==TK_SELECT_COLUMN ){
    assert( pExpr->pLeft->flags&EP_xIsSelect );
    return sqlite3ExprAffinity(
        pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr
    );
  }
  return pExpr->affinity;
}

/*
** pExpr is an operand of a comparison operator.  aff2 is the
** type affinity of the other operand.  This routine returns the
** type affinity that should be used for the comparison operator.
*/
SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){
  char aff1 = sqlite3ExprAffinity(pExpr);
  if( aff1 && aff2 ){      // 注意看下面英文说明就理解了
    /* Both sides of the comparison are columns. If one has numeric
    ** affinity, use that. Otherwise use no affinity.
    */
    if( sqlite3IsNumericAffinity(aff1) || sqlite3IsNumericAffinity(aff2) ){
      return SQLITE_AFF_NUMERIC; //这里是关键,绑定参数能不能作为数字来比较就靠这个返回值了!
    }else{
      return SQLITE_AFF_BLOB;
    }
  }else if( !aff1 && !aff2 ){
    /* Neither side of the comparison is a column.  Compare the
    ** results directly.
    */
    return SQLITE_AFF_BLOB;
  }else{
    /* One side is a column, the other is not. Use the columns affinity. */
    assert( aff1==0 || aff2==0 );
    return (aff1 + aff2); //这里是关键,绑定参数能不能作为数字来比较就靠这个返回值了!
  }
}

 

问题原因这就算说清楚了,怎们解决呢? 下次有空再写吧。。。

 

 

备注:aHR0cCUzQS8vd3d3LmNuYmxvZ3MuY29tL3poaGQv

以上是关于Android Database(SQLite)参数绑定问题初探的主要内容,如果未能解决你的问题,请参考以下文章

android.database.sqlite.SQLiteException:靠近“WHERE”:语法错误(Sqlite 代码 1)

android.database.sqlite.SQLiteException:靠近“”:语法错误

android.database.sqlite.SQLiteException:没有这样的表:

android.database.sqlite.SQLiteException:没有这样的表:国家(代码 1 SQLITE_ERROR)

android.database.sqlite.SQLiteException:靠近“编码”:语法错误(代码 1)

NullPointerException:尝试调用虚拟方法'android.database.sqlite.SQLiteDatabase android.content.Context.openOrC