]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add new file e_vacuum.test. Move part of e_select.test into e_select2.test.
authordan <dan@noemail.net>
Fri, 24 Sep 2010 18:04:22 +0000 (18:04 +0000)
committerdan <dan@noemail.net>
Fri, 24 Sep 2010 18:04:22 +0000 (18:04 +0000)
FossilOrigin-Name: 30801892c6036b8de2e26fc178389479c04b5dfa

manifest
manifest.uuid
test/e_select.test
test/e_select2.test [new file with mode: 0644]
test/e_vacuum.test [new file with mode: 0644]

index bc52824b414ce90e7bd50c10a2ac2b02a4985183..61bfe5d1b7cc1e1c4b8123f8007b11ff1f81d24f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sexperimental\sbranch\sdisallowing\sVACUUM\swhen\sthere\sare\sone\sor\smore\sactive\sSQL\sstatements.
-D 2010-09-24T09:32:45
+C Add\snew\sfile\se_vacuum.test.\sMove\spart\sof\se_select.test\sinto\se_select2.test.
+D 2010-09-24T18:04:23
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in c599a15d268b1db2aeadea19df2adc3bf2eb6bee
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -353,8 +353,10 @@ F test/e_expr.test 164e87c1d7b40ceb47c57c3bffa384c81d009aa7
 F test/e_fkey.test 6721a741c6499b3ab7e5385923233343c8f1ad05
 F test/e_fts3.test 75bb0aee26384ef586165e21018a17f7cd843469
 F test/e_insert.test d6af6e4a305afe1efbc8f0be7b68edc46abc17d8
-F test/e_select.test e9075e798a5f6d55f1dbacfe528f80b2d1a2750e
+F test/e_select.test 418edbdf914ccd3af23528bd17a6eaeaff58e652
+F test/e_select2.test 5c3d3da19c7b3e90ae444579db2b70098599ab92
 F test/e_update.test 652708422ba034d6d5da2c4486a0b6e73e54be09
+F test/e_vacuum.test f56e8af24412fcf7e9412567947349b9646ecb5e
 F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea
 F test/enc2.test 6d91a5286f59add0cfcbb2d0da913b76f2242398
 F test/enc3.test 5c550d59ff31dccdba5d1a02ae11c7047d77c041
@@ -863,10 +865,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 7893e525953da6c97eaea23fe94d26e1e635edea
-R 8fb8324a0195376ff3817c4a92466767
-T *branch * experimental
-T *sym-experimental *
-T -sym-trunk *
+P c1ebcacd9b31239aee065c64c4b4596d56dc397f
+R 631f8b37be3b55d6915c731a8852c03e
 U dan
-Z 23cdc7ff7ef6c793433c9a44dd319415
+Z 644ec5e80803b65e2fb74d5134b3243e
index 7f2db2319959e87a9ffb16006f262af1540f8cd1..ad849dab77b9b32705fd35a48268f28f6d9495ae 100644 (file)
@@ -1 +1 @@
-c1ebcacd9b31239aee065c64c4b4596d56dc397f
\ No newline at end of file
+30801892c6036b8de2e26fc178389479c04b5dfa
\ No newline at end of file
index c8baf1783152cae42e58e97310352bec809d4db2..25634f9e38fef0a770fb5c0ac594387f8f9732da 100644 (file)
@@ -706,567 +706,6 @@ foreach {tn sql} {
   " {1 {a NATURAL join may not have an ON or USING clause}}
 }
 
