Mailspring/internal_packages/thread-search/spec/search-query-parser-spec.es6
Mark Hahnenberg 668c2935c9 [thread-search] Improve local thread search
Summary:
Previously we waited to build the local ThreadSearch index until we were
done with the initial mail sync. This is undesirable because inboxes can
be very large, making local sync useless for a number of hours after a
user adds an account. This diff removes that restriction.

This diff also adds a new rudimentary new grammar (along with accompanying lexer
and parser) for local thread search queries. This grammar is then
translated into the appropriate SQL queries on the ThreadSearch index
table. More advanced features (e.g. in:category, date ranges, etc) can be added
easily in the future by augmenting this simple search query language.

Test Plan: Run locally, new unit tests

Reviewers: juan, evan, khamidou

Reviewed By: khamidou

Differential Revision: https://phab.nylas.com/D3614
2017-01-11 14:26:42 -08:00

133 lines
4.7 KiB
JavaScript

import {
ThreadQueryAST,
} from 'nylas-exports';
import {parseSearchQuery} from '../lib/search-query-parser'
const {
SearchQueryToken,
OrQueryExpression,
AndQueryExpression,
FromQueryExpression,
ToQueryExpression,
SubjectQueryExpression,
GenericQueryExpression,
TextQueryExpression,
UnreadStatusQueryExpression,
StarredStatusQueryExpression,
} = ThreadQueryAST;
const token = (text) => { return new SearchQueryToken(text); }
const and = (e1, e2) => { return new AndQueryExpression(e1, e2); }
const or = (e1, e2) => { return new OrQueryExpression(e1, e2); }
const from = (text) => { return new FromQueryExpression(text); }
const to = (text) => { return new ToQueryExpression(text); }
const subject = (text) => { return new SubjectQueryExpression(text); }
const generic = (text) => { return new GenericQueryExpression(text); }
const text = (tok) => { return new TextQueryExpression(tok); }
const unread = (status) => { return new UnreadStatusQueryExpression(status); }
const starred = (status) => { return new StarredStatusQueryExpression(status); }
fdescribe('parseSearchQuery', () => {
it('correctly parses simple queries', () => {
expect(parseSearchQuery('blah').equals(
generic(text(token('blah')))
)).toBe(true)
expect(parseSearchQuery('"foo bar"').equals(
generic(text(token('foo bar')))
)).toBe(true)
expect(parseSearchQuery('to:blah').equals(
to(text(token('blah')))
)).toBe(true)
expect(parseSearchQuery('from:blah').equals(
from(text(token('blah')))
)).toBe(true)
expect(parseSearchQuery('subject:blah').equals(
subject(text(token('blah')))
)).toBe(true)
expect(parseSearchQuery('to:mhahnenb@gmail.com').equals(
to(text(token('mhahnenb@gmail.com')))
)).toBe(true)
expect(parseSearchQuery('to:"mhahnenb@gmail.com"').equals(
to(text(token('mhahnenb@gmail.com')))
)).toBe(true)
expect(parseSearchQuery('to:"Mark mhahnenb@gmail.com"').equals(
to(text(token('Mark mhahnenb@gmail.com')))
)).toBe(true)
expect(parseSearchQuery('is:unread').equals(
unread(true)
)).toBe(true)
expect(parseSearchQuery('is:read').equals(
unread(false)
)).toBe(true)
expect(parseSearchQuery('is:starred').equals(
starred(true)
)).toBe(true)
expect(parseSearchQuery('is:unstarred').equals(
starred(false)
)).toBe(true)
});
it('correctly parses reserved words as normal text in certain places', () => {
expect(parseSearchQuery('to:blah').equals(
to(text(token('blah')))
)).toBe(true)
expect(parseSearchQuery('to:to').equals(
to(text(token('to')))
)).toBe(true)
expect(parseSearchQuery('to:subject').equals(
to(text(token('subject')))
)).toBe(true)
expect(parseSearchQuery('to:from').equals(
to(text(token('from')))
)).toBe(true)
expect(parseSearchQuery('to:unread').equals(
to(text(token('unread')))
)).toBe(true)
expect(parseSearchQuery('to:starred').equals(
to(text(token('starred')))
)).toBe(true)
});
it('correctly parses compound queries', () => {
expect(parseSearchQuery('foo bar').equals(
and(generic(text(token('foo'))), generic(text(token('bar'))))
)).toBe(true)
expect(parseSearchQuery('foo AND bar').equals(
and(generic(text(token('foo'))), generic(text(token('bar'))))
)).toBe(true)
expect(parseSearchQuery('foo OR bar').equals(
or(generic(text(token('foo'))), generic(text(token('bar'))))
)).toBe(true)
expect(parseSearchQuery('to:foo OR bar').equals(
or(to(text(token('foo'))), generic(text(token('bar'))))
)).toBe(true)
expect(parseSearchQuery('foo OR to:bar').equals(
or(generic(text(token('foo'))), to(text(token('bar'))))
)).toBe(true)
expect(parseSearchQuery('foo bar baz').equals(
and(generic(text(token('foo'))),
and(generic(text(token('bar'))), generic(text(token('baz')))))
)).toBe(true)
expect(parseSearchQuery('foo AND bar AND baz').equals(
and(generic(text(token('foo'))),
and(generic(text(token('bar'))), generic(text(token('baz')))))
)).toBe(true)
expect(parseSearchQuery('foo OR bar AND baz').equals(
and(
or(generic(text(token('foo'))), generic(text(token('bar')))),
generic(text(token('baz'))))
)).toBe(true)
expect(parseSearchQuery('foo OR bar OR baz').equals(
or(generic(text(token('foo'))),
or(generic(text(token('bar'))), generic(text(token('baz')))))
)).toBe(true)
expect(parseSearchQuery('foo is:unread').equals(
and(generic(text(token('foo'))), unread(true)),
)).toBe(true)
expect(parseSearchQuery('is:unread foo').equals(
and(unread(true), generic(text(token('foo'))))
)).toBe(true)
});
});