fts5_common.tcl 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. # 2014 Dec 19
  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. if {![info exists testdir]} {
  13. set testdir [file join [file dirname [info script]] .. .. .. test]
  14. }
  15. source $testdir/tester.tcl
  16. ifcapable !fts5 {
  17. proc return_if_no_fts5 {} {
  18. finish_test
  19. return -code return
  20. }
  21. return
  22. } else {
  23. proc return_if_no_fts5 {} {}
  24. }
  25. catch {
  26. sqlite3_fts5_may_be_corrupt 0
  27. reset_db
  28. }
  29. proc fts5_test_poslist {cmd} {
  30. set res [list]
  31. for {set i 0} {$i < [$cmd xInstCount]} {incr i} {
  32. lappend res [string map {{ } .} [$cmd xInst $i]]
  33. }
  34. set res
  35. }
  36. proc fts5_test_poslist2 {cmd} {
  37. set res [list]
  38. for {set i 0} {$i < [$cmd xPhraseCount]} {incr i} {
  39. $cmd xPhraseForeach $i c o {
  40. lappend res $i.$c.$o
  41. }
  42. }
  43. #set res
  44. sort_poslist $res
  45. }
  46. proc fts5_test_insttoken {cmd iInst iToken} {
  47. $cmd xInstToken $iInst $iToken
  48. }
  49. proc fts5_test_collist {cmd} {
  50. set res [list]
  51. for {set i 0} {$i < [$cmd xPhraseCount]} {incr i} {
  52. $cmd xPhraseColumnForeach $i c { lappend res $i.$c }
  53. }
  54. set res
  55. }
  56. proc fts5_collist {cmd iPhrase} {
  57. set res [list]
  58. $cmd xPhraseColumnForeach $iPhrase c { lappend res $c }
  59. set res
  60. }
  61. proc fts5_test_columnsize {cmd} {
  62. set res [list]
  63. for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
  64. lappend res [$cmd xColumnSize $i]
  65. }
  66. set res
  67. }
  68. proc fts5_columntext {cmd iCol} {
  69. $cmd xColumnText $iCol
  70. }
  71. proc fts5_columnlocale {cmd iCol} {
  72. $cmd xColumnLocale $iCol
  73. }
  74. proc fts5_test_columntext {cmd} {
  75. set res [list]
  76. for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
  77. lappend res [$cmd xColumnText $i]
  78. }
  79. set res
  80. }
  81. proc fts5_test_columnlocale {cmd} {
  82. set res [list]
  83. for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
  84. lappend res [$cmd xColumnLocale $i]
  85. }
  86. set res
  87. }
  88. proc fts5_test_columntotalsize {cmd} {
  89. set res [list]
  90. for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
  91. lappend res [$cmd xColumnTotalSize $i]
  92. }
  93. set res
  94. }
  95. proc test_append_token {varname token iStart iEnd} {
  96. upvar $varname var
  97. lappend var $token
  98. return "SQLITE_OK"
  99. }
  100. proc fts5_test_tokenize {cmd} {
  101. set res [list]
  102. for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
  103. set tokens [list]
  104. $cmd xTokenize [$cmd xColumnText $i] [list test_append_token tokens]
  105. lappend res $tokens
  106. }
  107. set res
  108. }
  109. proc fts5_test_rowcount {cmd} {
  110. $cmd xRowCount
  111. }
  112. proc fts5_test_rowid {cmd} {
  113. $cmd xRowid
  114. }
  115. proc test_queryphrase_cb {cnt cmd} {
  116. upvar $cnt L
  117. for {set i 0} {$i < [$cmd xInstCount]} {incr i} {
  118. foreach {ip ic io} [$cmd xInst $i] break
  119. set A($ic) 1
  120. }
  121. foreach ic [array names A] {
  122. lset L $ic [expr {[lindex $L $ic] + 1}]
  123. }
  124. }
  125. proc fts5_test_queryphrase {cmd} {
  126. set res [list]
  127. for {set i 0} {$i < [$cmd xPhraseCount]} {incr i} {
  128. set cnt [list]
  129. for {set j 0} {$j < [$cmd xColumnCount]} {incr j} { lappend cnt 0 }
  130. $cmd xQueryPhrase $i [list test_queryphrase_cb cnt]
  131. lappend res $cnt
  132. }
  133. set res
  134. }
  135. proc fts5_queryphrase {cmd iPhrase} {
  136. set cnt [list]
  137. for {set j 0} {$j < [$cmd xColumnCount]} {incr j} { lappend cnt 0 }
  138. $cmd xQueryPhrase $iPhrase [list test_queryphrase_cb cnt]
  139. set cnt
  140. }
  141. proc fts5_test_phrasecount {cmd} {
  142. $cmd xPhraseCount
  143. }
  144. proc fts5_test_all {cmd} {
  145. set res [list]
  146. lappend res columnsize [fts5_test_columnsize $cmd]
  147. lappend res columntext [fts5_test_columntext $cmd]
  148. lappend res columntotalsize [fts5_test_columntotalsize $cmd]
  149. lappend res poslist [fts5_test_poslist $cmd]
  150. lappend res tokenize [fts5_test_tokenize $cmd]
  151. lappend res rowcount [fts5_test_rowcount $cmd]
  152. set res
  153. }
  154. proc fts5_aux_test_functions {db} {
  155. foreach f {
  156. fts5_test_columnsize
  157. fts5_test_columntext
  158. fts5_test_columnlocale
  159. fts5_test_columntotalsize
  160. fts5_test_poslist
  161. fts5_test_poslist2
  162. fts5_test_collist
  163. fts5_test_insttoken
  164. fts5_test_tokenize
  165. fts5_test_rowcount
  166. fts5_test_rowid
  167. fts5_test_all
  168. fts5_test_queryphrase
  169. fts5_test_phrasecount
  170. fts5_columntext
  171. fts5_columnlocale
  172. fts5_queryphrase
  173. fts5_collist
  174. } {
  175. sqlite3_fts5_create_function $db $f $f
  176. }
  177. }
  178. proc fts5_segcount {tbl} {
  179. set N 0
  180. foreach n [fts5_level_segs $tbl] { incr N $n }
  181. set N
  182. }
  183. proc fts5_level_segs {tbl} {
  184. set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
  185. set ret [list]
  186. foreach L [lrange [db one $sql] 1 end] {
  187. lappend ret [expr [llength $L] - 3]
  188. }
  189. set ret
  190. }
  191. proc fts5_level_segids {tbl} {
  192. set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
  193. set ret [list]
  194. foreach L [lrange [db one $sql] 1 end] {
  195. set lvl [list]
  196. foreach S [lrange $L 3 end] {
  197. regexp {id=([1234567890]*)} $S -> segid
  198. lappend lvl $segid
  199. }
  200. lappend ret $lvl
  201. }
  202. set ret
  203. }
  204. proc fts5_rnddoc {n} {
  205. set map [list 0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j]
  206. set doc [list]
  207. for {set i 0} {$i < $n} {incr i} {
  208. lappend doc "x[string map $map [format %.3d [expr int(rand()*1000)]]]"
  209. }
  210. set doc
  211. }
  212. #-------------------------------------------------------------------------
  213. # Usage:
  214. #
  215. # nearset aCol ?-pc VARNAME? ?-near N? ?-col C? -- phrase1 phrase2...
  216. #
  217. # This command is used to test if a document (set of column values) matches
  218. # the logical equivalent of a single FTS5 NEAR() clump and, if so, return
  219. # the equivalent of an FTS5 position list.
  220. #
  221. # Parameter $aCol is passed a list of the column values for the document
  222. # to test. Parameters $phrase1 and so on are the phrases.
  223. #
  224. # The result is a list of phrase hits. Each phrase hit is formatted as
  225. # three integers separated by "." characters, in the following format:
  226. #
  227. # <phrase number> . <column number> . <token offset>
  228. #
  229. # Options:
  230. #
  231. # -near N (NEAR distance. Default 10)
  232. # -col C (List of column indexes to match against)
  233. # -pc VARNAME (variable in caller frame to use for phrase numbering)
  234. # -dict VARNAME (array in caller frame to use for synonyms)
  235. #
  236. proc nearset {aCol args} {
  237. # Process the command line options.
  238. #
  239. set O(-near) 10
  240. set O(-col) {}
  241. set O(-pc) ""
  242. set O(-dict) ""
  243. set nOpt [lsearch -exact $args --]
  244. if {$nOpt<0} { error "no -- option" }
  245. # Set $lPhrase to be a list of phrases. $nPhrase its length.
  246. set lPhrase [lrange $args [expr $nOpt+1] end]
  247. set nPhrase [llength $lPhrase]
  248. foreach {k v} [lrange $args 0 [expr $nOpt-1]] {
  249. if {[info exists O($k)]==0} { error "unrecognized option $k" }
  250. set O($k) $v
  251. }
  252. if {$O(-pc) == ""} {
  253. set counter 0
  254. } else {
  255. upvar $O(-pc) counter
  256. }
  257. if {$O(-dict)!=""} { upvar $O(-dict) aDict }
  258. for {set j 0} {$j < [llength $aCol]} {incr j} {
  259. for {set i 0} {$i < $nPhrase} {incr i} {
  260. set A($j,$i) [list]
  261. }
  262. }
  263. # Loop through each column of the current row.
  264. for {set iCol 0} {$iCol < [llength $aCol]} {incr iCol} {
  265. # If there is a column filter, test whether this column is excluded. If
  266. # so, skip to the next iteration of this loop. Otherwise, set zCol to the
  267. # column value and nToken to the number of tokens that comprise it.
  268. if {$O(-col)!="" && [lsearch $O(-col) $iCol]<0} continue
  269. set zCol [lindex $aCol $iCol]
  270. set nToken [llength $zCol]
  271. # Each iteration of the following loop searches a substring of the
  272. # column value for phrase matches. The last token of the substring
  273. # is token $iLast of the column value. The first token is:
  274. #
  275. # iFirst = ($iLast - $O(-near) - 1)
  276. #
  277. # where $sz is the length of the phrase being searched for. A phrase
  278. # counts as matching the substring if its first token lies on or before
  279. # $iLast and its last token on or after $iFirst.
  280. #
  281. # For example, if the query is "NEAR(a+b c, 2)" and the column value:
  282. #
  283. # "x x x x A B x x C x"
  284. # 0 1 2 3 4 5 6 7 8 9"
  285. #
  286. # when (iLast==8 && iFirst=5) the range will contain both phrases and
  287. # so both instances can be added to the output poslists.
  288. #
  289. set iLast [expr $O(-near) >= $nToken ? $nToken - 1 : $O(-near)]
  290. for { } {$iLast < $nToken} {incr iLast} {
  291. catch { array unset B }
  292. for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
  293. set p [lindex $lPhrase $iPhrase]
  294. set nPm1 [expr {[llength $p] - 1}]
  295. set iFirst [expr $iLast - $O(-near) - [llength $p]]
  296. for {set i $iFirst} {$i <= $iLast} {incr i} {
  297. set lCand [lrange $zCol $i [expr $i+$nPm1]]
  298. set bMatch 1
  299. foreach tok $p term $lCand {
  300. if {[nearset_match aDict $tok $term]==0} { set bMatch 0 ; break }
  301. }
  302. if {$bMatch} { lappend B($iPhrase) $i }
  303. }
  304. if {![info exists B($iPhrase)]} break
  305. }
  306. if {$iPhrase==$nPhrase} {
  307. for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
  308. set A($iCol,$iPhrase) [concat $A($iCol,$iPhrase) $B($iPhrase)]
  309. set A($iCol,$iPhrase) [lsort -integer -uniq $A($iCol,$iPhrase)]
  310. }
  311. }
  312. }
  313. }
  314. set res [list]
  315. #puts [array names A]
  316. for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
  317. for {set iCol 0} {$iCol < [llength $aCol]} {incr iCol} {
  318. foreach a $A($iCol,$iPhrase) {
  319. lappend res "$counter.$iCol.$a"
  320. }
  321. }
  322. incr counter
  323. }
  324. #puts "$aCol -> $res"
  325. sort_poslist $res
  326. }
  327. proc nearset_match {aDictVar tok term} {
  328. if {[string match $tok $term]} { return 1 }
  329. upvar $aDictVar aDict
  330. if {[info exists aDict($tok)]} {
  331. foreach s $aDict($tok) {
  332. if {[string match $s $term]} { return 1 }
  333. }
  334. }
  335. return 0;
  336. }
  337. #-------------------------------------------------------------------------
  338. # Usage:
  339. #
  340. # sort_poslist LIST
  341. #
  342. # Sort a position list of the type returned by command [nearset]
  343. #
  344. proc sort_poslist {L} {
  345. lsort -command instcompare $L
  346. }
  347. proc instcompare {lhs rhs} {
  348. foreach {p1 c1 o1} [split $lhs .] {}
  349. foreach {p2 c2 o2} [split $rhs .] {}
  350. set res [expr $c1 - $c2]
  351. if {$res==0} { set res [expr $o1 - $o2] }
  352. if {$res==0} { set res [expr $p1 - $p2] }
  353. return $res
  354. }
  355. #-------------------------------------------------------------------------
  356. # Logical operators used by the commands returned by fts5_tcl_expr().
  357. #
  358. proc AND {args} {
  359. foreach a $args {
  360. if {[llength $a]==0} { return [list] }
  361. }
  362. sort_poslist [concat {*}$args]
  363. }
  364. proc OR {args} {
  365. sort_poslist [concat {*}$args]
  366. }
  367. proc NOT {a b} {
  368. if {[llength $b]>0} { return [list] }
  369. return $a
  370. }
  371. #-------------------------------------------------------------------------
  372. # This command is similar to [split], except that it also provides the
  373. # start and end offsets of each token. For example:
  374. #
  375. # [fts5_tokenize_split "abc d ef"] -> {abc 0 3 d 4 5 ef 6 8}
  376. #
  377. proc gobble_whitespace {textvar} {
  378. upvar $textvar t
  379. regexp {([ ]*)(.*)} $t -> space t
  380. return [string length $space]
  381. }
  382. proc gobble_text {textvar wordvar} {
  383. upvar $textvar t
  384. upvar $wordvar w
  385. regexp {([^ ]*)(.*)} $t -> w t
  386. return [string length $w]
  387. }
  388. proc fts5_tokenize_split {text} {
  389. set token ""
  390. set ret [list]
  391. set iOff [gobble_whitespace text]
  392. while {[set nToken [gobble_text text word]]} {
  393. lappend ret $word $iOff [expr $iOff+$nToken]
  394. incr iOff $nToken
  395. incr iOff [gobble_whitespace text]
  396. }
  397. set ret
  398. }
  399. #-------------------------------------------------------------------------
  400. #
  401. proc foreach_detail_mode {prefix script} {
  402. set saved $::testprefix
  403. foreach d [list full col none] {
  404. set s [string map [list %DETAIL% $d] $script]
  405. set ::detail $d
  406. set ::testprefix "$prefix-$d"
  407. reset_db
  408. uplevel $s
  409. unset ::detail
  410. }
  411. set ::testprefix $saved
  412. }
  413. proc detail_check {} {
  414. if {$::detail != "none" && $::detail!="full" && $::detail!="col"} {
  415. error "not in foreach_detail_mode {...} block"
  416. }
  417. }
  418. proc detail_is_none {} { detail_check ; expr {$::detail == "none"} }
  419. proc detail_is_col {} { detail_check ; expr {$::detail == "col" } }
  420. proc detail_is_full {} { detail_check ; expr {$::detail == "full"} }
  421. proc foreach_tokenizer_mode {prefix script} {
  422. set saved $::testprefix
  423. foreach {d mapping} {
  424. "" {}
  425. "-origintext" {, tokenize="origintext unicode61", tokendata=1}
  426. } {
  427. set s [string map [list %TOKENIZER% $mapping] $script]
  428. set ::testprefix "$prefix$d"
  429. reset_db
  430. sqlite3_fts5_register_origintext db
  431. uplevel $s
  432. }
  433. set ::testprefix $saved
  434. }
  435. #-------------------------------------------------------------------------
  436. # Convert a poslist of the type returned by fts5_test_poslist() to a
  437. # collist as returned by fts5_test_collist().
  438. #
  439. proc fts5_poslist2collist {poslist} {
  440. set res [list]
  441. foreach h $poslist {
  442. regexp {(.*)\.[1234567890]+} $h -> cand
  443. lappend res $cand
  444. }
  445. set res [lsort -command fts5_collist_elem_compare -unique $res]
  446. return $res
  447. }
  448. # Comparison function used by fts5_poslist2collist to sort collist entries.
  449. proc fts5_collist_elem_compare {a b} {
  450. foreach {a1 a2} [split $a .] {}
  451. foreach {b1 b2} [split $b .] {}
  452. if {$a1==$b1} { return [expr $a2 - $b2] }
  453. return [expr $a1 - $b1]
  454. }
  455. #--------------------------------------------------------------------------
  456. # Construct and return a tcl list equivalent to that returned by the SQL
  457. # query executed against database handle [db]:
  458. #
  459. # SELECT
  460. # rowid,
  461. # fts5_test_poslist($tbl),
  462. # fts5_test_collist($tbl)
  463. # FROM $tbl('$expr')
  464. # ORDER BY rowid $order;
  465. #
  466. proc fts5_query_data {expr tbl {order ASC} {aDictVar ""}} {
  467. # Figure out the set of columns in the FTS5 table. This routine does
  468. # not handle tables with UNINDEXED columns, but if it did, it would
  469. # have to be here.
  470. db eval "PRAGMA table_info = $tbl" x { lappend lCols $x(name) }
  471. set d ""
  472. if {$aDictVar != ""} {
  473. upvar $aDictVar aDict
  474. set d aDict
  475. }
  476. set cols ""
  477. foreach e $lCols { append cols ", '$e'" }
  478. set tclexpr [db one [subst -novar {
  479. SELECT fts5_expr_tcl( $expr, 'nearset $cols -dict $d -pc ::pc' [set cols] )
  480. }]]
  481. set res [list]
  482. db eval "SELECT rowid, * FROM $tbl ORDER BY rowid $order" x {
  483. set cols [list]
  484. foreach col $lCols { lappend cols $x($col) }
  485. set ::pc 0
  486. set rowdata [eval $tclexpr]
  487. if {$rowdata != ""} {
  488. lappend res $x(rowid) $rowdata [fts5_poslist2collist $rowdata]
  489. }
  490. }
  491. set res
  492. }
  493. #-------------------------------------------------------------------------
  494. # Similar to [fts5_query_data], but omit the collist field.
  495. #
  496. proc fts5_poslist_data {expr tbl {order ASC} {aDictVar ""}} {
  497. set res [list]
  498. if {$aDictVar!=""} {
  499. upvar $aDictVar aDict
  500. set dict aDict
  501. } else {
  502. set dict ""
  503. }
  504. foreach {rowid poslist collist} [fts5_query_data $expr $tbl $order $dict] {
  505. lappend res $rowid $poslist
  506. }
  507. set res
  508. }
  509. proc fts5_collist_data {expr tbl {order ASC} {aDictVar ""}} {
  510. set res [list]
  511. if {$aDictVar!=""} {
  512. upvar $aDictVar aDict
  513. set dict aDict
  514. } else {
  515. set dict ""
  516. }
  517. foreach {rowid poslist collist} [fts5_query_data $expr $tbl $order $dict] {
  518. lappend res $rowid $collist
  519. }
  520. set res
  521. }
  522. #-------------------------------------------------------------------------
  523. #
  524. # This command will only work inside a [foreach_detail_mode] block. It tests
  525. # whether or not expression $expr run on FTS5 table $tbl is supported by
  526. # the current mode. If so, 1 is returned. If not, 0.
  527. #
  528. # detail=full (all queries supported)
  529. # detail=col (all but phrase queries and NEAR queries)
  530. # detail=none (all but phrase queries, NEAR queries, and column filters)
  531. #
  532. proc fts5_expr_ok {expr tbl} {
  533. if {![detail_is_full]} {
  534. set nearset "nearset_rc"
  535. if {[detail_is_col]} { set nearset "nearset_rf" }
  536. set ::expr_not_ok 0
  537. db eval "PRAGMA table_info = $tbl" x { lappend lCols $x(name) }
  538. set cols ""
  539. foreach e $lCols { append cols ", '$e'" }
  540. set ::pc 0
  541. set tclexpr [db one [subst -novar {
  542. SELECT fts5_expr_tcl( $expr, '[set nearset] $cols -pc ::pc' [set cols] )
  543. }]]
  544. eval $tclexpr
  545. if {$::expr_not_ok} { return 0 }
  546. }
  547. return 1
  548. }
  549. # Helper for [fts5_expr_ok]
  550. proc nearset_rf {aCol args} {
  551. set idx [lsearch -exact $args --]
  552. if {$idx != [llength $args]-2 || [llength [lindex $args end]]!=1} {
  553. set ::expr_not_ok 1
  554. }
  555. list
  556. }
  557. # Helper for [fts5_expr_ok]
  558. proc nearset_rc {aCol args} {
  559. nearset_rf $aCol {*}$args
  560. if {[lsearch $args -col]>=0} {
  561. set ::expr_not_ok 1
  562. }
  563. list
  564. }
  565. proc dump {tname} {
  566. execsql_pp "SELECT * FROM ${tname}_idx"
  567. execsql_pp "SELECT id, quote(block), fts5_decode(id,block) FROM ${tname}_data"
  568. }
  569. #-------------------------------------------------------------------------
  570. # Code for a simple Tcl tokenizer that supports synonyms at query time.
  571. #
  572. proc tclnum_tokenize {mode tflags text} {
  573. foreach {w iStart iEnd} [fts5_tokenize_split $text] {
  574. sqlite3_fts5_token $w $iStart $iEnd
  575. if {$tflags == $mode && [info exists ::tclnum_syn($w)]} {
  576. foreach s $::tclnum_syn($w) { sqlite3_fts5_token -colo $s $iStart $iEnd }
  577. }
  578. }
  579. }
  580. proc tclnum_create {args} {
  581. set mode query
  582. if {[llength $args]} {
  583. set mode [lindex $args 0]
  584. }
  585. if {$mode != "query" && $mode != "document"} { error "bad mode: $mode" }
  586. return [list tclnum_tokenize $mode]
  587. }
  588. proc fts5_tclnum_register {db} {
  589. foreach SYNDICT {
  590. {zero 0}
  591. {one 1 i}
  592. {two 2 ii}
  593. {three 3 iii}
  594. {four 4 iv}
  595. {five 5 v}
  596. {six 6 vi}
  597. {seven 7 vii}
  598. {eight 8 viii}
  599. {nine 9 ix}
  600. {a1 a2 a3 a4 a5 a6 a7 a8 a9}
  601. {b1 b2 b3 b4 b5 b6 b7 b8 b9}
  602. {c1 c2 c3 c4 c5 c6 c7 c8 c9}
  603. } {
  604. foreach s $SYNDICT {
  605. set o [list]
  606. foreach x $SYNDICT {if {$x!=$s} {lappend o $x}}
  607. set ::tclnum_syn($s) $o
  608. }
  609. }
  610. sqlite3_fts5_create_tokenizer db tclnum tclnum_create
  611. }
  612. #
  613. # End of tokenizer code.
  614. #-------------------------------------------------------------------------