-#-------------------------------------------------------------------------
-# te_* commands:
-#
-#
-#   te_read_sql DB SELECT-STATEMENT
-#   te_read_tbl DB TABLENAME
-#
-# These two commands are used to read a dataset from the database. A dataset
-# consists of N rows of M named columns of values each, where each value has a
-# type (null, integer, real, text or blob) and a value within the types domain.
-# The tcl format for a "dataset" is a list of two elements:
-#
-#   * A list of the column names.
-#   * A list of data rows. Each row is itself a list, where each element is
-#     the contents of a column of the row. Each of these is a list of two
-#     elements, the type name and the actual value.
-#
-# For example, the contents of table [t1] as a dataset is:
-#
-#   CREATE TABLE t1(a, b);
-#   INSERT INTO t1 VALUES('abc', NULL);
-#   INSERT INTO t1 VALUES(43.1, 22);
-#
-#   {a b} {{{TEXT abc} {NULL {}}} {{REAL 43.1} {INTEGER 22}}}
-#
-# The [te_read_tbl] command returns a dataset read from a table. The
-# [te_read_sql] returns the dataset that results from executing a SELECT
-# command.
-#
-#
-#   te_tbljoin ?SWITCHES? LHS-TABLE RHS-TABLE
-#   te_join ?SWITCHES? LHS-DATASET RHS-DATASET
-#
-# This command joins the two datasets and returns the resulting dataset. If 
-# there are no switches specified, then the results is the cartesian product
-# of the two inputs.  The [te_tbljoin] command reads the left and right-hand
-# datasets from the specified tables. The [te_join] command is passed the
-# datasets directly.
-#
-# Optional switches are as follows:
-#
-#   -on SCRIPT
-#   -using COLUMN-LIST
-#   -left
-#
-# The -on option specifies a tcl script that is executed for each row in the
-# cartesian product of the two datasets. The script has 4 arguments appended
-# to it, in the following order:
-#
-#   * The list of column-names from the left-hand dataset.
-#   * A single row from the left-hand dataset (one "data row" list as 
-#     described above.
-#   * The list of column-names from the right-hand dataset.
-#   * A single row from the right-hand dataset.
-#
-# The script must return a boolean value - true if the combination of rows
-# should be included in the output dataset, or false otherwise.
-#
-# The -using option specifies a list of the columns from the right-hand
-# dataset that should be omitted from the output dataset.
-#
-# If the -left option is present, the join is done LEFT JOIN style. 
-# Specifically, an extra row is inserted if after the -on script is run there
-# exist rows in the left-hand dataset that have no corresponding rows in
-# the output. See the implementation for more specific comments.
-#
-#
-#   te_equals ?SWITCHES? COLNAME1 COLNAME2 <-on script args>
-#
-# The only supported switch is "-nocase". If it is present, then text values
-# are compared in a case-independent fashion. Otherwise, they are compared
-# as if using the SQLite BINARY collation sequence.
-#
-#
-#   te_and ONSCRIPT1 ONSCRIPT2...
-#
-#
-
-
-#
-#   te_read_tbl DB TABLENAME
-#   te_read_sql DB SELECT-STATEMENT
-#
-# These two procs are used to extract datasets from the database, either
-# by reading the contents of a named table (te_read_tbl), or by executing
-# a SELECT statement (t3_read_sql).  
-#
-# See the comment above, describing "te_* commands", for details of the
-# return values.
-#
-proc te_read_tbl {db tbl} {
- te_read_sql $db "SELECT * FROM '$tbl'"
-}
-proc te_read_sql {db sql} {
-  set S [sqlite3_prepare_v2 $db $sql -1 DUMMY]
-
-  set cols [list]
-  for {set i 0} {$i < [sqlite3_column_count $S]} {incr i} {
-    lappend cols [sqlite3_column_name $S $i]
-  }
-
-  set rows [list]
-  while {[sqlite3_step $S] == "SQLITE_ROW"} {
-    set r [list]
-    for {set i 0} {$i < [sqlite3_column_count $S]} {incr i} {
-      lappend r [list [sqlite3_column_type $S $i] [sqlite3_column_text $S $i]]
-    }
-    lappend rows $r
-  }
-  sqlite3_finalize $S
-
-  return [list $cols $rows]
-}
-
-#-------
-# Usage:   te_join <table-data1> <table-data2> <join spec>...
-#
-# Where a join-spec is an optional list of arguments as follows:
-#
-#   ?-left?
-#   ?-using colname-list?
-#   ?-on on-expr-proc?
-#
-proc te_join {data1 data2 args} {
-
-  set testproc ""
-  set usinglist [list]
-  set isleft 0
-  for {set i 0} {$i < [llength $args]} {incr i} {
-    set a [lindex $args $i]
-    switch -- $a {
-      -on     { set testproc [lindex $args [incr i]] }
-      -using  { set usinglist [lindex $args [incr i]] }
-      -left   { set isleft 1 }
-      default {
-        error "Unknown argument: $a"
-      }
-    }
-  }
-
-  set c1 [lindex $data1 0]
-  set c2 [lindex $data2 0]
-  set omitlist [list]
-  set nullrowlist [list]
-  set cret $c1
-
-  set cidx 0
-  foreach col $c2 {
-    set idx [lsearch $usinglist $col]
-    if {$idx>=0} {lappend omitlist $cidx}
-    if {$idx<0} {
-      lappend nullrowlist {NULL {}}
-      lappend cret $col
-    }
-    incr cidx
-  }
-  set omitlist [lsort -integer -decreasing $omitlist]
-
-
-  set rret [list]
-  foreach r1 [lindex $data1 1] {
-    set one 0
-    foreach r2 [lindex $data2 1] {
-      set ok 1
-      if {$testproc != ""} {
-        set ok [eval $testproc [list $c1 $r1 $c2 $r2]]
-      }
-      if {$ok} {
-        set one 1
-        foreach idx $omitlist {set r2 [lreplace $r2 $idx $idx]}
-        lappend rret [concat $r1 $r2]
-      }
-    }
-
-    if {$isleft && $one==0} {
-      lappend rret [concat $r1 $nullrowlist]
-    }
-  }
-  
-  list $cret $rret
-}
-
-proc te_tbljoin {db t1 t2 args} {
-  te_join [te_read_tbl $db $t1] [te_read_tbl $db $t2] {*}$args
-}
-
-proc te_apply_affinity {affinity typevar valvar} {
-  upvar $typevar type
-  upvar $valvar val
-
-  switch -- $affinity {
-    integer {
-      if {[string is double $val]} { set type REAL }
-      if {[string is wideinteger $val]} { set type INTEGER }
-      if {$type == "REAL" && int($val)==$val} { 
-        set type INTEGER 
-        set val [expr {int($val)}]
-      }
-    }
-    text {
-      set type TEXT
-    }
-    none { }
-
-    default { error "invalid affinity: $affinity" }
-  }
-}
-
-#----------
-# te_equals ?SWITCHES? c1 c2 cols1 row1 cols2 row2
-#
-proc te_equals {args} {
-
-  if {[llength $args]<6} {error "invalid arguments to te_equals"}
-  foreach {c1 c2 cols1 row1 cols2 row2} [lrange $args end-5 end] break
-
-  set nocase 0
-  set affinity none
-
-  for {set i 0} {$i < ([llength $args]-6)} {incr i} {
-    set a [lindex $args $i]
-    switch -- $a {
-      -nocase {
-        set nocase 1
-      }
-      -affinity {
-        set affinity [string tolower [lindex $args [incr i]]]
-      }
-      default {
-        error "invalid arguments to te_equals"
-      }
-    }
-  }
-
-  set idx2 [if {[string is integer $c2]} { set c2 } else { lsearch $cols2 $c2 }]
-  set idx1 [if {[string is integer $c1]} { set c1 } else { lsearch $cols1 $c1 }]
-
-  set t1 [lindex $row1 $idx1 0]
-  set t2 [lindex $row2 $idx2 0]
-  set v1 [lindex $row1 $idx1 1]
-  set v2 [lindex $row2 $idx2 1]
-
-  te_apply_affinity $affinity t1 v1
-  te_apply_affinity $affinity t2 v2
-
-  if {$t1 == "NULL" || $t2 == "NULL"} { return 0 }
-  if {$nocase && $t1 == "TEXT"} { set v1 [string tolower $v1] }
-  if {$nocase && $t2 == "TEXT"} { set v2 [string tolower $v2] }
-
-
-  set res [expr {$t1 == $t2 && [string equal $v1 $v2]}]
-  return $res
-}
-
-proc te_false {args} { return 0 }
-proc te_true  {args} { return 1 }
-
-proc te_and {args} {
-  foreach a [lrange $args 0 end-4] {
-    set res [eval $a [lrange $args end-3 end]]
-    if {$res == 0} {return 0}
-  }
-  return 1
-}
-
-
-proc te_dataset_eq {testname got expected} {
-  uplevel #0 [list do_test $testname [list set {} $got] $expected]
-}
-proc te_dataset_eq_unordered {testname got expected} {
-  lset got      1 [lsort [lindex $got 1]]
-  lset expected 1 [lsort [lindex $expected 1]]
-  te_dataset_eq $testname $got $expected
-}
-
-proc te_dataset_ne {testname got unexpected} {
-  uplevel #0 [list do_test $testname [list string equal $got $unexpected] 0]
-}
-proc te_dataset_ne_unordered {testname got unexpected} {
-  lset got      1 [lsort [lindex $got 1]]
-  lset unexpected 1 [lsort [lindex $unexpected 1]]
-  te_dataset_ne $testname $got $unexpected
-}
-
-
-#-------------------------------------------------------------------------
-#
-proc test_join {tn sqljoin tbljoinargs} {
-  set sql [te_read_sql db "SELECT * FROM $sqljoin"]
-  set te  [te_tbljoin db {*}$tbljoinargs]
-  te_dataset_eq_unordered $tn $sql $te
-}
-
-drop_all_tables
-do_execsql_test e_select-2.0 {
-  CREATE TABLE t1(a, b);
-  CREATE TABLE t2(a, b);
-  CREATE TABLE t3(b COLLATE nocase);
-
-  INSERT INTO t1 VALUES(2, 'B');
-  INSERT INTO t1 VALUES(1, 'A');
-  INSERT INTO t1 VALUES(4, 'D');
-  INSERT INTO t1 VALUES(NULL, NULL);
-  INSERT INTO t1 VALUES(3, NULL);
-
-  INSERT INTO t2 VALUES(1, 'A');
-  INSERT INTO t2 VALUES(2, NULL);
-  INSERT INTO t2 VALUES(5, 'E');
-  INSERT INTO t2 VALUES(NULL, NULL);
-  INSERT INTO t2 VALUES(3, 'C');
-
-  INSERT INTO t3 VALUES('a');
-  INSERT INTO t3 VALUES('c');
-  INSERT INTO t3 VALUES('b');
-} {}
-
-foreach {tn indexes} {
-  e_select-2.1.1 { }
-  e_select-2.1.2 { CREATE INDEX i1 ON t1(a) }
-  e_select-2.1.3 { CREATE INDEX i1 ON t2(a) }
-  e_select-2.1.4 { CREATE INDEX i1 ON t3(b) }
-} {
-
-  catchsql { DROP INDEX i1 }
-  catchsql { DROP INDEX i2 }
-  catchsql { DROP INDEX i3 }
-  execsql $indexes
-
-  # EVIDENCE-OF: R-46122-14930 If the join-op is "CROSS JOIN", "INNER
-  # JOIN", "JOIN" or a comma (",") and there is no ON or USING clause,
-  # then the result of the join is simply the cartesian product of the
-  # left and right-hand datasets.
-  #
-  # EVIDENCE-OF: R-46256-57243 There is no difference between the "INNER
-  # JOIN", "JOIN" and "," join operators.
-  #
-  # EVIDENCE-OF: R-07544-24155 The "CROSS JOIN" join operator produces the
-  # same data as the "INNER JOIN", "JOIN" and "," operators
-  #
-  test_join $tn.1.1  "t1, t2"                {t1 t2}
-  test_join $tn.1.2  "t1 INNER JOIN t2"      {t1 t2}
-  test_join $tn.1.3  "t1 CROSS JOIN t2"      {t1 t2}
-  test_join $tn.1.4  "t1 JOIN t2"            {t1 t2}
-  test_join $tn.1.5  "t2, t3"                {t2 t3}
-  test_join $tn.1.6  "t2 INNER JOIN t3"      {t2 t3}
-  test_join $tn.1.7  "t2 CROSS JOIN t3"      {t2 t3}
-  test_join $tn.1.8  "t2 JOIN t3"            {t2 t3}
-  test_join $tn.1.9  "t2, t2 AS x"           {t2 t2}
-  test_join $tn.1.10 "t2 INNER JOIN t2 AS x" {t2 t2}
-  test_join $tn.1.11 "t2 CROSS JOIN t2 AS x" {t2 t2}
-  test_join $tn.1.12 "t2 JOIN t2 AS x"       {t2 t2}
-
-  # EVIDENCE-OF: R-22775-56496 If there is an ON clause specified, then
-  # the ON expression is evaluated for each row of the cartesian product
-  # as a boolean expression. All rows for which the expression evaluates
-  # to false are excluded from the dataset.
-  #
-  test_join $tn.2.1  "t1, t2 ON (t1.a=t2.a)"  {t1 t2 -on {te_equals a a}}
-  test_join $tn.2.2  "t2, t1 ON (t1.a=t2.a)"  {t2 t1 -on {te_equals a a}}
-  test_join $tn.2.3  "t2, t1 ON (1)"          {t2 t1 -on te_true}
-  test_join $tn.2.4  "t2, t1 ON (NULL)"       {t2 t1 -on te_false}
-  test_join $tn.2.5  "t2, t1 ON (1.1-1.1)"    {t2 t1 -on te_false}
-  test_join $tn.2.6  "t1, t2 ON (1.1-1.0)"    {t1 t2 -on te_true}
-
-
-  test_join $tn.3 "t1 LEFT JOIN t2 ON (t1.a=t2.a)" {t1 t2 -left -on {te_equals a a}}
-  test_join $tn.4 "t1 LEFT JOIN t2 USING (a)" {
-    t1 t2 -left -using a -on {te_equals a a}
-  }
-  test_join $tn.5 "t1 CROSS JOIN t2 USING(b, a)" {
-    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.6 "t1 NATURAL JOIN t2" {
-    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.7 "t1 NATURAL INNER JOIN t2" {
-    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.8 "t1 NATURAL CROSS JOIN t2" {
-    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.9 "t1 NATURAL INNER JOIN t2" {
-    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.10 "t1 NATURAL LEFT JOIN t2" {
-    t1 t2 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.11 "t1 NATURAL LEFT OUTER JOIN t2" {
-    t1 t2 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.12 "t2 NATURAL JOIN t1" {
-    t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.13 "t2 NATURAL INNER JOIN t1" {
-    t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.14 "t2 NATURAL CROSS JOIN t1" {
-    t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.15 "t2 NATURAL INNER JOIN t1" {
-    t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.16 "t2 NATURAL LEFT JOIN t1" {
-    t2 t1 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.17 "t2 NATURAL LEFT OUTER JOIN t1" {
-    t2 t1 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
-  }
-  test_join $tn.18 "t1 LEFT JOIN t2 USING (b)" {
-    t1 t2 -left -using b -on {te_equals b b}
-  }
-  test_join $tn.19 "t1 JOIN t3 USING(b)" {t1 t3 -using b -on {te_equals b b}}
-  test_join $tn.20 "t3 JOIN t1 USING(b)" {
-    t3 t1 -using b -on {te_equals -nocase b b}
-  }
-  test_join $tn.21 "t1 NATURAL JOIN t3"  {
-    t1 t3 -using b -on {te_equals b b}
-  }
-  test_join $tn.22 "t3 NATURAL JOIN t1"  {
-    t3 t1 -using b -on {te_equals -nocase b b}
-  }
-  test_join $tn.23 "t1 NATURAL LEFT JOIN t3" {
-    t1 t3 -left -using b -on {te_equals b b}
-  }
-  test_join $tn.24 "t3 NATURAL LEFT JOIN t1" {
-    t3 t1 -left -using b -on {te_equals -nocase b b}
-  }
-  test_join $tn.25 "t1 LEFT JOIN t3 ON (t3.b=t1.b)" {
-    t1 t3 -left -on {te_equals -nocase b b}
-  }
-  test_join $tn.26 "t1 LEFT JOIN t3 ON (t1.b=t3.b)" {
-    t1 t3 -left -on {te_equals b b}
-  }
-  test_join $tn.27 "t1 JOIN t3 ON (t1.b=t3.b)" { t1 t3 -on {te_equals b b} }
-
-  # EVIDENCE-OF: R-28760-53843 When more than two tables are joined
-  # together as part of a FROM clause, the join operations are processed
-  # in order from left to right. In other words, the FROM clause (A
-  # join-op-1 B join-op-2 C) is computed as ((A join-op-1 B) join-op-2 C).
-  #
-  #   Tests 28a and 28b show that the statement above is true for this case.
-  #   Test 28c shows that if the parenthesis force a different order of
-  #   evaluation the result is different. Test 28d verifies that the result
-  #   of the query with the parenthesis forcing a different order of evaluation
-  #   is as calculated by the [te_*] procs.
-  #
-  set t3_natural_left_join_t2 [
-    te_tbljoin db t3 t2 -left -using {b} -on {te_equals -nocase b b}
-  ]
-  set t1 [te_read_tbl db t1]
-  te_dataset_eq_unordered $tn.28a [
-    te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN t2 NATURAL JOIN t1"
-  ] [te_join $t3_natural_left_join_t2 $t1                                \
-      -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}}  \
-  ]
-
-  te_dataset_eq_unordered $tn.28b [
-    te_read_sql db "SELECT * FROM (t3 NATURAL LEFT JOIN t2) NATURAL JOIN t1"
-  ] [te_join $t3_natural_left_join_t2 $t1                                \
-      -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}}  \
-  ]
-
-  te_dataset_ne_unordered $tn.28c [
-    te_read_sql db "SELECT * FROM (t3 NATURAL LEFT JOIN t2) NATURAL JOIN t1"
-  ] [
-    te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN (t2 NATURAL JOIN t1)"
-  ]
-
-  set t2_natural_join_t1 [te_tbljoin db t2 t1 -using {a b}                 \
-        -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}}  \
-  ]
-  set t3 [te_read_tbl db t3]
-  te_dataset_eq_unordered $tn.28d [
-    te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN (t2 NATURAL JOIN t1)"
-  ] [te_join $t3 $t2_natural_join_t1                                       \
-      -left -using {b} -on {te_equals -nocase b b}                         \
-  ]
-}
-
-do_execsql_test e_select-2.2.0 {
-  CREATE TABLE t4(x TEXT COLLATE nocase);
-  CREATE TABLE t5(y INTEGER, z TEXT COLLATE binary);
-
-  INSERT INTO t4 VALUES('2.0');
-  INSERT INTO t4 VALUES('TWO');
-  INSERT INTO t5 VALUES(2, 'two');
-} {}
-
-# EVIDENCE-OF: R-55824-40976 A sub-select specified in the join-source
-# following the FROM clause in a simple SELECT statement is handled as
-# if it was a table containing the data returned by executing the
-# sub-select statement.
-#
-# EVIDENCE-OF: R-42612-06757 Each column of the sub-select dataset
-# inherits the collation sequence and affinity of the corresponding
-# expression in the sub-select statement.
-#
-foreach {tn subselect select spec} {
-  1   "SELECT * FROM t2"   "SELECT * FROM t1 JOIN %ss%" 
-      {t1 %ss%}
-
-  2   "SELECT * FROM t2"   "SELECT * FROM t1 JOIN %ss% AS x ON (t1.a=x.a)" 
-      {t1 %ss% -on {te_equals 0 0}}
-
-  3   "SELECT * FROM t2"   "SELECT * FROM %ss% AS x JOIN t1 ON (t1.a=x.a)" 
-      {%ss% t1 -on {te_equals 0 0}}
-
-  4   "SELECT * FROM t1, t2" "SELECT * FROM %ss% AS x JOIN t3"
-      {%ss% t3}
-
-  5   "SELECT * FROM t1, t2" "SELECT * FROM %ss% NATURAL JOIN t3"
-      {%ss% t3 -using b -on {te_equals 1 0}}
-
-  6   "SELECT * FROM t1, t2" "SELECT * FROM t3 NATURAL JOIN %ss%"
-      {t3 %ss% -using b -on {te_equals -nocase 0 1}}
-
-  7   "SELECT * FROM t1, t2" "SELECT * FROM t3 NATURAL LEFT JOIN %ss%"
-      {t3 %ss% -left -using b -on {te_equals -nocase 0 1}}
-
-  8   "SELECT count(*) AS y FROM t4"   "SELECT * FROM t5, %ss% USING (y)"
-      {t5 %ss% -using y -on {te_equals -affinity text 0 0}}
-
-  9   "SELECT count(*) AS y FROM t4"   "SELECT * FROM %ss%, t5 USING (y)"
-      {%ss% t5 -using y -on {te_equals -affinity text 0 0}}
-
-  10  "SELECT x AS y FROM t4"   "SELECT * FROM %ss% JOIN t5 USING (y)"
-      {%ss% t5 -using y -on {te_equals -nocase -affinity integer 0 0}}
-
-  11  "SELECT x AS y FROM t4"   "SELECT * FROM t5 JOIN %ss% USING (y)"
-      {t5 %ss% -using y -on {te_equals -nocase -affinity integer 0 0}}
-
-  12  "SELECT y AS x FROM t5"   "SELECT * FROM %ss% JOIN t4 USING (x)"
-      {%ss% t4 -using x -on {te_equals -nocase -affinity integer 0 0}}
-
-  13  "SELECT y AS x FROM t5"   "SELECT * FROM t4 JOIN %ss% USING (x)"
-      {t4 %ss% -using x -on {te_equals -nocase -affinity integer 0 0}}
-
-  14  "SELECT +y AS x FROM t5"   "SELECT * FROM %ss% JOIN t4 USING (x)"
-      {%ss% t4 -using x -on {te_equals -nocase -affinity text 0 0}}
-
-  15  "SELECT +y AS x FROM t5"   "SELECT * FROM t4 JOIN %ss% USING (x)"
-      {t4 %ss% -using x -on {te_equals -nocase -affinity text 0 0}}
-} {
-
-  # Create a temporary table named %ss% containing the data returned by
-  # the sub-select. Then have the [te_tbljoin] proc use this table to
-  # compute the expected results of the $select query. Drop the temporary
-  # table before continuing.
-  #
-  execsql "CREATE TEMP TABLE '%ss%' AS $subselect"
-  set te [eval te_tbljoin db $spec]
-  execsql "DROP TABLE '%ss%'"
-
-  # Check that the actual data returned by the $select query is the same
-  # as the expected data calculated using [te_tbljoin] above.
-  #
-  te_dataset_eq_unordered e_select-2.2.1.$tn [
-    te_read_sql db [string map [list %ss% "($subselect)"] $select]
-  ] $te
-}
-
 #-------------------------------------------------------------------------
 # The next block of tests - e_select-3.* - concentrate on verifying 
 # statements made regarding WHERE clause processing.
@@ -2718,5 +2157,4 @@ do_select_tests e_select-9.11 {
   12 { SELECT b FROM f1 ORDER BY a LIMIT 0, 5 } {a b c d e}
 }
 
-
 finish_test
diff --git a/test/e_select2.test b/test/e_select2.test
new file mode 100644 (file)
index 0000000..b338d4f
--- /dev/null
@@ -0,0 +1,580 @@
+# 2010 September 24
+#
+# 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.
+#
+#***********************************************************************
+#
+# This file implements tests to verify that the "testable statements" in 
+# the lang_select.html document are correct.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+#-------------------------------------------------------------------------
+# te_* commands:
+#
+#
+#   te_read_sql DB SELECT-STATEMENT
+#   te_read_tbl DB TABLENAME
+#
+# These two commands are used to read a dataset from the database. A dataset
+# consists of N rows of M named columns of values each, where each value has a
+# type (null, integer, real, text or blob) and a value within the types domain.
+# The tcl format for a "dataset" is a list of two elements:
+#
+#   * A list of the column names.
+#   * A list of data rows. Each row is itself a list, where each element is
+#     the contents of a column of the row. Each of these is a list of two
+#     elements, the type name and the actual value.
+#
+# For example, the contents of table [t1] as a dataset is:
+#
+#   CREATE TABLE t1(a, b);
+#   INSERT INTO t1 VALUES('abc', NULL);
+#   INSERT INTO t1 VALUES(43.1, 22);
+#
+#   {a b} {{{TEXT abc} {NULL {}}} {{REAL 43.1} {INTEGER 22}}}
+#
+# The [te_read_tbl] command returns a dataset read from a table. The
+# [te_read_sql] returns the dataset that results from executing a SELECT
+# command.
+#
+#
+#   te_tbljoin ?SWITCHES? LHS-TABLE RHS-TABLE
+#   te_join ?SWITCHES? LHS-DATASET RHS-DATASET
+#
+# This command joins the two datasets and returns the resulting dataset. If 
+# there are no switches specified, then the results is the cartesian product
+# of the two inputs.  The [te_tbljoin] command reads the left and right-hand
+# datasets from the specified tables. The [te_join] command is passed the
+# datasets directly.
+#
+# Optional switches are as follows:
+#
+#   -on SCRIPT
+#   -using COLUMN-LIST
+#   -left
+#
+# The -on option specifies a tcl script that is executed for each row in the
+# cartesian product of the two datasets. The script has 4 arguments appended
+# to it, in the following order:
+#
+#   * The list of column-names from the left-hand dataset.
+#   * A single row from the left-hand dataset (one "data row" list as 
+#     described above.
+#   * The list of column-names from the right-hand dataset.
+#   * A single row from the right-hand dataset.
+#
+# The script must return a boolean value - true if the combination of rows
+# should be included in the output dataset, or false otherwise.
+#
+# The -using option specifies a list of the columns from the right-hand
+# dataset that should be omitted from the output dataset.
+#
+# If the -left option is present, the join is done LEFT JOIN style. 
+# Specifically, an extra row is inserted if after the -on script is run there
+# exist rows in the left-hand dataset that have no corresponding rows in
+# the output. See the implementation for more specific comments.
+#
+#
+#   te_equals ?SWITCHES? COLNAME1 COLNAME2 <-on script args>
+#
+# The only supported switch is "-nocase". If it is present, then text values
+# are compared in a case-independent fashion. Otherwise, they are compared
+# as if using the SQLite BINARY collation sequence.
+#
+#
+#   te_and ONSCRIPT1 ONSCRIPT2...
+#
+#
+
+
+#
+#   te_read_tbl DB TABLENAME
+#   te_read_sql DB SELECT-STATEMENT
+#
+# These two procs are used to extract datasets from the database, either
+# by reading the contents of a named table (te_read_tbl), or by executing
+# a SELECT statement (t3_read_sql).  
+#
+# See the comment above, describing "te_* commands", for details of the
+# return values.
+#
+proc te_read_tbl {db tbl} {
+ te_read_sql $db "SELECT * FROM '$tbl'"
+}
+proc te_read_sql {db sql} {
+  set S [sqlite3_prepare_v2 $db $sql -1 DUMMY]
+
+  set cols [list]
+  for {set i 0} {$i < [sqlite3_column_count $S]} {incr i} {
+    lappend cols [sqlite3_column_name $S $i]
+  }
+
+  set rows [list]
+  while {[sqlite3_step $S] == "SQLITE_ROW"} {
+    set r [list]
+    for {set i 0} {$i < [sqlite3_column_count $S]} {incr i} {
+      lappend r [list [sqlite3_column_type $S $i] [sqlite3_column_text $S $i]]
+    }
+    lappend rows $r
+  }
+  sqlite3_finalize $S
+
+  return [list $cols $rows]
+}
+
+#-------
+# Usage:   te_join <table-data1> <table-data2> <join spec>...
+#
+# Where a join-spec is an optional list of arguments as follows:
+#
+#   ?-left?
+#   ?-using colname-list?
+#   ?-on on-expr-proc?
+#
+proc te_join {data1 data2 args} {
+
+  set testproc ""
+  set usinglist [list]
+  set isleft 0
+  for {set i 0} {$i < [llength $args]} {incr i} {
+    set a [lindex $args $i]
+    switch -- $a {
+      -on     { set testproc [lindex $args [incr i]] }
+      -using  { set usinglist [lindex $args [incr i]] }
+      -left   { set isleft 1 }
+      default {
+        error "Unknown argument: $a"
+      }
+    }
+  }
+
+  set c1 [lindex $data1 0]
+  set c2 [lindex $data2 0]
+  set omitlist [list]
+  set nullrowlist [list]
+  set cret $c1
+
+  set cidx 0
+  foreach col $c2 {
+    set idx [lsearch $usinglist $col]
+    if {$idx>=0} {lappend omitlist $cidx}
+    if {$idx<0} {
+      lappend nullrowlist {NULL {}}
+      lappend cret $col
+    }
+    incr cidx
+  }
+  set omitlist [lsort -integer -decreasing $omitlist]
+
+
+  set rret [list]
+  foreach r1 [lindex $data1 1] {
+    set one 0
+    foreach r2 [lindex $data2 1] {
+      set ok 1
+      if {$testproc != ""} {
+        set ok [eval $testproc [list $c1 $r1 $c2 $r2]]
+      }
+      if {$ok} {
+        set one 1
+        foreach idx $omitlist {set r2 [lreplace $r2 $idx $idx]}
+        lappend rret [concat $r1 $r2]
+      }
+    }
+
+    if {$isleft && $one==0} {
+      lappend rret [concat $r1 $nullrowlist]
+    }
+  }
+  
+  list $cret $rret
+}
+
+proc te_tbljoin {db t1 t2 args} {
+  te_join [te_read_tbl $db $t1] [te_read_tbl $db $t2] {*}$args
+}
+
+proc te_apply_affinity {affinity typevar valvar} {
+  upvar $typevar type
+  upvar $valvar val
+
+  switch -- $affinity {
+    integer {
+      if {[string is double $val]} { set type REAL }
+      if {[string is wideinteger $val]} { set type INTEGER }
+      if {$type == "REAL" && int($val)==$val} { 
+        set type INTEGER 
+        set val [expr {int($val)}]
+      }
+    }
+    text {
+      set type TEXT
+    }
+    none { }
+
+    default { error "invalid affinity: $affinity" }
+  }
+}
+
+#----------
+# te_equals ?SWITCHES? c1 c2 cols1 row1 cols2 row2
+#
+proc te_equals {args} {
+
+  if {[llength $args]<6} {error "invalid arguments to te_equals"}
+  foreach {c1 c2 cols1 row1 cols2 row2} [lrange $args end-5 end] break
+
+  set nocase 0
+  set affinity none
+
+  for {set i 0} {$i < ([llength $args]-6)} {incr i} {
+    set a [lindex $args $i]
+    switch -- $a {
+      -nocase {
+        set nocase 1
+      }
+      -affinity {
+        set affinity [string tolower [lindex $args [incr i]]]
+      }
+      default {
+        error "invalid arguments to te_equals"
+      }
+    }
+  }
+
+  set idx2 [if {[string is integer $c2]} { set c2 } else { lsearch $cols2 $c2 }]
+  set idx1 [if {[string is integer $c1]} { set c1 } else { lsearch $cols1 $c1 }]
+
+  set t1 [lindex $row1 $idx1 0]
+  set t2 [lindex $row2 $idx2 0]
+  set v1 [lindex $row1 $idx1 1]
+  set v2 [lindex $row2 $idx2 1]
+
+  te_apply_affinity $affinity t1 v1
+  te_apply_affinity $affinity t2 v2
+
+  if {$t1 == "NULL" || $t2 == "NULL"} { return 0 }
+  if {$nocase && $t1 == "TEXT"} { set v1 [string tolower $v1] }
+  if {$nocase && $t2 == "TEXT"} { set v2 [string tolower $v2] }
+
+
+  set res [expr {$t1 == $t2 && [string equal $v1 $v2]}]
+  return $res
+}
+
+proc te_false {args} { return 0 }
+proc te_true  {args} { return 1 }
+
+proc te_and {args} {
+  foreach a [lrange $args 0 end-4] {
+    set res [eval $a [lrange $args end-3 end]]
+    if {$res == 0} {return 0}
+  }
+  return 1
+}
+
+
+proc te_dataset_eq {testname got expected} {
+  uplevel #0 [list do_test $testname [list set {} $got] $expected]
+}
+proc te_dataset_eq_unordered {testname got expected} {
+  lset got      1 [lsort [lindex $got 1]]
+  lset expected 1 [lsort [lindex $expected 1]]
+  te_dataset_eq $testname $got $expected
+}
+
+proc te_dataset_ne {testname got unexpected} {
+  uplevel #0 [list do_test $testname [list string equal $got $unexpected] 0]
+}
+proc te_dataset_ne_unordered {testname got unexpected} {
+  lset got      1 [lsort [lindex $got 1]]
+  lset unexpected 1 [lsort [lindex $unexpected 1]]
+  te_dataset_ne $testname $got $unexpected
+}
+
+
+#-------------------------------------------------------------------------
+#
+proc test_join {tn sqljoin tbljoinargs} {
+  set sql [te_read_sql db "SELECT * FROM $sqljoin"]
+  set te  [te_tbljoin db {*}$tbljoinargs]
+  te_dataset_eq_unordered $tn $sql $te
+}
+
+drop_all_tables
+do_execsql_test e_select-2.0 {
+  CREATE TABLE t1(a, b);
+  CREATE TABLE t2(a, b);
+  CREATE TABLE t3(b COLLATE nocase);
+
+  INSERT INTO t1 VALUES(2, 'B');
+  INSERT INTO t1 VALUES(1, 'A');
+  INSERT INTO t1 VALUES(4, 'D');
+  INSERT INTO t1 VALUES(NULL, NULL);
+  INSERT INTO t1 VALUES(3, NULL);
+
+  INSERT INTO t2 VALUES(1, 'A');
+  INSERT INTO t2 VALUES(2, NULL);
+  INSERT INTO t2 VALUES(5, 'E');
+  INSERT INTO t2 VALUES(NULL, NULL);
+  INSERT INTO t2 VALUES(3, 'C');
+
+  INSERT INTO t3 VALUES('a');
+  INSERT INTO t3 VALUES('c');
+  INSERT INTO t3 VALUES('b');
+} {}
+
+foreach {tn indexes} {
+  e_select-2.1.1 { }
+  e_select-2.1.2 { CREATE INDEX i1 ON t1(a) }
+  e_select-2.1.3 { CREATE INDEX i1 ON t2(a) }
+  e_select-2.1.4 { CREATE INDEX i1 ON t3(b) }
+} {
+
+  catchsql { DROP INDEX i1 }
+  catchsql { DROP INDEX i2 }
+  catchsql { DROP INDEX i3 }
+  execsql $indexes
+
+  # EVIDENCE-OF: R-46122-14930 If the join-op is "CROSS JOIN", "INNER
+  # JOIN", "JOIN" or a comma (",") and there is no ON or USING clause,
+  # then the result of the join is simply the cartesian product of the
+  # left and right-hand datasets.
+  #
+  # EVIDENCE-OF: R-46256-57243 There is no difference between the "INNER
+  # JOIN", "JOIN" and "," join operators.
+  #
+  # EVIDENCE-OF: R-07544-24155 The "CROSS JOIN" join operator produces the
+  # same data as the "INNER JOIN", "JOIN" and "," operators
+  #
+  test_join $tn.1.1  "t1, t2"                {t1 t2}
+  test_join $tn.1.2  "t1 INNER JOIN t2"      {t1 t2}
+  test_join $tn.1.3  "t1 CROSS JOIN t2"      {t1 t2}
+  test_join $tn.1.4  "t1 JOIN t2"            {t1 t2}
+  test_join $tn.1.5  "t2, t3"                {t2 t3}
+  test_join $tn.1.6  "t2 INNER JOIN t3"      {t2 t3}
+  test_join $tn.1.7  "t2 CROSS JOIN t3"      {t2 t3}
+  test_join $tn.1.8  "t2 JOIN t3"            {t2 t3}
+  test_join $tn.1.9  "t2, t2 AS x"           {t2 t2}
+  test_join $tn.1.10 "t2 INNER JOIN t2 AS x" {t2 t2}
+  test_join $tn.1.11 "t2 CROSS JOIN t2 AS x" {t2 t2}
+  test_join $tn.1.12 "t2 JOIN t2 AS x"       {t2 t2}
+
+  # EVIDENCE-OF: R-22775-56496 If there is an ON clause specified, then
+  # the ON expression is evaluated for each row of the cartesian product
+  # as a boolean expression. All rows for which the expression evaluates
+  # to false are excluded from the dataset.
+  #
+  test_join $tn.2.1  "t1, t2 ON (t1.a=t2.a)"  {t1 t2 -on {te_equals a a}}
+  test_join $tn.2.2  "t2, t1 ON (t1.a=t2.a)"  {t2 t1 -on {te_equals a a}}
+  test_join $tn.2.3  "t2, t1 ON (1)"          {t2 t1 -on te_true}
+  test_join $tn.2.4  "t2, t1 ON (NULL)"       {t2 t1 -on te_false}
+  test_join $tn.2.5  "t2, t1 ON (1.1-1.1)"    {t2 t1 -on te_false}
+  test_join $tn.2.6  "t1, t2 ON (1.1-1.0)"    {t1 t2 -on te_true}
+
+
+  test_join $tn.3 "t1 LEFT JOIN t2 ON (t1.a=t2.a)" {t1 t2 -left -on {te_equals a a}}
+  test_join $tn.4 "t1 LEFT JOIN t2 USING (a)" {
+    t1 t2 -left -using a -on {te_equals a a}
+  }
+  test_join $tn.5 "t1 CROSS JOIN t2 USING(b, a)" {
+    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.6 "t1 NATURAL JOIN t2" {
+    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.7 "t1 NATURAL INNER JOIN t2" {
+    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.8 "t1 NATURAL CROSS JOIN t2" {
+    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.9 "t1 NATURAL INNER JOIN t2" {
+    t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.10 "t1 NATURAL LEFT JOIN t2" {
+    t1 t2 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.11 "t1 NATURAL LEFT OUTER JOIN t2" {
+    t1 t2 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.12 "t2 NATURAL JOIN t1" {
+    t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.13 "t2 NATURAL INNER JOIN t1" {
+    t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.14 "t2 NATURAL CROSS JOIN t1" {
+    t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.15 "t2 NATURAL INNER JOIN t1" {
+    t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.16 "t2 NATURAL LEFT JOIN t1" {
+    t2 t1 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.17 "t2 NATURAL LEFT OUTER JOIN t1" {
+    t2 t1 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}
+  }
+  test_join $tn.18 "t1 LEFT JOIN t2 USING (b)" {
+    t1 t2 -left -using b -on {te_equals b b}
+  }
+  test_join $tn.19 "t1 JOIN t3 USING(b)" {t1 t3 -using b -on {te_equals b b}}
+  test_join $tn.20 "t3 JOIN t1 USING(b)" {
+    t3 t1 -using b -on {te_equals -nocase b b}
+  }
+  test_join $tn.21 "t1 NATURAL JOIN t3"  {
+    t1 t3 -using b -on {te_equals b b}
+  }
+  test_join $tn.22 "t3 NATURAL JOIN t1"  {
+    t3 t1 -using b -on {te_equals -nocase b b}
+  }
+  test_join $tn.23 "t1 NATURAL LEFT JOIN t3" {
+    t1 t3 -left -using b -on {te_equals b b}
+  }
+  test_join $tn.24 "t3 NATURAL LEFT JOIN t1" {
+    t3 t1 -left -using b -on {te_equals -nocase b b}
+  }
+  test_join $tn.25 "t1 LEFT JOIN t3 ON (t3.b=t1.b)" {
+    t1 t3 -left -on {te_equals -nocase b b}
+  }
+  test_join $tn.26 "t1 LEFT JOIN t3 ON (t1.b=t3.b)" {
+    t1 t3 -left -on {te_equals b b}
+  }
+  test_join $tn.27 "t1 JOIN t3 ON (t1.b=t3.b)" { t1 t3 -on {te_equals b b} }
+
+  # EVIDENCE-OF: R-28760-53843 When more than two tables are joined
+  # together as part of a FROM clause, the join operations are processed
+  # in order from left to right. In other words, the FROM clause (A
+  # join-op-1 B join-op-2 C) is computed as ((A join-op-1 B) join-op-2 C).
+  #
+  #   Tests 28a and 28b show that the statement above is true for this case.
+  #   Test 28c shows that if the parenthesis force a different order of
+  #   evaluation the result is different. Test 28d verifies that the result
+  #   of the query with the parenthesis forcing a different order of evaluation
+  #   is as calculated by the [te_*] procs.
+  #
+  set t3_natural_left_join_t2 [
+    te_tbljoin db t3 t2 -left -using {b} -on {te_equals -nocase b b}
+  ]
+  set t1 [te_read_tbl db t1]
+  te_dataset_eq_unordered $tn.28a [
+    te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN t2 NATURAL JOIN t1"
+  ] [te_join $t3_natural_left_join_t2 $t1                                \
+      -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}}  \
+  ]
+
+  te_dataset_eq_unordered $tn.28b [
+    te_read_sql db "SELECT * FROM (t3 NATURAL LEFT JOIN t2) NATURAL JOIN t1"
+  ] [te_join $t3_natural_left_join_t2 $t1                                \
+      -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}}  \
+  ]
+
+  te_dataset_ne_unordered $tn.28c [
+    te_read_sql db "SELECT * FROM (t3 NATURAL LEFT JOIN t2) NATURAL JOIN t1"
+  ] [
+    te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN (t2 NATURAL JOIN t1)"
+  ]
+
+  set t2_natural_join_t1 [te_tbljoin db t2 t1 -using {a b}                 \
+        -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}}  \
+  ]
+  set t3 [te_read_tbl db t3]
+  te_dataset_eq_unordered $tn.28d [
+    te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN (t2 NATURAL JOIN t1)"
+  ] [te_join $t3 $t2_natural_join_t1                                       \
+      -left -using {b} -on {te_equals -nocase b b}                         \
+  ]
+}
+
+do_execsql_test e_select-2.2.0 {
+  CREATE TABLE t4(x TEXT COLLATE nocase);
+  CREATE TABLE t5(y INTEGER, z TEXT COLLATE binary);
+
+  INSERT INTO t4 VALUES('2.0');
+  INSERT INTO t4 VALUES('TWO');
+  INSERT INTO t5 VALUES(2, 'two');
+} {}
+
+# EVIDENCE-OF: R-55824-40976 A sub-select specified in the join-source
+# following the FROM clause in a simple SELECT statement is handled as
+# if it was a table containing the data returned by executing the
+# sub-select statement.
+#
+# EVIDENCE-OF: R-42612-06757 Each column of the sub-select dataset
+# inherits the collation sequence and affinity of the corresponding
+# expression in the sub-select statement.
+#
+foreach {tn subselect select spec} {
+  1   "SELECT * FROM t2"   "SELECT * FROM t1 JOIN %ss%" 
+      {t1 %ss%}
+
+  2   "SELECT * FROM t2"   "SELECT * FROM t1 JOIN %ss% AS x ON (t1.a=x.a)" 
+      {t1 %ss% -on {te_equals 0 0}}
+
+  3   "SELECT * FROM t2"   "SELECT * FROM %ss% AS x JOIN t1 ON (t1.a=x.a)" 
+      {%ss% t1 -on {te_equals 0 0}}
+
+  4   "SELECT * FROM t1, t2" "SELECT * FROM %ss% AS x JOIN t3"
+      {%ss% t3}
+
+  5   "SELECT * FROM t1, t2" "SELECT * FROM %ss% NATURAL JOIN t3"
+      {%ss% t3 -using b -on {te_equals 1 0}}
+
+  6   "SELECT * FROM t1, t2" "SELECT * FROM t3 NATURAL JOIN %ss%"
+      {t3 %ss% -using b -on {te_equals -nocase 0 1}}
+
+  7   "SELECT * FROM t1, t2" "SELECT * FROM t3 NATURAL LEFT JOIN %ss%"
+      {t3 %ss% -left -using b -on {te_equals -nocase 0 1}}
+
+  8   "SELECT count(*) AS y FROM t4"   "SELECT * FROM t5, %ss% USING (y)"
+      {t5 %ss% -using y -on {te_equals -affinity text 0 0}}
+
+  9   "SELECT count(*) AS y FROM t4"   "SELECT * FROM %ss%, t5 USING (y)"
+      {%ss% t5 -using y -on {te_equals -affinity text 0 0}}
+
+  10  "SELECT x AS y FROM t4"   "SELECT * FROM %ss% JOIN t5 USING (y)"
+      {%ss% t5 -using y -on {te_equals -nocase -affinity integer 0 0}}
+
+  11  "SELECT x AS y FROM t4"   "SELECT * FROM t5 JOIN %ss% USING (y)"
+      {t5 %ss% -using y -on {te_equals -nocase -affinity integer 0 0}}
+
+  12  "SELECT y AS x FROM t5"   "SELECT * FROM %ss% JOIN t4 USING (x)"
+      {%ss% t4 -using x -on {te_equals -nocase -affinity integer 0 0}}
+
+  13  "SELECT y AS x FROM t5"   "SELECT * FROM t4 JOIN %ss% USING (x)"
+      {t4 %ss% -using x -on {te_equals -nocase -affinity integer 0 0}}
+
+  14  "SELECT +y AS x FROM t5"   "SELECT * FROM %ss% JOIN t4 USING (x)"
+      {%ss% t4 -using x -on {te_equals -nocase -affinity text 0 0}}
+
+  15  "SELECT +y AS x FROM t5"   "SELECT * FROM t4 JOIN %ss% USING (x)"
+      {t4 %ss% -using x -on {te_equals -nocase -affinity text 0 0}}
+} {
+
+  # Create a temporary table named %ss% containing the data returned by
+  # the sub-select. Then have the [te_tbljoin] proc use this table to
+  # compute the expected results of the $select query. Drop the temporary
+  # table before continuing.
+  #
+  execsql "CREATE TEMP TABLE '%ss%' AS $subselect"
+  set te [eval te_tbljoin db $spec]
+  execsql "DROP TABLE '%ss%'"
+
+  # Check that the actual data returned by the $select query is the same
+  # as the expected data calculated using [te_tbljoin] above.
+  #
+  te_dataset_eq_unordered e_select-2.2.1.$tn [
+    te_read_sql db [string map [list %ss% "($subselect)"] $select]
+  ] $te
+}
+
+finish_test
diff --git a/test/e_vacuum.test b/test/e_vacuum.test
new file mode 100644 (file)
index 0000000..b525c0e
--- /dev/null
@@ -0,0 +1,299 @@
+# 2010 September 24
+#
+# 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.
+#
+#***********************************************************************
+#
+# This file implements tests to verify that the "testable statements" in 
+# the lang_vacuum.html document are correct.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+sqlite3_test_control_pending_byte 0x1000000
+
+proc create_db {{sql ""}} {
+  catch { db close }
+  forcedelete test.db
+  sqlite3 db test.db
+
+  db transaction {
+    execsql { PRAGMA page_size = 1024; }
+    execsql $sql
+    execsql {
+      CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
+      INSERT INTO t1 VALUES(1, randomblob(400));
+      INSERT INTO t1 SELECT a+1,  randomblob(400) FROM t1;
+      INSERT INTO t1 SELECT a+2,  randomblob(400) FROM t1;
+      INSERT INTO t1 SELECT a+4,  randomblob(400) FROM t1;
+      INSERT INTO t1 SELECT a+8,  randomblob(400) FROM t1;
+      INSERT INTO t1 SELECT a+16, randomblob(400) FROM t1;
+      INSERT INTO t1 SELECT a+32, randomblob(400) FROM t1;
+      INSERT INTO t1 SELECT a+64, randomblob(400) FROM t1;
+
+      CREATE TABLE t2(a PRIMARY KEY, b UNIQUE);
+      INSERT INTO t2 SELECT * FROM t1;
+    }
+  }
+
+  return [expr {[file size test.db] / 1024}]
+}
+
+# This proc returns the number of contiguous blocks of pages that make up
+# the table or index named by the only argument. For example, if the table
+# occupies database pages 3, 4, 8 and 9, then this command returns 2 (there
+# are 2 fragments - one consisting of pages 3 and 4, the other of fragments
+# 8 and 9).
+#
+proc fragment_count {name} {
+  execsql { CREATE VIRTUAL TABLE temp.stat USING dbstat }
+  set nFrag 1
+  db eval {SELECT pageno FROM stat WHERE name = 't1' ORDER BY pageno} {
+    if {[info exists prevpageno] && $prevpageno != $pageno-1} {
+      incr nFrag
+    }
+    set prevpageno $pageno
+  }
+  execsql { DROP TABLE temp.stat }
+  set nFrag
+}
+
+
+# EVIDENCE-OF: R-63707-33375 -- syntax diagram vacuum-stmt
+#
+do_execsql_test e_vacuum-0.1 { VACUUM } {}
+
+# EVIDENCE-OF: R-51469-36013 Unless SQLite is running in
+# "auto_vacuum=FULL" mode, when a large amount of data is deleted from
+# the database file it leaves behind empty space, or "free" database
+# pages.
+#
+# EVIDENCE-OF: R-60541-63059 Running VACUUM to rebuild the database
+# reclaims this space and reduces the size of the database file.
+#
+foreach {tn avmode sz} {
+  1 none        7 
+  2 full        8 
+  3 incremental 8
+} {
+  set nPage [create_db "PRAGMA auto_vacuum = $avmode"]
+
+  do_execsql_test e_vacuum-1.1.$tn.1 {
+    DELETE FROM t1;
+    DELETE FROM t2;
+  } {}
+
+  if {$avmode == "full"} {
+    # This branch tests the "unless ... auto_vacuum=FULL" in the requirement
+    # above. If auto_vacuum is set to FULL, then no empty space is left in
+    # the database file.
+    do_execsql_test e_vacuum-1.1.$tn.2 {PRAGMA freelist_count} 0
+  } else {
+    set freelist [expr {$nPage - $sz}]
+    if {$avmode == "incremental"} { 
+      # The page size is 1024 bytes. Therefore, assuming the database contains
+      # somewhere between 207 and 411 pages (it does), there are 2 pointer-map
+      # pages.
+      incr freelist -2
+    }
+    do_execsql_test e_vacuum-1.1.$tn.3 {PRAGMA freelist_count} $freelist
+    do_execsql_test e_vacuum-1.1.$tn.4 {VACUUM} {}
+  }
+
+  do_test e_vacuum-1.1.$tn.5 { expr {[file size test.db] / 1024} } $sz
+}
+
+# EVIDENCE-OF: R-50943-18433 Frequent inserts, updates, and deletes can
+# cause the database file to become fragmented - where data for a single
+# table or index is scattered around the database file.
+#
+# EVIDENCE-OF: R-05791-54928 Running VACUUM ensures that each table and
+# index is largely stored contiguously within the database file.
+#
+#   e_vacuum-1.2.1 - Perform many INSERT, UPDATE and DELETE ops on table t1.
+#   e_vacuum-1.2.2 - Verify that t1 and its indexes are now quite fragmented.
+#   e_vacuum-1.2.3 - Run VACUUM.
+#   e_vacuum-1.2.4 - Verify that t1 and its indexes are now much 
+#                    less fragmented.
+#
+create_db 
+register_dbstat_vtab db
+do_execsql_test e_vacuum-1.2.1 {
+  DELETE FROM t1 WHERE a%2;
+  INSERT INTO t1 SELECT b, a FROM t2 WHERE a%2;
+  UPDATE t1 SET b=randomblob(600) WHERE (a%2)==0;
+} {}
+
+do_test e_vacuum-1.2.2.1 { expr [fragment_count t1]>100 } 1
+do_test e_vacuum-1.2.2.2 { expr [fragment_count sqlite_autoindex_t1_1]>100 } 1
+do_test e_vacuum-1.2.2.3 { expr [fragment_count sqlite_autoindex_t1_2]>100 } 1
+
+do_execsql_test e_vacuum-1.2.3 { VACUUM } {}
+
+# In practice, the tables and indexes each end up stored as two fragments -
+# one containing the root page and another containing all other pages.
+#
+do_test e_vacuum-1.2.4.1 { fragment_count t1 }                    2
+do_test e_vacuum-1.2.4.2 { fragment_count sqlite_autoindex_t1_1 } 2
+do_test e_vacuum-1.2.4.3 { fragment_count sqlite_autoindex_t1_2 } 2
+
+# EVIDENCE-OF: R-20474-44465 Normally, the database page_size and
+# whether or not the database supports auto_vacuum must be configured
+# before the database file is actually created.
+#
+do_test e_vacuum-1.3.1.1 {
+  create_db "PRAGMA page_size = 1024 ; PRAGMA auto_vacuum = FULL"
+  execsql { PRAGMA page_size ; PRAGMA auto_vacuum }
+} {1024 1}
+do_test e_vacuum-1.3.1.2 {
+  execsql { PRAGMA page_size = 2048 }
+  execsql { PRAGMA auto_vacuum = NONE }
+  execsql { PRAGMA page_size ; PRAGMA auto_vacuum }
+} {1024 1}
+
+# EVIDENCE-OF: R-08570-19916 However, when not in write-ahead log mode,
+# the page_size and/or auto_vacuum properties of an existing database
+# may be changed by using the page_size and/or pragma auto_vacuum
+# pragmas and then immediately VACUUMing the database.
+#
+do_test e_vacuum-1.3.2.1 {
+  execsql { PRAGMA journal_mode = delete }
+  execsql { PRAGMA page_size = 2048 }
+  execsql { PRAGMA auto_vacuum = NONE }
+  execsql VACUUM
+  execsql { PRAGMA page_size ; PRAGMA auto_vacuum }
+} {2048 0}
+
+# EVIDENCE-OF: R-48521-51450 When in write-ahead log mode, only the
+# auto_vacuum support property can be changed using VACUUM.
+#
+do_test e_vacuum-1.3.3.1 {
+  execsql { PRAGMA journal_mode = wal }
+  execsql { PRAGMA page_size ; PRAGMA auto_vacuum }
+} {2048 0}
+do_test e_vacuum-1.3.3.2 {
+  execsql { PRAGMA page_size = 1024 }
+  execsql { PRAGMA auto_vacuum = FULL }
+  execsql VACUUM
+  execsql { PRAGMA page_size ; PRAGMA auto_vacuum }
+} {2048 1}
+
+# EVIDENCE-OF: R-38001-03952 VACUUM only works on the main database. It
+# is not possible to VACUUM an attached database file.
+forcedelete test.db2
+create_db
+do_execsql_test e_vacuum-2.1.1 {
+  ATTACH 'test.db2' AS aux;
+  PRAGMA aux.page_size = 1024;
+  CREATE TABLE aux.t3 AS SELECT * FROM t1;
+  DELETE FROM t3;
+} {}
+do_test e_vacuum-2.1.2 { expr { ([file size test.db2] / 1024)>50 } } 1
+
+# Try everything we can think of to get the aux database vacuumed:
+do_execsql_test e_vacuum-2.1.3 { VACUUM } {}
+do_execsql_test e_vacuum-2.1.4 { VACUUM aux } {}
+do_execsql_test e_vacuum-2.1.5 { VACUUM 'test.db2' } {}
+
+# Despite our efforts, space in the aux database has not been reclaimed:
+do_test e_vacuum-2.1.6 { expr { ([file size test.db2] / 1024)>50 } } 1
+
+# EVIDENCE-OF: R-17495-17419 The VACUUM command may change the ROWIDs of
+# entries in any tables that do not have an explicit INTEGER PRIMARY
+# KEY.
+#
+#   Tests e_vacuum-3.1.1 - 3.1.2 demonstrate that rowids can change when
+#   a database is VACUUMed. Tests e_vacuum-3.1.3 - 3.1.4 show that adding
+#   an INTEGER PRIMARY KEY column to a table stops this from happening.
+#
+do_execsql_test e_vacuum-3.1.1 {
+  CREATE TABLE t4(x);
+  INSERT INTO t4(x) VALUES('x');
+  INSERT INTO t4(x) VALUES('y');
+  INSERT INTO t4(x) VALUES('z');
+  DELETE FROM t4 WHERE x = 'y';
+  SELECT rowid, x FROM t4;
+} {1 x 3 z}
+do_execsql_test e_vacuum-3.1.2 {
+  VACUUM;
+  SELECT rowid, x FROM t4;
+} {1 x 2 z}
+
+do_execsql_test e_vacuum-3.1.3 {
+  CREATE TABLE t5(x, y INTEGER PRIMARY KEY);
+  INSERT INTO t5(x) VALUES('x');
+  INSERT INTO t5(x) VALUES('y');
+  INSERT INTO t5(x) VALUES('z');
+  DELETE FROM t5 WHERE x = 'y';
+  SELECT rowid, x FROM t5;
+} {1 x 3 z}
+do_execsql_test e_vacuum-3.1.4 {
+  VACUUM;
+  SELECT rowid, x FROM t5;
+} {1 x 3 z}
+
+# EVIDENCE-OF: R-49563-33883 A VACUUM will fail if there is an open
+# transaction, or if there are one or more active SQL statements when it
+# is run.
+#
+do_execsql_test  e_vacuum-3.2.1.1 { BEGIN } {}
+do_catchsql_test e_vacuum-3.2.1.2 { 
+  VACUUM 
+} {1 {cannot VACUUM from within a transaction}}
+do_execsql_test  e_vacuum-3.2.1.3 { COMMIT } {}
+do_execsql_test  e_vacuum-3.2.1.4 { VACUUM } {}
+do_execsql_test  e_vacuum-3.2.1.5 { SAVEPOINT x } {}
+do_catchsql_test e_vacuum-3.2.1.6 { 
+  VACUUM 
+} {1 {cannot VACUUM from within a transaction}}
+do_execsql_test  e_vacuum-3.2.1.7 { COMMIT } {}
+do_execsql_test  e_vacuum-3.2.1.8 { VACUUM } {}
+
+create_db
+do_test e_vacuum-3.2.2.1 {
+  set res ""
+  db eval { SELECT a FROM t1 } {
+    if {$a == 10} { set res [catchsql VACUUM] }
+  }
+  set res
+} {1 {cannot VACUUM - SQL statements in progress}}
+
+
+# EVIDENCE-OF: R-38735-12540 As of SQLite version 3.1, an alternative to
+# using the VACUUM command to reclaim space after data has been deleted
+# is auto-vacuum mode, enabled using the auto_vacuum pragma.
+#
+do_test e_vacuum-3.3.1 {
+  create_db { PRAGMA auto_vacuum = FULL }
+  execsql { PRAGMA auto_vacuum }
+} {1}
+
+# EVIDENCE-OF: R-64844-34873 When auto_vacuum is enabled for a database
+# free pages may be reclaimed after deleting data, causing the file to
+# shrink, without rebuilding the entire database using VACUUM.
+#
+do_test e_vacuum-3.3.2.1 {
+  create_db { PRAGMA auto_vacuum = FULL }
+  execsql {
+    DELETE FROM t1;
+    DELETE FROM t2;
+  }
+  expr {[file size test.db] / 1024}
+} {8}
+do_test e_vacuum-3.3.2.2 {
+  create_db { PRAGMA auto_vacuum = INCREMENTAL }
+  execsql {
+    DELETE FROM t1;
+    DELETE FROM t2;
+    PRAGMA incremental_vacuum;
+  }
+  expr {[file size test.db] / 1024}
+} {8}
+
+finish_test