/ Check-in [492b39b6]
Login

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

Overview
Comment:Make sure that INSERT INTO ... SELECT ... always uses an ephemeral intermediate table if the SELECT clause refers to the destination table, even if the SELECT clause is compound or uses the destination table in a subquery. This fixes a long-standing bug that can cause an infinite loop for some SQL statements. (CVS 4552)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 492b39b6a8bf4ad8792d7a7949f77827a5047fd8
User & Date: drh 2007-11-23 15:02:19
Context
2007-11-23
15:12
Remove most documentation from the source code repository. Documentation configuration management is now handled by a separate repository at http://www.sqlite.org/docsrc. (CVS 4553) check-in: b20f3a6f user: drh tags: trunk
15:02
Make sure that INSERT INTO ... SELECT ... always uses an ephemeral intermediate table if the SELECT clause refers to the destination table, even if the SELECT clause is compound or uses the destination table in a subquery. This fixes a long-standing bug that can cause an infinite loop for some SQL statements. (CVS 4552) check-in: 492b39b6 user: drh tags: trunk
13:42
Avoid a double-free in an out-of-memory situation with a USING clause or NATURAL JOIN. Ticket #2789. (CVS 4551) check-in: 59669475 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/insert.c.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
95
96
97
98
99
100
101
102
103
104

105
106
107
108
109

110

111
112
113
114
115
116
117
















118
119
120
121
122
123
124
...
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
...
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle INSERT statements in SQLite.
**
** $Id: insert.c,v 1.192 2007/09/03 17:30:07 danielk1977 Exp $
*/
#include "sqliteInt.h"

/*
** Set P3 of the most recently inserted opcode to a column affinity
** string for index pIdx. A column affinity string has one character
** for each column in the table, according to the affinity of the column:
................................................................................
    pTab->zColAff = zColAff;
  }

  sqlite3VdbeChangeP3(v, -1, pTab->zColAff, 0);
}

/*
** Return non-zero if SELECT statement p opens the table with rootpage
** iTab in database iDb.  This is used to see if a statement of the form 
** "INSERT INTO <iDb, iTab> SELECT ..." can run without using temporary

** table for the results of the SELECT. 
**
** No checking is done for sub-selects that are part of expressions.
*/
static int selectReadsTable(Select *p, Schema *pSchema, int iTab){

  int i;

  struct SrcList_item *pItem;
  if( p->pSrc==0 ) return 0;
  for(i=0, pItem=p->pSrc->a; i<p->pSrc->nSrc; i++, pItem++){
    if( pItem->pSelect ){
      if( selectReadsTable(pItem->pSelect, pSchema, iTab) ) return 1;
    }else{
      if( pItem->pTab->pSchema==pSchema && pItem->pTab->tnum==iTab ) return 1;
















    }
  }
  return 0;
}

#ifndef SQLITE_OMIT_AUTOINCREMENT
/*
................................................................................
    ** should be written into a temporary table.  Set to FALSE if each
    ** row of the SELECT can be written directly into the result table.
    **
    ** A temp table must be used if the table being updated is also one
    ** of the tables being read by the SELECT statement.  Also use a 
    ** temp table in the case of row triggers.
    */
    if( triggers_exist || selectReadsTable(pSelect,pTab->pSchema,pTab->tnum) ){
      useTempTable = 1;
    }

    if( useTempTable ){
      /* Generate the subroutine that SELECT calls to process each row of
      ** the result.  Store the result in a temporary table
      */
................................................................................
    /* This is the case if the data for the INSERT is coming from a VALUES
    ** clause
    */
    NameContext sNC;
    memset(&sNC, 0, sizeof(sNC));
    sNC.pParse = pParse;
    srcTab = -1;
    useTempTable = 0;
    nColumn = pList ? pList->nExpr : 0;
    for(i=0; i<nColumn; i++){
      if( sqlite3ExprResolveNames(&sNC, pList->a[i].pExpr) ){
        goto insert_cleanup;
      }
    }
  }







|







 







|
|
|
>
|
<
<

<
>

>
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
95
96
97
98
99
100
101
102
103
104
105
106


107

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
...
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
...
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle INSERT statements in SQLite.
**
** $Id: insert.c,v 1.193 2007/11/23 15:02:19 drh Exp $
*/
#include "sqliteInt.h"

/*
** Set P3 of the most recently inserted opcode to a column affinity
** string for index pIdx. A column affinity string has one character
** for each column in the table, according to the affinity of the column:
................................................................................
    pTab->zColAff = zColAff;
  }

  sqlite3VdbeChangeP3(v, -1, pTab->zColAff, 0);
}

/*
** Return non-zero if the table pTab in database iDb or any of its indices
** have been opened at any point in the VDBE program beginning at location
** iStartAddr throught the end of the program.  This is used to see if 
** a statement of the form  "INSERT INTO <iDb, pTab> SELECT ..." can 
** run without using temporary table for the results of the SELECT. 


*/

