1pub const MIGRATION_SQL: &[(&str, &str)] = &[
8 ("ALTER TABLE papers ADD COLUMN rating INTEGER", "rating"),
9 (
10 "ALTER TABLE papers ADD COLUMN openalex_id TEXT",
11 "openalex_id",
12 ),
13 (
14 "ALTER TABLE papers ADD COLUMN keywords TEXT NOT NULL DEFAULT ''",
15 "keywords",
16 ),
17];
18
19pub const FTS_V3_SQL: &str = r#"
29DROP TRIGGER IF EXISTS papers_ai;
30DROP TRIGGER IF EXISTS papers_ad;
31DROP TRIGGER IF EXISTS papers_au;
32DROP TABLE IF EXISTS papers_fts;
33
34CREATE VIRTUAL TABLE papers_fts USING fts5(
35 title, abstract_text, notes, tags, keywords,
36 content=papers, content_rowid=rowid, tokenize='trigram'
37);
38
39CREATE TRIGGER papers_ai AFTER INSERT ON papers BEGIN
40 INSERT INTO papers_fts(rowid, title, abstract_text, notes, tags, keywords)
41 VALUES (new.rowid, new.title, new.abstract_text, new.notes, new.tags, new.keywords);
42END;
43
44CREATE TRIGGER papers_ad AFTER DELETE ON papers BEGIN
45 INSERT INTO papers_fts(papers_fts, rowid, title, abstract_text, notes, tags, keywords)
46 VALUES('delete', old.rowid, old.title, old.abstract_text, old.notes, old.tags, old.keywords);
47END;
48
49CREATE TRIGGER papers_au AFTER UPDATE ON papers BEGIN
50 INSERT INTO papers_fts(papers_fts, rowid, title, abstract_text, notes, tags, keywords)
51 VALUES('delete', old.rowid, old.title, old.abstract_text, old.notes, old.tags, old.keywords);
52 INSERT INTO papers_fts(rowid, title, abstract_text, notes, tags, keywords)
53 VALUES (new.rowid, new.title, new.abstract_text, new.notes, new.tags, new.keywords);
54END;
55
56INSERT INTO papers_fts(papers_fts) VALUES('rebuild');
57"#;
58
59pub const TARGET_SCHEMA_VERSION: i64 = 3;
61
62pub const SCHEMA_SQL: &str = r#"
63CREATE TABLE IF NOT EXISTS _meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
64INSERT OR IGNORE INTO _meta (key, value) VALUES ('schema_version', '1');
65
66CREATE TABLE IF NOT EXISTS papers (
67 id TEXT PRIMARY KEY,
68 title TEXT NOT NULL,
69 authors TEXT NOT NULL DEFAULT '[]',
70 abstract_text TEXT NOT NULL DEFAULT '',
71 year INTEGER,
72 venue TEXT,
73 doi TEXT,
74 arxiv_id TEXT,
75 s2_id TEXT,
76 openalex_id TEXT,
77 url TEXT,
78 pdf_path TEXT,
79 status TEXT NOT NULL DEFAULT 'discovered',
80 reading_status TEXT NOT NULL DEFAULT 'unread',
81 notes TEXT NOT NULL DEFAULT '',
82 tags TEXT NOT NULL DEFAULT '[]',
83 relevance_score REAL NOT NULL DEFAULT 0.5,
84 rating INTEGER,
85 keywords TEXT NOT NULL DEFAULT '',
86 created_at TEXT NOT NULL,
87 updated_at TEXT NOT NULL
88);
89
90CREATE VIRTUAL TABLE IF NOT EXISTS papers_fts USING fts5(
91 title, abstract_text, notes, tags, keywords,
92 content=papers, content_rowid=rowid, tokenize='trigram'
93);
94
95CREATE TABLE IF NOT EXISTS research_topics (
96 id TEXT PRIMARY KEY,
97 name TEXT NOT NULL,
98 description TEXT NOT NULL DEFAULT '',
99 parent_topic_id TEXT REFERENCES research_topics(id),
100 depth INTEGER NOT NULL DEFAULT 0,
101 priority REAL NOT NULL DEFAULT 0.5,
102 created_at TEXT NOT NULL
103);
104
105CREATE TABLE IF NOT EXISTS topic_papers (
106 topic_id TEXT NOT NULL REFERENCES research_topics(id),
107 paper_id TEXT NOT NULL REFERENCES papers(id),
108 relevance REAL NOT NULL DEFAULT 0.5,
109 PRIMARY KEY (topic_id, paper_id)
110);
111
112CREATE TABLE IF NOT EXISTS knowledge_gaps (
113 id TEXT PRIMARY KEY,
114 description TEXT NOT NULL,
115 topic_id TEXT NOT NULL REFERENCES research_topics(id),
116 gap_type TEXT NOT NULL DEFAULT 'missing_literature',
117 priority REAL NOT NULL DEFAULT 0.5,
118 discovered_at TEXT NOT NULL
119);
120
121CREATE TABLE IF NOT EXISTS research_state (
122 topic_id TEXT PRIMARY KEY REFERENCES research_topics(id),
123 papers_read INTEGER NOT NULL DEFAULT 0,
124 papers_queued INTEGER NOT NULL DEFAULT 0,
125 gaps_identified INTEGER NOT NULL DEFAULT 0,
126 coverage_score REAL NOT NULL DEFAULT 0.0,
127 last_updated TEXT NOT NULL
128);
129
130CREATE TABLE IF NOT EXISTS research_reports (
131 id TEXT PRIMARY KEY,
132 title TEXT NOT NULL,
133 topic_ids TEXT NOT NULL DEFAULT '[]',
134 content TEXT NOT NULL DEFAULT '',
135 format TEXT NOT NULL DEFAULT 'markdown',
136 output_path TEXT,
137 generated_at TEXT NOT NULL
138);
139
140CREATE TRIGGER IF NOT EXISTS papers_ai AFTER INSERT ON papers BEGIN
141 INSERT INTO papers_fts(rowid, title, abstract_text, notes, tags, keywords)
142 VALUES (new.rowid, new.title, new.abstract_text, new.notes, new.tags, new.keywords);
143END;
144
145CREATE TRIGGER IF NOT EXISTS papers_ad AFTER DELETE ON papers BEGIN
146 INSERT INTO papers_fts(papers_fts, rowid, title, abstract_text, notes, tags, keywords)
147 VALUES('delete', old.rowid, old.title, old.abstract_text, old.notes, old.tags, old.keywords);
148END;
149
150CREATE TRIGGER IF NOT EXISTS papers_au AFTER UPDATE ON papers BEGIN
151 INSERT INTO papers_fts(papers_fts, rowid, title, abstract_text, notes, tags, keywords)
152 VALUES('delete', old.rowid, old.title, old.abstract_text, old.notes, old.tags, old.keywords);
153 INSERT INTO papers_fts(rowid, title, abstract_text, notes, tags, keywords)
154 VALUES (new.rowid, new.title, new.abstract_text, new.notes, new.tags, new.keywords);
155END;
156
157-- Full extracted body text lives outside `papers` so the Paper domain type and
158-- every tool response stay small; FTS5 searches it through its own index.
159-- Paper-to-paper reference edges (citation graph). Logical keys only: a
160-- referenced paper may be ingested in the same call as the citing one, so
161-- foreign keys would force an ordering the fetch cannot guarantee.
162CREATE TABLE IF NOT EXISTS citations (
163 citing_paper_id TEXT NOT NULL,
164 cited_paper_id TEXT NOT NULL,
165 context TEXT NOT NULL DEFAULT '',
166 PRIMARY KEY (citing_paper_id, cited_paper_id)
167);
168
169CREATE TABLE IF NOT EXISTS paper_bodies (
170 paper_id TEXT PRIMARY KEY REFERENCES papers(id) ON DELETE CASCADE,
171 body TEXT NOT NULL DEFAULT ''
172);
173
174CREATE VIRTUAL TABLE IF NOT EXISTS bodies_fts USING fts5(
175 body, content=paper_bodies, content_rowid=rowid, tokenize='trigram'
176);
177
178CREATE TRIGGER IF NOT EXISTS bodies_ai AFTER INSERT ON paper_bodies BEGIN
179 INSERT INTO bodies_fts(rowid, body) VALUES (new.rowid, new.body);
180END;
181
182CREATE TRIGGER IF NOT EXISTS bodies_ad AFTER DELETE ON paper_bodies BEGIN
183 INSERT INTO bodies_fts(bodies_fts, rowid, body)
184 VALUES('delete', old.rowid, old.body);
185END;
186
187CREATE TRIGGER IF NOT EXISTS bodies_au AFTER UPDATE ON paper_bodies BEGIN
188 INSERT INTO bodies_fts(bodies_fts, rowid, body)
189 VALUES('delete', old.rowid, old.body);
190 INSERT INTO bodies_fts(rowid, body) VALUES (new.rowid, new.body);
191END;
192"#;