···1111)
12121313type Databases struct {
1414- Users *DB
1515- Articles *DB
1616- Recs *DB
1414+ Users *UserStore
1515+ Articles *ArticleStore
1616+1717+ db *DB
1718}
18191920var multiDriverSeq int64
···2122func OpenAll(basePath string) (*Databases, error) {
2223 articlesPath := basePath + "_articles"
2324 recsPath := basePath + "_recs"
2525+2626+ for _, p := range []string{articlesPath, recsPath} {
2727+ f, err := sql.Open("sqlite3", p+"?"+DSN)
2828+ if err != nil {
2929+ return nil, err
3030+ }
3131+ f.Close()
3232+ }
24332534 seq := atomic.AddInt64(&multiDriverSeq, 1)
2635 driverName := fmt.Sprintf("sqlite3_glean_multi_%d", seq)
···5261 },
5362 })
54635555- usersDB, err := sql.Open(driverName, basePath+"_users?cache=shared&"+DSN)
6464+ db, err := sql.Open(driverName, basePath+"_users?cache=shared&"+DSN)
5665 if err != nil {
5766 return nil, err
5867 }
5959- usersDB.SetMaxOpenConns(10)
6060- usersDB.SetMaxIdleConns(5)
6161- usersDB.SetConnMaxLifetime(30 * time.Minute)
6262- users := &DB{usersDB}
6868+ db.SetMaxOpenConns(10)
6969+ db.SetMaxIdleConns(5)
7070+ db.SetConnMaxLifetime(30 * time.Minute)
7171+ d := &DB{db}
63726464- articles, err := Open(articlesPath)
6565- if err != nil {
6666- users.Close()
6767- return nil, err
6868- }
6969-7070- recs, err := Open(recsPath)
7171- if err != nil {
7272- users.Close()
7373- articles.Close()
7474- return nil, err
7575- }
7676-7777- if err := initUsersSchema(users); err != nil {
7878- users.Close()
7979- articles.Close()
8080- recs.Close()
7373+ if err := initUsersSchema(d); err != nil {
7474+ d.Close()
8175 return nil, err
8276 }
83778484- if err := initArticlesSchema(articles); err != nil {
8585- users.Close()
8686- articles.Close()
8787- recs.Close()
7878+ if err := initArticlesSchema(d); err != nil {
7979+ d.Close()
8880 return nil, err
8981 }
90829191- if err := initRecsSchema(recs); err != nil {
9292- users.Close()
9393- articles.Close()
9494- recs.Close()
8383+ if err := initRecsSchema(d); err != nil {
8484+ d.Close()
9585 return nil, err
9686 }
97879888 return &Databases{
9999- Users: users,
100100- Articles: articles,
101101- Recs: recs,
8989+ Users: NewUserStore(d),
9090+ Articles: NewArticleStore(d),
9191+ db: d,
10292 }, nil
10393}
1049410595func (d *Databases) Close() error {
106106- if d.Users != nil {
107107- _ = d.Users.Close()
108108- }
109109- if d.Articles != nil {
110110- _ = d.Articles.Close()
111111- }
112112- if d.Recs != nil {
113113- _ = d.Recs.Close()
9696+ if d.db != nil {
9797+ _ = d.db.Close()
11498 }
11599 return nil
100100+}
101101+102102+func (d *Databases) DB() *sql.DB {
103103+ return d.db.DB
116104}
117105118106func initUsersSchema(db *DB) error {
···181169}
182170183171var articlesSchema = []string{
184184- `CREATE TABLE IF NOT EXISTS feeds (
172172+ `CREATE TABLE IF NOT EXISTS articles.feeds (
185173 feed_url TEXT PRIMARY KEY,
186174 title TEXT,
187175 site_url TEXT,
···192180 subscriber_count INTEGER NOT NULL DEFAULT 0,
193181 etag TEXT,
194182 last_modified TEXT,
195195- fetch_interval_minutes INTEGER NOT NULL DEFAULT 30,
196196- next_fetch_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
197183 consecutive_empty_fetches INTEGER NOT NULL DEFAULT 0,
198184 error_count INTEGER NOT NULL DEFAULT 0,
199185 favicon_url TEXT
200186 )`,
201187202202- `CREATE TABLE IF NOT EXISTS subscriptions (
188188+ `CREATE TABLE IF NOT EXISTS articles.subscriptions (
203189 id INTEGER PRIMARY KEY AUTOINCREMENT,
204190 user_did TEXT NOT NULL,
205191 feed_url TEXT NOT NULL,
···211197 UNIQUE(user_did, feed_url)
212198 )`,
213199214214- `CREATE TABLE IF NOT EXISTS articles (
200200+ `CREATE TABLE IF NOT EXISTS articles.articles (
215201 id INTEGER PRIMARY KEY AUTOINCREMENT,
216202 feed_url TEXT NOT NULL,
217203 guid TEXT NOT NULL,
···227213 UNIQUE(feed_url, guid)
228214 )`,
229215230230- `CREATE TABLE IF NOT EXISTS read_state (
216216+ `CREATE TABLE IF NOT EXISTS articles.read_state (
231217 user_did TEXT NOT NULL,
232218 article_id INTEGER NOT NULL,
233219 is_read BOOLEAN NOT NULL DEFAULT 0,
···235221 PRIMARY KEY (user_did, article_id)
236222 )`,
237223238238- `CREATE TABLE IF NOT EXISTS annotations (
224224+ `CREATE TABLE IF NOT EXISTS articles.annotations (
239225 id INTEGER PRIMARY KEY AUTOINCREMENT,
240226 uri TEXT NOT NULL UNIQUE,
241227 author_did TEXT NOT NULL,
···249235 cid TEXT
250236 )`,
251237252252- `CREATE TABLE IF NOT EXISTS likes (
238238+ `CREATE TABLE IF NOT EXISTS articles.likes (
253239 id INTEGER PRIMARY KEY AUTOINCREMENT,
254240 uri TEXT NOT NULL UNIQUE,
255241 author_did TEXT NOT NULL,
···260246 UNIQUE(author_did, feed_url, article_url)
261247 )`,
262248263263- `CREATE INDEX IF NOT EXISTS idx_subscriptions_feed ON subscriptions(feed_url)`,
264264- `CREATE INDEX IF NOT EXISTS idx_subscriptions_feed_user ON subscriptions(feed_url, user_did)`,
265265- `CREATE INDEX IF NOT EXISTS idx_subscriptions_user ON subscriptions(user_did)`,
266266- `CREATE INDEX IF NOT EXISTS idx_subscriptions_uri ON subscriptions(uri)`,
267267- `CREATE INDEX IF NOT EXISTS idx_likes_author_feed ON likes(author_did, feed_url, created_at)`,
268268- `CREATE INDEX IF NOT EXISTS idx_articles_feed ON articles(feed_url)`,
269269- `CREATE INDEX IF NOT EXISTS idx_articles_published ON articles(published DESC)`,
270270- `CREATE INDEX IF NOT EXISTS idx_articles_url ON articles(url)`,
271271- `CREATE INDEX IF NOT EXISTS idx_read_state_unread ON read_state(user_did, is_read) WHERE is_read = 0`,
272272- `CREATE INDEX IF NOT EXISTS idx_annotations_article ON annotations(article_url)`,
273273- `CREATE INDEX IF NOT EXISTS idx_annotations_author ON annotations(author_did)`,
274274- `CREATE INDEX IF NOT EXISTS idx_annotations_created_at ON annotations(created_at DESC)`,
275275- `CREATE INDEX IF NOT EXISTS idx_likes_article ON likes(feed_url, article_url)`,
276276- `CREATE INDEX IF NOT EXISTS idx_likes_author ON likes(author_did)`,
277277- `CREATE INDEX IF NOT EXISTS idx_likes_created_at ON likes(created_at DESC)`,
249249+ `CREATE INDEX IF NOT EXISTS articles.idx_subscriptions_feed ON subscriptions(feed_url)`,
250250+ `CREATE INDEX IF NOT EXISTS articles.idx_subscriptions_feed_user ON subscriptions(feed_url, user_did)`,
251251+ `CREATE INDEX IF NOT EXISTS articles.idx_subscriptions_user ON subscriptions(user_did)`,
252252+ `CREATE INDEX IF NOT EXISTS articles.idx_subscriptions_uri ON subscriptions(uri)`,
253253+ `CREATE INDEX IF NOT EXISTS articles.idx_likes_author_feed ON likes(author_did, feed_url, created_at)`,
254254+ `CREATE INDEX IF NOT EXISTS articles.idx_articles_feed ON articles(feed_url)`,
255255+ `CREATE INDEX IF NOT EXISTS articles.idx_articles_published ON articles(published DESC)`,
256256+ `CREATE INDEX IF NOT EXISTS articles.idx_articles_url ON articles(url)`,
257257+ `CREATE INDEX IF NOT EXISTS articles.idx_read_state_unread ON read_state(user_did, is_read) WHERE is_read = 0`,
258258+ `CREATE INDEX IF NOT EXISTS articles.idx_annotations_article ON annotations(article_url)`,
259259+ `CREATE INDEX IF NOT EXISTS articles.idx_annotations_author ON annotations(author_did)`,
260260+ `CREATE INDEX IF NOT EXISTS articles.idx_annotations_created_at ON annotations(created_at DESC)`,
261261+ `CREATE INDEX IF NOT EXISTS articles.idx_likes_article ON likes(feed_url, article_url)`,
262262+ `CREATE INDEX IF NOT EXISTS articles.idx_likes_author ON likes(author_did)`,
263263+ `CREATE INDEX IF NOT EXISTS articles.idx_likes_created_at ON likes(created_at DESC)`,
278264279279- `CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts USING fts5(title, summary, content, author, content=articles, content_rowid=id)`,
280280- `CREATE TRIGGER IF NOT EXISTS articles_ai AFTER INSERT ON articles BEGIN
265265+ `CREATE VIRTUAL TABLE IF NOT EXISTS articles.articles_fts USING fts5(title, summary, content, author, content=articles, content_rowid=id)`,
266266+ `CREATE TRIGGER IF NOT EXISTS articles.articles_ai AFTER INSERT ON articles BEGIN
281267 INSERT INTO articles_fts(rowid, title, summary, content, author) VALUES (new.id, new.title, new.summary, new.content, new.author);
282268 END`,
283283- `CREATE TRIGGER IF NOT EXISTS articles_ad AFTER DELETE ON articles BEGIN
269269+ `CREATE TRIGGER IF NOT EXISTS articles.articles_ad AFTER DELETE ON articles BEGIN
284270 INSERT INTO articles_fts(articles_fts, rowid, title, summary, content, author) VALUES('delete', old.id, old.title, old.summary, old.content, old.author);
285271 END`,
286286- `CREATE TRIGGER IF NOT EXISTS articles_au AFTER UPDATE ON articles BEGIN
272272+ `CREATE TRIGGER IF NOT EXISTS articles.articles_au AFTER UPDATE ON articles BEGIN
287273 INSERT INTO articles_fts(articles_fts, rowid, title, summary, content, author) VALUES('delete', old.id, old.title, old.summary, old.content, old.author);
288274 INSERT INTO articles_fts(rowid, title, summary, content, author) VALUES (new.id, new.title, new.summary, new.content, new.author);
289275 END`,
290276}
291277292278var recsSchema = []string{
293293- `CREATE TABLE IF NOT EXISTS feed_similarity (
279279+ `CREATE TABLE IF NOT EXISTS recs.feed_similarity (
294280 feed_a TEXT NOT NULL,
295281 feed_b TEXT NOT NULL,
296282 jaccard REAL NOT NULL,
···299285 CHECK(feed_a < feed_b)
300286 )`,
301287302302- `CREATE TABLE IF NOT EXISTS user_similarity (
288288+ `CREATE TABLE IF NOT EXISTS recs.user_similarity (
303289 user_a TEXT NOT NULL,
304290 user_b TEXT NOT NULL,
305291 jaccard REAL NOT NULL,
···311297 CHECK(user_a < user_b)
312298 )`,
313299314314- `CREATE TABLE IF NOT EXISTS dismissed_recommendations (
300300+ `CREATE TABLE IF NOT EXISTS recs.dismissed_recommendations (
315301 user_did TEXT NOT NULL,
316302 target_type TEXT NOT NULL CHECK(target_type IN ('feed', 'article')),
317303 target_id TEXT NOT NULL,
···320306 PRIMARY KEY (user_did, target_type, target_id)
321307 )`,
322308323323- `CREATE TABLE IF NOT EXISTS recommendation_impressions (
309309+ `CREATE TABLE IF NOT EXISTS recs.recommendation_impressions (
324310 user_did TEXT NOT NULL,
325311 target_type TEXT NOT NULL CHECK(target_type IN ('feed', 'article')),
326312 target_id TEXT NOT NULL,
···331317 PRIMARY KEY (user_did, target_type, target_id)
332318 )`,
333319334334- `CREATE TABLE IF NOT EXISTS follow_distances (
320320+ `CREATE TABLE IF NOT EXISTS recs.follow_distances (
335321 user_a TEXT NOT NULL,
336322 user_b TEXT NOT NULL,
337323 distance INTEGER NOT NULL CHECK(distance IN (1, 2)),
338324 PRIMARY KEY (user_a, user_b)
339325 )`,
340326341341- `CREATE TABLE IF NOT EXISTS user_signal_weights (
327327+ `CREATE TABLE IF NOT EXISTS recs.user_signal_weights (
342328 user_did TEXT PRIMARY KEY,
343329 w_sub REAL NOT NULL DEFAULT 1.0,
344330 w_like REAL NOT NULL DEFAULT 0.5,
···349335 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
350336 )`,
351337352352- `CREATE TABLE IF NOT EXISTS user_signal_profiles (
338338+ `CREATE TABLE IF NOT EXISTS recs.user_signal_profiles (
353339 user_did TEXT PRIMARY KEY,
354340 total_likes INTEGER NOT NULL DEFAULT 0,
355341 total_tags INTEGER NOT NULL DEFAULT 0,
···357343 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
358344 )`,
359345360360- `CREATE INDEX IF NOT EXISTS idx_dismissed_user_type ON dismissed_recommendations(user_did, target_type)`,
361361- `CREATE INDEX IF NOT EXISTS idx_impressions_user_unacted ON recommendation_impressions(user_did, acted, shown_count)`,
362362- `CREATE INDEX IF NOT EXISTS idx_impressions_last_shown ON recommendation_impressions(last_shown_at)`,
363363- `CREATE INDEX IF NOT EXISTS idx_follow_distances_b ON follow_distances(user_b)`,
364364- `CREATE INDEX IF NOT EXISTS idx_follow_distances_a_dist ON follow_distances(user_a, distance)`,
365365- `CREATE INDEX IF NOT EXISTS idx_user_similarity_b ON user_similarity(user_b)`,
366366- `CREATE INDEX IF NOT EXISTS idx_user_similarity_a ON user_similarity(user_a)`,
346346+ `CREATE INDEX IF NOT EXISTS recs.idx_dismissed_user_type ON dismissed_recommendations(user_did, target_type)`,
347347+ `CREATE INDEX IF NOT EXISTS recs.idx_impressions_user_unacted ON recommendation_impressions(user_did, acted, shown_count)`,
348348+ `CREATE INDEX IF NOT EXISTS recs.idx_impressions_last_shown ON recommendation_impressions(last_shown_at)`,
349349+ `CREATE INDEX IF NOT EXISTS recs.idx_follow_distances_b ON follow_distances(user_b)`,
350350+ `CREATE INDEX IF NOT EXISTS recs.idx_follow_distances_a_dist ON follow_distances(user_a, distance)`,
351351+ `CREATE INDEX IF NOT EXISTS recs.idx_user_similarity_b ON user_similarity(user_b)`,
352352+ `CREATE INDEX IF NOT EXISTS recs.idx_user_similarity_a ON user_similarity(user_a)`,
367353}