SQLite

Check-in [e9543911]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Do not use an expression index on a generated column if generated column has the wrong affinity. dbsqlfuzz 65f5eb57f8859344d5f1f33e08c77ee12960ed83
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: e95439119ac200cb47d0e277622f41ee7986b364487cd252b485ce5fa030d70f
User & Date: drh 2023-03-03 15:12:46
References
2023-03-07
23:47
Fix a bug introduced 4 days ago by [e95439119ac200cb]: do not set the Expr.affExpr field of a generated column expression if the expression is a RAISE() function, as affExpr has a different meaning for RAISE. Forum post b312e075b5. (check-in: 1096b5a7 user: drh tags: trunk)
2023-03-03
19:43
Follow-up to [e95439119ac200cb] to fix a case where a pointer is NULL due to OOM. (check-in: 2535bc8c user: drh tags: trunk)
Context
2023-03-03
19:47
Do not use an expression index on a generated column if generated column has the wrong affinity. (check-in: 65ffee23 user: drh tags: branch-3.41)
16:25
When it is known when preparing a statement that X cannot be NULL or is always NULL, transform the expression (X IS NULL) to integer value 1 or 0 instead of 'true' or 'false'. This is because under some circumstances, "Y IS TRUE" or "Y IS FALSE" may not be equivalent to "Y IS 1" of "Y IS 0". This problem was introduced by [de9c86c9e4cdb34f] and was reported by forum post 2cd11c2d37. (check-in: cc4bb05b user: dan tags: trunk)
15:12
Do not use an expression index on a generated column if generated column has the wrong affinity. dbsqlfuzz 65f5eb57f8859344d5f1f33e08c77ee12960ed83 (check-in: e9543911 user: drh tags: trunk)
10:42
Remove unnecessary call to sqlite3_dbdata_init() from shell.c.in. (check-in: c4d083a3 user: dan tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/build.c.

2008
2009
2010
2011
2012
2013
2014

2015
2016
2017
2018
2019
2020
2021
  if( ALWAYS(pExpr) && pExpr->op==TK_ID ){
    /* The value of a generated column needs to be a real expression, not
    ** just a reference to another column, in order for covering index
    ** optimizations to work correctly.  So if the value is not an expression,
    ** turn it into one by adding a unary "+" operator. */
    pExpr = sqlite3PExpr(pParse, TK_UPLUS, pExpr, 0);
  }

  sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr);
  pExpr = 0;
  goto generated_done;

generated_error:
  sqlite3ErrorMsg(pParse, "error in generated column \"%s\"",
                  pCol->zCnName);







>







2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
  if( ALWAYS(pExpr) && pExpr->op==TK_ID ){
    /* The value of a generated column needs to be a real expression, not
    ** just a reference to another column, in order for covering index
    ** optimizations to work correctly.  So if the value is not an expression,
    ** turn it into one by adding a unary "+" operator. */
    pExpr = sqlite3PExpr(pParse, TK_UPLUS, pExpr, 0);
  }
  pExpr->affExpr = pCol->affinity;
  sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr);
  pExpr = 0;
  goto generated_done;

generated_error:
  sqlite3ErrorMsg(pParse, "error in generated column \"%s\"",
                  pCol->zCnName);

Changes to src/expr.c.

4129
4130
4131
4132
4133
4134
4135

4136
4137
4138
4139
4140
4141
4142










