@@ -8,14 +8,18 @@ package govarnam
88
99import (
1010 "context"
11- sql "database/sql "
11+ "embed "
1212 "fmt"
13+ "io/fs"
1314 "log"
1415 "os"
1516 "path"
1617 "time"
1718)
1819
20+ //go:embed migrations/*.sql
21+ var embedFS embed.FS
22+
1923// DictionaryResult result from dictionary search
2024type DictionaryResult struct {
2125 // Exactly found starting word matches.
@@ -54,108 +58,42 @@ func (varnam *Varnam) InitDict(dictPath string) error {
5458 var err error
5559
5660 if ! fileExists (dictPath ) {
57- log .Printf ("Making Varnam Learnings File at %s\n " , dictPath )
58- os .MkdirAll (path .Dir (dictPath ), 0750 )
59-
60- varnam .dictConn , err = makeDictionary (dictPath )
61- } else {
62- varnam .dictConn , err = openDB (dictPath )
61+ log .Printf ("Making Varnam Learnings Dir for %s\n " , dictPath )
62+ err := os .MkdirAll (path .Dir (dictPath ), 0750 )
63+ if err != nil {
64+ return err
65+ }
6366 }
6467
65- // Since SQLite v3.12.0, default page size is 4096
66- varnam . dictConn . Exec ( "PRAGMA page_size=4096;" )
67- // WAL makes writes & reads happen concurrently => significantly fast
68- varnam . dictConn . Exec ( "PRAGMA journal_mode=wal;" )
68+ varnam . dictConn , err = openDB ( dictPath )
69+ if err != nil {
70+ return err
71+ }
6972
7073 varnam .DictPath = dictPath
7174
72- return err
73- }
74-
75- func makeDictionary (dictPath string ) (* sql.DB , error ) {
76- conn , err := openDB (dictPath )
75+ // cd into migrations directory
76+ migrationsFS , err := fs .Sub (embedFS , "migrations" )
7777 if err != nil {
78- return nil , err
78+ return err
7979 }
8080
81- queries := []string {
82- `
83- CREATE TABLE IF NOT EXISTS metadata (
84- key TEXT UNIQUE,
85- value TEXT
86- );
87- ` ,
88- `
89- CREATE TABLE IF NOT EXISTS words (
90- id INTEGER PRIMARY KEY,
91- word TEXT UNIQUE,
92- weight INTEGER DEFAULT 1,
93- learned_on INTEGER
94- );
95- ` ,
96- `
97- CREATE VIRTUAL TABLE IF NOT EXISTS words_fts USING FTS5(
98- word,
99- weight UNINDEXED,
100- learned_on UNINDEXED,
101- content='words',
102- content_rowid='id',
103- tokenize='ascii',
104- prefix='1 2',
105- );
106- ` ,
107- `
108- CREATE TRIGGER words_ai AFTER INSERT ON words
109- BEGIN
110- INSERT INTO words_fts (rowid, word)
111- VALUES (new.id, new.word);
112- END;
113- ` ,
114- `
115- CREATE TRIGGER words_ad AFTER DELETE ON words
116- BEGIN
117- INSERT INTO words_fts (words_fts, rowid, word)
118- VALUES ('delete', old.id, old.word);
119- END;
120- ` ,
121- `
122- CREATE TRIGGER words_au AFTER UPDATE ON words
123- BEGIN
124- INSERT INTO words_fts (words_fts, rowid, word)
125- VALUES ('delete', old.id, old.word);
126- INSERT INTO words_fts (rowid, word)
127- VALUES (new.id, new.word);
128- END;
129- ` ,
130- `
131- CREATE TABLE IF NOT EXISTS patterns (
132- pattern TEXT NOT NULL COLLATE NOCASE,
133- word_id INTEGER NOT NULL,
134- FOREIGN KEY(word_id) REFERENCES words(id) ON DELETE CASCADE,
135- PRIMARY KEY(pattern, word_id)
136- );
137- ` }
138-
139- // Note: FTS can't be applied on patterns because
140- // we require partial word search which FTS doesn't support
141-
142- for _ , query := range queries {
143- ctx , cancelFunc := context .WithTimeout (context .Background (), 5 * time .Second )
144- defer cancelFunc ()
145-
146- stmt , err := conn .PrepareContext (ctx , query )
147- if err != nil {
148- return nil , err
149- }
150- defer stmt .Close ()
81+ mg , err := InitMigrate (varnam .dictConn , migrationsFS )
82+ if err != nil {
83+ return err
84+ }
15185
152- _ , err = stmt .ExecContext (ctx )
153- if err != nil {
154- return nil , err
155- }
86+ ranMigrations , err := mg .Run ()
87+ if ranMigrations != 0 {
88+ log .Printf ("ran %d migrations" , ranMigrations )
15689 }
15790
158- return conn , nil
91+ // Since SQLite v3.12.0, default page size is 4096
92+ varnam .dictConn .Exec ("PRAGMA page_size=4096;" )
93+ // WAL makes writes & reads happen concurrently => significantly fast
94+ varnam .dictConn .Exec ("PRAGMA journal_mode=wal;" )
95+
96+ return err
15997}
16098
16199type searchDictionaryType int32
@@ -372,42 +310,6 @@ func (varnam *Varnam) getMoreFromDictionary(ctx context.Context, words []Suggest
372310 }
373311}
374312
375- // A simpler function to get matches from pattern dictionary
376- // Gets incomplete matches.
377- // Eg: If pattern = "chin", will return "china"
378- // TODO better function name ? Ambiguous ?
379- func (varnam * Varnam ) getTrailingFromPatternDictionary (ctx context.Context , pattern string ) []Suggestion {
380- var results []Suggestion
381-
382- select {
383- case <- ctx .Done ():
384- return results
385- default :
386- rows , err := varnam .dictConn .QueryContext (ctx , "SELECT word, weight FROM words WHERE id IN (SELECT word_id FROM patterns WHERE pattern LIKE ?) ORDER BY weight DESC LIMIT 10" , pattern + "%" )
387-
388- if err != nil {
389- log .Print (err )
390- return results
391- }
392-
393- defer rows .Close ()
394-
395- for rows .Next () {
396- var item Suggestion
397- rows .Scan (& item .Word , & item .Weight )
398- item .Weight += VARNAM_LEARNT_WORD_MIN_WEIGHT
399- results = append (results , item )
400- }
401-
402- err = rows .Err ()
403- if err != nil {
404- log .Print (err )
405- }
406-
407- return results
408- }
409- }
410-
411313// Gets incomplete and complete matches from pattern dictionary
412314// Eg: If pattern = "chin" or "chinayil", will return "china"
413315func (varnam * Varnam ) getFromPatternDictionary (ctx context.Context , pattern string ) []PatternDictionarySuggestion {
0 commit comments