Skip to main content

aven_core/
query.rs

1use anyhow::{Result, bail};
2use chrono::{DateTime, Utc};
3
4use crate::db::Database;
5use crate::ids::{TaskId, WorkspaceId};
6use crate::recurrence::RecurrenceSeriesId;
7use crate::refs::DisplayRefContext;
8use crate::types::Task;
9
10mod database_stats;
11mod dependencies;
12mod details;
13mod doctor;
14pub mod fragments;
15mod hydration;
16mod projects;
17mod recent_actions;
18mod recurrence;
19mod search;
20mod sidebar;
21mod sorting;
22mod sync_history;
23mod tasks;
24#[cfg(test)]
25mod test_support;
26mod types;
27
28pub use database_stats::{DatabaseStats, DatabaseStatsPriorityCounts, DatabaseStatsStatusCounts};
29pub use dependencies::{TaskDependencyItem, TaskDependencySummary};
30pub(crate) use dependencies::{task_dependency_summary, task_dependency_summary_with_display_refs};
31pub use details::{TaskDetail, TaskDetailConflict};
32pub(crate) use details::{conflict_display_value, task_detail, task_detail_with_display_refs};
33pub use doctor::WorkspaceTaskCounts;
34pub(crate) use doctor::{unresolved_conflict_count, workspace_task_counts};
35pub(crate) use projects::list_project_items_in_workspace;
36pub(crate) use recent_actions::list_recent_actions_in_workspace;
37pub(crate) use recurrence::task_recurrence_summaries;
38pub use search::{
39    SearchMatchedField, TaskSearchPreviewResultSet, TaskSearchQuery, TaskSearchResult,
40};
41pub(crate) use search::{
42    search_task_items_in_workspace, search_task_occurrence_items_in_workspace,
43    search_task_preview_set_in_workspace,
44};
45pub(crate) use sidebar::sidebar_counts_for_scope_in_workspace;
46pub use sync_history::SyncHistoryStats;
47pub(crate) use sync_history::sync_history_stats;
48pub(crate) use tasks::{
49    list_task_items_in_workspace, list_task_items_with_display_refs,
50    list_task_summary_items_in_workspace,
51};
52pub use types::RecentActionTarget;
53pub use types::{
54    AttachmentMetadata, EpicRollup, MetadataFilter, ProjectListItem, RecentActionItem,
55    RecurrenceCounts, RecurrenceHistoryEntry, RecurrenceHistoryKind, RecurrenceHistoryPage,
56    RecurrenceOccurrenceLink, RecurrenceReconciliation, RecurrenceSeriesConflict,
57    RecurrenceSeriesDetail, RecurrenceSeriesLifecycleFilter, RecurrenceSeriesListItem,
58    RecurrenceSeriesListQuery, RecurrenceSeriesSummary, RecurrenceTaskGroup, SidebarCounts,
59    SortDirection, TaskAvailabilityFilter, TaskDependencyLink, TaskFilters, TaskIdFilter,
60    TaskListItem, TaskNote, TaskQueryMode, TaskRecurrenceSummary, TaskSort,
61};
62
63pub const MAX_RECURRENCE_HISTORY_LIMIT: usize = 500;
64
65fn validate_search_limit(limit: usize) -> Result<()> {
66    if limit == 0 {
67        bail!("error search-limit-invalid limit=0 min=1 hint=\"pass a limit of 1 or greater\"");
68    }
69    Ok(())
70}
71
72fn validate_recurrence_history_limit(limit: usize) -> Result<()> {
73    if !(1..=MAX_RECURRENCE_HISTORY_LIMIT).contains(&limit) {
74        bail!(
75            "error recurrence-history-limit-invalid limit={} min=1 max={} hint=\"pass a limit between 1 and {}\"",
76            limit,
77            MAX_RECURRENCE_HISTORY_LIMIT,
78            MAX_RECURRENCE_HISTORY_LIMIT
79        );
80    }
81    Ok(())
82}
83
84impl Database {
85    /// Reconciles recurrence projections before a report reads current state.
86    ///
87    /// The candidate count is bounded. Each changed series writes one atomic projection
88    /// transaction that can archive one superseded task and materialize one successor.
89    pub async fn reconcile_recurrence_reports_at(
90        &self,
91        workspace_id: &WorkspaceId,
92        at: DateTime<Utc>,
93    ) -> Result<RecurrenceReconciliation> {
94        let (workspace, candidates, incomplete) = {
95            let mut conn = self.acquire_reader().await?;
96            let workspace = crate::workspaces::workspace_for_id(&mut conn, workspace_id).await?;
97            let (candidates, incomplete) =
98                recurrence::recurrence_reconciliation_candidates(&mut conn, workspace_id, at)
99                    .await?;
100            (workspace, candidates, incomplete)
101        };
102        let mut report = RecurrenceReconciliation {
103            workspace_id: Some(workspace_id.clone()),
104            examined: candidates.len(),
105            incomplete,
106            ..RecurrenceReconciliation::default()
107        };
108        for series_id in candidates {
109            let result = self
110                .reconcile_recurrence_series(&workspace, &series_id, at)
111                .await?;
112            report.changed += usize::from(result.changed);
113            report.lifecycle_blocked += usize::from(result.lifecycle_blocked);
114        }
115        recurrence::validate_reconciliation(&report)?;
116        Ok(report)
117    }
118
119    pub async fn reconcile_recurrence_reports(
120        &self,
121        workspace_id: &WorkspaceId,
122    ) -> Result<RecurrenceReconciliation> {
123        let at = crate::ids::now_utc();
124        self.reconcile_recurrence_reports_at(workspace_id, at).await
125    }
126
127    pub async fn list_recurrence_series_view_at(
128        &self,
129        workspace_id: &WorkspaceId,
130        at: DateTime<Utc>,
131        query: RecurrenceSeriesListQuery,
132    ) -> Result<Vec<RecurrenceSeriesListItem>> {
133        self.reconcile_recurrence_reports_at(workspace_id, at)
134            .await?;
135        self.list_recurrence_series_view_from_current_projection(workspace_id, query)
136            .await
137    }
138
139    /// Reads the persisted recurrence projection as-is.
140    ///
141    /// Callers establish projection currency by reconciling the workspace for the report snapshot.
142    #[doc(hidden)]
143    pub async fn list_recurrence_series_view_from_current_projection(
144        &self,
145        workspace_id: &WorkspaceId,
146        query: RecurrenceSeriesListQuery,
147    ) -> Result<Vec<RecurrenceSeriesListItem>> {
148        let mut conn = self.acquire_reader().await?;
149        recurrence::list_recurrence_series_view(&mut conn, workspace_id, &query).await
150    }
151
152    pub async fn list_recurrence_series_view(
153        &self,
154        workspace_id: &WorkspaceId,
155        query: RecurrenceSeriesListQuery,
156    ) -> Result<Vec<RecurrenceSeriesListItem>> {
157        let at = crate::ids::now_utc();
158        self.list_recurrence_series_view_at(workspace_id, at, query)
159            .await
160    }
161
162    pub async fn list_recurrence_series_at(
163        &self,
164        workspace_id: &WorkspaceId,
165        at: DateTime<Utc>,
166    ) -> Result<Vec<RecurrenceSeriesSummary>> {
167        self.reconcile_recurrence_reports_at(workspace_id, at)
168            .await?;
169        let mut conn = self.acquire_reader().await?;
170        recurrence::list_recurrence_series(&mut conn, workspace_id, at).await
171    }
172
173    pub async fn recurrence_series_detail_at(
174        &self,
175        workspace_id: &WorkspaceId,
176        series_id: &RecurrenceSeriesId,
177        at: DateTime<Utc>,
178    ) -> Result<RecurrenceSeriesDetail> {
179        self.reconcile_recurrence_reports_at(workspace_id, at)
180            .await?;
181        self.recurrence_series_detail_at_from_current_projection(workspace_id, series_id, at)
182            .await
183    }
184
185    /// Reads the persisted recurrence projection as-is.
186    ///
187    /// Callers establish projection currency by reconciling the workspace for the report snapshot.
188    #[doc(hidden)]
189    pub async fn recurrence_series_detail_at_from_current_projection(
190        &self,
191        workspace_id: &WorkspaceId,
192        series_id: &RecurrenceSeriesId,
193        at: DateTime<Utc>,
194    ) -> Result<RecurrenceSeriesDetail> {
195        let mut conn = self.acquire_reader().await?;
196        recurrence::recurrence_series_detail(&mut conn, workspace_id, series_id, at).await
197    }
198
199    pub async fn recurrence_history_at(
200        &self,
201        workspace_id: &WorkspaceId,
202        series_id: &RecurrenceSeriesId,
203        at: DateTime<Utc>,
204        offset: usize,
205        limit: usize,
206    ) -> Result<RecurrenceHistoryPage> {
207        validate_recurrence_history_limit(limit)?;
208        self.reconcile_recurrence_reports_at(workspace_id, at)
209            .await?;
210        let mut conn = self.acquire_reader().await?;
211        recurrence::recurrence_history(&mut conn, workspace_id, series_id, at, offset, limit).await
212    }
213
214    pub async fn list_recurrence_series(
215        &self,
216        workspace_id: &WorkspaceId,
217    ) -> Result<Vec<RecurrenceSeriesSummary>> {
218        let at = crate::ids::now_utc();
219        self.list_recurrence_series_at(workspace_id, at).await
220    }
221
222    pub async fn recurrence_series_detail(
223        &self,
224        workspace_id: &WorkspaceId,
225        series_id: &RecurrenceSeriesId,
226    ) -> Result<RecurrenceSeriesDetail> {
227        let at = crate::ids::now_utc();
228        self.recurrence_series_detail_at(workspace_id, series_id, at)
229            .await
230    }
231
232    /// Reads the persisted recurrence projection as-is.
233    ///
234    /// Callers establish projection currency by reconciling the workspace for the report snapshot.
235    #[doc(hidden)]
236    pub async fn recurrence_series_detail_from_current_projection(
237        &self,
238        workspace_id: &WorkspaceId,
239        series_id: &RecurrenceSeriesId,
240    ) -> Result<RecurrenceSeriesDetail> {
241        let at = crate::ids::now_utc();
242        self.recurrence_series_detail_at_from_current_projection(workspace_id, series_id, at)
243            .await
244    }
245
246    pub async fn recurrence_history(
247        &self,
248        workspace_id: &WorkspaceId,
249        series_id: &RecurrenceSeriesId,
250        offset: usize,
251        limit: usize,
252    ) -> Result<RecurrenceHistoryPage> {
253        let at = crate::ids::now_utc();
254        self.recurrence_history_at(workspace_id, series_id, at, offset, limit)
255            .await
256    }
257
258    pub async fn list_project_items(
259        &self,
260        workspace_id: &WorkspaceId,
261    ) -> Result<Vec<ProjectListItem>> {
262        self.reconcile_recurrence_reports(workspace_id).await?;
263        self.list_project_items_from_current_projection(workspace_id)
264            .await
265    }
266
267    /// Reads the persisted recurrence projection as-is.
268    ///
269    /// Callers establish projection currency by reconciling the workspace for the report snapshot.
270    #[doc(hidden)]
271    pub async fn list_project_items_from_current_projection(
272        &self,
273        workspace_id: &WorkspaceId,
274    ) -> Result<Vec<ProjectListItem>> {
275        let mut conn = self.acquire_reader().await?;
276        list_project_items_in_workspace(&mut conn, workspace_id).await
277    }
278
279    pub async fn list_task_items(
280        &self,
281        workspace_id: &WorkspaceId,
282        filters: TaskFilters,
283        mode: TaskQueryMode,
284        sort: TaskSort,
285        direction: SortDirection,
286    ) -> Result<Vec<TaskListItem>> {
287        self.reconcile_recurrence_reports(workspace_id).await?;
288        self.list_task_items_from_current_projection(workspace_id, filters, mode, sort, direction)
289            .await
290    }
291
292    /// Reads the persisted recurrence projection as-is.
293    ///
294    /// Callers establish projection currency by reconciling the workspace for the report snapshot.
295    #[doc(hidden)]
296    pub async fn list_task_items_from_current_projection(
297        &self,
298        workspace_id: &WorkspaceId,
299        filters: TaskFilters,
300        mode: TaskQueryMode,
301        sort: TaskSort,
302        direction: SortDirection,
303    ) -> Result<Vec<TaskListItem>> {
304        let mut conn = self.acquire_reader().await?;
305        list_task_items_in_workspace(&mut conn, workspace_id, filters, mode, sort, direction).await
306    }
307
308    pub async fn list_task_summary_items(
309        &self,
310        workspace_id: &WorkspaceId,
311        filters: TaskFilters,
312        mode: TaskQueryMode,
313        sort: TaskSort,
314        direction: SortDirection,
315        limit: Option<usize>,
316    ) -> Result<Vec<TaskListItem>> {
317        self.reconcile_recurrence_reports(workspace_id).await?;
318        let mut conn = self.acquire_reader().await?;
319        list_task_summary_items_in_workspace(
320            &mut conn,
321            workspace_id,
322            filters,
323            mode,
324            sort,
325            direction,
326            limit,
327        )
328        .await
329    }
330
331    pub async fn sidebar_counts_for_scope(
332        &self,
333        workspace_id: &WorkspaceId,
334        project_key: Option<&str>,
335    ) -> Result<SidebarCounts> {
336        self.reconcile_recurrence_reports(workspace_id).await?;
337        self.sidebar_counts_for_scope_from_current_projection(workspace_id, project_key)
338            .await
339    }
340
341    /// Reads the persisted recurrence projection as-is.
342    ///
343    /// Callers establish projection currency by reconciling the workspace for the report snapshot.
344    #[doc(hidden)]
345    pub async fn sidebar_counts_for_scope_from_current_projection(
346        &self,
347        workspace_id: &WorkspaceId,
348        project_key: Option<&str>,
349    ) -> Result<SidebarCounts> {
350        let mut conn = self.acquire_reader().await?;
351        sidebar_counts_for_scope_in_workspace(&mut conn, workspace_id, project_key).await
352    }
353
354    pub async fn list_recent_actions(
355        &self,
356        workspace_id: &WorkspaceId,
357        project_scope: Option<&str>,
358    ) -> Result<Vec<RecentActionItem>> {
359        self.reconcile_recurrence_reports(workspace_id).await?;
360        self.list_recent_actions_from_current_projection(workspace_id, project_scope)
361            .await
362    }
363
364    /// Reads the persisted recurrence projection as-is.
365    ///
366    /// Callers establish projection currency by reconciling the workspace for the report snapshot.
367    #[doc(hidden)]
368    pub async fn list_recent_actions_from_current_projection(
369        &self,
370        workspace_id: &WorkspaceId,
371        project_scope: Option<&str>,
372    ) -> Result<Vec<RecentActionItem>> {
373        let mut conn = self.acquire_reader().await?;
374        list_recent_actions_in_workspace(&mut conn, workspace_id, project_scope).await
375    }
376
377    pub async fn search_task_items(
378        &self,
379        workspace_id: &WorkspaceId,
380        query: TaskSearchQuery,
381    ) -> Result<Vec<TaskSearchResult>> {
382        validate_search_limit(query.limit)?;
383        self.reconcile_recurrence_reports(workspace_id).await?;
384        let mut conn = self.acquire_reader().await?;
385        search_task_items_in_workspace(&mut conn, workspace_id, query).await
386    }
387
388    pub async fn search_task_occurrence_items(
389        &self,
390        workspace_id: &WorkspaceId,
391        query: TaskSearchQuery,
392    ) -> Result<Vec<TaskSearchResult>> {
393        validate_search_limit(query.limit)?;
394        self.reconcile_recurrence_reports(workspace_id).await?;
395        let mut conn = self.acquire_reader().await?;
396        search_task_occurrence_items_in_workspace(&mut conn, workspace_id, query).await
397    }
398
399    pub async fn search_task_preview_set(
400        &self,
401        workspace_id: &WorkspaceId,
402        query: TaskSearchQuery,
403    ) -> Result<TaskSearchPreviewResultSet> {
404        validate_search_limit(query.limit)?;
405        self.reconcile_recurrence_reports(workspace_id).await?;
406        self.search_task_preview_set_from_current_projection(workspace_id, query)
407            .await
408    }
409
410    /// Reads the persisted recurrence projection as-is.
411    ///
412    /// Callers establish projection currency by reconciling the workspace for the report snapshot.
413    #[doc(hidden)]
414    pub async fn search_task_preview_set_from_current_projection(
415        &self,
416        workspace_id: &WorkspaceId,
417        query: TaskSearchQuery,
418    ) -> Result<TaskSearchPreviewResultSet> {
419        validate_search_limit(query.limit)?;
420        let mut conn = self.acquire_reader().await?;
421        search_task_preview_set_in_workspace(&mut conn, workspace_id, query).await
422    }
423
424    pub async fn task_detail(&self, task: &Task) -> Result<TaskDetail> {
425        self.reconcile_recurrence_reports(&task.workspace_id)
426            .await?;
427        let mut conn = self.acquire_reader().await?;
428        task_detail(&mut conn, task).await
429    }
430
431    pub async fn task_detail_with_display_refs(
432        &self,
433        task: &Task,
434        display_refs: &DisplayRefContext,
435    ) -> Result<TaskDetail> {
436        self.reconcile_recurrence_reports(&task.workspace_id)
437            .await?;
438        let mut conn = self.acquire_reader().await?;
439        task_detail_with_display_refs(&mut conn, task, display_refs).await
440    }
441
442    pub async fn conflict_display_value(
443        &self,
444        workspace_id: &WorkspaceId,
445        field: &str,
446        value: &str,
447    ) -> Result<String> {
448        let mut conn = self.acquire_reader().await?;
449        conflict_display_value(&mut conn, workspace_id, field, value).await
450    }
451
452    pub async fn task_dependency_summary(
453        &self,
454        workspace_id: &WorkspaceId,
455        task_id: &TaskId,
456    ) -> Result<TaskDependencySummary> {
457        let mut conn = self.acquire_reader().await?;
458        task_dependency_summary(&mut conn, workspace_id, task_id).await
459    }
460
461    pub async fn workspace_task_counts(
462        &self,
463        workspace_id: &WorkspaceId,
464    ) -> Result<WorkspaceTaskCounts> {
465        let mut conn = self.acquire_reader().await?;
466        workspace_task_counts(&mut conn, workspace_id).await
467    }
468
469    pub async fn unresolved_conflict_count(&self) -> Result<i64> {
470        let mut conn = self.acquire_reader().await?;
471        unresolved_conflict_count(&mut conn).await
472    }
473
474    pub async fn sync_history_stats(&self) -> Result<SyncHistoryStats> {
475        let mut conn = self.acquire_reader().await?;
476        sync_history_stats(&mut conn).await
477    }
478
479    pub async fn database_stats(
480        &self,
481        workspace: &crate::workspaces::Workspace,
482    ) -> Result<DatabaseStats> {
483        let mut conn = self.acquire_reader().await?;
484        database_stats::database_stats(&mut conn, workspace).await
485    }
486}