Geary.ImapDb.SearchQuery: Use expression to generate FTS5 queries

Move SQL generation for FTS search from ImapDb.Account to SearchQuery.
Convert to use Geary.SearchQuery.Term instances to generate SQL, rather
than parsing the expression. Simplify the generated SQL substantially
and generate MATCH values that work with SQLite FTS5.
This commit is contained in:
Michael Gratton 2020-11-04 19:36:39 +11:00 committed by Michael James Gratton
parent e0396c322e
commit 6a614adf73
6 changed files with 482 additions and 909 deletions

View file

@ -0,0 +1,199 @@
/*
* Copyright © 2020 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Geary.ImapDB.SearchQueryTest : TestCase {
private GLib.File? tmp_dir = null;
private Geary.AccountInformation? config = null;
private Account? account = null;
private SnowBall.Stemmer? stemmer = null;
public SearchQueryTest() {
base("Geary.ImapDB.SearchQueryTest");
add_test("email_text_terms", email_text_terms);
add_test("email_text_terms_stemmed", email_text_terms_stemmed);
add_test("email_text_terms_specific", email_text_terms_specific);
add_test("email_flag_terms", email_flag_terms);
}
public override void set_up() throws GLib.Error {
this.tmp_dir = GLib.File.new_for_path(
GLib.DirUtils.make_tmp("geary-imap-db-search-query-test-XXXXXX")
);
this.config = new Geary.AccountInformation(
"test",
ServiceProvider.OTHER,
new Mock.CredentialsMediator(),
new Geary.RFC822.MailboxAddress(null, "test@example.com")
);
this.account = new Account(
config,
this.tmp_dir,
GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql")
);
this.account.open_async.begin(
null,
this.async_completion
);
this.account.open_async.end(async_result());
this.stemmer = new SnowBall.Stemmer("english");
Db.Context.enable_sql_logging = true;
}
public override void tear_down() throws GLib.Error {
Db.Context.enable_sql_logging = false;
this.stemmer = null;
this.account.close_async.begin(
null,
this.async_completion
);
this.account.close_async.end(async_result());
this.account = null;
this.config = null;
delete_file(this.tmp_dir);
this.tmp_dir = null;
}
public void email_text_terms() throws GLib.Error {
var single_all_term = new_search_query(
{ new Geary.SearchQuery.EmailTextTerm(ALL, EXACT, "test")},
"test"
);
assert_queries(single_all_term);
var multiple_all_term = new_search_query(
{
new Geary.SearchQuery.EmailTextTerm(ALL, EXACT, "foo"),
new Geary.SearchQuery.EmailTextTerm(ALL, EXACT, "bar")
},
"foo bar"
);
assert_queries(multiple_all_term);
var all_to_term = new_search_query(
{
new Geary.SearchQuery.EmailTextTerm(ALL, EXACT, "foo"),
new Geary.SearchQuery.EmailTextTerm(TO, EXACT, "bar")
},
"foo to:bar"
);
assert_queries(all_to_term);
var all_to_all_term = new_search_query(
{
new Geary.SearchQuery.EmailTextTerm(ALL, EXACT, "foo"),
new Geary.SearchQuery.EmailTextTerm(TO, EXACT, "bar"),
new Geary.SearchQuery.EmailTextTerm(ALL, EXACT, "baz")
},
"foo to:bar baz"
);
assert_queries(all_to_all_term);
}
public void email_text_terms_stemmed() throws GLib.Error {
var single_all_term = new_search_query(
{ new Geary.SearchQuery.EmailTextTerm(ALL, CONSERVATIVE, "universal")},
"universal"
);
assert_queries(single_all_term);
var multiple_all_term = new_search_query(
{
new Geary.SearchQuery.EmailTextTerm(ALL, CONSERVATIVE, "universal"),
new Geary.SearchQuery.EmailTextTerm(ALL, EXACT, "bar")
},
"universal bar"
);
assert_queries(multiple_all_term);
var all_to_term = new_search_query(
{
new Geary.SearchQuery.EmailTextTerm(ALL, CONSERVATIVE, "universal"),
new Geary.SearchQuery.EmailTextTerm(TO, EXACT, "bar")
},
"universal to:bar"
);
assert_queries(all_to_term);
}
public void email_text_terms_specific() throws GLib.Error {
var single_term = new_search_query(
{ new Geary.SearchQuery.EmailTextTerm(SUBJECT, EXACT, "test")},
"subject:test"
);
assert_queries(single_term);
var missing_term = new_search_query(
{ new Geary.SearchQuery.EmailTextTerm(SUBJECT, EXACT, "")},
"subject:"
);
assert_queries(missing_term);
var conflicting_property = new_search_query(
{ new Geary.SearchQuery.EmailTextTerm(ALL, EXACT, "subject:")},
"subject:"
);
assert_queries(conflicting_property);
var conflicting_property_and_term = new_search_query(
{ new Geary.SearchQuery.EmailTextTerm(SUBJECT, EXACT, "subject:")},
"subject:subject:"
);
assert_queries(conflicting_property_and_term);
}
public void email_flag_terms() throws GLib.Error {
var unread = new_search_query(
{ new Geary.SearchQuery.EmailFlagTerm(Geary.EmailFlags.UNREAD)},
"is:unread"
);
assert_queries(unread);
var flagged = new_search_query(
{ new Geary.SearchQuery.EmailFlagTerm(Geary.EmailFlags.FLAGGED)},
"is:flagged"
);
assert_queries(flagged);
}
private SearchQuery new_search_query(Geary.SearchQuery.Term[] ops, string raw)
throws GLib.Error {
return new SearchQuery(
new Gee.ArrayList<Geary.SearchQuery.Term>.wrap(ops),
raw,
this.stemmer
);
}
private void assert_queries(SearchQuery query) throws GLib.Error {
var search = query.get_search_query(
this.account.db.get_primary_connection(),
null,
null,
0,
10,
null
);
search.exec(null);
var match = query.get_match_query(
this.account.db.get_primary_connection(),
""
);
match.exec(null);
}
}

View file

@ -53,6 +53,7 @@ test_engine_sources = [
'engine/imap-db/imap-db-database-test.vala',
'engine/imap-db/imap-db-email-identifier-test.vala',
'engine/imap-db/imap-db-folder-test.vala',
'engine/imap-db/imap-db-search-query-test.vala',
'engine/imap-engine/account-processor-test.vala',
'engine/imap-engine/imap-engine-generic-account-test.vala',
'engine/mime/mime-content-type-test.vala',

View file

@ -72,6 +72,7 @@ int main(string[] args) {
engine.add_suite(new Geary.ImapDB.DatabaseTest().suite);
engine.add_suite(new Geary.ImapDB.EmailIdentifierTest().suite);
engine.add_suite(new Geary.ImapDB.FolderTest().suite);
engine.add_suite(new Geary.ImapDB.SearchQueryTest().suite);
engine.add_suite(new Geary.ImapEngine.AccountProcessorTest().suite);
engine.add_suite(new Geary.ImapEngine.GenericAccountTest().suite);