scitadel_db/sqlite/
questions.rs1use rusqlite::{OptionalExtension, params};
2use scitadel_core::error::CoreError;
3use scitadel_core::models::{QuestionId, ResearchQuestion, SearchTerm, SearchTermId};
4use scitadel_core::ports::QuestionRepository;
5
6use super::Database;
7use crate::error::DbError;
8
9pub struct SqliteQuestionRepository {
10 db: Database,
11}
12
13impl SqliteQuestionRepository {
14 pub fn new(db: Database) -> Self {
15 Self { db }
16 }
17}
18
19impl QuestionRepository for SqliteQuestionRepository {
20 fn save_question(&self, question: &ResearchQuestion) -> Result<(), CoreError> {
21 let conn = self.db.conn()?;
22 conn.execute(
23 "INSERT OR REPLACE INTO research_questions
24 (id, text, description, created_at, updated_at)
25 VALUES (?1, ?2, ?3, ?4, ?5)",
26 params![
27 question.id.as_str(),
28 question.text,
29 question.description,
30 question.created_at.to_rfc3339(),
31 question.updated_at.to_rfc3339(),
32 ],
33 )
34 .map_err(DbError::Sqlite)?;
35 Ok(())
36 }
37
38 fn get_question(&self, question_id: &str) -> Result<Option<ResearchQuestion>, CoreError> {
39 let conn = self.db.conn()?;
40 let mut stmt = conn
41 .prepare("SELECT * FROM research_questions WHERE id = ?1")
42 .map_err(DbError::Sqlite)?;
43 let result = stmt
44 .query_row(params![question_id], |row| {
45 let id: String = row.get("id")?;
46 let created_at: String = row.get("created_at")?;
47 let updated_at: String = row.get("updated_at")?;
48 Ok(ResearchQuestion {
49 id: QuestionId::from(id),
50 text: row.get("text")?,
51 description: row.get("description")?,
52 created_at: super::parse_rfc3339_or_now(&created_at),
53 updated_at: super::parse_rfc3339_or_now(&updated_at),
54 })
55 })
56 .optional()
57 .map_err(DbError::Sqlite)?;
58 Ok(result)
59 }
60
61 fn list_questions(&self) -> Result<Vec<ResearchQuestion>, CoreError> {
62 let conn = self.db.conn()?;
63 let mut stmt = conn
64 .prepare("SELECT * FROM research_questions ORDER BY created_at DESC")
65 .map_err(DbError::Sqlite)?;
66 let questions = stmt
67 .query_map([], |row| {
68 let id: String = row.get("id")?;
69 let created_at: String = row.get("created_at")?;
70 let updated_at: String = row.get("updated_at")?;
71 Ok(ResearchQuestion {
72 id: QuestionId::from(id),
73 text: row.get("text")?,
74 description: row.get("description")?,
75 created_at: super::parse_rfc3339_or_now(&created_at),
76 updated_at: super::parse_rfc3339_or_now(&updated_at),
77 })
78 })
79 .map_err(DbError::Sqlite)?
80 .filter_map(Result::ok)
81 .collect();
82 Ok(questions)
83 }
84
85 fn save_term(&self, term: &SearchTerm) -> Result<(), CoreError> {
86 let conn = self.db.conn()?;
87 conn.execute(
88 "INSERT OR REPLACE INTO search_terms
89 (id, question_id, terms, query_string, created_at)
90 VALUES (?1, ?2, ?3, ?4, ?5)",
91 params![
92 term.id.as_str(),
93 term.question_id.as_str(),
94 serde_json::to_string(&term.terms).unwrap_or_default(),
95 term.query_string,
96 term.created_at.to_rfc3339(),
97 ],
98 )
99 .map_err(DbError::Sqlite)?;
100 Ok(())
101 }
102
103 fn get_terms(&self, question_id: &str) -> Result<Vec<SearchTerm>, CoreError> {
104 let conn = self.db.conn()?;
105 let mut stmt = conn
106 .prepare("SELECT * FROM search_terms WHERE question_id = ?1")
107 .map_err(DbError::Sqlite)?;
108 let terms = stmt
109 .query_map(params![question_id], |row| {
110 let id: String = row.get("id")?;
111 let question_id: String = row.get("question_id")?;
112 let terms_json: String = row.get("terms")?;
113 let created_at: String = row.get("created_at")?;
114 Ok(SearchTerm {
115 id: SearchTermId::from(id),
116 question_id: QuestionId::from(question_id),
117 terms: serde_json::from_str(&terms_json).unwrap_or_default(),
118 query_string: row.get("query_string")?,
119 created_at: super::parse_rfc3339_or_now(&created_at),
120 })
121 })
122 .map_err(DbError::Sqlite)?
123 .filter_map(Result::ok)
124 .collect();
125 Ok(terms)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::sqlite::Database;
133
134 #[test]
135 fn test_question_crud() {
136 let db = Database::open_in_memory().unwrap();
137 db.migrate().unwrap();
138 let repo = SqliteQuestionRepository::new(db);
139
140 let q = ResearchQuestion::new("What is dark matter?");
141 repo.save_question(&q).unwrap();
142
143 let loaded = repo.get_question(q.id.as_str()).unwrap().unwrap();
144 assert_eq!(loaded.text, "What is dark matter?");
145
146 let all = repo.list_questions().unwrap();
147 assert_eq!(all.len(), 1);
148 }
149
150 #[test]
151 fn test_search_terms() {
152 let db = Database::open_in_memory().unwrap();
153 db.migrate().unwrap();
154 let repo = SqliteQuestionRepository::new(db);
155
156 let q = ResearchQuestion::new("Test question");
157 repo.save_question(&q).unwrap();
158
159 let mut term = SearchTerm::new(q.id.clone());
160 term.terms = vec!["dark".into(), "matter".into()];
161 term.query_string = "dark matter".into();
162 repo.save_term(&term).unwrap();
163
164 let terms = repo.get_terms(q.id.as_str()).unwrap();
165 assert_eq!(terms.len(), 1);
166 assert_eq!(terms[0].query_string, "dark matter");
167 }
168}