feat(filters): saved filters with a query language, and label management
"today & p1", "#Firma & @regnskab", "overdue | (today & assigned to: me)". The grammar is Todoist's, so the import path from it does not require relearning anything, and both languages' keywords are accepted. It compiles to parameterised SQL rather than filtering in Go: one indexed query instead of ten thousand rows crossing the boundary. No typed text is ever concatenated into the statement — this is a language people write by hand, which makes it exactly where string-building would become an injection. An expression is compiled before it is saved. A filter that cannot run is worse than none: it is discovered later, as a list that is empty for a reason nobody can see. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a96aae57ec
commit
4d142b2fa7
@@ -62,6 +62,12 @@ type TaskFilter struct {
|
||||
CompletedOnly bool
|
||||
|
||||
Search string
|
||||
// FilterSQL is a compiled saved-filter expression: a WHERE fragment over `t`
|
||||
// with its own bound arguments. It is produced by the filterquery package and
|
||||
// never assembled from user text here.
|
||||
FilterSQL string
|
||||
FilterArgs []any
|
||||
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
@@ -133,6 +139,10 @@ func (db *DB) ListTasks(ctx context.Context, userID string, f TaskFilter) ([]Tas
|
||||
where = append(where, `t.rowid IN (SELECT rowid FROM tasks_fts WHERE tasks_fts MATCH ?)`)
|
||||
args = append(args, expr)
|
||||
}
|
||||
if f.FilterSQL != "" {
|
||||
where = append(where, "("+f.FilterSQL+")")
|
||||
args = append(args, f.FilterArgs...)
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT t.id, t.project_id, t.section_id, t.parent_id, t.content, t.description,
|
||||
|
||||
Reference in New Issue
Block a user