fts5aux.test 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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. source [file join [file dirname [info script]] fts5_common.tcl]
  15. set testprefix fts5aux
  16. # If SQLITE_ENABLE_FTS5 is not defined, omit this file.
  17. ifcapable !fts5 {
  18. finish_test
  19. return
  20. }
  21. proc inst {cmd i} {
  22. $cmd xInst $i
  23. }
  24. sqlite3_fts5_create_function db inst inst
  25. proc colsize {cmd i} {
  26. $cmd xColumnSize $i
  27. }
  28. sqlite3_fts5_create_function db colsize colsize
  29. proc totalsize {cmd i} {
  30. $cmd xColumnTotalSize $i
  31. }
  32. sqlite3_fts5_create_function db totalsize totalsize
  33. do_execsql_test 1.0 {
  34. CREATE VIRTUAL TABLE f1 USING fts5(a, b);
  35. INSERT INTO f1 VALUES('one two', 'two one zero');
  36. INSERT INTO f1 VALUES('one one', 'one one one');
  37. }
  38. do_catchsql_test 1.1 {
  39. SELECT inst(f1, -1) FROM f1 WHERE f1 MATCH 'two';
  40. } {1 SQLITE_RANGE}
  41. do_catchsql_test 1.2 {
  42. SELECT inst(f1, 0) FROM f1 WHERE f1 MATCH 'two';
  43. } {0 {{0 0 1}}}
  44. do_catchsql_test 1.3 {
  45. SELECT inst(f1, 1) FROM f1 WHERE f1 MATCH 'two';
  46. } {0 {{0 1 0}}}
  47. do_catchsql_test 1.4 {
  48. SELECT inst(f1, 2) FROM f1 WHERE f1 MATCH 'two';
  49. } {1 SQLITE_RANGE}
  50. do_catchsql_test 2.1 {
  51. SELECT colsize(f1, 2) FROM f1 WHERE f1 MATCH 'two';
  52. } {1 SQLITE_RANGE}
  53. do_execsql_test 2.2 {
  54. SELECT colsize(f1, 0), colsize(f1, 1) FROM f1 WHERE f1 MATCH 'zero';
  55. } {2 3}
  56. do_execsql_test 2.3 {
  57. SELECT colsize(f1, -1) FROM f1 WHERE f1 MATCH 'zero';
  58. } {5}
  59. do_execsql_test 2.4.1 {
  60. SELECT totalsize(f1, -1) FROM f1 WHERE f1 MATCH 'zero';
  61. } {10}
  62. do_execsql_test 2.4.2 {
  63. SELECT totalsize(f1, 0) FROM f1 WHERE f1 MATCH 'zero';
  64. } {4}
  65. do_execsql_test 2.4.3 {
  66. SELECT totalsize(f1, 1) FROM f1 WHERE f1 MATCH 'zero';
  67. } {6}
  68. do_catchsql_test 2.4.4 {
  69. SELECT totalsize(f1, 2) FROM f1 WHERE f1 MATCH 'zero';
  70. } {1 SQLITE_RANGE}
  71. #-------------------------------------------------------------------------
  72. # Test the xSet and xGetAuxdata APIs with a NULL destructor.
  73. #
  74. proc prevrowid {add cmd} {
  75. set res [$cmd xGetAuxdataInt 0]
  76. set r [$cmd xRowid]
  77. $cmd xSetAuxdataInt $r
  78. return [expr $res + $add]
  79. }
  80. sqlite3_fts5_create_function db prevrowid [list prevrowid 0]
  81. sqlite3_fts5_create_function db prevrowid1 [list prevrowid 1]
  82. do_execsql_test 3.0 {
  83. CREATE VIRTUAL TABLE e5 USING fts5(x);
  84. INSERT INTO e5 VALUES('a b c');
  85. INSERT INTO e5 VALUES('d e f');
  86. INSERT INTO e5 VALUES('a b c');
  87. INSERT INTO e5 VALUES('d e f');
  88. INSERT INTO e5 VALUES('a b c');
  89. }
  90. do_execsql_test 3.1 {
  91. SELECT prevrowid(e5) || '+' || rowid FROM e5 WHERE e5 MATCH 'c'
  92. } {0+1 1+3 3+5}
  93. do_execsql_test 3.2 {
  94. SELECT prevrowid(e5) || '+' || prevrowid1(e5) || '+' || rowid
  95. FROM e5 WHERE e5 MATCH 'e'
  96. } {0+1+2 2+3+4}
  97. #-------------------------------------------------------------------------
  98. # Test that if the xQueryPhrase callback returns other than SQLITE_OK,
  99. # the query is abandoned. And that if it returns an error code other than
  100. # SQLITE_DONE, the error is propagated back to the caller.
  101. #
  102. do_execsql_test 4.0 {
  103. CREATE VIRTUAL TABLE e7 USING fts5(x);
  104. INSERT INTO e7 VALUES('a x a');
  105. INSERT INTO e7 VALUES('b x b');
  106. INSERT INTO e7 VALUES('c x c');
  107. INSERT INTO e7 VALUES('d x d');
  108. INSERT INTO e7 VALUES('e x e');
  109. }
  110. proc xCallback {rowid code cmd} {
  111. set r [$cmd xRowid]
  112. lappend ::cb $r
  113. if {$r==$rowid} { return $code }
  114. return ""
  115. }
  116. proc phrasequery {cmd code} {
  117. set ::cb [list]
  118. $cmd xQueryPhrase 1 [list xCallback [$cmd xRowid] $code]
  119. set ::cb
  120. }
  121. sqlite3_fts5_create_function db phrasequery phrasequery
  122. do_execsql_test 4.1 {
  123. SELECT phrasequery(e7, 'SQLITE_OK') FROM e7 WHERE e7 MATCH 'c x'
  124. } {{1 2 3 4 5}}
  125. do_execsql_test 4.2 {
  126. SELECT phrasequery(e7, 'SQLITE_DONE') FROM e7 WHERE e7 MATCH 'c x'
  127. } {{1 2 3}}
  128. do_catchsql_test 4.3 {
  129. SELECT phrasequery(e7, 'SQLITE_ERROR') FROM e7 WHERE e7 MATCH 'c x'
  130. } {1 SQLITE_ERROR}
  131. #-------------------------------------------------------------------------
  132. # Auxiliary function calls with many cursors in the global cursor list.
  133. #
  134. do_execsql_test 5.0 {
  135. CREATE VIRTUAL TABLE e9 USING fts5(y);
  136. INSERT INTO e9(rowid, y) VALUES(1, 'i iii');
  137. INSERT INTO e9(rowid, y) VALUES(2, 'ii iv');
  138. INSERT INTO e9(rowid, y) VALUES(3, 'ii');
  139. INSERT INTO e9(rowid, y) VALUES(4, 'i iv');
  140. INSERT INTO e9(rowid, y) VALUES(5, 'iii');
  141. }
  142. proc my_rowid {cmd} { $cmd xRowid }
  143. sqlite3_fts5_create_function db my_rowid my_rowid
  144. foreach {var q} {
  145. s1 i
  146. s2 ii
  147. s3 iii
  148. s4 iv
  149. } {
  150. set sql "SELECT my_rowid(e9) FROM e9 WHERE e9 MATCH '$q'"
  151. set $var [sqlite3_prepare db $sql -1 dummy]
  152. }
  153. do_test 5.1.1 { sqlite3_step $s1 ; sqlite3_column_int $s1 0 } 1
  154. do_test 5.1.2 { sqlite3_step $s2 ; sqlite3_column_int $s2 0 } 2
  155. do_test 5.1.3 { sqlite3_step $s3 ; sqlite3_column_int $s3 0 } 1
  156. do_test 5.1.4 { sqlite3_step $s4 ; sqlite3_column_int $s4 0 } 2
  157. do_test 5.2.1 { sqlite3_step $s1 ; sqlite3_column_int $s1 0 } 4
  158. do_test 5.2.2 { sqlite3_step $s2 ; sqlite3_column_int $s2 0 } 3
  159. do_test 5.2.3 { sqlite3_step $s3 ; sqlite3_column_int $s3 0 } 5
  160. do_test 5.2.4 { sqlite3_step $s4 ; sqlite3_column_int $s4 0 } 4
  161. sqlite3_finalize $s1
  162. sqlite3_finalize $s2
  163. sqlite3_finalize $s3
  164. sqlite3_finalize $s4
  165. #-------------------------------------------------------------------------
  166. # Passing an invalid first argument to an auxiliary function is detected.
  167. #
  168. do_execsql_test 6.0 {
  169. CREATE VIRTUAL TABLE e11 USING fts5(y, z);
  170. INSERT INTO e11(rowid, y, z) VALUES(1, 'a b', 45);
  171. INSERT INTO e11(rowid, y, z) VALUES(2, 'b c', 46);
  172. }
  173. do_catchsql_test 6.1 {
  174. SELECT my_rowid(z) FROM e11 WHERE e11 MATCH 'b'
  175. } {1 {no such cursor: 45}}
  176. do_catchsql_test 6.2 {
  177. SELECT my_rowid(y) FROM e11 WHERE e11 MATCH 'b'
  178. } {1 {no such cursor: 0}}
  179. #-------------------------------------------------------------------------
  180. # Test passing an out-of-range phrase number to xPhraseSize (should
  181. # return 0).
  182. #
  183. proc my_phrasesize {cmd iPhrase} { $cmd xPhraseSize $iPhrase }
  184. sqlite3_fts5_create_function db my_phrasesize my_phrasesize
  185. do_execsql_test 7.1 {
  186. CREATE VIRTUAL TABLE t1 USING fts5(a);
  187. INSERT INTO t1 VALUES('a b c');
  188. }
  189. do_execsql_test 7.2 {
  190. SELECT
  191. my_phrasesize(t1, -1),
  192. my_phrasesize(t1, 0),
  193. my_phrasesize(t1, 1),
  194. my_phrasesize(t1, 2)
  195. FROM t1 WHERE t1 MATCH 'a OR b+c'
  196. } {0 1 2 0}
  197. #-------------------------------------------------------------------------
  198. #
  199. do_execsql_test 8.0 {
  200. CREATE VIRTUAL TABLE x1 USING fts5(a);
  201. }
  202. foreach {tn lRow res} {
  203. 4 {"a a a" "b" "a d"} {"[a] [a] [a]" "[a] d"}
  204. 1 {"b d" "a b"} {"[b] [d]" "[a] b"}
  205. 2 {"d b" "a d"} {"[d] [b]" "[a] d"}
  206. 3 {"a a d"} {"[a] [a] d"}
  207. } {
  208. execsql { DELETE FROM x1 }
  209. foreach row $lRow { execsql { INSERT INTO x1 VALUES($row) } }
  210. do_execsql_test 8.$tn {
  211. SELECT highlight(x1, 0, '[', ']') FROM x1 WHERE x1 MATCH 'a OR (b AND d)';
  212. } $res
  213. }
  214. #-------------------------------------------------------------------------
  215. # Test the built-in bm25() demo.
  216. #
  217. reset_db
  218. do_execsql_test 9.1 {
  219. CREATE VIRTUAL TABLE t1 USING fts5(a, b);
  220. INSERT INTO t1 VALUES('a', NULL); -- 1
  221. INSERT INTO t1 VALUES('a', NULL); -- 2
  222. INSERT INTO t1 VALUES('a', NULL); -- 3
  223. INSERT INTO t1 VALUES('a', NULL); -- 4
  224. INSERT INTO t1 VALUES('a', NULL); -- 5
  225. INSERT INTO t1 VALUES('a', NULL); -- 6
  226. INSERT INTO t1 VALUES('a', NULL); -- 7
  227. INSERT INTO t1 VALUES('a', NULL); -- 8
  228. INSERT INTO t1 VALUES(NULL, 'a a b'); -- 9
  229. INSERT INTO t1 VALUES(NULL, 'b b a'); -- 10
  230. }
  231. do_execsql_test 9.2 {
  232. SELECT rowid FROM t1('a AND b') ORDER BY rank;
  233. } {
  234. 10 9
  235. }
  236. do_execsql_test 9.3 {
  237. SELECT rowid FROM t1('b:a AND b:b') ORDER BY rank;
  238. } {
  239. 9 10
  240. }
  241. #-------------------------------------------------------------------------
  242. # Test that aux. functions may not be used in aggregate queries.
  243. #
  244. reset_db
  245. do_execsql_test 10.0 {
  246. CREATE VIRTUAL TABLE t1 USING fts5(x, y, z);
  247. INSERT INTO t1 VALUES('a', 'one two', 1);
  248. INSERT INTO t1 VALUES('b', 'two three', 2);
  249. INSERT INTO t1 VALUES('c', 'three four', 1);
  250. INSERT INTO t1 VALUES('d', 'four five', 2);
  251. INSERT INTO t1 VALUES('e', 'five six', 1);
  252. INSERT INTO t1 VALUES('f', 'six seven', 2);
  253. }
  254. proc firstcol {cmd} { $cmd xColumnText 0 }
  255. sqlite3_fts5_create_function db firstcol firstcol
  256. do_execsql_test 10.1.1 {
  257. SELECT firstcol(t1) FROM t1
  258. } {a b c d e f}
  259. do_execsql_test 10.1.2 {
  260. SELECT group_concat(x, '.') FROM t1
  261. } {a.b.c.d.e.f}
  262. do_catchsql_test 10.1.3 {
  263. SELECT group_concat(firstcol(t1), '.') FROM t1
  264. } {1 {unable to use function firstcol in the requested context}}
  265. do_catchsql_test 10.1.4 {
  266. SELECT group_concat(firstcol(t1), '.') FROM t1 GROUP BY rowid
  267. } {1 {unable to use function firstcol in the requested context}}
  268. #-------------------------------------------------------------------------
  269. # Test that xInstCount() works from within an xPhraseQuery() callback.
  270. #
  271. reset_db
  272. proc xCallback {cmd} {
  273. incr ::hitcount [$cmd xInstCount]
  274. return SQLITE_OK
  275. }
  276. proc fts5_hitcount {cmd} {
  277. set ::hitcount 0
  278. $cmd xQueryPhrase 0 xCallback
  279. return $::hitcount
  280. }
  281. sqlite3_fts5_create_function db fts5_hitcount fts5_hitcount
  282. do_execsql_test 11.1 {
  283. CREATE VIRTUAL TABLE x1 USING fts5(z);
  284. INSERT INTO x1 VALUES('one two three');
  285. INSERT INTO x1 VALUES('one two one three one');
  286. INSERT INTO x1 VALUES('one two three');
  287. }
  288. do_execsql_test 11.2 {
  289. SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1;
  290. } {5}
  291. #-------------------------------------------------------------------------
  292. # Test that xColumnText returns SQLITE_RANGE when it should.
  293. #
  294. reset_db
  295. fts5_aux_test_functions db
  296. do_execsql_test 12.0 {
  297. CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
  298. INSERT INTO t1 VALUES('one', 'two', 'three');
  299. INSERT INTO t1 VALUES('one', 'one', 'one');
  300. INSERT INTO t1 VALUES('two', 'two', 'two');
  301. INSERT INTO t1 VALUES('three', 'three', 'three');
  302. }
  303. do_catchsql_test 12.1.1 {
  304. SELECT fts5_columntext(t1, -1) FROM t1('two');
  305. } {1 SQLITE_RANGE}
  306. do_catchsql_test 12.1.2 {
  307. SELECT fts5_columntext(t1, 3) FROM t1('two');
  308. } {1 SQLITE_RANGE}
  309. do_catchsql_test 12.1.2 {
  310. SELECT fts5_columntext(t1, 1) FROM t1('one AND two');
  311. } {0 two}
  312. do_catchsql_test 12.2.1 {
  313. SELECT fts5_queryphrase(t1, -1) FROM t1('one AND two');
  314. } {1 SQLITE_RANGE}
  315. do_catchsql_test 12.2.2 {
  316. SELECT fts5_queryphrase(t1, 2) FROM t1('one AND two');
  317. } {1 SQLITE_RANGE}
  318. do_catchsql_test 12.2.3 {
  319. SELECT fts5_queryphrase(t1, 1) FROM t1('one AND two');
  320. } {0 {{1 2 1}}}
  321. do_catchsql_test 12.3.1 {
  322. SELECT fts5_collist(t1, -1) FROM t1('one AND two');
  323. } {1 SQLITE_RANGE}
  324. do_catchsql_test 12.3.2 {
  325. SELECT fts5_collist(t1, 2) FROM t1('one AND two');
  326. } {1 SQLITE_RANGE}
  327. do_catchsql_test 12.3.3 {
  328. SELECT fts5_collist(t1, 1) FROM t1('one AND two');
  329. } {0 1}
  330. #-------------------------------------------------------------------------
  331. reset_db
  332. do_execsql_test 13.1 {
  333. CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=ascii);
  334. INSERT INTO t1 VALUES('a b c'), ('d e f');
  335. PRAGMA integrity_check;
  336. } {ok}
  337. do_catchsql_test 13.2 {
  338. SELECT highlight(t1, 0, '[', ']') FROM t1
  339. } {0 {{a b c} {d e f}}}
  340. do_execsql_test 13.3 {
  341. PRAGMA writable_schema = 1;
  342. UPDATE sqlite_schema SET sql = 'CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=blah)'
  343. WHERE name = 't1';
  344. }
  345. db close
  346. sqlite3 db test.db
  347. do_catchsql_test 13.4 {
  348. SELECT highlight(t1, 0, '[', ']') FROM t1
  349. } {1 {SQL logic error}}
  350. finish_test