#+TITLE: MU QUERY
#+MAN_CLASS_OPTIONS: :section-id "@SECTION_ID@" :date "@MAN_DATE@"
#+include: macros.inc

* NAME

mu-query - a language for finding messages in *mu* databases.

* DESCRIPTION

The *mu* query language is the language used by *mu find* and *mu4e* to find messages
in *mu*'s Xapian database. The language is quite similar to Xapian's default
query-parser, but is an independent implementation that is customized for the
mu/mu4e use-case.

Here, we give a structured but informal overview of the query language and
provide examples. As a companion to this, we recommend the *mu info fields*
command to get an up-to-date list of the available fields and flags.

Furthermore, *mu find* provides the *--analyze* option, which shows how *mu*
interprets your query; similarly, mu4e has a command. mu4e-analyze-last-query.
See the *ANALYZING QUERIES* section for further details.

*NOTE:* if you use queries on the command-line (say, for *mu find*), you need to
quote any characters that would otherwise be interpreted by the shell, such as
`"', `*', `(' and `)'. The details are shell-specific. In case of doubt, the
*--analyze* option can be useful.

* TERMS

The basic building blocks of a query are *terms*; these are just normal words like
"banana" or "hello", or words prefixed with a field-name which makes them apply
to just that field. See *mu info fields* for all the available fields.

Some example queries:

#+begin_example
vacation
subject:capybara
maildir:/inbox
#+end_example

Terms without an explicit field-prefix, (like "vacation" above) are interpreted
as:

#+begin_example
to:vacation or subject:vacation or body:vacation or ...
#+end_example

The language is case-insensitive for terms and attempts to "flatten" diacritics,
so =angtrom= matches =Ångström=.

If terms contain whitespace, they need to be quoted.

#+begin_example
subject:"hi there"
#+end_example

This is a so-called =phrase query=, which means that we match against subjects
that contain the literal phrase "hi there". Phrase queries only work for certain
fields; they have the word *phrase* in their *mu info fields* search column.

** Quoting queries for the shell

Remember that you need to escape the quotes for a search query when using this
from the command-line; otherwise, the shell (or most shells) process the queries
and *mu* never sees them.

In this case, that means the difference between search for a subject "hi there"
versus and subject "hi" and some word "there" that can appear in any of the
combination fields for <empty> (combination fields are discussed below).

We can use the mentioned *--analyze* option to show the difference:

#+begin_example
mu find subject:"hi there" --analyze
* query:
  subject:hi there
* parsed query:
  (and (subject "hi") (_ "there"))
* parsed query (expanded):
  (and (subject "hi") (or (to "there") (cc "there") (bcc "there") (from "there") (subject "there") (body "there") (embed "there")))
* Xapian query:
  Query((Shi AND (Tthere OR Cthere OR Hthere OR Fthere OR Sthere OR Bthere OR Ethere)))
#+end_example

And with quotes escaped:

#+begin_example
mu find subject:\"hi there\" --analyze
* query:
  subject:"hi there"
* parsed query:
  (or (subject "hi there") (subject (phrase "hi there")))
* Xapian query:
  Query((Shi there OR (Shi PHRASE 2 Sthere)))
#+end_example

We won't dwell on the details of the *--analyze* output here, but hopefully this
illustrates the difference between quoted and unquoted queries.

* LOGICAL OPERATORS

We can combine terms with logical operators -- binary ones: *and*, *or*, *xor* and the
unary *not*, with the conventional rules for precedence and association. The
operators are case-insensitive.

You can also group things with *(* and *)*, so you can write:
#+begin_example
(subject:beethoven or subject:bach) and not body:elvis
#+end_example

If you do not explicitly specify an operator between terms, *and* is implied, so
the queries
#+begin_example
subject:chip subject:dale
#+end_example
#+begin_example
subject:chip AND subject:dale
#+end_example
are equivalent. For readability, we recommend the second version.

Note that a =pure not= - e.g. searching for *not apples* is quite a "heavy" query.

*  WILDCARDS

Wildcards are a Xapian built-in mechanism for matching.

A search term with a rightmost *** (and =only= in that position) matches any term
that starts with the part before the ***; they are less powerful than regular
expressions, but also much faster:

An example:
#+begin_example
$ mu find "hello*"
#+end_example

Quoting the "hello*" is recommended; some shells (but not all) would otherwise
expand the `*' to all files in the current directory.

