1use anyhow::Result;
2use rusqlite::{params, Connection, OptionalExtension};
3use crate::models::{Project, Session, Tag};
4use std::path::PathBuf;
5
6pub struct ProjectQueries;
7
8impl ProjectQueries {
9 pub fn create(conn: &Connection, project: &Project) -> Result<i64> {
10 let mut stmt = conn.prepare(
11 "INSERT INTO projects (name, path, git_hash, description, is_archived)
12 VALUES (?1, ?2, ?3, ?4, ?5)"
13 )?;
14
15 stmt.execute(params![
16 project.name,
17 project.path.to_string_lossy().to_string(),
18 project.git_hash,
19 project.description,
20 project.is_archived
21 ])?;
22
23 Ok(conn.last_insert_rowid())
24 }
25
26 pub fn find_by_path(conn: &Connection, path: &PathBuf) -> Result<Option<Project>> {
27 let mut stmt = conn.prepare(
28 "SELECT id, name, path, git_hash, created_at, updated_at, is_archived, description
29 FROM projects WHERE path = ?1"
30 )?;
31
32 let project = stmt.query_row([path.to_string_lossy().to_string()], |row| {
33 Ok(Project {
34 id: Some(row.get(0)?),
35 name: row.get(1)?,
36 path: PathBuf::from(row.get::<_, String>(2)?),
37 git_hash: row.get(3)?,
38 created_at: row.get(4)?,
39 updated_at: row.get(5)?,
40 is_archived: row.get(6)?,
41 description: row.get(7)?,
42 })
43 }).optional()?;
44
45 Ok(project)
46 }
47
48 pub fn list_all(conn: &Connection, include_archived: bool) -> Result<Vec<Project>> {
49 let sql = if include_archived {
50 "SELECT id, name, path, git_hash, created_at, updated_at, is_archived, description
51 FROM projects ORDER BY name"
52 } else {
53 "SELECT id, name, path, git_hash, created_at, updated_at, is_archived, description
54 FROM projects WHERE is_archived = 0 ORDER BY name"
55 };
56
57 let mut stmt = conn.prepare(sql)?;
58 let projects = stmt.query_map([], |row| {
59 Ok(Project {
60 id: Some(row.get(0)?),
61 name: row.get(1)?,
62 path: PathBuf::from(row.get::<_, String>(2)?),
63 git_hash: row.get(3)?,
64 created_at: row.get(4)?,
65 updated_at: row.get(5)?,
66 is_archived: row.get(6)?,
67 description: row.get(7)?,
68 })
69 })?.collect::<Result<Vec<_>, _>>()?;
70
71 Ok(projects)
72 }
73
74 pub fn find_by_id(conn: &Connection, project_id: i64) -> Result<Option<Project>> {
75 let mut stmt = conn.prepare(
76 "SELECT id, name, path, git_hash, created_at, updated_at, is_archived, description
77 FROM projects WHERE id = ?1"
78 )?;
79
80 let project = stmt.query_row([project_id], |row| {
81 Ok(Project {
82 id: Some(row.get(0)?),
83 name: row.get(1)?,
84 path: PathBuf::from(row.get::<_, String>(2)?),
85 git_hash: row.get(3)?,
86 created_at: row.get(4)?,
87 updated_at: row.get(5)?,
88 is_archived: row.get(6)?,
89 description: row.get(7)?,
90 })
91 }).optional()?;
92
93 Ok(project)
94 }
95
96 pub fn find_by_name(conn: &Connection, name: &str) -> Result<Option<Project>> {
97 let mut stmt = conn.prepare(
98 "SELECT id, name, path, git_hash, created_at, updated_at, is_archived, description
99 FROM projects WHERE name = ?1"
100 )?;
101
102 let project = stmt.query_row([name], |row| {
103 Ok(Project {
104 id: Some(row.get(0)?),
105 name: row.get(1)?,
106 path: PathBuf::from(row.get::<_, String>(2)?),
107 git_hash: row.get(3)?,
108 created_at: row.get(4)?,
109 updated_at: row.get(5)?,
110 is_archived: row.get(6)?,
111 description: row.get(7)?,
112 })
113 }).optional()?;
114
115 Ok(project)
116 }
117
118 pub fn archive_project(conn: &Connection, project_id: i64) -> Result<bool> {
119 let mut stmt = conn.prepare(
120 "UPDATE projects SET is_archived = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?1"
121 )?;
122 let changes = stmt.execute([project_id])?;
123 Ok(changes > 0)
124 }
125
126 pub fn unarchive_project(conn: &Connection, project_id: i64) -> Result<bool> {
127 let mut stmt = conn.prepare(
128 "UPDATE projects SET is_archived = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?1"
129 )?;
130 let changes = stmt.execute([project_id])?;
131 Ok(changes > 0)
132 }
133
134 pub fn update_project_path(conn: &Connection, project_id: i64, new_path: &PathBuf) -> Result<bool> {
135 let mut stmt = conn.prepare(
136 "UPDATE projects SET path = ?1, updated_at = CURRENT_TIMESTAMP WHERE id = ?2"
137 )?;
138 let changes = stmt.execute([new_path.to_string_lossy().to_string(), project_id.to_string()])?;
139 Ok(changes > 0)
140 }
141
142 pub fn update_project_description(conn: &Connection, project_id: i64, description: Option<String>) -> Result<bool> {
143 let mut stmt = conn.prepare(
144 "UPDATE projects SET description = ?1, updated_at = CURRENT_TIMESTAMP WHERE id = ?2"
145 )?;
146 let changes = stmt.execute([description, Some(project_id.to_string())])?;
147 Ok(changes > 0)
148 }
149
150 pub fn delete_project(conn: &Connection, project_id: i64) -> Result<bool> {
151 let mut stmt = conn.prepare("DELETE FROM projects WHERE id = ?1")?;
152 let changes = stmt.execute([project_id])?;
153 Ok(changes > 0)
154 }
155
156 pub fn get_project_stats(conn: &Connection, project_id: i64) -> Result<Option<(i64, i64, i64)>> { let mut stmt = conn.prepare(
158 "SELECT
159 COUNT(*) as session_count,
160 COALESCE(SUM(CASE
161 WHEN end_time IS NOT NULL THEN
162 (julianday(end_time) - julianday(start_time)) * 86400 - paused_duration
163 ELSE 0
164 END), 0) as total_time,
165 COALESCE(AVG(CASE
166 WHEN end_time IS NOT NULL THEN
167 (julianday(end_time) - julianday(start_time)) * 86400 - paused_duration
168 ELSE 0
169 END), 0) as avg_time
170 FROM sessions WHERE project_id = ?1 AND end_time IS NOT NULL"
171 )?;
172
173 let stats = stmt.query_row([project_id], |row| {
174 Ok((
175 row.get::<_, i64>(0)?,
176 row.get::<_, f64>(1)? as i64,
177 row.get::<_, f64>(2)? as i64,
178 ))
179 }).optional()?;
180
181 Ok(stats)
182 }
183}
184
185pub struct SessionQueries;
186
187impl SessionQueries {
188 pub fn create(conn: &Connection, session: &Session) -> Result<i64> {
189 let mut stmt = conn.prepare(
190 "INSERT INTO sessions (project_id, start_time, end_time, context, paused_duration, notes)
191 VALUES (?1, ?2, ?3, ?4, ?5, ?6)"
192 )?;
193
194 stmt.execute(params![
195 session.project_id,
196 session.start_time,
197 session.end_time,
198 session.context.to_string(),
199 session.paused_duration.num_seconds(),
200 session.notes
201 ])?;
202
203 Ok(conn.last_insert_rowid())
204 }
205
206 pub fn find_active_session(conn: &Connection) -> Result<Option<Session>> {
207 let mut stmt = conn.prepare(
208 "SELECT id, project_id, start_time, end_time, context, paused_duration, notes, created_at
209 FROM sessions WHERE end_time IS NULL LIMIT 1"
210 )?;
211
212 let session = stmt.query_row([], |row| {
213 Ok(Session {
214 id: Some(row.get(0)?),
215 project_id: row.get(1)?,
216 start_time: row.get(2)?,
217 end_time: row.get(3)?,
218 context: row.get::<_, String>(4)?.parse().unwrap(),
219 paused_duration: chrono::Duration::seconds(row.get::<_, i64>(5)?),
220 notes: row.get(6)?,
221 created_at: row.get(7)?,
222 })
223 }).optional()?;
224
225 Ok(session)
226 }
227
228 pub fn end_session(conn: &Connection, session_id: i64) -> Result<()> {
229 let mut stmt = conn.prepare(
230 "UPDATE sessions SET end_time = CURRENT_TIMESTAMP WHERE id = ?1"
231 )?;
232
233 stmt.execute([session_id])?;
234 Ok(())
235 }
236
237 pub fn list_recent(conn: &Connection, limit: usize) -> Result<Vec<Session>> {
238 let mut stmt = conn.prepare(
239 "SELECT id, project_id, start_time, end_time, context, paused_duration, notes, created_at
240 FROM sessions ORDER BY start_time DESC LIMIT ?1"
241 )?;
242
243 let sessions = stmt.query_map([limit], |row| {
244 Ok(Session {
245 id: Some(row.get(0)?),
246 project_id: row.get(1)?,
247 start_time: row.get(2)?,
248 end_time: row.get(3)?,
249 context: row.get::<_, String>(4)?.parse().unwrap(),
250 paused_duration: chrono::Duration::seconds(row.get::<_, i64>(5)?),
251 notes: row.get(6)?,
252 created_at: row.get(7)?,
253 })
254 })?.collect::<Result<Vec<_>, _>>()?;
255
256 Ok(sessions)
257 }
258
259 pub fn find_by_id(conn: &Connection, session_id: i64) -> Result<Option<Session>> {
260 let mut stmt = conn.prepare(
261 "SELECT id, project_id, start_time, end_time, context, paused_duration, notes, created_at
262 FROM sessions WHERE id = ?1"
263 )?;
264
265 let session = stmt.query_row([session_id], |row| {
266 Ok(Session {
267 id: Some(row.get(0)?),
268 project_id: row.get(1)?,
269 start_time: row.get(2)?,
270 end_time: row.get(3)?,
271 context: row.get::<_, String>(4)?.parse().unwrap(),
272 paused_duration: chrono::Duration::seconds(row.get::<_, i64>(5)?),
273 notes: row.get(6)?,
274 created_at: row.get(7)?,
275 })
276 }).optional()?;
277
278 Ok(session)
279 }
280
281 pub fn update_session(conn: &Connection, session_id: i64, start_time: Option<chrono::DateTime<chrono::Utc>>, end_time: Option<Option<chrono::DateTime<chrono::Utc>>>, project_id: Option<i64>, notes: Option<Option<String>>) -> Result<()> {
282 let mut updates = Vec::new();
283 let mut params: Vec<Box<dyn rusqlite::ToSql>> = Vec::new();
284
285 if let Some(st) = start_time {
286 updates.push("start_time = ?");
287 params.push(Box::new(st));
288 }
289
290 if let Some(et) = end_time {
291 updates.push("end_time = ?");
292 params.push(Box::new(et));
293 }
294
295 if let Some(pid) = project_id {
296 updates.push("project_id = ?");
297 params.push(Box::new(pid));
298 }
299
300 if let Some(n) = notes {
301 updates.push("notes = ?");
302 params.push(Box::new(n));
303 }
304
305 if updates.is_empty() {
306 return Ok(());
307 }
308
309 params.push(Box::new(session_id));
310
311 let sql = format!("UPDATE sessions SET {} WHERE id = ?", updates.join(", "));
312 let mut stmt = conn.prepare(&sql)?;
313 let param_refs: Vec<&dyn rusqlite::ToSql> = params.iter().map(|p| p.as_ref()).collect();
314 stmt.execute(¶m_refs[..])?;
315
316 Ok(())
317 }
318
319 pub fn delete_session(conn: &Connection, session_id: i64) -> Result<()> {
320 let mut stmt = conn.prepare("DELETE FROM sessions WHERE id = ?1")?;
321 stmt.execute([session_id])?;
322 Ok(())
323 }
324
325 pub fn list_with_filter(conn: &Connection, project_id: Option<i64>, start_date: Option<chrono::NaiveDate>, end_date: Option<chrono::NaiveDate>, limit: Option<usize>) -> Result<Vec<Session>> {
326 let mut sql = "SELECT id, project_id, start_time, end_time, context, paused_duration, notes, created_at FROM sessions WHERE 1=1".to_string();
327 let mut params: Vec<Box<dyn rusqlite::ToSql>> = Vec::new();
328
329 if let Some(pid) = project_id {
330 sql.push_str(" AND project_id = ?");
331 params.push(Box::new(pid));
332 }
333
334 if let Some(sd) = start_date {
335 sql.push_str(" AND date(start_time) >= ?");
336 params.push(Box::new(sd.format("%Y-%m-%d").to_string()));
337 }
338
339 if let Some(ed) = end_date {
340 sql.push_str(" AND date(start_time) <= ?");
341 params.push(Box::new(ed.format("%Y-%m-%d").to_string()));
342 }
343
344 sql.push_str(" ORDER BY start_time DESC");
345
346 if let Some(lim) = limit {
347 sql.push_str(" LIMIT ?");
348 params.push(Box::new(lim));
349 }
350
351 let mut stmt = conn.prepare(&sql)?;
352 let param_refs: Vec<&dyn rusqlite::ToSql> = params.iter().map(|p| p.as_ref()).collect();
353
354 let sessions = stmt.query_map(¶m_refs[..], |row| {
355 Ok(Session {
356 id: Some(row.get(0)?),
357 project_id: row.get(1)?,
358 start_time: row.get(2)?,
359 end_time: row.get(3)?,
360 context: row.get::<_, String>(4)?.parse().unwrap(),
361 paused_duration: chrono::Duration::seconds(row.get::<_, i64>(5)?),
362 notes: row.get(6)?,
363 created_at: row.get(7)?,
364 })
365 })?.collect::<Result<Vec<_>, _>>()?;
366
367 Ok(sessions)
368 }
369
370 pub fn bulk_update_project(conn: &Connection, session_ids: &[i64], new_project_id: i64) -> Result<usize> {
371 let placeholders = vec!["?"; session_ids.len()].join(",");
372 let sql = format!("UPDATE sessions SET project_id = ? WHERE id IN ({})", placeholders);
373
374 let mut stmt = conn.prepare(&sql)?;
375 let mut params: Vec<&dyn rusqlite::ToSql> = vec![&new_project_id];
376 for id in session_ids {
377 params.push(id);
378 }
379
380 let changes = stmt.execute(¶ms[..])?;
381 Ok(changes)
382 }
383
384 pub fn bulk_delete(conn: &Connection, session_ids: &[i64]) -> Result<usize> {
385 let placeholders = vec!["?"; session_ids.len()].join(",");
386 let sql = format!("DELETE FROM sessions WHERE id IN ({})", placeholders);
387
388 let mut stmt = conn.prepare(&sql)?;
389 let params: Vec<&dyn rusqlite::ToSql> = session_ids.iter().map(|id| id as &dyn rusqlite::ToSql).collect();
390
391 let changes = stmt.execute(¶ms[..])?;
392 Ok(changes)
393 }
394
395 pub fn merge_sessions(conn: &Connection, session_ids: &[i64], target_project_id: Option<i64>, notes: Option<String>) -> Result<i64> {
396 if session_ids.is_empty() {
397 return Err(anyhow::anyhow!("No sessions to merge"));
398 }
399
400 let placeholders = vec!["?"; session_ids.len()].join(",");
402 let sql = format!(
403 "SELECT id, project_id, start_time, end_time, context, paused_duration, notes, created_at
404 FROM sessions WHERE id IN ({}) ORDER BY start_time",
405 placeholders
406 );
407
408 let mut stmt = conn.prepare(&sql)?;
409 let params: Vec<&dyn rusqlite::ToSql> = session_ids.iter().map(|id| id as &dyn rusqlite::ToSql).collect();
410
411 let sessions: Result<Vec<Session>, _> = stmt.query_map(¶ms[..], |row| {
412 Ok(Session {
413 id: Some(row.get(0)?),
414 project_id: row.get(1)?,
415 start_time: row.get(2)?,
416 end_time: row.get(3)?,
417 context: row.get::<_, String>(4)?.parse().unwrap(),
418 paused_duration: chrono::Duration::seconds(row.get::<_, i64>(5)?),
419 notes: row.get(6)?,
420 created_at: row.get(7)?,
421 })
422 })?.collect();
423
424 let sessions = sessions?;
425 if sessions.is_empty() {
426 return Err(anyhow::anyhow!("No valid sessions found to merge"));
427 }
428
429 let earliest_start = sessions.iter().map(|s| s.start_time).min().unwrap();
431 let latest_end = sessions.iter().filter_map(|s| s.end_time).max();
432 let total_paused = sessions.iter().map(|s| s.paused_duration).fold(chrono::Duration::zero(), |acc, d| acc + d);
433 let merged_project_id = target_project_id.unwrap_or(sessions[0].project_id);
434 let merged_context = sessions[0].context; let merged_session = Session {
438 id: None,
439 project_id: merged_project_id,
440 start_time: earliest_start,
441 end_time: latest_end,
442 context: merged_context,
443 paused_duration: total_paused,
444 notes,
445 created_at: chrono::Utc::now(),
446 };
447
448 let merged_id = Self::create(conn, &merged_session)?;
450
451 for session in &sessions {
453 if let Some(session_id) = session.id {
454 SessionEditQueries::create_edit_record(
455 conn,
456 session_id,
457 session.start_time,
458 session.end_time,
459 merged_session.start_time,
460 merged_session.end_time,
461 Some(format!("Merged into session {}", merged_id))
462 )?;
463 }
464 }
465
466 Self::bulk_delete(conn, session_ids)?;
468
469 Ok(merged_id)
470 }
471
472 pub fn split_session(conn: &Connection, session_id: i64, split_times: &[chrono::DateTime<chrono::Utc>], notes_list: Option<Vec<String>>) -> Result<Vec<i64>> {
473 let original_session = Self::find_by_id(conn, session_id)?
475 .ok_or_else(|| anyhow::anyhow!("Session {} not found", session_id))?;
476
477 if split_times.is_empty() {
478 return Err(anyhow::anyhow!("No split times provided"));
479 }
480
481 for &split_time in split_times {
483 if split_time <= original_session.start_time {
484 return Err(anyhow::anyhow!("Split time {} is before session start", split_time));
485 }
486 if let Some(end_time) = original_session.end_time {
487 if split_time >= end_time {
488 return Err(anyhow::anyhow!("Split time {} is after session end", split_time));
489 }
490 }
491 }
492
493 let mut sorted_splits = split_times.to_vec();
495 sorted_splits.sort();
496
497 let mut new_session_ids = Vec::new();
498 let mut current_start = original_session.start_time;
499
500 for (i, &split_time) in sorted_splits.iter().enumerate() {
502 let segment_notes = notes_list.as_ref()
503 .and_then(|list| list.get(i))
504 .cloned()
505 .or_else(|| original_session.notes.clone());
506
507 let split_session = Session {
508 id: None,
509 project_id: original_session.project_id,
510 start_time: current_start,
511 end_time: Some(split_time),
512 context: original_session.context,
513 paused_duration: chrono::Duration::zero(), notes: segment_notes,
515 created_at: chrono::Utc::now(),
516 };
517
518 let split_id = Self::create(conn, &split_session)?;
519 new_session_ids.push(split_id);
520 current_start = split_time;
521 }
522
523 let final_notes = notes_list.as_ref()
525 .and_then(|list| list.get(sorted_splits.len()))
526 .cloned()
527 .or_else(|| original_session.notes.clone());
528
529 let final_session = Session {
530 id: None,
531 project_id: original_session.project_id,
532 start_time: current_start,
533 end_time: original_session.end_time,
534 context: original_session.context,
535 paused_duration: chrono::Duration::zero(),
536 notes: final_notes,
537 created_at: chrono::Utc::now(),
538 };
539
540 let final_id = Self::create(conn, &final_session)?;
541 new_session_ids.push(final_id);
542
543 SessionEditQueries::create_edit_record(
545 conn,
546 session_id,
547 original_session.start_time,
548 original_session.end_time,
549 original_session.start_time,
550 original_session.end_time,
551 Some(format!("Split into sessions: {}", new_session_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>().join(", ")))
552 )?;
553
554 Self::delete_session(conn, session_id)?;
556
557 Ok(new_session_ids)
558 }
559}
560
561pub struct SessionEditQueries;
562
563impl SessionEditQueries {
564 pub fn create_edit_record(conn: &Connection, session_id: i64, original_start: chrono::DateTime<chrono::Utc>, original_end: Option<chrono::DateTime<chrono::Utc>>, new_start: chrono::DateTime<chrono::Utc>, new_end: Option<chrono::DateTime<chrono::Utc>>, reason: Option<String>) -> Result<i64> {
565 let mut stmt = conn.prepare(
566 "INSERT INTO session_edits (session_id, original_start_time, original_end_time, new_start_time, new_end_time, edit_reason)
567 VALUES (?1, ?2, ?3, ?4, ?5, ?6)"
568 )?;
569
570 stmt.execute(params![
571 session_id,
572 original_start,
573 original_end,
574 new_start,
575 new_end,
576 reason
577 ])?;
578
579 Ok(conn.last_insert_rowid())
580 }
581
582 pub fn list_session_edits(conn: &Connection, session_id: i64) -> Result<Vec<crate::models::SessionEdit>> {
583 let mut stmt = conn.prepare(
584 "SELECT id, session_id, original_start_time, original_end_time, new_start_time, new_end_time, edit_reason, created_at
585 FROM session_edits WHERE session_id = ?1 ORDER BY created_at DESC"
586 )?;
587
588 let edits = stmt.query_map([session_id], |row| {
589 Ok(crate::models::SessionEdit {
590 id: Some(row.get(0)?),
591 session_id: row.get(1)?,
592 original_start_time: row.get(2)?,
593 original_end_time: row.get(3)?,
594 new_start_time: row.get(4)?,
595 new_end_time: row.get(5)?,
596 edit_reason: row.get(6)?,
597 created_at: row.get(7)?,
598 })
599 })?.collect::<Result<Vec<_>, _>>()?;
600
601 Ok(edits)
602 }
603}
604
605pub struct TagQueries;
606
607impl TagQueries {
608 pub fn create(conn: &Connection, tag: &Tag) -> Result<i64> {
609 let mut stmt = conn.prepare(
610 "INSERT INTO tags (name, color, description) VALUES (?1, ?2, ?3)"
611 )?;
612
613 stmt.execute(params![
614 tag.name,
615 tag.color,
616 tag.description
617 ])?;
618
619 Ok(conn.last_insert_rowid())
620 }
621
622 pub fn list_all(conn: &Connection) -> Result<Vec<Tag>> {
623 let mut stmt = conn.prepare(
624 "SELECT id, name, color, description, created_at FROM tags ORDER BY name"
625 )?;
626
627 let tags = stmt.query_map([], |row| {
628 Ok(Tag {
629 id: Some(row.get(0)?),
630 name: row.get(1)?,
631 color: row.get(2)?,
632 description: row.get(3)?,
633 created_at: row.get(4)?,
634 })
635 })?.collect::<Result<Vec<_>, _>>()?;
636
637 Ok(tags)
638 }
639
640 pub fn find_by_name(conn: &Connection, name: &str) -> Result<Option<Tag>> {
641 let mut stmt = conn.prepare(
642 "SELECT id, name, color, description, created_at FROM tags WHERE name = ?1"
643 )?;
644
645 let tag = stmt.query_row([name], |row| {
646 Ok(Tag {
647 id: Some(row.get(0)?),
648 name: row.get(1)?,
649 color: row.get(2)?,
650 description: row.get(3)?,
651 created_at: row.get(4)?,
652 })
653 }).optional()?;
654
655 Ok(tag)
656 }
657
658 pub fn delete_by_name(conn: &Connection, name: &str) -> Result<bool> {
659 let mut stmt = conn.prepare("DELETE FROM tags WHERE name = ?1")?;
660 let changes = stmt.execute([name])?;
661 Ok(changes > 0)
662 }
663
664 pub fn update_tag(conn: &Connection, name: &str, color: Option<String>, description: Option<String>) -> Result<bool> {
665 let mut updates = Vec::new();
666 let mut params: Vec<&dyn rusqlite::ToSql> = Vec::new();
667
668 if let Some(c) = &color {
669 updates.push("color = ?");
670 params.push(c);
671 }
672
673 if let Some(d) = &description {
674 updates.push("description = ?");
675 params.push(d);
676 }
677
678 if updates.is_empty() {
679 return Ok(false);
680 }
681
682 params.push(&name);
683
684 let sql = format!("UPDATE tags SET {} WHERE name = ?", updates.join(", "));
685 let mut stmt = conn.prepare(&sql)?;
686 let changes = stmt.execute(¶ms[..])?;
687
688 Ok(changes > 0)
689 }
690}