Home | History | Annotate | Download | only in test
      1 # 2008 June 24
      2 #
      3 # The author disclaims copyright to this source code.  In place of
      4 # a legal notice, here is a blessing:
      5 #
      6 #    May you do good and not evil.
      7 #    May you find forgiveness for yourself and forgive others.
      8 #    May you share freely, never taking more than you give.
      9 #
     10 #***********************************************************************
     11 # This file implements regression tests for SQLite library. 
     12 #
     13 # $Id: select9.test,v 1.4 2008/07/01 14:39:35 danielk1977 Exp $
     14 
     15 # The tests in this file are focused on test compound SELECT statements 
     16 # that have any or all of an ORDER BY, LIMIT or OFFSET clauses. As of
     17 # version 3.6.0, SQLite contains code to use SQL indexes where possible 
     18 # to optimize such statements.
     19 #
     20 
     21 # TODO Points:
     22 #
     23 #   * Are there any "column affinity" issues to consider?
     24 
     25 set testdir [file dirname $argv0]
     26 source $testdir/tester.tcl
     27 
     28 #-------------------------------------------------------------------------
     29 # test_compound_select TESTNAME SELECT RESULT
     30 #
     31 #   This command is used to run multiple LIMIT/OFFSET test cases based on 
     32 #   the single SELECT statement passed as the second argument. The SELECT
     33 #   statement may not contain a LIMIT or OFFSET clause. This proc tests
     34 #   many statements of the form:
     35 #    
     36 #     "$SELECT limit $X offset $Y"
     37 #    
     38 #   for various values of $X and $Y.
     39 #    
     40 #   The third argument, $RESULT, should contain the expected result of
     41 #   the command [execsql $SELECT].
     42 #    
     43 #   The first argument, $TESTNAME, is used as the base test case name to
     44 #   pass to [do_test] for each individual LIMIT OFFSET test case.
     45 # 
     46 proc test_compound_select {testname sql result} {
     47 
     48   set nCol 1
     49   db eval $sql A {
     50     set nCol [llength $A(*)]
     51     break
     52   }
     53   set nRow [expr {[llength $result] / $nCol}]
     54 
     55   set ::compound_sql $sql
     56   do_test $testname { 
     57     execsql $::compound_sql
     58   } $result
     59 #return
     60 
     61   set iLimitIncr  1
     62   set iOffsetIncr 1
     63   if {[info exists ::G(isquick)] && $::G(isquick) && $nRow>=5} {
     64     set iOffsetIncr [expr $nRow / 5]
     65     set iLimitIncr [expr $nRow / 5]
     66   }
     67 
     68   set iLimitEnd   [expr $nRow+$iLimitIncr]
     69   set iOffsetEnd  [expr $nRow+$iOffsetIncr]
     70 
     71   for {set iOffset 0} {$iOffset < $iOffsetEnd} {incr iOffset $iOffsetIncr} {
     72     for {set iLimit 0} {$iLimit < $iLimitEnd} {incr iLimit} {
     73   
     74       set ::compound_sql "$sql LIMIT $iLimit"
     75       if {$iOffset != 0} {
     76         append ::compound_sql " OFFSET $iOffset"
     77       }
     78   
     79       set iStart [expr {$iOffset*$nCol}]
     80       set iEnd [expr {($iOffset*$nCol) + ($iLimit*$nCol) -1}]
     81   
     82       do_test $testname.limit=$iLimit.offset=$iOffset { 
     83         execsql $::compound_sql
     84       } [lrange $result $iStart $iEnd]
     85     }
     86   }
     87 }
     88 
     89 #-------------------------------------------------------------------------
     90 # test_compound_select_flippable TESTNAME SELECT RESULT
     91 #
     92 #   This command is for testing statements of the form:
     93 #
     94 #     <simple select 1> <compound op> <simple select 2> ORDER BY <order by>
     95 #
     96 #   where each <simple select> is a simple (non-compound) select statement
     97 #   and <compound op> is one of "INTERSECT", "UNION ALL" or "UNION".
     98 #
     99 #   This proc calls [test_compound_select] twice, once with the select
    100 #   statement as it is passed to this command, and once with the positions
    101 #   of <select statement 1> and <select statement 2> exchanged.
    102 #
    103 proc test_compound_select_flippable {testname sql result} {
    104   test_compound_select $testname $sql $result
    105 
    106   set select [string trim $sql]
    107   set RE {(.*)(UNION ALL|INTERSECT|UNION)(.*)(ORDER BY.*)}
    108   set rc [regexp $RE $select -> s1 op s2 order_by]
    109   if {!$rc} {error "Statement is unflippable: $select"}
    110 
    111   set flipsql "$s2 $op $s1 $order_by"
    112   test_compound_select $testname.flipped $flipsql $result
    113 }
    114 
    115 #############################################################################
    116 # Begin tests.
    117 #
    118 
    119 # Create and populate a sample database.
    120 #
    121 do_test select9-1.0 {
    122   execsql {
    123     CREATE TABLE t1(a, b, c);
    124     CREATE TABLE t2(d, e, f);
    125     BEGIN;
    126       INSERT INTO t1 VALUES(1,  'one',   'I');
    127       INSERT INTO t1 VALUES(3,  NULL,    NULL);
    128       INSERT INTO t1 VALUES(5,  'five',  'V');
    129       INSERT INTO t1 VALUES(7,  'seven', 'VII');
    130       INSERT INTO t1 VALUES(9,  NULL,    NULL);
    131       INSERT INTO t1 VALUES(2,  'two',   'II');
    132       INSERT INTO t1 VALUES(4,  'four',  'IV');
    133       INSERT INTO t1 VALUES(6,  NULL,    NULL);
    134       INSERT INTO t1 VALUES(8,  'eight', 'VIII');
    135       INSERT INTO t1 VALUES(10, 'ten',   'X');
    136 
    137       INSERT INTO t2 VALUES(1,  'two',      'IV');
    138       INSERT INTO t2 VALUES(2,  'four',     'VIII');
    139       INSERT INTO t2 VALUES(3,  NULL,       NULL);
    140       INSERT INTO t2 VALUES(4,  'eight',    'XVI');
    141       INSERT INTO t2 VALUES(5,  'ten',      'XX');
    142       INSERT INTO t2 VALUES(6,  NULL,       NULL);
    143       INSERT INTO t2 VALUES(7,  'fourteen', 'XXVIII');
    144       INSERT INTO t2 VALUES(8,  'sixteen',  'XXXII');
    145       INSERT INTO t2 VALUES(9,  NULL,       NULL);
    146       INSERT INTO t2 VALUES(10, 'twenty',   'XL');
    147 
    148     COMMIT;
    149   }
    150 } {}
    151 
    152 # Each iteration of this loop runs the same tests with a different set
    153 # of indexes present within the database schema. The data returned by
    154 # the compound SELECT statements in the test cases should be the same 
    155 # in each case.
    156 #
    157 set iOuterLoop 1
    158 foreach indexes [list {
    159   /* Do not create any indexes. */
    160 } {
    161   CREATE INDEX i1 ON t1(a)
    162 } {
    163   CREATE INDEX i2 ON t1(b)
    164 } {
    165   CREATE INDEX i3 ON t2(d)
    166 } {
    167   CREATE INDEX i4 ON t2(e)
    168 }] {
    169 
    170   do_test select9-1.$iOuterLoop.1 {
    171     execsql $indexes
    172   } {}
    173 
    174   # Test some 2-way UNION ALL queries. No WHERE clauses.
    175   #
    176   test_compound_select select9-1.$iOuterLoop.2 {
    177     SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 
    178   } {1 one 3 {} 5 five 7 seven 9 {} 2 two 4 four 6 {} 8 eight 10 ten 1 two 2 four 3 {} 4 eight 5 ten 6 {} 7 fourteen 8 sixteen 9 {} 10 twenty}
    179   test_compound_select select9-1.$iOuterLoop.3 {
    180     SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 ORDER BY 1 
    181   } {1 one 1 two 2 two 2 four 3 {} 3 {} 4 four 4 eight 5 five 5 ten 6 {} 6 {} 7 seven 7 fourteen 8 eight 8 sixteen 9 {} 9 {} 10 ten 10 twenty}
    182   test_compound_select select9-1.$iOuterLoop.4 {
    183     SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 ORDER BY 2 
    184   } {3 {} 9 {} 6 {} 3 {} 6 {} 9 {} 8 eight 4 eight 5 five 4 four 2 four 7 fourteen 1 one 7 seven 8 sixteen 10 ten 5 ten 10 twenty 2 two 1 two}
    185   test_compound_select_flippable select9-1.$iOuterLoop.5 {
    186     SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 ORDER BY 1, 2
    187   } {1 one 1 two 2 four 2 two 3 {} 3 {} 4 eight 4 four 5 five 5 ten 6 {} 6 {} 7 fourteen 7 seven 8 eight 8 sixteen 9 {} 9 {} 10 ten 10 twenty}
    188   test_compound_select_flippable select9-1.$iOuterLoop.6 {
    189     SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 ORDER BY 2, 1
    190   } {3 {} 3 {} 6 {} 6 {} 9 {} 9 {} 4 eight 8 eight 5 five 2 four 4 four 7 fourteen 1 one 7 seven 8 sixteen 5 ten 10 ten 10 twenty 1 two 2 two}
    191 
    192   # Test some 2-way UNION queries.
    193   #
    194   test_compound_select select9-1.$iOuterLoop.7 {
    195     SELECT a, b FROM t1 UNION SELECT d, e FROM t2 
    196   } {1 one 1 two 2 four 2 two 3 {} 4 eight 4 four 5 five 5 ten 6 {} 7 fourteen 7 seven 8 eight 8 sixteen 9 {} 10 ten 10 twenty}
    197 
    198   test_compound_select select9-1.$iOuterLoop.8 {
    199     SELECT a, b FROM t1 UNION SELECT d, e FROM t2 ORDER BY 1 
    200   } {1 one 1 two 2 four 2 two 3 {} 4 eight 4 four 5 five 5 ten 6 {} 7 fourteen 7 seven 8 eight 8 sixteen 9 {} 10 ten 10 twenty}
    201 
    202   test_compound_select select9-1.$iOuterLoop.9 {
    203     SELECT a, b FROM t1 UNION SELECT d, e FROM t2 ORDER BY 2 
    204   } {3 {} 6 {} 9 {} 4 eight 8 eight 5 five 2 four 4 four 7 fourteen 1 one 7 seven 8 sixteen 5 ten 10 ten 10 twenty 1 two 2 two}
    205 
    206   test_compound_select_flippable select9-1.$iOuterLoop.10 {
    207     SELECT a, b FROM t1 UNION SELECT d, e FROM t2 ORDER BY 1, 2
    208   } {1 one 1 two 2 four 2 two 3 {} 4 eight 4 four 5 five 5 ten 6 {} 7 fourteen 7 seven 8 eight 8 sixteen 9 {} 10 ten 10 twenty}
    209 
    210   test_compound_select_flippable select9-1.$iOuterLoop.11 {
    211     SELECT a, b FROM t1 UNION SELECT d, e FROM t2 ORDER BY 2, 1
    212   } {3 {} 6 {} 9 {} 4 eight 8 eight 5 five 2 four 4 four 7 fourteen 1 one 7 seven 8 sixteen 5 ten 10 ten 10 twenty 1 two 2 two}
    213 
    214   # Test some 2-way INTERSECT queries.
    215   #
    216   test_compound_select select9-1.$iOuterLoop.11 {
    217     SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 
    218   } {3 {} 6 {} 9 {}}
    219   test_compound_select_flippable select9-1.$iOuterLoop.12 {
    220     SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 ORDER BY 1
    221   } {3 {} 6 {} 9 {}}
    222   test_compound_select select9-1.$iOuterLoop.13 {
    223     SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 ORDER BY 2
    224   } {3 {} 6 {} 9 {}}
    225   test_compound_select_flippable select9-1.$iOuterLoop.14 {
    226     SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 ORDER BY 2, 1
    227   } {3 {} 6 {} 9 {}}
    228   test_compound_select_flippable select9-1.$iOuterLoop.15 {
    229     SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 ORDER BY 1, 2
    230   } {3 {} 6 {} 9 {}}
    231 
    232   # Test some 2-way EXCEPT queries.
    233   #
    234   test_compound_select select9-1.$iOuterLoop.16 {
    235     SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 
    236   } {1 one 2 two 4 four 5 five 7 seven 8 eight 10 ten}
    237 
    238   test_compound_select select9-1.$iOuterLoop.17 {
    239     SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 ORDER BY 1 
    240   } {1 one 2 two 4 four 5 five 7 seven 8 eight 10 ten}
    241 
    242   test_compound_select select9-1.$iOuterLoop.18 {
    243     SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 ORDER BY 2 
    244   } {8 eight 5 five 4 four 1 one 7 seven 10 ten 2 two}
    245 
    246   test_compound_select select9-1.$iOuterLoop.19 {
    247     SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 ORDER BY 1, 2
    248   } {1 one 2 two 4 four 5 five 7 seven 8 eight 10 ten}
    249 
    250   test_compound_select select9-1.$iOuterLoop.20 {
    251     SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 ORDER BY 2, 1
    252   } {8 eight 5 five 4 four 1 one 7 seven 10 ten 2 two}
    253 
    254   incr iOuterLoop
    255 }
    256 
    257 do_test select9-2.0 {
    258   execsql {
    259     DROP INDEX i1;
    260     DROP INDEX i2;
    261     DROP INDEX i3;
    262     DROP INDEX i4;
    263   }
    264 } {}
    265 
    266 proc reverse {lhs rhs} {
    267   return [string compare $rhs $lhs]
    268 }
    269 db collate reverse reverse
    270 
    271 # This loop is similar to the previous one (test cases select9-1.*) 
    272 # except that the simple select statements have WHERE clauses attached
    273 # to them. Sometimes the WHERE clause may be satisfied using the same
    274 # index used for ORDER BY, sometimes not.
    275 #
    276 set iOuterLoop 1
    277 foreach indexes [list {
    278   /* Do not create any indexes. */
    279 } {
    280   CREATE INDEX i1 ON t1(a)
    281 } {
    282   DROP INDEX i1;
    283   CREATE INDEX i1 ON t1(b, a)
    284 } {
    285   CREATE INDEX i2 ON t2(d DESC, e COLLATE REVERSE ASC);
    286 } {
    287   CREATE INDEX i3 ON t1(a DESC);
    288 }] {
    289   do_test select9-2.$iOuterLoop.1 {
    290     execsql $indexes
    291   } {}
    292 
    293   test_compound_select_flippable select9-2.$iOuterLoop.2 {
    294     SELECT * FROM t1 WHERE a<5 UNION SELECT * FROM t2 WHERE d>=5 ORDER BY 1
    295   } {1 one I 2 two II 3 {} {} 4 four IV 5 ten XX 6 {} {} 7 fourteen XXVIII 8 sixteen XXXII 9 {} {} 10 twenty XL}
    296 
    297   test_compound_select_flippable select9-2.$iOuterLoop.2 {
    298     SELECT * FROM t1 WHERE a<5 UNION SELECT * FROM t2 WHERE d>=5 ORDER BY 2, 1
    299   } {3 {} {} 6 {} {} 9 {} {} 4 four IV 7 fourteen XXVIII 1 one I 8 sixteen XXXII 5 ten XX 10 twenty XL 2 two II}
    300 
    301   test_compound_select_flippable select9-2.$iOuterLoop.3 {
    302     SELECT * FROM t1 WHERE a<5 UNION SELECT * FROM t2 WHERE d>=5 
    303     ORDER BY 2 COLLATE reverse, 1
    304   } {3 {} {} 6 {} {} 9 {} {} 2 two II 10 twenty XL 5 ten XX 8 sixteen XXXII 1 one I 7 fourteen XXVIII 4 four IV}
    305 
    306   test_compound_select_flippable select9-2.$iOuterLoop.4 {
    307     SELECT * FROM t1 WHERE a<5 UNION ALL SELECT * FROM t2 WHERE d>=5 ORDER BY 1
    308   } {1 one I 2 two II 3 {} {} 4 four IV 5 ten XX 6 {} {} 7 fourteen XXVIII 8 sixteen XXXII 9 {} {} 10 twenty XL}
    309 
    310   test_compound_select_flippable select9-2.$iOuterLoop.5 {
    311     SELECT * FROM t1 WHERE a<5 UNION ALL SELECT * FROM t2 WHERE d>=5 ORDER BY 2, 1
    312   } {3 {} {} 6 {} {} 9 {} {} 4 four IV 7 fourteen XXVIII 1 one I 8 sixteen XXXII 5 ten XX 10 twenty XL 2 two II}
    313 
    314   test_compound_select_flippable select9-2.$iOuterLoop.6 {
    315     SELECT * FROM t1 WHERE a<5 UNION ALL SELECT * FROM t2 WHERE d>=5 
    316     ORDER BY 2 COLLATE reverse, 1
    317   } {3 {} {} 6 {} {} 9 {} {} 2 two II 10 twenty XL 5 ten XX 8 sixteen XXXII 1 one I 7 fourteen XXVIII 4 four IV}
    318 
    319   test_compound_select select9-2.$iOuterLoop.4 {
    320     SELECT a FROM t1 WHERE a<8 EXCEPT SELECT d FROM t2 WHERE d<=3 ORDER BY 1
    321   } {4 5 6 7}
    322 
    323   test_compound_select select9-2.$iOuterLoop.4 {
    324     SELECT a FROM t1 WHERE a<8 INTERSECT SELECT d FROM t2 WHERE d<=3 ORDER BY 1
    325   } {1 2 3}
    326 
    327 }
    328 
    329 do_test select9-2.X {
    330   execsql {
    331     DROP INDEX i1;
    332     DROP INDEX i2;
    333     DROP INDEX i3;
    334   }
    335 } {}
    336 
    337 # This procedure executes the SQL.  Then it checks the generated program
    338 # for the SQL and appends a "nosort" to the result if the program contains the
    339 # SortCallback opcode.  If the program does not contain the SortCallback
    340 # opcode it appends "sort"
    341 #
    342 proc cksort {sql} {
    343   set ::sqlite_sort_count 0
    344   set data [execsql $sql]
    345   if {$::sqlite_sort_count} {set x sort} {set x nosort}
    346   lappend data $x
    347   return $data
    348 }
    349 
    350 # If the right indexes exist, the following query:
    351 #
    352 #     SELECT t1.a FROM t1 UNION ALL SELECT t2.d FROM t2 ORDER BY 1
    353 #
    354 # can use indexes to run without doing a in-memory sort operation.
    355 # This block of tests (select9-3.*) is used to check if the same 
    356 # is possible with:
    357 #
    358 #     CREATE VIEW v1 AS SELECT a FROM t1 UNION ALL SELECT d FROM t2
    359 #     SELECT a FROM v1 ORDER BY 1
    360 #
    361 # It turns out that it is.
    362 #
    363 do_test select9-3.1 {
    364   cksort { SELECT a FROM t1 ORDER BY 1 }
    365 } {1 2 3 4 5 6 7 8 9 10 sort}
    366 do_test select9-3.2 {
    367   execsql { CREATE INDEX i1 ON t1(a) }
    368   cksort { SELECT a FROM t1 ORDER BY 1 }
    369 } {1 2 3 4 5 6 7 8 9 10 nosort}
    370 do_test select9-3.3 {
    371   cksort { SELECT a FROM t1 UNION ALL SELECT d FROM t2 ORDER BY 1 LIMIT 5 }
    372 } {1 1 2 2 3 sort}
    373 do_test select9-3.4 {
    374   execsql { CREATE INDEX i2 ON t2(d) }
    375   cksort { SELECT a FROM t1 UNION ALL SELECT d FROM t2 ORDER BY 1 LIMIT 5 }
    376 } {1 1 2 2 3 nosort}
    377 do_test select9-3.5 {
    378   execsql { CREATE VIEW v1 AS SELECT a FROM t1 UNION ALL SELECT d FROM t2 }
    379   cksort { SELECT a FROM v1 ORDER BY 1 LIMIT 5 }
    380 } {1 1 2 2 3 nosort}
    381 do_test select9-3.X {
    382   execsql {
    383     DROP INDEX i1;
    384     DROP INDEX i2;
    385     DROP VIEW v1;
    386   }
    387 } {}
    388 
    389 # This block of tests is the same as the preceding one, except that
    390 # "UNION" is tested instead of "UNION ALL".
    391 #
    392 do_test select9-4.1 {
    393   cksort { SELECT a FROM t1 ORDER BY 1 }
    394 } {1 2 3 4 5 6 7 8 9 10 sort}
    395 do_test select9-4.2 {
    396   execsql { CREATE INDEX i1 ON t1(a) }
    397   cksort { SELECT a FROM t1 ORDER BY 1 }
    398 } {1 2 3 4 5 6 7 8 9 10 nosort}
    399 do_test select9-4.3 {
    400   cksort { SELECT a FROM t1 UNION SELECT d FROM t2 ORDER BY 1 LIMIT 5 }
    401 } {1 2 3 4 5 sort}
    402 do_test select9-4.4 {
    403   execsql { CREATE INDEX i2 ON t2(d) }
    404   cksort { SELECT a FROM t1 UNION SELECT d FROM t2 ORDER BY 1 LIMIT 5 }
    405 } {1 2 3 4 5 nosort}
    406 do_test select9-4.5 {
    407   execsql { CREATE VIEW v1 AS SELECT a FROM t1 UNION SELECT d FROM t2 }
    408   cksort { SELECT a FROM v1 ORDER BY 1 LIMIT 5 }
    409 } {1 2 3 4 5 sort}
    410 do_test select9-4.X {
    411   execsql {
    412     DROP INDEX i1;
    413     DROP INDEX i2;
    414     DROP VIEW v1;
    415   }
    416 } {}
    417 
    418 
    419 finish_test
    420