* REGULAR EXPRESSIONS

The query language supports matching basic PCRE regular expressions, as per
{{{man-link(pcre,3)}}}, with some limitations.

Regular expressions are enclosed in *//*. For example:

#+begin_example
subject:/h.llo/		# match hallo, hello, ...
#+end_example

Note the difference between "maildir:/foo" and "maildir:/foo/"; the former
matches messages in the "/foo" maildir, while the latter matches all messages in
all maildirs that match "foo", such as "/foo", "/bar/cuux/foo", "/fooishbar",
and so on.

Regular expressions are more powerful than wildcards, but are also much slower.
Moreover, their behavior in *mu* can be a bit confusing, due to some
implementation details. See below for some of the caveats.

** Whitespace in regular expression literals

To avoid ambiguities in the query parsing, regular express *must not* contain
whitespace, so the search for a message with subject "hello world", you can write
#+begin_example
mu find 'subject:/hello\\040world/'
#+end_example
(with the \\040 specifying a space in the regular expression, and and extra `\\'
to escape it). In many cases,
#+begin_example
mu find 'subject:/hello.world/'
#+end_example
may be good enough, and easier to type.

** Anchors in regular expressions

Since the underlying Xapian database does not support regular expressions (it
does support wildcards), *mu* implements the regular-expression search by matching
the user's regular expression against all "terms" (words or phrases) that in the
database for a given field.

That implementation detail explain why "anchored" regular expressions (with *^*
and *$* to mark begin/end, respectively) can get unexpected results.

Suppose you want to match all messages that start with "pie", and you search
with *subject:/^pie/*. This /also/ matches messages with subject "apple pie", since
both those words are indexed as terms separately (as well as phrases), and thus
"^pie" matches as well for a message with subject "apple pie".

* FIELDS

We already saw a number of search fields, such as *subject:* and *body:*. For the
full table with all details, including single-char shortcuts, try the command:
*mu info fields*.

#+ATTR_MAN: :disable-caption t
#+begin_example
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| field-name | alias     | short | search  | value | sexp | example query                 | description                      |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| bcc        |           | h     | phrase  | yes   | yes  | bcc:foo@example.com           | Blind carbon-copy recipient      |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| body       |           | b     | phrase  | no    | no   | body:capybara                 | Message plain-text body          |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| cc         |           | c     | phrase  | yes   | yes  | cc:quinn@example.com          | Carbon-copy recipient            |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| changed    |           | k     | range   | yes   | yes  | changed:30M..                 | Last change time                 |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| date       |           | d     | range   | yes   | yes  | date:20220101..20220505       | Message date                     |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| embed      |           | e     | phrase  | no    | no   | embed:war OR embed:peace      | Embedded text                    |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| file       |           | j     | boolean | no    | no   | file:/image\.*.jpg/           | Attachment file name             |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| flags      | flag      | g     | boolean | yes   | yes  | flag:unread AND flag:personal | Message properties               |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| from       |           | f     | phrase  | yes   | yes  | from:jimbo                    | Message sender                   |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| language   | lang      | a     | boolean | yes   | yes  | lang:nl                       | ISO 639-1 language code for body |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| maildir    |           | m     | boolean | yes   | yes  | maildir:/private/archive      | Maildir path for message         |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| list       |           | v     | boolean | yes   | yes  | list:mu-discuss.example.com   | Mailing list (List-Id:)          |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| message-id | msgid     | i     | boolean | yes   | yes  | msgid:abc@123                 | Message-Id                       |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| mime       | mime-type | y     | boolean | no    | no   | mime:image/jpeg               | Attachment MIME-type             |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| path       |           | l     | boolean | yes   | yes  | path:/a/b/Maildir/cur/msg:2,S | File system path to message      |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| priority   | prio      | p     | boolean | yes   | yes  | prio:high                     | Priority                         |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| references | ref       | r     | boolean | yes   | yes  |                               | References to related messages   |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| size       |           | z     | range   | yes   | yes  | size:1M..5M                   | Message size in bytes            |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| subject    |           | s     | phrase  | yes   | yes  | subject:wombat                | Message subject                  |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| tags       | tag       | x     | boolean | yes   | yes  | tag:projectx                  | Message tags                     |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| thread     |           | w     | boolean | yes   | no   |                               | Thread a message belongs to      |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
| to         |           | t     | phrase  | yes   | yes  | to:flimflam@example.com       | Message recipient                |
+------------+-----------+-------+---------+-------+------+-------------------------------+----------------------------------+
#+end_example

There are also *combination fields* which allow you to search for multiple related
fields at once:

#+ATTR_MAN: :disable-caption t
#+begin_example
# Combination fields
+-------------+-----------------------------------------+
| combi-field | fields                                  |
+-------------+-----------------------------------------+
| recip       | to, cc, bcc                             |
+-------------+-----------------------------------------+
| contact     | to, cc, bcc, from                       |
+-------------+-----------------------------------------+
| related     | message-id, references                  |
+-------------+-----------------------------------------+
| <empty>     | to, cc, bcc, from, subject, body, embed |
+-------------+-----------------------------------------+
#+end_example

Hence, for instance,
#+begin_example
contact:fnorb@example.com
#+end_example
is equivalent to
#+begin_example
(from:fnorb@example.com or to:fnorb@example.com or
      cc:from:fnorb@example.com or bcc:fnorb@example.com)
#+end_example

* DATE RANGES

The *date:* field takes a date-range, expressed as the lower and upper bound,
separated by *..*. Either lower or upper (but not both) can be omitted to create
an open range.

Dates are expressed in local time and using ISO-8601 format (YYYY-MM-DD
HH:MM:SS); you can leave out the right part and *mu* adds the rest, depending on
whether this is the beginning or end of the range (e.g., as a lower bound,
"2015" would be interpreted as the start of that year; as an upper bound as the
end of the year).

You can use `/' , `.', `-', `:' and "T" to make dates more human-readable.

Some examples:
#+begin_example
date:20170505..20170602
date:2017-05-05..2017-06-02
date:..2017-10-01T12:00
date:2015-06-01..
date:2016..2016
#+end_example

You can also use the special "dates" *now* and *today*:
#+begin_example
date:20170505..now
date:today..
#+end_example

Finally, you can use relative "ago" times which express some time before now and
consist of a number followed by a unit, with units *s* for seconds, *M* for minutes,
*h* for hours, *d* for days, *w* for week, *m* for months and *y* for years. Some
examples:

#+begin_example
date:3m..
date:2017.01.01..5w
#+end_example

* SIZE RANGES

The *size* or *z* field allows you to match =size ranges= -- that is, match messages
that have a byte-size within a certain range. Units (b (for bytes), K (for 1000
bytes) and M (for 1000 * 1000 bytes) are supported). Some examples:

#+begin_example
size:10k..2m
size:10m..
#+end_example

* FLAG FIELD

The *flag/g* field allows you to match message flags. The following fields are
available:
#+begin_example
+-----------+----------+----------+-----------------------------+
| flag      | shortcut | category | description                 |
+-----------+----------+----------+-----------------------------+
| draft     | D        | file     | Draft (in progress)         |
+-----------+----------+----------+-----------------------------+
| flagged   | F        | file     | User-flagged                |
+-----------+----------+----------+-----------------------------+
| passed    | P        | file     | Forwarded message           |
+-----------+----------+----------+-----------------------------+
| replied   | R        | file     | Replied-to                  |
+-----------+----------+----------+-----------------------------+
| seen      | S        | file     | Viewed at least once        |
+-----------+----------+----------+-----------------------------+
| trashed   | T        | file     | Marked for deletion         |
+-----------+----------+----------+-----------------------------+
| new       | N        | maildir  | New message                 |
+-----------+----------+----------+-----------------------------+
| signed    | z        | content  | Cryptographically signed    |
+-----------+----------+----------+-----------------------------+
| encrypted | x        | content  | Encrypted                   |
+-----------+----------+----------+-----------------------------+
| attach    | a        | content  | Has at least one attachment |
+-----------+----------+----------+-----------------------------+
| unread    | u        | pseudo   | New or not seen message     |
+-----------+----------+----------+-----------------------------+
| list      | l        | content  | Mailing list message        |
+-----------+----------+----------+-----------------------------+
| personal  | q        | content  | Personal message            |
+-----------+----------+----------+-----------------------------+
| calendar  | c        | content  | Calendar invitation         |
+-----------+----------+----------+-----------------------------+
#+end_example

Some examples:
#+begin_example
flag:attach
flag:replied
g:x
#+end_example

Encrypted messages may be signed as well, but this is only visible after
decrypting and thus invisible to *mu*.

* PRIORITY FIELD

The message priority field (*prio:*) has three possible values: *low*, *normal* or
*high*. For instance, to match high-priority messages:
#+begin_example
prio:high
#+end_example

* MAILDIR

The Maildir field describes the directory path starting *after* the Maildir root
directory, and before the =/cur/= or =/new/= part. So, for example, if there's a
message with the file name _~/Maildir/lists/running/cur/1234.213:2,_, you could
find it (and all the other messages in that same maildir) with:
#+begin_example
maildir:/lists/running
#+end_example

Note the starting `/'. If you want to match mails in the "root" maildir, you can
do with a single `/':
#+begin_example
maildir:/
#+end_example

If you have maildirs (or any fields) that include spaces, you need to quote
them, i.e.,
#+begin_example
maildir:"/Sent Items"
#+end_example

And once again, note that when using the command-line, such queries must be
quoted:
#+begin_example
mu find 'maildir:"/Sent Items"'
#+end_example

Also note that you should *not* end the maildir with a ~/~, or it can be
misinterpreted as a regular expression term; see aforementioned.

* MORE EXAMPLES

Here are some simple examples of *mu* queries; you can make many more complicated
queries using various logical operators, parentheses and so on, but in the
author's experience, it's usually faster to find a message with a simple query
just searching for some words.

Find all messages with both "bee" and "bird" (in any field)
#+begin_example
bee AND bird
#+end_example

Find all messages with either Frodo or Sam:
#+begin_example
Frodo OR Sam
#+end_example

Find all messages with the "wombat" as subject, and "capybara" anywhere:
#+begin_example
subject:wombat and capybara
#+end_example

Find all messages in the "Archive" folder from Fred:
#+begin_example
from:fred and maildir:/Archive
#+end_example

Find all unread messages with attachments:
#+begin_example
flag:attach and flag:unread
#+end_example

Find all messages with PDF-attachments:
#+begin_example
mime:application/pdf
#+end_example

Find all messages with attached images:
#+begin_example
mime:image/*
#+end_example
(and beware that on the command-line, you need to put this in quotes or it would
expand the ~*~.

Find a messages with the given message-id:
#+begin_example
msgid:CAE56pjGU2oNxN-wWku69@mail.gmail.com
#+end_example

Find all messages written in Dutch or German with the word "hallo":
#+begin_example
hallo and (lang:nl or lang:de)
#+end_example

This is only available if your *mu* has support for this; see *mu info* and check
for "cld2-support*.

* ANALZYING QUERIES

Despite all the excellent documentation, in some cases it can be non-obvious to
understand how *mu* interprets your query, especially when shell interpretation is
involved as well.

For that, you can ask *mu* to analyze the
query -- that is, show how *mu* interprets the query. We already saw an example of
this.

This uses the the *--analyze* option to *mu find*.
#+begin_example
$ mu find subject:wombat AND date:3m.. size:..2000  --analyze
,*query:
  subject:wombat AND date:3m.. size:..2000
,* parsed query:
  (and (subject "wombat") (date (range "2023-05-30T06:10:09Z" "")) (size (range "" "2000")))
,* Xapian query:
  Query((Swombat AND VALUE_GE 4 n64759341 AND VALUE_LE 17 i7d0))
#+end_example

The ~parsed query~ is usually the most useful one for understanding how *mu*
interprets your query; it shows the query as *mu* sees it, in s-expression
notation.

In *mu4e* there is the *mu4e-analyze-last-query* command, which provides similar
information.

#+include: "prefooter.inc" :minlevel 1

* SEE ALSO

{{{man-link(mu-find,1)}}},
{{{man-link(mu-info,1)}}},
{{{man-link(pcre,3)}}}
