SQLite

Check-in [c510377b]
Login

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

Overview
Comment:When flattening UNION ALL subqueries into a join query, ensure that separate cursor numbers are used for each segment of the newly flattened query.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | union-all-flattener
Files: files | file ages | folders
SHA3-256: c510377b0b052e400f2ee4f20220b61cdf74ee44b9bb9e6490787c88dd4c55aa
User & Date: dan 2020-12-18 16:13:39
Context
2020-12-18
18:04
Fix for the previous fix in the case where a UNION ALL sub-query is joined against some other compound query. (Closed-Leaf check-in: 63c5cfb9 user: dan tags: union-all-flattener)
16:13
When flattening UNION ALL subqueries into a join query, ensure that separate cursor numbers are used for each segment of the newly flattened query. (check-in: c510377b user: dan tags: union-all-flattener)
2020-12-17
17:17
Fix a part of the header comment for flattenSubquery(). (check-in: dc0937ce user: dan tags: union-all-flattener)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/select.c.

3645
3646
3647
3648
3649
3650
3651
















































































3652
3653
3654
3655
3656
3657
3658
  w.xSelectCallback = sqlite3SelectWalkNoop;
  w.u.pSrcItem = pSrcItem;
  pSrcItem->colUsed = 0;
  sqlite3WalkSelect(&w, pSelect);
}
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */

















































































#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
/*
** This routine attempts to flatten subqueries as a performance optimization.
** This routine returns 1 if it makes changes and 0 if no flattening occurs.
**
** To understand the concept of flattening, consider the following
** query:







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







3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
  w.xSelectCallback = sqlite3SelectWalkNoop;
  w.u.pSrcItem = pSrcItem;
  pSrcItem->colUsed = 0;
  sqlite3WalkSelect(&w, pSelect);
}
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */

#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
/*
** Assign new cursor numbers to each of the items in pSrc. For each
** new cursor number assigned, set an entry in the aCsrMap[] array 
** to map the old cursor number to the new:
**
**     aCsrMap[iOld] = iNew;
**
** The array is guaranteed by the caller to be large enough for all
** existing cursor numbers in pSrc.
**
** If pSrc contains any sub-selects, call this routine recursively
** on the FROM clause of each such sub-select, with iExcept set to -1.
*/
static void srclistRenumberCursors(
  Parse *pParse,                  /* Parse context */
  int *aCsrMap,                   /* Array to store cursor mappings in */
  SrcList *pSrc,                  /* FROM clause to renumber */
  int iExcept                     /* FROM clause item to skip */
){
  int i;
  struct SrcList_item *pItem;
  for(i=0, pItem=pSrc->a; i<pSrc->nSrc; i++, pItem++){
    if( i!=iExcept ){
      int iNew = pParse->nTab++;
      aCsrMap[pItem->iCursor] = iNew;
      pItem->iCursor = iNew;
      if( pItem->pSelect ){
        srclistRenumberCursors(pParse, aCsrMap, pItem->pSelect->pSrc, -1);
      }
    }
  }
}

/*
** Expression walker callback used by renumberCursors() to update
** Expr objects to match newly assigned cursor numbers.
*/
static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){
  int *aCsrMap = pWalker->u.aiCol;
  if( pExpr->op==TK_COLUMN && aCsrMap[pExpr->iTable] ){
    pExpr->iTable = aCsrMap[pExpr->iTable];
  }
  return WRC_Continue;
}

/*
** Assign a new cursor number to each cursor in the FROM clause (Select.pSrc)
** of the SELECT statement passed as the second argument, and to each 
** cursor in the FROM clause of any FROM clause sub-selects, recursively.
** Except, do not assign a new cursor number to the iExcept'th element in
** the FROM clause of (*p). Update all expressions and other references 
** to refer to the new cursor numbers.
**
** Argument aCsrMap is an array that may be used for temporary working
** space. Two guarantees are made by the caller:
**
**   * the array is larger than the largest cursor number used within the
**     select statement passed as an argument, and
**
**   * the array entries for all cursor numbers that do *not* appear in 
**     FROM clauses of the select statement as described above are 
**     initialized to zero.
*/
static void renumberCursors(
  Parse *pParse,                  /* Parse context */
  Select *p,                      /* Select to renumber cursors within */
  int iExcept,                    /* FROM clause item to skip */
  int *aCsrMap                    /* Working space */
){
  Walker w;
  srclistRenumberCursors(pParse, aCsrMap, p->pSrc, iExcept);
  memset(&w, 0, sizeof(w));
  w.u.aiCol = aCsrMap;
  w.xExprCallback = renumberCursorsCb;
  w.xSelectCallback = sqlite3SelectWalkNoop;
  sqlite3WalkSelect(&w, p);
}
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */

