Skip to content

Commit

Permalink
Extend support for NOT to include where clauses outside FTS5 match
Browse files Browse the repository at this point in the history
  • Loading branch information
bengotow committed Apr 16, 2020
1 parent fc03869 commit a976d71
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 16 deletions.
12 changes: 8 additions & 4 deletions app/src/services/search/search-query-backend-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ class StructuredSearchQueryVisitor extends SearchQueryExpressionVisitor {
return this.visitAndGetResult(root);
}

visitNot(node) {
const lhs = this.visitAndGetResult(node.e1);
const rhs = this.visitAndGetResult(node.e2);
this._result = `(${lhs} AND NOT ${rhs})`;
}

visitAnd(node) {
const lhs = this.visitAndGetResult(node.e1);
const rhs = this.visitAndGetResult(node.e2);
Expand Down Expand Up @@ -230,13 +236,11 @@ class StructuredSearchQueryVisitor extends SearchQueryExpressionVisitor {
}

visitUnread(node) {
const unread = node.status ? 1 : 0;
this._result = `(\`${this._className}\`.\`unread\` = ${unread})`;
this._result = `(\`${this._className}\`.\`unread\` ${node.status ? '>' : '='} 0)`;
}

visitStarred(node) {
const starred = node.status ? 1 : 0;
this._result = `(\`${this._className}\`.\`starred\` = ${starred})`;
this._result = `(\`${this._className}\`.\`starred\` ${node.status ? '>' : '='} 0)`;
}

visitHasAttachment(/* node */) {
Expand Down
39 changes: 27 additions & 12 deletions app/src/services/search/search-query-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,27 +315,42 @@ const parseOrQuery = (text: string) => {
};

const parseAndQuery = text => {
const [lhs, afterLhs] = parseOrQuery(text);
const [tok, afterAnd] = nextToken(afterLhs);
let [lhs, afterLhs] = parseOrQuery(text);
let [tok, afterTok] = nextToken(afterLhs);
if (tok === null) {
return [lhs, afterLhs];
}
// Ben Edit: within a search group eg (test is:unread), we assume tokens (eg is:)
// are separated by an implicit AND when one is not present. The only things that
// break us out of the AND query are a close paren or an explicit OR token.
const nextTok = tok.s.toUpperCase();
if (nextTok === 'OR' || nextTok === ')') {
if (tok.s.toUpperCase() === 'OR' || tok.s.toUpperCase() === ')') {
return [lhs, afterLhs];
} else if (nextTok === 'NOT') {
const [rhs, afterRhs] = parseAndQuery(afterAnd);
return [new NotQueryExpression(lhs, rhs), afterRhs];
} else if (nextTok === 'AND') {
const [rhs, afterRhs] = parseAndQuery(afterAnd);
return [new AndQueryExpression(lhs, rhs), afterRhs];
} else {
const [rhs, afterRhs] = parseAndQuery(afterLhs);
return [new AndQueryExpression(lhs, rhs), afterRhs];
let rhsStart = afterLhs;
if (tok.s.toUpperCase() === 'AND') {
rhsStart = afterTok;
[tok, afterTok] = nextToken(afterTok);
}
if (tok.s.toUpperCase() === 'NOT') {
rhsStart = afterTok;
const [rhs, afterRhs] = parseAndQuery(rhsStart);
return [new NotQueryExpression(lhs, rhs), afterRhs];
} else {
const [rhs, afterRhs] = parseAndQuery(rhsStart);
return [new AndQueryExpression(lhs, rhs), afterRhs];
}
}

// NOTE: There is a bug in here somewhere where an entire match-based clause NOT'd with
// a WHERE-based claused has the wrong precedence. For example:
//
// is:unread NOT hello AND in:inbox
//
// is interpreted as:
//
// is:unread (NOT hello AND in:inbox)
//
// but typically the NOT should only apply to the very next clause... tbd how to fix.
};

parseQuery = text => {
Expand Down

0 comments on commit a976d71

Please sign in to comment.