AryaWu/sqlite
0
1# 2010 September 242#3# The author disclaims copyright to this source code. In place of4# 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#12# This file implements tests to verify that the "testable statements" in 13# the lang_select.html document are correct.14#15 16set testdir [file dirname $argv0]17source $testdir/tester.tcl18 19#-------------------------------------------------------------------------20# te_* commands:21#22#23# te_read_sql DB SELECT-STATEMENT24# te_read_tbl DB TABLENAME25#26# These two commands are used to read a dataset from the database. A dataset27# consists of N rows of M named columns of values each, where each value has a28# type (null, integer, real, text or blob) and a value within the types domain.29# The tcl format for a "dataset" is a list of two elements:30#31# * A list of the column names.32# * A list of data rows. Each row is itself a list, where each element is33# the contents of a column of the row. Each of these is a list of two34# elements, the type name and the actual value.35#36# For example, the contents of table [t1] as a dataset is:37#38# CREATE TABLE t1(a, b);39# INSERT INTO t1 VALUES('abc', NULL);40# INSERT INTO t1 VALUES(43.1, 22);41#42# {a b} {{{TEXT abc} {NULL {}}} {{REAL 43.1} {INTEGER 22}}}43#44# The [te_read_tbl] command returns a dataset read from a table. The45# [te_read_sql] returns the dataset that results from executing a SELECT46# command.47#48#49# te_tbljoin ?SWITCHES? LHS-TABLE RHS-TABLE50# te_join ?SWITCHES? LHS-DATASET RHS-DATASET51#52# This command joins the two datasets and returns the resulting dataset. If 53# there are no switches specified, then the results is the cartesian product54# of the two inputs. The [te_tbljoin] command reads the left and right-hand55# datasets from the specified tables. The [te_join] command is passed the56# datasets directly.57#58# Optional switches are as follows:59#60# -on SCRIPT61# -using COLUMN-LIST62# -left63#64# The -on option specifies a tcl script that is executed for each row in the65# cartesian product of the two datasets. The script has 4 arguments appended66# to it, in the following order:67#68# * The list of column-names from the left-hand dataset.69# * A single row from the left-hand dataset (one "data row" list as 70# described above.71# * The list of column-names from the right-hand dataset.72# * A single row from the right-hand dataset.73#74# The script must return a boolean value - true if the combination of rows75# should be included in the output dataset, or false otherwise.76#77# The -using option specifies a list of the columns from the right-hand78# dataset that should be omitted from the output dataset.79#80# If the -left option is present, the join is done LEFT JOIN style. 81# Specifically, an extra row is inserted if after the -on script is run there82# exist rows in the left-hand dataset that have no corresponding rows in83# the output. See the implementation for more specific comments.84#85#86# te_equals ?SWITCHES? COLNAME1 COLNAME2 <-on script args>87#88# The only supported switch is "-nocase". If it is present, then text values89# are compared in a case-independent fashion. Otherwise, they are compared90# as if using the SQLite BINARY collation sequence.91#92#93# te_and ONSCRIPT1 ONSCRIPT2...94#95#96 97 98#99# te_read_tbl DB TABLENAME100# te_read_sql DB SELECT-STATEMENT101#102# These two procs are used to extract datasets from the database, either103# by reading the contents of a named table (te_read_tbl), or by executing104# a SELECT statement (t3_read_sql). 105#106# See the comment above, describing "te_* commands", for details of the107# return values.108#109proc te_read_tbl {db tbl} {110 te_read_sql $db "SELECT * FROM '$tbl'"111}112proc te_read_sql {db sql} {113 set S [sqlite3_prepare_v2 $db $sql -1 DUMMY]114 115 set cols [list]116 for {set i 0} {$i < [sqlite3_column_count $S]} {incr i} {117 lappend cols [sqlite3_column_name $S $i]118 }119 120 set rows [list]121 while {[sqlite3_step $S] == "SQLITE_ROW"} {122 set r [list]123 for {set i 0} {$i < [sqlite3_column_count $S]} {incr i} {124 lappend r [list [sqlite3_column_type $S $i] [sqlite3_column_text $S $i]]125 }126 lappend rows $r127 }128 sqlite3_finalize $S129 130 return [list $cols $rows]131}132 133#-------134# Usage: te_join <table-data1> <table-data2> <join spec>...135#136# Where a join-spec is an optional list of arguments as follows:137#138# ?-left?139# ?-using colname-list?140# ?-on on-expr-proc?141#142proc te_join {data1 data2 args} {143 144 set testproc ""145 set usinglist [list]146 set isleft 0147 for {set i 0} {$i < [llength $args]} {incr i} {148 set a [lindex $args $i]149 switch -- $a {150 -on { set testproc [lindex $args [incr i]] }151 -using { set usinglist [lindex $args [incr i]] }152 -left { set isleft 1 }153 default {154 error "Unknown argument: $a"155 }156 }157 }158 159 set c1 [lindex $data1 0]160 set c2 [lindex $data2 0]161 set omitlist [list]162 set nullrowlist [list]163 set cret $c1164 165 set cidx 0166 foreach col $c2 {167 set idx [lsearch $usinglist $col]168 if {$idx>=0} {lappend omitlist $cidx}169 if {$idx<0} {170 lappend nullrowlist {NULL {}}171 lappend cret $col172 }173 incr cidx174 }175 set omitlist [lsort -integer -decreasing $omitlist]176 177 178 set rret [list]179 foreach r1 [lindex $data1 1] {180 set one 0181 foreach r2 [lindex $data2 1] {182 set ok 1183 if {$testproc != ""} {184 set ok [eval $testproc [list $c1 $r1 $c2 $r2]]185 }186 if {$ok} {187 set one 1188 foreach idx $omitlist {set r2 [lreplace $r2 $idx $idx]}189 lappend rret [concat $r1 $r2]190 }191 }192 193 if {$isleft && $one==0} {194 lappend rret [concat $r1 $nullrowlist]195 }196 }197 198 list $cret $rret199}200 201proc te_tbljoin {db t1 t2 args} {202 te_join [te_read_tbl $db $t1] [te_read_tbl $db $t2] {*}$args203}204 205proc te_apply_affinity {affinity typevar valvar} {206 upvar $typevar type207 upvar $valvar val208 209 switch -- $affinity {210 integer {211 if {[string is double $val]} { set type REAL }212 if {[string is wideinteger $val]} { set type INTEGER }213 if {$type == "REAL" && int($val)==$val} { 214 set type INTEGER 215 set val [expr {int($val)}]216 }217 }218 text {219 set type TEXT220 }221 none { }222 223 default { error "invalid affinity: $affinity" }224 }225}226 227#----------228# te_equals ?SWITCHES? c1 c2 cols1 row1 cols2 row2229#230proc te_equals {args} {231 232 if {[llength $args]<6} {error "invalid arguments to te_equals"}233 foreach {c1 c2 cols1 row1 cols2 row2} [lrange $args end-5 end] break234 235 set nocase 0236 set affinity none237 238 for {set i 0} {$i < ([llength $args]-6)} {incr i} {239 set a [lindex $args $i]240 switch -- $a {241 -nocase {242 set nocase 1243 }244 -affinity {245 set affinity [string tolower [lindex $args [incr i]]]246 }247 default {248 error "invalid arguments to te_equals"249 }250 }251 }252 253 set idx2 [if {[string is integer $c2]} { set c2 } else { lsearch $cols2 $c2 }]254 set idx1 [if {[string is integer $c1]} { set c1 } else { lsearch $cols1 $c1 }]255 256 set t1 [lindex $row1 $idx1 0]257 set t2 [lindex $row2 $idx2 0]258 set v1 [lindex $row1 $idx1 1]259 set v2 [lindex $row2 $idx2 1]260 261 te_apply_affinity $affinity t1 v1262 te_apply_affinity $affinity t2 v2263 264 if {$t1 == "NULL" || $t2 == "NULL"} { return 0 }265 if {$nocase && $t1 == "TEXT"} { set v1 [string tolower $v1] }266 if {$nocase && $t2 == "TEXT"} { set v2 [string tolower $v2] }267 268 269 set res [expr {$t1 == $t2 && [string equal $v1 $v2]}]270 return $res271}272 273proc te_false {args} { return 0 }274proc te_true {args} { return 1 }275 276proc te_and {args} {277 foreach a [lrange $args 0 end-4] {278 set res [eval $a [lrange $args end-3 end]]279 if {$res == 0} {return 0}280 }281 return 1282}283 284 285proc te_dataset_eq {testname got expected} {286 uplevel #0 [list do_test $testname [list set {} $got] $expected]287}288proc te_dataset_eq_unordered {testname got expected} {289 lset got 1 [lsort [lindex $got 1]]290 lset expected 1 [lsort [lindex $expected 1]]291 te_dataset_eq $testname $got $expected292}293 294proc te_dataset_ne {testname got unexpected} {295 uplevel #0 [list do_test $testname [list string equal $got $unexpected] 0]296}297proc te_dataset_ne_unordered {testname got unexpected} {298 lset got 1 [lsort [lindex $got 1]]299 lset unexpected 1 [lsort [lindex $unexpected 1]]300 te_dataset_ne $testname $got $unexpected301}302 303 304#-------------------------------------------------------------------------305#306proc test_join {tn sqljoin tbljoinargs} {307 set sql [te_read_sql db "SELECT * FROM $sqljoin"]308 set te [te_tbljoin db {*}$tbljoinargs]309 te_dataset_eq_unordered $tn $sql $te310}311 312drop_all_tables313do_execsql_test e_select-2.0 {314 CREATE TABLE t1(a, b);315 CREATE TABLE t2(a, b);316 CREATE TABLE t3(b COLLATE nocase);317 318 INSERT INTO t1 VALUES(2, 'B');319 INSERT INTO t1 VALUES(1, 'A');320 INSERT INTO t1 VALUES(4, 'D');321 INSERT INTO t1 VALUES(NULL, NULL);322 INSERT INTO t1 VALUES(3, NULL);323 324 INSERT INTO t2 VALUES(1, 'A');325 INSERT INTO t2 VALUES(2, NULL);326 INSERT INTO t2 VALUES(5, 'E');327 INSERT INTO t2 VALUES(NULL, NULL);328 INSERT INTO t2 VALUES(3, 'C');329 330 INSERT INTO t3 VALUES('a');331 INSERT INTO t3 VALUES('c');332 INSERT INTO t3 VALUES('b');333} {}334 335foreach {tn indexes} {336 e_select-2.1.1 { }337 e_select-2.1.2 { CREATE INDEX i1 ON t1(a) }338 e_select-2.1.3 { CREATE INDEX i1 ON t2(a) }339 e_select-2.1.4 { CREATE INDEX i1 ON t3(b) }340} {341 342 catchsql { DROP INDEX i1 }343 catchsql { DROP INDEX i2 }344 catchsql { DROP INDEX i3 }345 execsql $indexes346 347 # EVIDENCE-OF: R-49872-03192 If the join-operator is "CROSS JOIN",348 # "INNER JOIN", "JOIN" or a comma (",") and there is no ON or USING349 # clause, then the result of the join is simply the cartesian product of350 # the left and right-hand datasets.351 #352 # EVIDENCE-OF: R-46256-57243 There is no difference between the "INNER353 # JOIN", "JOIN" and "," join operators.354 #355 # EVIDENCE-OF: R-25071-21202 The "CROSS JOIN" join operator produces the356 # same result as the "INNER JOIN", "JOIN" and "," operators357 #358 test_join $tn.1.1 "t1, t2" {t1 t2}359 test_join $tn.1.2 "t1 INNER JOIN t2" {t1 t2}360 test_join $tn.1.3 "t1 CROSS JOIN t2" {t1 t2}361 test_join $tn.1.4 "t1 JOIN t2" {t1 t2}362 test_join $tn.1.5 "t2, t3" {t2 t3}363 test_join $tn.1.6 "t2 INNER JOIN t3" {t2 t3}364 test_join $tn.1.7 "t2 CROSS JOIN t3" {t2 t3}365 test_join $tn.1.8 "t2 JOIN t3" {t2 t3}366 test_join $tn.1.9 "t2, t2 AS x" {t2 t2}367 test_join $tn.1.10 "t2 INNER JOIN t2 AS x" {t2 t2}368 test_join $tn.1.11 "t2 CROSS JOIN t2 AS x" {t2 t2}369 test_join $tn.1.12 "t2 JOIN t2 AS x" {t2 t2}370 371 # EVIDENCE-OF: R-38465-03616 If there is an ON clause then the ON372 # expression is evaluated for each row of the cartesian product as a373 # boolean expression. Only rows for which the expression evaluates to374 # true are included from the dataset.375 #376 test_join $tn.2.1 "t1, t2 ON (t1.a=t2.a)" {t1 t2 -on {te_equals a a}}377 test_join $tn.2.2 "t2, t1 ON (t1.a=t2.a)" {t2 t1 -on {te_equals a a}}378 test_join $tn.2.3 "t2, t1 ON (1)" {t2 t1 -on te_true}379 test_join $tn.2.4 "t2, t1 ON (NULL)" {t2 t1 -on te_false}380 test_join $tn.2.5 "t2, t1 ON (1.1-1.1)" {t2 t1 -on te_false}381 test_join $tn.2.6 "t1, t2 ON (1.1-1.0)" {t1 t2 -on te_true}382 383 384 test_join $tn.3 "t1 LEFT JOIN t2 ON (t1.a=t2.a)" {t1 t2 -left -on {te_equals a a}}385 test_join $tn.4 "t1 LEFT JOIN t2 USING (a)" {386 t1 t2 -left -using a -on {te_equals a a}387 }388 test_join $tn.5 "t1 CROSS JOIN t2 USING(b, a)" {389 t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}390 }391 test_join $tn.6 "t1 NATURAL JOIN t2" {392 t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}393 }394 test_join $tn.7 "t1 NATURAL INNER JOIN t2" {395 t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}396 }397 test_join $tn.8 "t1 NATURAL CROSS JOIN t2" {398 t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}399 }400 test_join $tn.9 "t1 NATURAL INNER JOIN t2" {401 t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}402 }403 test_join $tn.10 "t1 NATURAL LEFT JOIN t2" {404 t1 t2 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}405 }406 test_join $tn.11 "t1 NATURAL LEFT OUTER JOIN t2" {407 t1 t2 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}408 }409 test_join $tn.12 "t2 NATURAL JOIN t1" {410 t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}411 }412 test_join $tn.13 "t2 NATURAL INNER JOIN t1" {413 t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}414 }415 test_join $tn.14 "t2 NATURAL CROSS JOIN t1" {416 t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}417 }418 test_join $tn.15 "t2 NATURAL INNER JOIN t1" {419 t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}}420 }421 test_join $tn.16 "t2 NATURAL LEFT JOIN t1" {422 t2 t1 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}423 }424 test_join $tn.17 "t2 NATURAL LEFT OUTER JOIN t1" {425 t2 t1 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}}426 }427 test_join $tn.18 "t1 LEFT JOIN t2 USING (b)" {428 t1 t2 -left -using b -on {te_equals b b}429 }430 test_join $tn.19 "t1 JOIN t3 USING(b)" {t1 t3 -using b -on {te_equals b b}}431 test_join $tn.20 "t3 JOIN t1 USING(b)" {432 t3 t1 -using b -on {te_equals -nocase b b}433 }434 test_join $tn.21 "t1 NATURAL JOIN t3" {435 t1 t3 -using b -on {te_equals b b}436 }437 test_join $tn.22 "t3 NATURAL JOIN t1" {438 t3 t1 -using b -on {te_equals -nocase b b}439 }440 test_join $tn.23 "t1 NATURAL LEFT JOIN t3" {441 t1 t3 -left -using b -on {te_equals b b}442 }443 test_join $tn.24 "t3 NATURAL LEFT JOIN t1" {444 t3 t1 -left -using b -on {te_equals -nocase b b}445 }446 test_join $tn.25 "t1 LEFT JOIN t3 ON (t3.b=t1.b)" {447 t1 t3 -left -on {te_equals -nocase b b}448 }449 test_join $tn.26 "t1 LEFT JOIN t3 ON (t1.b=t3.b)" {450 t1 t3 -left -on {te_equals b b}451 }452 test_join $tn.27 "t1 JOIN t3 ON (t1.b=t3.b)" { t1 t3 -on {te_equals b b} }453 454 # EVIDENCE-OF: R-28760-53843 When more than two tables are joined455 # together as part of a FROM clause, the join operations are processed456 # in order from left to right. In other words, the FROM clause (A457 # join-op-1 B join-op-2 C) is computed as ((A join-op-1 B) join-op-2 C).458 #459 # Tests 28a and 28b show that the statement above is true for this case.460 # Test 28c shows that if the parenthesis force a different order of461 # evaluation the result is different. Test 28d verifies that the result462 # of the query with the parenthesis forcing a different order of evaluation463 # is as calculated by the [te_*] procs.464 #465 set t3_natural_left_join_t2 [466 te_tbljoin db t3 t2 -left -using {b} -on {te_equals -nocase b b}467 ]468 set t1 [te_read_tbl db t1]469 te_dataset_eq_unordered $tn.28a [470 te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN t2 NATURAL JOIN t1"471 ] [te_join $t3_natural_left_join_t2 $t1 \472 -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}} \473 ]474 475 te_dataset_eq_unordered $tn.28b [476 te_read_sql db "SELECT * FROM (t3 NATURAL LEFT JOIN t2) NATURAL JOIN t1"477 ] [te_join $t3_natural_left_join_t2 $t1 \478 -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}} \479 ]480 481 te_dataset_ne_unordered $tn.28c [482 te_read_sql db "SELECT * FROM (t3 NATURAL LEFT JOIN t2) NATURAL JOIN t1"483 ] [484 te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN (t2 NATURAL JOIN t1)"485 ]486 487 set t2_natural_join_t1 [te_tbljoin db t2 t1 -using {a b} \488 -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}} \489 ]490 set t3 [te_read_tbl db t3]491 te_dataset_eq_unordered $tn.28d [492 te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN (t2 NATURAL JOIN t1)"493 ] [te_join $t3 $t2_natural_join_t1 \494 -left -using {b} -on {te_equals -nocase b b} \495 ]496}497 498do_execsql_test e_select-2.2.0 {499 CREATE TABLE t4(x TEXT COLLATE nocase);500 CREATE TABLE t5(y INTEGER, z TEXT COLLATE binary);501 502 INSERT INTO t4 VALUES('2.0');503 INSERT INTO t4 VALUES('TWO');504 INSERT INTO t5 VALUES(2, 'two');505} {}506 507# EVIDENCE-OF: R-59237-46742 A subquery specified in the508# table-or-subquery following the FROM clause in a simple SELECT509# statement is handled as if it was a table containing the data returned510# by executing the subquery statement.511#512# EVIDENCE-OF: R-27438-53558 Each column of the subquery has the513# collation sequence and affinity of the corresponding expression in the514# subquery statement.515#516foreach {tn subselect select spec} {517 1 "SELECT * FROM t2" "SELECT * FROM t1 JOIN %ss%" 518 {t1 %ss%}519 520 2 "SELECT * FROM t2" "SELECT * FROM t1 JOIN %ss% AS x ON (t1.a=x.a)" 521 {t1 %ss% -on {te_equals 0 0}}522 523 3 "SELECT * FROM t2" "SELECT * FROM %ss% AS x JOIN t1 ON (t1.a=x.a)" 524 {%ss% t1 -on {te_equals 0 0}}525 526 4 "SELECT * FROM t1, t2" "SELECT * FROM %ss% AS x JOIN t3"527 {%ss% t3}528 529 5 "SELECT * FROM t1, t2" "SELECT * FROM %ss% NATURAL JOIN t3"530 {%ss% t3 -using b -on {te_equals 1 0}}531 532 6 "SELECT * FROM t1, t2" "SELECT * FROM t3 NATURAL JOIN %ss%"533 {t3 %ss% -using b -on {te_equals -nocase 0 1}}534 535 7 "SELECT * FROM t1, t2" "SELECT * FROM t3 NATURAL LEFT JOIN %ss%"536 {t3 %ss% -left -using b -on {te_equals -nocase 0 1}}537 538 8 "SELECT count(*) AS y FROM t4" "SELECT * FROM t5, %ss% USING (y)"539 {t5 %ss% -using y -on {te_equals -affinity text 0 0}}540 541 9 "SELECT count(*) AS y FROM t4" "SELECT * FROM %ss%, t5 USING (y)"542 {%ss% t5 -using y -on {te_equals -affinity text 0 0}}543 544 10 "SELECT x AS y FROM t4" "SELECT * FROM %ss% JOIN t5 USING (y)"545 {%ss% t5 -using y -on {te_equals -nocase -affinity integer 0 0}}546 547 11 "SELECT x AS y FROM t4" "SELECT * FROM t5 JOIN %ss% USING (y)"548 {t5 %ss% -using y -on {te_equals -nocase -affinity integer 0 0}}549 550 12 "SELECT y AS x FROM t5" "SELECT * FROM %ss% JOIN t4 USING (x)"551 {%ss% t4 -using x -on {te_equals -nocase -affinity integer 0 0}}552 553 13 "SELECT y AS x FROM t5" "SELECT * FROM t4 JOIN %ss% USING (x)"554 {t4 %ss% -using x -on {te_equals -nocase -affinity integer 0 0}}555 556 14 "SELECT +y AS x FROM t5" "SELECT * FROM %ss% JOIN t4 USING (x)"557 {%ss% t4 -using x -on {te_equals -nocase -affinity text 0 0}}558 559 15 "SELECT +y AS x FROM t5" "SELECT * FROM t4 JOIN %ss% USING (x)"560 {t4 %ss% -using x -on {te_equals -nocase -affinity text 0 0}}561} {562 563 # Create a temporary table named %ss% containing the data returned by564 # the sub-select. Then have the [te_tbljoin] proc use this table to565 # compute the expected results of the $select query. Drop the temporary566 # table before continuing.567 #568 execsql "CREATE TEMP TABLE '%ss%' AS $subselect"569 set te [eval te_tbljoin db $spec]570 execsql "DROP TABLE '%ss%'"571 572 # Check that the actual data returned by the $select query is the same573 # as the expected data calculated using [te_tbljoin] above.574 #575 te_dataset_eq_unordered e_select-2.2.1.$tn [576 te_read_sql db [string map [list %ss% "($subselect)"] $select]577 ] $te578}579 580finish_test581 