#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
/*
** This routine attempts to flatten subqueries as a performance optimization.
** This routine returns 1 if it makes changes and 0 if no flattening occurs.
**
** To understand the concept of flattening, consider the following
** query:
3820
3821
3822
3823
3824
3825
3826

3827
3828
3829
3830
3831
3832
3833
  int iNewParent = -1;/* Replacement table for iParent */
  int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */    
  int i;              /* Loop counter */
  Expr *pWhere;                    /* The WHERE clause */
  struct SrcList_item *pSubitem;   /* The subquery */
  sqlite3 *db = pParse->db;
  Walker w;                        /* Walker to persist agginfo data */


  /* Check to see if flattening is permitted.  Return 0 if not.
  */
  assert( p!=0 );
  assert( p->pPrior==0 );
  if( OptimizationDisabled(db, SQLITE_QueryFlattener) ) return 0;
  pSrc = p->pSrc;







>







3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
  int iNewParent = -1;/* Replacement table for iParent */
  int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */    
  int i;              /* Loop counter */
  Expr *pWhere;                    /* The WHERE clause */
  struct SrcList_item *pSubitem;   /* The subquery */
  sqlite3 *db = pParse->db;
  Walker w;                        /* Walker to persist agginfo data */
  int *aCsrMap = 0;

  /* Check to see if flattening is permitted.  Return 0 if not.
  */
  assert( p!=0 );
  assert( p->pPrior==0 );
  if( OptimizationDisabled(db, SQLITE_QueryFlattener) ) return 0;
  pSrc = p->pSrc;
3946
3947
3948
3949
3950
3951
3952




3953
3954
3955
3956
3957
3958
3959
      for(ii=0; ii<p->pOrderBy->nExpr; ii++){
        if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0;
      }
    }

    /* Restriction (23) */
    if( (p->selFlags & SF_Recursive) ) return 0;




  }

  /***** If we reach this point, flattening is permitted. *****/
  SELECTTRACE(1,pParse,p,("flatten %u.%p from term %d\n",
                   pSub->selId, pSub, iFrom));

  /* Authorize the subquery */







>
>
>
>







4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
      for(ii=0; ii<p->pOrderBy->nExpr; ii++){
        if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0;
      }
    }

    /* Restriction (23) */
    if( (p->selFlags & SF_Recursive) ) return 0;

    if( pSrc->nSrc>1 ){
      aCsrMap = sqlite3DbMallocZero(db, pParse->nTab*sizeof(int));
    }
  }

  /***** If we reach this point, flattening is permitted. *****/
  SELECTTRACE(1,pParse,p,("flatten %u.%p from term %d\n",
                   pSub->selId, pSub, iFrom));

  /* Authorize the subquery */
4020
4021
4022
4023
4024
4025
4026



4027
4028
4029
4030
4031
4032
4033
4034


