1# 2014 Dec 20
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# Tests focusing on the auxiliary function APIs.
13#
14
15source [file join [file dirname [info script]] fts5_common.tcl]
16set testprefix fts5aux
17
18# If SQLITE_ENABLE_FTS5 is defined, omit this file.
19ifcapable !fts5 {
20  finish_test
21  return
22}
23
24proc inst {cmd i} {
25  $cmd xInst $i
26}
27sqlite3_fts5_create_function db inst inst
28
29proc colsize {cmd i} {
30  $cmd xColumnSize $i
31}
32sqlite3_fts5_create_function db colsize colsize
33
34proc totalsize {cmd i} {
35  $cmd xColumnTotalSize $i
36}
37sqlite3_fts5_create_function db totalsize totalsize
38
39do_execsql_test 1.0 {
40  CREATE VIRTUAL TABLE f1 USING fts5(a, b);
41  INSERT INTO f1 VALUES('one two', 'two one zero');
42  INSERT INTO f1 VALUES('one one', 'one one one');
43}
44
45do_catchsql_test 1.1 {
46  SELECT inst(f1, -1) FROM f1 WHERE f1 MATCH 'two';
47} {1 SQLITE_RANGE}
48do_catchsql_test 1.2 {
49  SELECT inst(f1, 0) FROM f1 WHERE f1 MATCH 'two';
50} {0 {{0 0 1}}}
51do_catchsql_test 1.3 {
52  SELECT inst(f1, 1) FROM f1 WHERE f1 MATCH 'two';
53} {0 {{0 1 0}}}
54do_catchsql_test 1.4 {
55  SELECT inst(f1, 2) FROM f1 WHERE f1 MATCH 'two';
56} {1 SQLITE_RANGE}
57
58do_catchsql_test 2.1 {
59  SELECT colsize(f1, 2) FROM f1 WHERE f1 MATCH 'two';
60} {1 SQLITE_RANGE}
61do_execsql_test 2.2 {
62  SELECT colsize(f1, 0), colsize(f1, 1) FROM f1 WHERE f1 MATCH 'zero';
63} {2 3}
64do_execsql_test 2.3 {
65  SELECT colsize(f1, -1) FROM f1 WHERE f1 MATCH 'zero';
66} {5}
67
68do_execsql_test 2.4.1 {
69  SELECT totalsize(f1, -1) FROM f1 WHERE f1 MATCH 'zero';
70} {10}
71do_execsql_test 2.4.2 {
72  SELECT totalsize(f1, 0) FROM f1 WHERE f1 MATCH 'zero';
73} {4}
74do_execsql_test 2.4.3 {
75  SELECT totalsize(f1, 1) FROM f1 WHERE f1 MATCH 'zero';
76} {6}
77do_catchsql_test 2.4.4 {
78  SELECT totalsize(f1, 2) FROM f1 WHERE f1 MATCH 'zero';
79} {1 SQLITE_RANGE}
80
81#-------------------------------------------------------------------------
82# Test the xSet and xGetAuxdata APIs with a NULL destructor.
83#
84proc prevrowid {add cmd} {
85  set res [$cmd xGetAuxdataInt 0]
86  set r [$cmd xRowid]
87  $cmd xSetAuxdataInt $r
88  return [expr $res + $add]
89}
90sqlite3_fts5_create_function db prevrowid  [list prevrowid 0]
91sqlite3_fts5_create_function db prevrowid1 [list prevrowid 1]
92
93do_execsql_test 3.0 {
94  CREATE VIRTUAL TABLE e5 USING fts5(x);
95  INSERT INTO e5 VALUES('a b c');
96  INSERT INTO e5 VALUES('d e f');
97  INSERT INTO e5 VALUES('a b c');
98  INSERT INTO e5 VALUES('d e f');
99  INSERT INTO e5 VALUES('a b c');
100}
101
102do_execsql_test 3.1 {
103  SELECT prevrowid(e5) || '+' || rowid FROM e5 WHERE e5 MATCH 'c'
104} {0+1   1+3   3+5}
105
106do_execsql_test 3.2 {
107  SELECT prevrowid(e5) || '+' || prevrowid1(e5) || '+' || rowid
108  FROM e5 WHERE e5 MATCH 'e'
109} {0+1+2    2+3+4}
110
111#-------------------------------------------------------------------------
112# Test that if the xQueryPhrase callback returns other than SQLITE_OK,
113# the query is abandoned. And that if it returns an error code other than
114# SQLITE_DONE, the error is propagated back to the caller.
115#
116do_execsql_test 4.0 {
117  CREATE VIRTUAL TABLE e7 USING fts5(x);
118  INSERT INTO e7 VALUES('a x a');
119  INSERT INTO e7 VALUES('b x b');
120  INSERT INTO e7 VALUES('c x c');
121  INSERT INTO e7 VALUES('d x d');
122  INSERT INTO e7 VALUES('e x e');
123}
124
125proc xCallback {rowid code cmd} {
126  set r [$cmd xRowid]
127  lappend ::cb $r
128  if {$r==$rowid} { return $code }
129  return ""
130}
131
132proc phrasequery {cmd code} {
133  set ::cb [list]
134  $cmd xQueryPhrase 1 [list xCallback [$cmd xRowid] $code]
135  set ::cb
136}
137
138sqlite3_fts5_create_function db phrasequery phrasequery
139
140do_execsql_test 4.1 {
141  SELECT phrasequery(e7, 'SQLITE_OK') FROM e7 WHERE e7 MATCH 'c x'
142} {{1 2 3 4 5}}
143
144do_execsql_test 4.2 {
145  SELECT phrasequery(e7, 'SQLITE_DONE') FROM e7 WHERE e7 MATCH 'c x'
146} {{1 2 3}}
147
148do_catchsql_test 4.3 {
149  SELECT phrasequery(e7, 'SQLITE_ERROR') FROM e7 WHERE e7 MATCH 'c x'
150} {1 SQLITE_ERROR}
151
152#-------------------------------------------------------------------------
153# Auxiliary function calls with many cursors in the global cursor list.
154#
155do_execsql_test 5.0 {
156  CREATE VIRTUAL TABLE e9 USING fts5(y);
157  INSERT INTO e9(rowid, y) VALUES(1, 'i iii');
158  INSERT INTO e9(rowid, y) VALUES(2, 'ii iv');
159  INSERT INTO e9(rowid, y) VALUES(3, 'ii');
160  INSERT INTO e9(rowid, y) VALUES(4, 'i iv');
161  INSERT INTO e9(rowid, y) VALUES(5, 'iii');
162}
163
164proc my_rowid {cmd} { $cmd xRowid }
165sqlite3_fts5_create_function db my_rowid my_rowid
166
167foreach {var q} {
168  s1 i
169  s2 ii
170  s3 iii
171  s4 iv
172} {
173  set sql "SELECT my_rowid(e9) FROM e9 WHERE e9 MATCH '$q'"
174  set $var [sqlite3_prepare db $sql -1 dummy]
175}
176
177do_test 5.1.1 { sqlite3_step $s1 ; sqlite3_column_int $s1 0 } 1
178do_test 5.1.2 { sqlite3_step $s2 ; sqlite3_column_int $s2 0 } 2
179do_test 5.1.3 { sqlite3_step $s3 ; sqlite3_column_int $s3 0 } 1
180do_test 5.1.4 { sqlite3_step $s4 ; sqlite3_column_int $s4 0 } 2
181
182do_test 5.2.1 { sqlite3_step $s1 ; sqlite3_column_int $s1 0 } 4
183do_test 5.2.2 { sqlite3_step $s2 ; sqlite3_column_int $s2 0 } 3
184do_test 5.2.3 { sqlite3_step $s3 ; sqlite3_column_int $s3 0 } 5
185do_test 5.2.4 { sqlite3_step $s4 ; sqlite3_column_int $s4 0 } 4
186
187sqlite3_finalize $s1
188sqlite3_finalize $s2
189sqlite3_finalize $s3
190sqlite3_finalize $s4
191
192#-------------------------------------------------------------------------
193# Passing an invalid first argument to an auxiliary function is detected.
194#
195do_execsql_test 6.0 {
196  CREATE VIRTUAL TABLE e11 USING fts5(y, z);
197  INSERT INTO e11(rowid, y, z) VALUES(1, 'a b', 45);
198  INSERT INTO e11(rowid, y, z) VALUES(2, 'b c', 46);
199}
200
201do_catchsql_test 6.1 {
202  SELECT my_rowid(z) FROM e11 WHERE e11 MATCH 'b'
203} {1 {no such cursor: 45}}
204
205do_catchsql_test 6.2 {
206  SELECT my_rowid(y) FROM e11 WHERE e11 MATCH 'b'
207} {1 {no such cursor: 0}}
208
209#-------------------------------------------------------------------------
210# Test passing an out-of-range phrase number to xPhraseSize (should
211# return 0).
212#
213proc my_phrasesize {cmd iPhrase} { $cmd xPhraseSize $iPhrase }
214sqlite3_fts5_create_function db my_phrasesize my_phrasesize
215
216do_execsql_test 7.1 {
217  CREATE VIRTUAL TABLE t1 USING fts5(a);
218  INSERT INTO t1 VALUES('a b c');
219}
220do_execsql_test 7.2 {
221  SELECT
222    my_phrasesize(t1, -1),
223    my_phrasesize(t1, 0),
224    my_phrasesize(t1, 1),
225    my_phrasesize(t1, 2)
226  FROM t1 WHERE t1 MATCH 'a OR b+c'
227} {0 1 2 0}
228
229#-------------------------------------------------------------------------
230#
231do_execsql_test 8.0 {
232  CREATE VIRTUAL TABLE x1 USING fts5(a);
233}
234
235foreach {tn lRow res} {
236  4  {"a a a" "b" "a d"} {"[a] [a] [a]" "[a] d"}
237  1  {"b d" "a b"}       {"[b] [d]" "[a] b"}
238  2  {"d b" "a d"}       {"[d] [b]" "[a] d"}
239  3  {"a a d"}           {"[a] [a] d"}
240} {
241  execsql { DELETE FROM x1 }
242  foreach row $lRow { execsql { INSERT INTO x1 VALUES($row) } }
243  do_execsql_test 8.$tn {
244    SELECT highlight(x1, 0, '[', ']') FROM x1 WHERE x1 MATCH 'a OR (b AND d)';
245  } $res
246}
247
248#-------------------------------------------------------------------------
249# Test the built-in bm25() demo.
250#
251reset_db
252do_execsql_test 9.1 {
253  CREATE VIRTUAL TABLE t1 USING fts5(a, b);
254  INSERT INTO t1 VALUES('a',   NULL);           -- 1
255  INSERT INTO t1 VALUES('a',   NULL);           -- 2
256  INSERT INTO t1 VALUES('a',   NULL);           -- 3
257  INSERT INTO t1 VALUES('a',   NULL);           -- 4
258  INSERT INTO t1 VALUES('a',   NULL);           -- 5
259  INSERT INTO t1 VALUES('a',   NULL);           -- 6
260  INSERT INTO t1 VALUES('a',   NULL);           -- 7
261  INSERT INTO t1 VALUES('a',   NULL);           -- 8
262  INSERT INTO t1 VALUES(NULL,  'a a b');        -- 9
263  INSERT INTO t1 VALUES(NULL,  'b b a');        -- 10
264}
265
266do_execsql_test 9.2 {
267  SELECT rowid FROM t1('a AND b') ORDER BY rank;
268} {
269  10 9
270}
271
272do_execsql_test 9.3 {
273  SELECT rowid FROM t1('b:a AND b:b') ORDER BY rank;
274} {
275  9 10
276}
277
278
279
280finish_test
281