-
Notifications
You must be signed in to change notification settings - Fork 2
/
db.go
174 lines (130 loc) · 4.28 KB
/
db.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package main
import (
// _ "encoding/binary"
// _ "errors"
// _ "os"
"database/sql"
"math"
"sync"
// 3rd-party
"github.com/jmoiron/sqlx" // replacement for "database/sql"
sqlite "github.com/mattn/go-sqlite3"
)
type Database struct {
name string
filename string
instance *sqlx.DB
sqliteConn *sqlite.SQLiteConn
// dbFilePointer *os.File // used for data syncing. (wip)
}
var once sync.Once
var mutex = &sync.Mutex{}
var sqlite3Conn *sqlite.SQLiteConn
func FetchDatabase(name string) (*Database, error) {
mutex.Lock()
once.Do(func() {
// adapted from: https://github.com/mattn/go-sqlite3/blob/master/_example/custom_func/main.go
sql.Register("sqlite3_custom", &sqlite.SQLiteDriver{
ConnectHook: func(conn *sqlite.SQLiteConn) error {
// register custom user defined function.
// this calculates the normalized score of a card with respect to its
// metadata attributes.
if err := conn.RegisterFunc("norm_score", norm_score, true); err != nil {
return err
}
// source: https://github.com/mattn/go-sqlite3/issues/104#issuecomment-33213801
sqlite3Conn = conn
return nil
},
})
})
var db *Database = &Database{name: name}
// if necessary, bootstrap database
var err error = db.Init()
if err != nil {
return nil, err
}
db.sqliteConn = sqlite3Conn
sqlite3Conn = nil
mutex.Unlock()
return db, nil
}
func (db *Database) Init() error {
var err error
db.NormalizeFileName()
db.instance, err = sqlx.Connect("sqlite3_custom", db.filename)
if err != nil {
return err
}
// TODO: remove if no longer needed
// db.dbFilePointer, err = os.Open(db.filename)
// if err != nil {
// return err
// }
// set up connection and create any necessary tables
var queries []string = []string{
BOOTSTRAP_QUERY,
SETUP_CONFIG_TABLE_QUERY,
SETUP_DECKS_TABLE_QUERY,
SETUP_CARDS_TABLE_QUERY,
STASHES_TABLE_QUERY,
}
var instance = db.instance
for _, query := range queries {
// TODO: run queries in transaction??
_, err = instance.Exec(query)
if err != nil {
return err
}
}
return nil
}
func (db *Database) NormalizeFileName() {
// TODO: be able to set any filename
// TODO: check if db.filename needs to be populated
db.filename = db.name + ".db"
}
func (db *Database) CleanUp() {
// db.dbFilePointer.Close()
db.instance.Close()
}
func norm_score(success int64, fail int64, age int64, times_reviewed int64) float64 {
var total int64 = success + fail
var __total float64 = float64(total + 1)
var __fail float64 = float64(fail)
// this is Jeffrey-Perks law where h = 0.5
// References:
// - http://www.dcs.bbk.ac.uk/~dell/publications/dellzhang_ictir2011.pdf
// - http://bl.ocks.org/ajschumacher/b9645724d9d842810613
var lidstone float64 = (__fail + 0.5) / __total
// - favour cards that are seen less frequently
// - favour less successful cards
// - penalize more successful cards
var bias_factor float64 = (1.0 + __fail) / (__total + float64(success) + float64(times_reviewed)/3.0)
var base float64 = lidstone + 1.0
var normalized float64 = lidstone * math.Log(float64(age)*bias_factor+base) / math.Log(base)
return normalized
}
// func (db *Database) Counter() (uint32, error) {
// // get file change counter for sqlite file.
// // useful to get somthing akin to a 'checksum' of the db
// // reference: http://www.sqlite.org/fileformat.html
// fptr := db.dbFilePointer
// // seek to byte offset of 24
// _, err := fptr.Seek(24, 0)
// if err != nil {
// return 0, err
// }
// b2 := make([]byte, 4)
// bytesRead, err := fptr.Read(b2)
// if err != nil {
// return 0, err
// }
// if bytesRead != 4 {
// // TODO: display db's filename
// return 0, errors.New("Unable to read database")
// }
// // The file change counter is a 4-byte big-endian integer
// counter := binary.BigEndian.Uint32(b2)
// return counter, nil
// }