1. 程式人生 > >Android Database(SQLite)參數綁定問題初探

Android Database(SQLite)參數綁定問題初探

對比 opc http android hang parsing end intvalue _id

不廢話,先貼一下測試代碼:

    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)參數綁定問題初探