static int readsTable(Vdbe *v, int iStartAddr, int iDb, Table *pTab){
  int i;
  int iEnd = sqlite3VdbeCurrentAddr(v);
  for(i=iStartAddr; i<iEnd; i++){
    VdbeOp *pOp = sqlite3VdbeGetOp(v, i);
    if( pOp->opcode==OP_OpenRead ){
      VdbeOp *pPrior = &pOp[-1];
      int tnum = pOp->p2;
      assert( i>iStartAddr );
      assert( pPrior->opcode==OP_Integer );
      if( pPrior->p1==iDb ){
        Index *pIndex;
        if( tnum==pTab->tnum ){
          return 1;
        }
        for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){
          if( tnum==pIndex->tnum ){
            return 1;
          }
        }
      }
    }
    if( pOp->opcode==OP_VOpen && pOp->p3==(const char*)pTab->pVtab ){
      assert( pOp->p3!=0 );
      assert( pOp->p3type==P3_VTAB );
      return 1;
    }
  }
  return 0;
}

#ifndef SQLITE_OMIT_AUTOINCREMENT
/*
................................................................................
    ** should be written into a temporary table.  Set to FALSE if each
    ** row of the SELECT can be written directly into the result table.
    **
    ** A temp table must be used if the table being updated is also one
    ** of the tables being read by the SELECT statement.  Also use a 
    ** temp table in the case of row triggers.
    */
    if( triggers_exist || readsTable(v, iSelectLoop, iDb, pTab) ){
      useTempTable = 1;
    }

    if( useTempTable ){
      /* Generate the subroutine that SELECT calls to process each row of
      ** the result.  Store the result in a temporary table
      */
................................................................................
    /* This is the case if the data for the INSERT is coming from a VALUES
    ** clause
    */
    NameContext sNC;
    memset(&sNC, 0, sizeof(sNC));
    sNC.pParse = pParse;
    srcTab = -1;
    assert( useTempTable==0 );
    nColumn = pList ? pList->nExpr : 0;
    for(i=0; i<nColumn; i++){
      if( sqlite3ExprResolveNames(&sNC, pList->a[i].pExpr) ){
        goto insert_cleanup;
      }
    }
  }

Added test/insert5.test.























































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# 2007 November 23
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# The tests in this file ensure that a temporary table is used
# when required by an "INSERT INTO ... SELECT ..." statement.
#
# $Id: insert5.test,v 1.1 2007/11/23 15:02:19 drh Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

# Return true if the compilation of the sql passed as an argument 
# includes the opcode OpenEphemeral. An "INSERT INTO ... SELECT"
# statement includes such an opcode if a temp-table is used
# to store intermediate results.
# 
proc uses_temp_table {sql} {
  return [expr {[lsearch [execsql "EXPLAIN $sql"] OpenEphemeral]>=0}]
}

# Construct the sample database.
#
do_test insert5-1.0 {
  file delete -force test2.db test2.db-journal
  execsql {
    CREATE TABLE MAIN(Id INTEGER, Id1 INTEGER); 
    CREATE TABLE B(Id INTEGER, Id1 INTEGER); 
    CREATE VIEW v1 AS SELECT * FROM B;
    CREATE VIEW v2 AS SELECT * FROM MAIN;
    INSERT INTO MAIN(Id,Id1) VALUES(2,3); 
    INSERT INTO B(Id,Id1) VALUES(2,3); 
  }
} {}

# Run the query.
#
do_test insert5-1.1 {
  execsql {
    INSERT INTO B 
      SELECT * FROM B UNION ALL 
      SELECT * FROM MAIN WHERE exists (select * FROM B WHERE B.Id = MAIN.Id);
    SELECT * FROM B;
  }
} {2 3 2 3 2 3}

do_test insert5-2.1 {
  uses_temp_table { INSERT INTO b SELECT * FROM main }
} {0}
do_test insert5-2.2 {
  uses_temp_table { INSERT INTO b SELECT * FROM b }
} {1}
do_test insert5-2.3 {
  uses_temp_table { INSERT INTO b SELECT (SELECT id FROM b), id1 FROM main }
} {1}
do_test insert5-2.4 {
  uses_temp_table { INSERT INTO b SELECT id1, (SELECT id FROM b) FROM main }
} {1}
do_test insert5-2.5 {
  uses_temp_table { 
    INSERT INTO b 
      SELECT * FROM main WHERE id = (SELECT id1 FROM b WHERE main.id = b.id) }
} {1}
do_test insert5-2.6 {
  uses_temp_table { INSERT INTO b SELECT * FROM v1 }
} {1}
do_test insert5-2.7 {
  uses_temp_table { INSERT INTO b SELECT * FROM v2 }
} {0}
do_test insert5-2.8 {
  uses_temp_table { 
    INSERT INTO b 
    SELECT * FROM main WHERE id > 10 AND max(id1, (SELECT id FROM b)) > 10;
  }
} {1}
do_test insert5-2.9 {
  uses_temp_table { 
    INSERT INTO b 
    SELECT * FROM main 
    WHERE id > 10 AND (SELECT count(*) FROM v2 GROUP BY main.id)
  }
} {1}

finish_test