things3_core/database/
query_builders.rs1use crate::models::UpdateTaskRequest;
7
8#[derive(Debug, Clone)]
13pub struct TaskUpdateBuilder {
14 updates: Vec<String>,
15}
16
17impl TaskUpdateBuilder {
18 #[must_use]
20 pub fn new() -> Self {
21 Self {
22 updates: Vec::new(),
23 }
24 }
25
26 #[must_use]
30 pub fn from_request(request: &UpdateTaskRequest) -> Self {
31 let mut builder = Self::new();
32
33 if request.title.is_some() {
34 builder = builder.add_field("title");
35 }
36
37 if request.notes.is_some() {
38 builder = builder.add_field("notes");
39 }
40
41 if request.start_date.is_some() {
42 builder = builder.add_field("startDate");
43 }
44
45 if request.deadline.is_some() {
46 builder = builder.add_field("deadline");
47 }
48
49 if request.status.is_some() {
50 builder = builder.add_field("status");
51 }
52
53 if request.project_uuid.is_some() {
54 builder = builder.add_field("project");
55 }
56
57 if request.area_uuid.is_some() {
58 builder = builder.add_field("area");
59 }
60
61 builder
62 }
63
64 #[must_use]
66 pub fn add_field(mut self, field_name: &str) -> Self {
67 self.updates.push(format!("{field_name} = ?"));
68 self
69 }
70
71 #[must_use]
73 pub fn is_empty(&self) -> bool {
74 self.updates.is_empty()
75 }
76
77 #[must_use]
79 pub fn len(&self) -> usize {
80 self.updates.len()
81 }
82
83 #[must_use]
87 pub fn build_query_string(&self) -> String {
88 if self.updates.is_empty() {
89 return "UPDATE TMTask SET userModificationDate = ? WHERE uuid = ?".to_string();
91 }
92
93 let mut all_updates = self.updates.clone();
94 all_updates.push("userModificationDate = ?".to_string());
95 format!(
96 "UPDATE TMTask SET {} WHERE uuid = ?",
97 all_updates.join(", ")
98 )
99 }
100
101 #[must_use]
103 pub fn fields(&self) -> Vec<String> {
104 self.updates
105 .iter()
106 .map(|u| u.split(" = ").next().unwrap_or("").to_string())
107 .collect()
108 }
109}
110
111impl Default for TaskUpdateBuilder {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::models::TaskStatus;
121 use crate::models::ThingsId;
122 use chrono::NaiveDate;
123
124 #[test]
125 fn test_task_update_builder_empty() {
126 let builder = TaskUpdateBuilder::new();
127 assert!(builder.is_empty());
128 assert_eq!(builder.len(), 0);
129 let query = builder.build_query_string();
131 assert!(query.contains("userModificationDate = ?"));
132 }
133
134 #[test]
135 fn test_task_update_builder_single_field() {
136 let builder = TaskUpdateBuilder::new().add_field("title");
137 assert!(!builder.is_empty());
138 assert_eq!(builder.len(), 1);
139 let query = builder.build_query_string();
140 assert!(query.contains("title = ?"));
141 assert!(query.contains("userModificationDate = ?"));
142 }
143
144 #[test]
145 fn test_task_update_builder_multiple_fields() {
146 let builder = TaskUpdateBuilder::new()
147 .add_field("title")
148 .add_field("notes")
149 .add_field("status");
150 assert_eq!(builder.len(), 3);
151 let query = builder.build_query_string();
152 assert!(query.contains("title = ?"));
153 assert!(query.contains("notes = ?"));
154 assert!(query.contains("status = ?"));
155 }
156
157 #[test]
158 fn test_task_update_builder_from_request() {
159 let request = UpdateTaskRequest {
160 uuid: ThingsId::new_v4(),
161 title: Some("Updated Title".to_string()),
162 notes: Some("Updated Notes".to_string()),
163 start_date: Some(NaiveDate::from_ymd_opt(2025, 1, 15).unwrap()),
164 deadline: Some(NaiveDate::from_ymd_opt(2025, 2, 1).unwrap()),
165 status: Some(TaskStatus::Incomplete),
166 project_uuid: Some(ThingsId::new_v4()),
167 area_uuid: Some(ThingsId::new_v4()),
168 tags: Some(vec!["tag1".to_string(), "tag2".to_string()]),
169 };
170
171 let builder = TaskUpdateBuilder::from_request(&request);
172 assert_eq!(builder.len(), 7);
173
174 let query = builder.build_query_string();
175 assert!(query.contains("title = ?"));
176 assert!(query.contains("notes = ?"));
177 assert!(query.contains("startDate = ?"));
178 assert!(query.contains("deadline = ?"));
179 assert!(query.contains("status = ?"));
180 assert!(query.contains("project = ?"));
181 assert!(query.contains("area = ?"));
182 assert!(!query.contains("cachedTags = ?"));
183 }
184
185 #[test]
186 fn test_task_update_builder_from_partial_request() {
187 let request = UpdateTaskRequest {
188 uuid: ThingsId::new_v4(),
189 title: Some("Updated Title".to_string()),
190 notes: None,
191 start_date: None,
192 deadline: None,
193 status: None,
194 project_uuid: None,
195 area_uuid: None,
196 tags: None,
197 };
198
199 let builder = TaskUpdateBuilder::from_request(&request);
200 assert_eq!(builder.len(), 1);
201
202 let query = builder.build_query_string();
203 assert!(query.contains("title = ?"));
204 assert!(!query.contains("notes = ?"));
205 assert!(!query.contains("status = ?"));
206 }
207
208 #[test]
209 fn test_task_update_builder_fields() {
210 let builder = TaskUpdateBuilder::new()
211 .add_field("title")
212 .add_field("status");
213 let fields = builder.fields();
214 assert_eq!(fields.len(), 2);
215 assert!(fields.contains(&"title".to_string()));
216 assert!(fields.contains(&"status".to_string()));
217 }
218}