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 if request.tags.is_some() {
62 builder = builder.add_field("cachedTags");
63 }
64
65 builder
66 }
67
68 #[must_use]
70 pub fn add_field(mut self, field_name: &str) -> Self {
71 self.updates.push(format!("{field_name} = ?"));
72 self
73 }
74
75 #[must_use]
77 pub fn is_empty(&self) -> bool {
78 self.updates.is_empty()
79 }
80
81 #[must_use]
83 pub fn len(&self) -> usize {
84 self.updates.len()
85 }
86
87 #[must_use]
91 pub fn build_query_string(&self) -> String {
92 if self.updates.is_empty() {
93 return "UPDATE TMTask SET userModificationDate = ? WHERE uuid = ?".to_string();
95 }
96
97 let mut all_updates = self.updates.clone();
98 all_updates.push("userModificationDate = ?".to_string());
99 format!(
100 "UPDATE TMTask SET {} WHERE uuid = ?",
101 all_updates.join(", ")
102 )
103 }
104
105 #[must_use]
107 pub fn fields(&self) -> Vec<String> {
108 self.updates
109 .iter()
110 .map(|u| u.split(" = ").next().unwrap_or("").to_string())
111 .collect()
112 }
113}
114
115impl Default for TaskUpdateBuilder {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::models::TaskStatus;
125 use chrono::NaiveDate;
126 use uuid::Uuid;
127
128 #[test]
129 fn test_task_update_builder_empty() {
130 let builder = TaskUpdateBuilder::new();
131 assert!(builder.is_empty());
132 assert_eq!(builder.len(), 0);
133 let query = builder.build_query_string();
135 assert!(query.contains("userModificationDate = ?"));
136 }
137
138 #[test]
139 fn test_task_update_builder_single_field() {
140 let builder = TaskUpdateBuilder::new().add_field("title");
141 assert!(!builder.is_empty());
142 assert_eq!(builder.len(), 1);
143 let query = builder.build_query_string();
144 assert!(query.contains("title = ?"));
145 assert!(query.contains("userModificationDate = ?"));
146 }
147
148 #[test]
149 fn test_task_update_builder_multiple_fields() {
150 let builder = TaskUpdateBuilder::new()
151 .add_field("title")
152 .add_field("notes")
153 .add_field("status");
154 assert_eq!(builder.len(), 3);
155 let query = builder.build_query_string();
156 assert!(query.contains("title = ?"));
157 assert!(query.contains("notes = ?"));
158 assert!(query.contains("status = ?"));
159 }
160
161 #[test]
162 fn test_task_update_builder_from_request() {
163 let request = UpdateTaskRequest {
164 uuid: Uuid::new_v4(),
165 title: Some("Updated Title".to_string()),
166 notes: Some("Updated Notes".to_string()),
167 start_date: Some(NaiveDate::from_ymd_opt(2025, 1, 15).unwrap()),
168 deadline: Some(NaiveDate::from_ymd_opt(2025, 2, 1).unwrap()),
169 status: Some(TaskStatus::Incomplete),
170 project_uuid: Some(Uuid::new_v4()),
171 area_uuid: Some(Uuid::new_v4()),
172 tags: Some(vec!["tag1".to_string(), "tag2".to_string()]),
173 };
174
175 let builder = TaskUpdateBuilder::from_request(&request);
176 assert_eq!(builder.len(), 8);
177
178 let query = builder.build_query_string();
179 assert!(query.contains("title = ?"));
180 assert!(query.contains("notes = ?"));
181 assert!(query.contains("startDate = ?"));
182 assert!(query.contains("deadline = ?"));
183 assert!(query.contains("status = ?"));
184 assert!(query.contains("project = ?"));
185 assert!(query.contains("area = ?"));
186 assert!(query.contains("cachedTags = ?"));
187 }
188
189 #[test]
190 fn test_task_update_builder_from_partial_request() {
191 let request = UpdateTaskRequest {
192 uuid: Uuid::new_v4(),
193 title: Some("Updated Title".to_string()),
194 notes: None,
195 start_date: None,
196 deadline: None,
197 status: None,
198 project_uuid: None,
199 area_uuid: None,
200 tags: None,
201 };
202
203 let builder = TaskUpdateBuilder::from_request(&request);
204 assert_eq!(builder.len(), 1);
205
206 let query = builder.build_query_string();
207 assert!(query.contains("title = ?"));
208 assert!(!query.contains("notes = ?"));
209 assert!(!query.contains("status = ?"));
210 }
211
212 #[test]
213 fn test_task_update_builder_fields() {
214 let builder = TaskUpdateBuilder::new()
215 .add_field("title")
216 .add_field("status");
217 let fields = builder.fields();
218 assert_eq!(fields.len(), 2);
219 assert!(fields.contains(&"title".to_string()));
220 assert!(fields.contains(&"status".to_string()));
221 }
222}