4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
    p->pLimit = pLimit;
    p->pOrderBy = pOrderBy;
    p->op = TK_ALL;
    pSubitem->pTab = pItemTab;
    if( pNew==0 ){
      p->pPrior = pPrior;
    }else{



      pNew->pPrior = pPrior;
      if( pPrior ) pPrior->pNext = pNew;
      pNew->pNext = p;
      p->pPrior = pNew;
      SELECTTRACE(2,pParse,p,("compound-subquery flattener"
                              " creates %u as peer\n",pNew->selId));
    }
    assert( pSubitem->pSelect==0 );


    if( db->mallocFailed ){
      pSubitem->pSelect = pSub1;
      return 1;
    }
  }

  /* Defer deleting the Table object associated with the
  ** subquery until code generation is
  ** complete, since there may still exist Expr.pTab entries that
  ** refer to the subquery even after flattening.  Ticket #3346.
  **







>
>
>








>
>
|
|
|
<







4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127

4128
4129
4130
4131
4132
4133
4134
    p->pLimit = pLimit;
    p->pOrderBy = pOrderBy;
    p->op = TK_ALL;
    pSubitem->pTab = pItemTab;
    if( pNew==0 ){
      p->pPrior = pPrior;
    }else{
      if( aCsrMap && db->mallocFailed==0 ){
        renumberCursors(pParse, pNew, iFrom, aCsrMap);
      }
      pNew->pPrior = pPrior;
      if( pPrior ) pPrior->pNext = pNew;
      pNew->pNext = p;
      p->pPrior = pNew;
      SELECTTRACE(2,pParse,p,("compound-subquery flattener"
                              " creates %u as peer\n",pNew->selId));
    }
    assert( pSubitem->pSelect==0 );
  }
  sqlite3DbFree(db, aCsrMap);
  if( db->mallocFailed ){
    pSubitem->pSelect = pSub1;
    return 1;

  }

  /* Defer deleting the Table object associated with the
  ** subquery until code generation is
  ** complete, since there may still exist Expr.pTab entries that
  ** refer to the subquery even after flattening.  Ticket #3346.
  **

Changes to test/unionall.test.

161
162
163
164
165
166
167































168
169
170
  SELECT * FROM t1, t2 WHERE x=k ORDER BY k,v||''
} {
  2 two   2 ii
  2 TWO   2 ii 
  3 three 3 iii 
  3 THREE 3 iii 
}

































finish_test







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



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
  SELECT * FROM t1, t2 WHERE x=k ORDER BY k,v||''
} {
  2 two   2 ii
  2 TWO   2 ii 
  3 three 3 iii 
  3 THREE 3 iii 
}
do_execsql_test 2.2.9a {
  SELECT * FROM t1, t2 ORDER BY +k
} {
  1 one 2 ii 1 ONE 2 ii 2 two 2 ii 
  2 TWO 2 ii 3 three 2 ii 3 THREE 2 ii 
  
  1 one 3 iii 1 ONE 3 iii 2 two 3 iii 
  2 TWO 3 iii 3 three 3 iii 3 THREE 3 iii 

  1 one 4 iv 1 ONE 4 iv 2 two 4 iv 
  2 TWO 4 iv 3 three 4 iv 3 THREE 4 iv 

  1 one 5 v 1 ONE 5 v 2 two 5 v 
  2 TWO 5 v 3 three 5 v 3 THREE 5 v
}

do_execsql_test 2.2.9b {
  SELECT * FROM t1, t2 ORDER BY k
} {
  1 one 2 ii 1 ONE 2 ii 2 two 2 ii 
  2 TWO 2 ii 3 three 2 ii 3 THREE 2 ii 
  
  1 one 3 iii 1 ONE 3 iii 2 two 3 iii 
  2 TWO 3 iii 3 three 3 iii 3 THREE 3 iii 

  1 one 4 iv 1 ONE 4 iv 2 two 4 iv 
  2 TWO 4 iv 3 three 4 iv 3 THREE 4 iv 

  1 one 5 v 1 ONE 5 v 2 two 5 v 
  2 TWO 5 v 3 three 5 v 3 THREE 5 v
}


finish_test

Changes to test/unionallfault.test.

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
faultsim_save_and_close


do_faultsim_test 1 -faults oom-t* -prep {
  faultsim_restore_and_reopen
} -body {
  execsql {
    SELECT * FROM (
      SELECT x FROM t1 UNION ALL SELECT y FROM t1
    )
  }
} -test {
  faultsim_test_result {0 {}}
}

finish_test







|

|






21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
faultsim_save_and_close


do_faultsim_test 1 -faults oom-t* -prep {
  faultsim_restore_and_reopen
} -body {
  execsql {
    SELECT * FROM t1, (
      SELECT x FROM t1 UNION ALL SELECT y FROM t1
    ), t3
  }
} -test {
  faultsim_test_result {0 {}}
}

finish_test