| 221 |
221 |
|
.iter()
|
| 222 |
222 |
|
.map(|f| {
|
| 223 |
223 |
|
match f {
|
| 224 |
|
- |
IsFilter::Overdue => "(t.due IS NOT NULL AND datetime(t.due) < datetime('now'))".to_string(),
|
|
224 |
+ |
IsFilter::Overdue => "(t.due IS NOT NULL AND t.due < datetime('now'))".to_string(),
|
|
225 |
+ |
// Today/Tomorrow must convert the UTC-stored due to the local
|
|
226 |
+ |
// calendar day, so date(..., 'localtime') is required and these
|
|
227 |
+ |
// two predicates are intentionally non-sargable (a bare-column
|
|
228 |
+ |
// compare would test the UTC date, not the user's local date).
|
| 225 |
229 |
|
IsFilter::Today => "(t.due IS NOT NULL AND date(t.due, 'localtime') = date('now', 'localtime'))".to_string(),
|
| 226 |
230 |
|
IsFilter::Tomorrow => "(t.due IS NOT NULL AND date(t.due, 'localtime') = date('now', '+1 day', 'localtime'))".to_string(),
|
| 227 |
231 |
|
IsFilter::ThisWeek => {
|
| 228 |
232 |
|
// Due on or before end of this week (Sunday).
|
| 229 |
233 |
|
// weekday 1 = next Monday; < Monday midnight = through Sunday 23:59:59.
|
| 230 |
|
- |
"(t.due IS NOT NULL AND datetime(t.due) < datetime('now', 'weekday 1'))".to_string()
|
|
234 |
+ |
"(t.due IS NOT NULL AND t.due < datetime('now', 'weekday 1'))".to_string()
|
| 231 |
235 |
|
}
|
| 232 |
|
- |
IsFilter::Snoozed => "(t.snoozed_until IS NOT NULL AND datetime(t.snoozed_until) > datetime('now'))".to_string(),
|
|
236 |
+ |
IsFilter::Snoozed => "(t.snoozed_until IS NOT NULL AND t.snoozed_until > datetime('now'))".to_string(),
|
| 233 |
237 |
|
IsFilter::Pending => "t.status = 'Pending'".to_string(),
|
| 234 |
238 |
|
IsFilter::Started => "t.status = 'Started'".to_string(),
|
| 235 |
239 |
|
IsFilter::Completed => "t.status = 'Completed'".to_string(),
|
| 350 |
354 |
|
param_idx += 1;
|
| 351 |
355 |
|
}
|
| 352 |
356 |
|
|
| 353 |
|
- |
// Date filters
|
|
357 |
+ |
// Date filters. Compare the stored `due` text directly (sortable
|
|
358 |
+ |
// "%Y-%m-%d %H:%M:%S" format) and bind the bound in the same format, so the
|
|
359 |
+ |
// predicate is sargable against the due index instead of wrapping the column
|
|
360 |
+ |
// in datetime().
|
| 354 |
361 |
|
if let Some(df) = &query.date_from {
|
| 355 |
362 |
|
sql.push_str(&format!(
|
| 356 |
|
- |
" AND (t.due IS NULL OR datetime(t.due) >= datetime(${})) ",
|
|
363 |
+ |
" AND (t.due IS NULL OR t.due >= ${}) ",
|
| 357 |
364 |
|
param_idx
|
| 358 |
365 |
|
));
|
| 359 |
|
- |
params.push(df.to_rfc3339());
|
|
366 |
+ |
params.push(format_datetime(df));
|
| 360 |
367 |
|
param_idx += 1;
|
| 361 |
368 |
|
}
|
| 362 |
369 |
|
if let Some(dt) = &query.date_to {
|
| 363 |
370 |
|
sql.push_str(&format!(
|
| 364 |
|
- |
" AND (t.due IS NULL OR datetime(t.due) <= datetime(${})) ",
|
|
371 |
+ |
" AND (t.due IS NULL OR t.due <= ${}) ",
|
| 365 |
372 |
|
param_idx
|
| 366 |
373 |
|
));
|
| 367 |
|
- |
params.push(dt.to_rfc3339());
|
|
374 |
+ |
params.push(format_datetime(dt));
|
| 368 |
375 |
|
}
|
| 369 |
376 |
|
|
| 370 |
377 |
|
sql.push_str(&format!(" ORDER BY rank LIMIT {}", per_type_limit));
|
| 559 |
566 |
|
let mut params: Vec<String> = Vec::new();
|
| 560 |
567 |
|
let mut param_idx = 3;
|
| 561 |
568 |
|
|
| 562 |
|
- |
// Date filters (on created_at)
|
|
569 |
+ |
// Date filters (on created_at). Sargable bare-column comparison against a
|
|
570 |
+ |
// same-format bound (see the task/email date filters).
|
| 563 |
571 |
|
if let Some(df) = &query.date_from {
|
| 564 |
572 |
|
sql.push_str(&format!(
|
| 565 |
|
- |
" AND datetime(p.created_at) >= datetime(${})",
|
|
573 |
+ |
" AND p.created_at >= ${}",
|
| 566 |
574 |
|
param_idx
|
| 567 |
575 |
|
));
|
| 568 |
|
- |
params.push(df.to_rfc3339());
|
|
576 |
+ |
params.push(format_datetime(df));
|
| 569 |
577 |
|
param_idx += 1;
|
| 570 |
578 |
|
}
|
| 571 |
579 |
|
if let Some(dt) = &query.date_to {
|
| 572 |
580 |
|
sql.push_str(&format!(
|
| 573 |
|
- |
" AND datetime(p.created_at) <= datetime(${})",
|
|
581 |
+ |
" AND p.created_at <= ${}",
|
| 574 |
582 |
|
param_idx
|
| 575 |
583 |
|
));
|
| 576 |
|
- |
params.push(dt.to_rfc3339());
|
|
584 |
+ |
params.push(format_datetime(dt));
|
| 577 |
585 |
|
}
|
| 578 |
586 |
|
|
| 579 |
587 |
|
sql.push_str(&format!(" ORDER BY rank LIMIT {}", per_type_limit));
|
| 685 |
693 |
|
);
|
| 686 |
694 |
|
}
|
| 687 |
695 |
|
IsFilter::ThisWeek => {
|
| 688 |
|
- |
sql.push_str(" AND datetime(ev.start_time) < datetime('now', 'weekday 1')");
|
|
696 |
+ |
sql.push_str(" AND ev.start_time < datetime('now', 'weekday 1')");
|
| 689 |
697 |
|
}
|
| 690 |
698 |
|
IsFilter::Overdue => {
|
| 691 |
|
- |
sql.push_str(" AND datetime(ev.start_time) < datetime('now')");
|
|
699 |
+ |
sql.push_str(" AND ev.start_time < datetime('now')");
|
| 692 |
700 |
|
}
|
| 693 |
701 |
|
_ => {} // Other is: filters don't apply to events
|
| 694 |
702 |
|
}
|
| 711 |
719 |
|
param_idx += 1;
|
| 712 |
720 |
|
}
|
| 713 |
721 |
|
|
| 714 |
|
- |
// Date filters
|
|
722 |
+ |
// Date filters. Sargable bare-column comparison against a same-format bound.
|
| 715 |
723 |
|
if let Some(df) = &query.date_from {
|
| 716 |
724 |
|
sql.push_str(&format!(
|
| 717 |
|
- |
" AND datetime(ev.start_time) >= datetime(${})",
|
|
725 |
+ |
" AND ev.start_time >= ${}",
|
| 718 |
726 |
|
param_idx
|
| 719 |
727 |
|
));
|
| 720 |
|
- |
params.push(df.to_rfc3339());
|
|
728 |
+ |
params.push(format_datetime(df));
|
| 721 |
729 |
|
param_idx += 1;
|
| 722 |
730 |
|
}
|
| 723 |
731 |
|
if let Some(dt) = &query.date_to {
|
| 724 |
732 |
|
sql.push_str(&format!(
|
| 725 |
|
- |
" AND datetime(ev.start_time) <= datetime(${})",
|
|
733 |
+ |
" AND ev.start_time <= ${}",
|
| 726 |
734 |
|
param_idx
|
| 727 |
735 |
|
));
|
| 728 |
|
- |
params.push(dt.to_rfc3339());
|
|
736 |
+ |
params.push(format_datetime(dt));
|
| 729 |
737 |
|
}
|
| 730 |
738 |
|
|
| 731 |
739 |
|
sql.push_str(&format!(" ORDER BY rank LIMIT {}", per_type_limit));
|