MergeFilterIntoQuery combines a MessageFilter context with a search.Query. Context filters are appended to existing query filters. Note on semantics: Appending to FromAddrs/ToAddrs produces OR semantics within each dimension (IN clause). Labels use per-term EXISTS subqueries with AND semantics (mes
(q *search.Query, filter MessageFilter)
| 1680 | // with AND semantics (message must have all labels). Context filters widen |
| 1681 | // the search within other constraints. |
| 1682 | func MergeFilterIntoQuery(q *search.Query, filter MessageFilter) *search.Query { |
| 1683 | // Copy all fields from original query (preserves any future non-slice fields) |
| 1684 | merged := *q |
| 1685 | |
| 1686 | // Deep copy slices to avoid mutating original (shallow copy + append can |
| 1687 | // mutate if original slice has spare capacity) |
| 1688 | merged.TextTerms = append([]string(nil), q.TextTerms...) |
| 1689 | merged.FromAddrs = append([]string(nil), q.FromAddrs...) |
| 1690 | merged.ToAddrs = append([]string(nil), q.ToAddrs...) |
| 1691 | merged.CcAddrs = append([]string(nil), q.CcAddrs...) |
| 1692 | merged.BccAddrs = append([]string(nil), q.BccAddrs...) |
| 1693 | merged.SubjectTerms = append([]string(nil), q.SubjectTerms...) |
| 1694 | merged.Labels = append([]string(nil), q.Labels...) |
| 1695 | // Deep-copy AccountIDs alongside the other slices so the merged |
| 1696 | // query never aliases the original's slice header. Filter overrides |
| 1697 | // below replace the deep-copied slice when set. |
| 1698 | merged.AccountIDs = append([]int64(nil), q.AccountIDs...) |
| 1699 | |
| 1700 | // Account filter - always apply if set. Multi-source SourceIDs takes |
| 1701 | // precedence over single SourceID, matching appendSourceFilter |
| 1702 | // semantics elsewhere in the package: a non-nil but empty SourceIDs |
| 1703 | // slice is "match nothing" (the caller explicitly scoped to no |
| 1704 | // sources) and must clear any AccountIDs the original query carried. |
| 1705 | // Allocate a fresh slice (not append-from-nil, which would collapse |
| 1706 | // an explicit empty back to nil and lose the match-nothing signal). |
| 1707 | if filter.SourceIDs != nil { |
| 1708 | merged.AccountIDs = make([]int64, len(filter.SourceIDs)) |
| 1709 | copy(merged.AccountIDs, filter.SourceIDs) |
| 1710 | } else if filter.SourceID != nil { |
| 1711 | merged.AccountIDs = []int64{*filter.SourceID} |
| 1712 | } |
| 1713 | |
| 1714 | // Sender filter - append to existing from: filters |
| 1715 | if filter.Sender != "" { |
| 1716 | merged.FromAddrs = append(merged.FromAddrs, filter.Sender) |
| 1717 | } |
| 1718 | |
| 1719 | // Recipient filter - append to existing to: filters |
| 1720 | if filter.Recipient != "" { |
| 1721 | merged.ToAddrs = append(merged.ToAddrs, filter.Recipient) |
| 1722 | } |
| 1723 | |
| 1724 | // Label filter - append to existing label: filters |
| 1725 | if filter.Label != "" { |
| 1726 | merged.Labels = append(merged.Labels, filter.Label) |
| 1727 | } |
| 1728 | |
| 1729 | // Attachment filter - set if context requires attachments |
| 1730 | if filter.WithAttachmentsOnly { |
| 1731 | hasAttachment := true |
| 1732 | merged.HasAttachment = &hasAttachment |
| 1733 | } |
| 1734 | |
| 1735 | // Domain filter - add as @domain pattern (handled specially in Search) |
| 1736 | if filter.Domain != "" { |
| 1737 | merged.FromAddrs = append(merged.FromAddrs, "@"+filter.Domain) |
| 1738 | } |
| 1739 |