things3_core/mutations/
sqlx.rs1use std::sync::Arc;
9
10use async_trait::async_trait;
11
12use super::MutationBackend;
13use crate::database::ThingsDatabase;
14use crate::error::Result as ThingsResult;
15use crate::models::{
16 BulkCompleteRequest, BulkCreateTasksRequest, BulkDeleteRequest, BulkMoveRequest,
17 BulkOperationResult, BulkUpdateDatesRequest, CreateAreaRequest, CreateProjectRequest,
18 CreateTagRequest, CreateTaskRequest, DeleteChildHandling, ProjectChildHandling,
19 TagAssignmentResult, TagCreationResult, TagMatch, ThingsId, UpdateAreaRequest,
20 UpdateProjectRequest, UpdateTagRequest, UpdateTaskRequest,
21};
22
23pub struct SqlxBackend {
24 db: Arc<ThingsDatabase>,
25}
26
27impl SqlxBackend {
28 #[must_use]
29 pub fn new(db: Arc<ThingsDatabase>) -> Self {
30 Self { db }
31 }
32}
33
34#[async_trait]
35impl MutationBackend for SqlxBackend {
36 fn kind(&self) -> &'static str {
37 "sqlx"
38 }
39
40 async fn create_task(&self, request: CreateTaskRequest) -> ThingsResult<ThingsId> {
43 self.db.create_task(request).await
44 }
45
46 async fn bulk_create_tasks(
47 &self,
48 request: BulkCreateTasksRequest,
49 ) -> ThingsResult<BulkOperationResult> {
50 const MAX_BULK_BATCH_SIZE: usize = 1000;
51 if request.tasks.is_empty() {
52 return Err(crate::error::ThingsError::validation(
53 "Tasks array cannot be empty",
54 ));
55 }
56 if request.tasks.len() > MAX_BULK_BATCH_SIZE {
57 return Err(crate::error::ThingsError::validation(format!(
58 "Batch size {} exceeds maximum of {}",
59 request.tasks.len(),
60 MAX_BULK_BATCH_SIZE
61 )));
62 }
63 let total = request.tasks.len();
64 let mut processed = 0usize;
65 let mut errors: Vec<String> = Vec::new();
66 for (idx, task) in request.tasks.into_iter().enumerate() {
67 match self.db.create_task(task).await {
68 Ok(_) => processed += 1,
69 Err(e) => errors.push(format!("task {idx}: {e}")),
70 }
71 }
72 let success = errors.is_empty();
73 let message = if success {
74 format!("Successfully created {processed} task(s)")
75 } else {
76 format!("Created {processed}/{total}; errors: {}", errors.join("; "))
77 };
78 Ok(BulkOperationResult {
79 success,
80 processed_count: processed,
81 message,
82 })
83 }
84
85 async fn update_task(&self, request: UpdateTaskRequest) -> ThingsResult<()> {
86 self.db.update_task(request).await
87 }
88
89 async fn complete_task(&self, id: &ThingsId) -> ThingsResult<()> {
90 self.db.complete_task(id).await
91 }
92
93 async fn uncomplete_task(&self, id: &ThingsId) -> ThingsResult<()> {
94 self.db.uncomplete_task(id).await
95 }
96
97 async fn delete_task(
98 &self,
99 id: &ThingsId,
100 child_handling: DeleteChildHandling,
101 ) -> ThingsResult<()> {
102 self.db.delete_task(id, child_handling).await
103 }
104
105 async fn bulk_delete(&self, request: BulkDeleteRequest) -> ThingsResult<BulkOperationResult> {
106 self.db.bulk_delete(request).await
107 }
108
109 async fn bulk_move(&self, request: BulkMoveRequest) -> ThingsResult<BulkOperationResult> {
110 self.db.bulk_move(request).await
111 }
112
113 async fn bulk_update_dates(
114 &self,
115 request: BulkUpdateDatesRequest,
116 ) -> ThingsResult<BulkOperationResult> {
117 self.db.bulk_update_dates(request).await
118 }
119
120 async fn bulk_complete(
121 &self,
122 request: BulkCompleteRequest,
123 ) -> ThingsResult<BulkOperationResult> {
124 self.db.bulk_complete(request).await
125 }
126
127 async fn create_project(&self, request: CreateProjectRequest) -> ThingsResult<ThingsId> {
130 self.db.create_project(request).await
131 }
132
133 async fn update_project(&self, request: UpdateProjectRequest) -> ThingsResult<()> {
134 self.db.update_project(request).await
135 }
136
137 async fn complete_project(
138 &self,
139 id: &ThingsId,
140 child_handling: ProjectChildHandling,
141 ) -> ThingsResult<()> {
142 self.db.complete_project(id, child_handling).await
143 }
144
145 async fn delete_project(
146 &self,
147 id: &ThingsId,
148 child_handling: ProjectChildHandling,
149 ) -> ThingsResult<()> {
150 self.db.delete_project(id, child_handling).await
151 }
152
153 async fn create_area(&self, request: CreateAreaRequest) -> ThingsResult<ThingsId> {
156 self.db.create_area(request).await
157 }
158
159 async fn update_area(&self, request: UpdateAreaRequest) -> ThingsResult<()> {
160 self.db.update_area(request).await
161 }
162
163 async fn delete_area(&self, id: &ThingsId) -> ThingsResult<()> {
164 self.db.delete_area(id).await
165 }
166
167 async fn create_tag(
170 &self,
171 request: CreateTagRequest,
172 force: bool,
173 ) -> ThingsResult<TagCreationResult> {
174 if force {
175 let id = self.db.create_tag_force(request).await?;
176 Ok(TagCreationResult::Created {
177 uuid: id,
178 is_new: true,
179 })
180 } else {
181 self.db.create_tag_smart(request).await
182 }
183 }
184
185 async fn update_tag(&self, request: UpdateTagRequest) -> ThingsResult<()> {
186 self.db.update_tag(request).await
187 }
188
189 async fn delete_tag(&self, id: &ThingsId, remove_from_tasks: bool) -> ThingsResult<()> {
190 self.db.delete_tag(id, remove_from_tasks).await
191 }
192
193 async fn merge_tags(&self, source_id: &ThingsId, target_id: &ThingsId) -> ThingsResult<()> {
194 self.db.merge_tags(source_id, target_id).await
195 }
196
197 async fn add_tag_to_task(
198 &self,
199 task_id: &ThingsId,
200 tag_title: &str,
201 ) -> ThingsResult<TagAssignmentResult> {
202 self.db.add_tag_to_task(task_id, tag_title).await
203 }
204
205 async fn remove_tag_from_task(&self, task_id: &ThingsId, tag_title: &str) -> ThingsResult<()> {
206 self.db.remove_tag_from_task(task_id, tag_title).await
207 }
208
209 async fn set_task_tags(
210 &self,
211 task_id: &ThingsId,
212 tag_titles: Vec<String>,
213 ) -> ThingsResult<Vec<TagMatch>> {
214 self.db.set_task_tags(task_id, tag_titles).await
215 }
216}