• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 2010 September 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#
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.tcl
18
19#-------------------------------------------------------------------------
20# te_* commands:
21#
22#
23#   te_read_sql DB SELECT-STATEMENT
24#   te_read_tbl DB TABLENAME
25#
26# These two commands are used to read a dataset from the database. A dataset
27# consists of N rows of M named columns of values each, where each value has a
28# 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 is
33#     the contents of a column of the row. Each of these is a list of two
34#     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. The
45# [te_read_sql] returns the dataset that results from executing a SELECT
46# command.
47#
48#
49#   te_tbljoin ?SWITCHES? LHS-TABLE RHS-TABLE
50#   te_join ?SWITCHES? LHS-DATASET RHS-DATASET
51#
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 product
54# of the two inputs.  The [te_tbljoin] command reads the left and right-hand
55# datasets from the specified tables. The [te_join] command is passed the
56# datasets directly.
57#
58# Optional switches are as follows:
59#
60#   -on SCRIPT
61#   -using COLUMN-LIST
62#   -left
63#
64# The -on option specifies a tcl script that is executed for each row in the
65# cartesian product of the two datasets. The script has 4 arguments appended
66# 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 rows
75# should be included in the output dataset, or false otherwise.
76#
77# The -using option specifies a list of the columns from the right-hand
78# 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 there
82# exist rows in the left-hand dataset that have no corresponding rows in
83# 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 values
89# are compared in a case-independent fashion. Otherwise, they are compared
90# 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 TABLENAME
100#   te_read_sql DB SELECT-STATEMENT
101#
102# These two procs are used to extract datasets from the database, either
103# by reading the contents of a named table (te_read_tbl), or by executing
104# a SELECT statement (t3_read_sql).
105#
106# See the comment above, describing "te_* commands", for details of the
107# 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 $r
127  }
128  sqlite3_finalize $S
129
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 0
147  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 $c1
164
165  set cidx 0
166  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 $col
172    }
173    incr cidx
174  }
175  set omitlist [lsort -integer -decreasing $omitlist]
176
177
178  set rret [list]
179  foreach r1 [lindex $data1 1] {
180    set one 0
181    foreach r2 [lindex $data2 1] {
182      set ok 1
183      if {$testproc != ""} {
184        set ok [eval $testproc [list $c1 $r1 $c2 $r2]]
185      }
186      if {$ok} {
187        set one 1
188        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 $rret
199}
200
201proc te_tbljoin {db t1 t2 args} {
202  te_join [te_read_tbl $db $t1] [te_read_tbl $db $t2] {*}$args
203}
204
205proc te_apply_affinity {affinity typevar valvar} {
206  upvar $typevar type
207  upvar $valvar val
208
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 TEXT
220    }
221    none { }
222
223    default { error "invalid affinity: $affinity" }
224  }
225}
226
227#----------
228# te_equals ?SWITCHES? c1 c2 cols1 row1 cols2 row2
229#
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] break
234
235  set nocase 0
236  set affinity none
237
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 1
243      }
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 v1
262  te_apply_affinity $affinity t2 v2
263
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 $res
271}
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 1
282}
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 $expected
292}
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 $unexpected
301}
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 $te
310}
311
312drop_all_tables
313do_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 $indexes
346
347  # EVIDENCE-OF: R-46122-14930 If the join-op is "CROSS JOIN", "INNER
348  # JOIN", "JOIN" or a comma (",") and there is no ON or USING clause,
349  # then the result of the join is simply the cartesian product of the
350  # left and right-hand datasets.
351  #
352  # EVIDENCE-OF: R-46256-57243 There is no difference between the "INNER
353  # JOIN", "JOIN" and "," join operators.
354  #
355  # EVIDENCE-OF: R-07544-24155 The "CROSS JOIN" join operator produces the
356  # same data as the "INNER JOIN", "JOIN" and "," operators
357  #
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-22775-56496 If there is an ON clause specified, then
372  # the ON expression is evaluated for each row of the cartesian product
373  # as a boolean expression. All rows for which the expression evaluates
374  # to false are excluded 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 joined
455  # together as part of a FROM clause, the join operations are processed
456  # in order from left to right. In other words, the FROM clause (A
457  # 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 of
461  #   evaluation the result is different. Test 28d verifies that the result
462  #   of the query with the parenthesis forcing a different order of evaluation
463  #   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-55824-40976 A sub-select specified in the join-source
508# following the FROM clause in a simple SELECT statement is handled as
509# if it was a table containing the data returned by executing the
510# sub-select statement.
511#
512# EVIDENCE-OF: R-42612-06757 Each column of the sub-select dataset
513# inherits the collation sequence and affinity of the corresponding
514# expression in the sub-select 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 by
564  # the sub-select. Then have the [te_tbljoin] proc use this table to
565  # compute the expected results of the $select query. Drop the temporary
566  # 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 same
573  # 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  ] $te
578}
579
580finish_test
581