4143
4144
4145
4146
4147
4148
4149
  Parse *pParse,   /* The parsing context */
  Expr *pExpr,     /* The expression to potentially bypass */
  int target       /* Where to store the result of the expression */
){
  IndexedExpr *p;
  Vdbe *v;
  for(p=pParse->pIdxEpr; p; p=p->pIENext){

    int iDataCur = p->iDataCur;
    if( iDataCur<0 ) continue;
    if( pParse->iSelfTab ){
      if( p->iDataCur!=pParse->iSelfTab-1 ) continue;
      iDataCur = -1;
    }
    if( sqlite3ExprCompare(0, pExpr, p->pExpr, iDataCur)!=0 ) continue;










    v = pParse->pVdbe;
    assert( v!=0 );
    if( p->bMaybeNullRow ){
      /* If the index is on a NULL row due to an outer join, then we
      ** cannot extract the value from the index.  The value must be
      ** computed using the original expression. */
      int addr = sqlite3VdbeCurrentAddr(v);







>







>
>
>
>
>
>
>
>
>
>







4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
  Parse *pParse,   /* The parsing context */
  Expr *pExpr,     /* The expression to potentially bypass */
  int target       /* Where to store the result of the expression */
){
  IndexedExpr *p;
  Vdbe *v;
  for(p=pParse->pIdxEpr; p; p=p->pIENext){
    u8 exprAff;
    int iDataCur = p->iDataCur;
    if( iDataCur<0 ) continue;
    if( pParse->iSelfTab ){
      if( p->iDataCur!=pParse->iSelfTab-1 ) continue;
      iDataCur = -1;
    }
    if( sqlite3ExprCompare(0, pExpr, p->pExpr, iDataCur)!=0 ) continue;
    assert( p->aff>=SQLITE_AFF_BLOB && p->aff<=SQLITE_AFF_NUMERIC );
    exprAff = sqlite3ExprAffinity(pExpr);
    if( (exprAff<=SQLITE_AFF_BLOB && p->aff!=SQLITE_AFF_BLOB)
     || (exprAff==SQLITE_AFF_TEXT && p->aff!=SQLITE_AFF_TEXT)
     || (exprAff>=SQLITE_AFF_NUMERIC && p->aff!=SQLITE_AFF_NUMERIC)
    ){
      /* Affinity mismatch on a generated column */
      continue;
    }

    v = pParse->pVdbe;
    assert( v!=0 );
    if( p->bMaybeNullRow ){
      /* If the index is on a NULL row due to an outer join, then we
      ** cannot extract the value from the index.  The value must be
      ** computed using the original expression. */
      int addr = sqlite3VdbeCurrentAddr(v);

Changes to src/sqliteInt.h.

3658
3659
3660
3661
3662
3663
3664

3665
3666
3667
3668
3669
3670
3671
*/
struct IndexedExpr {
  Expr *pExpr;            /* The expression contained in the index */
  int iDataCur;           /* The data cursor associated with the index */
  int iIdxCur;            /* The index cursor */
  int iIdxCol;            /* The index column that contains value of pExpr */
  u8 bMaybeNullRow;       /* True if we need an OP_IfNullRow check */

  IndexedExpr *pIENext;   /* Next in a list of all indexed expressions */
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
  const char *zIdxName;   /* Name of index, used only for bytecode comments */
#endif
};

/*







>







3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
*/
struct IndexedExpr {
  Expr *pExpr;            /* The expression contained in the index */
  int iDataCur;           /* The data cursor associated with the index */
  int iIdxCur;            /* The index cursor */
  int iIdxCol;            /* The index column that contains value of pExpr */
  u8 bMaybeNullRow;       /* True if we need an OP_IfNullRow check */
  u8 aff;                 /* Affinity of the pExpr expression */
  IndexedExpr *pIENext;   /* Next in a list of all indexed expressions */
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
  const char *zIdxName;   /* Name of index, used only for bytecode comments */
#endif
};

/*

Changes to src/where.c.

5702
5703
5704
5705
5706
5707
5708



5709
5710
5711
5712
5713
5714
5715
    }
#endif
    p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0);
    p->iDataCur = pTabItem->iCursor;
    p->iIdxCur = iIdxCur;
    p->iIdxCol = i;
    p->bMaybeNullRow = bMaybeNullRow;



#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
    p->zIdxName = pIdx->zName;
#endif
    pParse->pIdxEpr = p;
    if( p->pIENext==0 ){
      sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse);
    }







>
>
>







5702
5703
5704
5705
5706
5707
5708
5709
5710
5711
5712
5713
5714
5715
5716
5717
5718
    }
#endif
    p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0);
    p->iDataCur = pTabItem->iCursor;
    p->iIdxCur = iIdxCur;
    p->iIdxCol = i;
    p->bMaybeNullRow = bMaybeNullRow;
    if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){
      p->aff = pIdx->zColAff[i];
    }
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
    p->zIdxName = pIdx->zName;
#endif
    pParse->pIdxEpr = p;
    if( p->pIENext==0 ){
      sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse);
    }

Changes to test/gencol1.test.

611
612
613
614
615
616
617













































618
  CREATE TABLE t0(a PRIMARY KEY,b TEXT AS ('2') UNIQUE);
  INSERT INTO t0(a) VALUES(2);
  SELECT * FROM t0 AS x JOIN t0 AS y
   WHERE x.b='2'
     AND (y.a=2 OR (x.b LIKE '2*' AND y.a=x.b));
} {2 2 2 2}














































finish_test







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
  CREATE TABLE t0(a PRIMARY KEY,b TEXT AS ('2') UNIQUE);
  INSERT INTO t0(a) VALUES(2);
  SELECT * FROM t0 AS x JOIN t0 AS y
   WHERE x.b='2'
     AND (y.a=2 OR (x.b LIKE '2*' AND y.a=x.b));
} {2 2 2 2}


# 2023-03-02 dbsqlfuzz 65f5eb57f8859344d5f1f33e08c77ee12960ed83
#
set typelist {ANY INT REAL BLOB TEXT {}}
set cnt 0
foreach t1 $typelist {
  foreach t2 $typelist {
    incr cnt
    db eval "
      DROP TABLE IF EXISTS t1;
      CREATE TABLE t1(
        x $t1,
        a $t2 AS (x) VIRTUAL,
        b BLOB AS (x) VIRTUAL
      );
      CREATE INDEX x2 ON t1(a);
      INSERT INTO t1(x) VALUES(NULL),('1'),(2),(3.5),('xyz');
    "
    set x1 [lsort [db eval {SELECT typeof(b) FROM t1}]]
    do_test gencol1-23.1.$cnt {
      lsort [db eval {SELECT typeof(b) FROM t1 INDEXED BY x2}]
    } $x1
  }
}
do_execsql_test gencol1-23.2 {
  DROP TABLE t1;
  CREATE TABLE t1(
    x,
    a INT AS (x) VIRTUAL,
    b BLOB AS (x) VIRTUAL
  );
  CREATE INDEX x2 ON t1(a);
  INSERT INTO t1(x) VALUES(NULL),('1'),('xyz'),(2),(3.5);
  SELECT quote(a) FROM t1 INDEXED BY x2;
} {NULL 1 2 3.5 'xyz'}
do_execsql_test gencol1-23.3 {
  EXPLAIN SELECT a FROM t1 INDEXED BY x2;
} {~/Column 0/}
#    ^^^^^^^^---- verfies that x2 acts like a covering index
do_execsql_test gencol1-23.4 {
  EXPLAIN SELECT b FROM t1 INDEXED BY x2;
} {/Column 0/}
#  ^^^^^^^^^^--- Must reference the original table in this case because
# of the different datatype on column b.

finish_test