[PATCH 1.5/8] Query parser testing framework and basic tests.
Austin Clements
amdragon at MIT.EDU
Thu Jan 20 22:37:14 PST 2011
The query parser test is implemented as a separate binary that calls
directly in to the lexer, parser, and generator to make it easy to
isolate test failures.
---
Sorry for the patch ordering. This is intended to be applied after
patch 1/8 in this series,
id:1295165458-9573-2-git-send-email-amdragon at mit.edu
test/Makefile.local | 7 +-
test/basic | 2 +-
test/notmuch-test | 2 +-
test/qparser | 32 ++++++
test/qparser-test.cc | 153 +++++++++++++++++++++++++++
test/qparser.expected-output/operators | 145 +++++++++++++++++++++++++
test/qparser.expected-output/prefixes | 33 ++++++
test/qparser.expected-output/probs | 46 ++++++++
test/qparser.expected-output/quoted-phrases | 29 +++++
test/qparser.expected-output/terms | 136 ++++++++++++++++++++++++
10 files changed, 581 insertions(+), 4 deletions(-)
create mode 100755 test/qparser
create mode 100644 test/qparser-test.cc
create mode 100644 test/qparser.expected-output/operators
create mode 100644 test/qparser.expected-output/prefixes
create mode 100644 test/qparser.expected-output/probs
create mode 100644 test/qparser.expected-output/quoted-phrases
create mode 100644 test/qparser.expected-output/terms
diff --git a/test/Makefile.local b/test/Makefile.local
index 7b602bc..302482b 100644
--- a/test/Makefile.local
+++ b/test/Makefile.local
@@ -5,10 +5,13 @@ dir := test
$(dir)/smtp-dummy: $(dir)/smtp-dummy.c
$(call quiet,CC) $^ -o $@
+$(dir)/qparser-test: $(dir)/qparser-test.o notmuch-config.o query-string.o lib/libnotmuch.a
+ $(call quiet,CXX $(CXXFLAGS)) $^ $(FINAL_LIBNOTMUCH_LDFLAGS) -o $@
+
.PHONY: test check
-test: all $(dir)/smtp-dummy
+test: all $(dir)/smtp-dummy $(dir)/qparser-test
@${dir}/notmuch-test $(OPTIONS)
check: test
-CLEAN := $(CLEAN) $(dir)/smtp-dummy
+CLEAN := $(CLEAN) $(dir)/smtp-dummy $(dir)/qparser-test.o $(dir)/qparser-test
diff --git a/test/basic b/test/basic
index b4410f2..3191bcc 100755
--- a/test/basic
+++ b/test/basic
@@ -52,7 +52,7 @@ test_expect_code 2 'failure to clean up causes the test to fail' '
# Ensure that all tests are being run
test_begin_subtest 'Ensure that all available tests will be run by notmuch-test'
tests_in_suite=$(grep TESTS= ../notmuch-test | sed -e "s/TESTS=\"\(.*\)\"/\1/" | tr " " "\n" | sort)
-available=$(ls -1 ../ | grep -v -E "^(aggregate-results.sh|Makefile|Makefile.local|notmuch-test|README|test-lib.sh|test-results|tmp.*|valgrind|corpus*|emacs.expected-output|smtp-dummy|smtp-dummy.c|test-verbose|test.expected-output)" | sort)
+available=$(ls -1 ../ | grep -v -E "^(aggregate-results.sh|Makefile|Makefile.local|notmuch-test|README|test-lib.sh|test-results|tmp.*|valgrind|corpus*|emacs.expected-output|smtp-dummy|smtp-dummy.c|test-verbose|test.expected-output|qparser-test.*|qparser-test|qparser.expected-output)" | sort)
test_expect_equal "$tests_in_suite" "$available"
EXPECTED=../test.expected-output
diff --git a/test/notmuch-test b/test/notmuch-test
index 4889e49..1e331b3 100755
--- a/test/notmuch-test
+++ b/test/notmuch-test
@@ -16,7 +16,7 @@ fi
cd $(dirname "$0")
-TESTS="basic new search search-output json thread-naming raw reply dump-restore uuencode thread-order author-order from-guessing long-id encoding emacs maildir-sync"
+TESTS="basic new qparser search search-output json thread-naming raw reply dump-restore uuencode thread-order author-order from-guessing long-id encoding emacs maildir-sync"
# Clean up any results from a previous run
rm -r test-results >/dev/null 2>/dev/null
diff --git a/test/qparser b/test/qparser
new file mode 100755
index 0000000..0e7b022
--- /dev/null
+++ b/test/qparser
@@ -0,0 +1,32 @@
+#!/bin/bash
+test_description="query parser"
+. ./test-lib.sh
+
+EXPECTED=../qparser.expected-output
+
+test_begin_subtest "Quoted phrases"
+output=$(../qparser-test < $EXPECTED/quoted-phrases)
+expected=$(cat $EXPECTED/quoted-phrases)
+test_expect_equal "$output" "$expected"
+
+test_begin_subtest "Prefixes"
+output=$(../qparser-test < $EXPECTED/prefixes)
+expected=$(cat $EXPECTED/prefixes)
+test_expect_equal "$output" "$expected"
+
+test_begin_subtest "Terms"
+output=$(../qparser-test < $EXPECTED/terms)
+expected=$(cat $EXPECTED/terms)
+test_expect_equal "$output" "$expected"
+
+test_begin_subtest "Operators"
+output=$(../qparser-test < $EXPECTED/operators)
+expected=$(cat $EXPECTED/operators)
+test_expect_equal "$output" "$expected"
+
+test_begin_subtest "Probs"
+output=$(../qparser-test < $EXPECTED/probs)
+expected=$(cat $EXPECTED/probs)
+test_expect_equal "$output" "$expected"
+
+test_done
diff --git a/test/qparser-test.cc b/test/qparser-test.cc
new file mode 100644
index 0000000..01d6bae
--- /dev/null
+++ b/test/qparser-test.cc
@@ -0,0 +1,153 @@
+/* qparser-test - Display the lex, parse, and query tree for a query
+ *
+ * Copyright © 2011 Austin Clements
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Authors: Austin Clements <amdragon at mit.edu>
+ */
+
+/* If command-line arguments are given, they are used as the query
+ * string. Otherwise, qparser-test enters "echo mode", in which it
+ * accepts queries from stdin. In echo mode, lines beginning with '['
+ * are ignored and lines consisting of whitespace or comments are
+ * echoed back to stdout. All other lines are treated as queries and
+ * are echoed back, followed by the results of parsing the query.
+ * This allows the output of qparser-test to be fed back in as input.
+ *
+ * For each, qparser-test displays the lex list of that query, the
+ * parse tree of that query, and the generated query tree. Finally,
+ * if the generated query tree differs from that generated by Xapian's
+ * query parser, it also displays what Xapian's query parser
+ * generated.
+ */
+
+#include "../lib/notmuch-private.h"
+#include "../lib/database-private.h"
+
+extern "C" {
+/* notmuch-client.h also defines INTERNAL_ERROR */
+#undef INTERNAL_ERROR
+#include "../notmuch-client.h"
+}
+
+static _notmuch_qparser_t *qparser;
+static Xapian::QueryParser xqparser;
+
+static char *
+query_desc (void *ctx, Xapian::Query q)
+{
+ char *desc = talloc_strdup (ctx, q.get_description ().c_str ());
+ desc += strlen ("Xapian::Query(");
+ desc[strlen(desc) - 1] = 0;
+ return desc;
+}
+
+static void
+test_one (void *ctx, const char *query_str)
+{
+ void *local = talloc_new (ctx);
+ Xapian::Query q;
+ _notmuch_token_t *toks, *root;
+ char *error, *qparser_desc, *xqparser_desc;
+
+ toks = _notmuch_qparser_lex (local, qparser, query_str);
+ printf("[lex] %s\n", _notmuch_token_show_list (local, toks));
+
+ root = _notmuch_qparser_parse (local, qparser, query_str);
+ printf("[parse] %s\n", _notmuch_token_show_tree (local, root));
+
+ root = _notmuch_qparser_transform (qparser, root);
+ q = _notmuch_qparser_generate (local, qparser, root, &error);
+ if (error)
+ printf("[gen] error %s\n", error);
+ else {
+ qparser_desc = query_desc (local, q);
+ printf("[gen] %s\n", qparser_desc);
+ }
+
+ try {
+ unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |
+ Xapian::QueryParser::FLAG_PHRASE |
+ Xapian::QueryParser::FLAG_LOVEHATE |
+ Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE |
+ Xapian::QueryParser::FLAG_WILDCARD |
+ Xapian::QueryParser::FLAG_PURE_NOT);
+ q = xqparser.parse_query (query_str, flags);
+ xqparser_desc = query_desc (local, q);
+ if (strcmp (qparser_desc, xqparser_desc) != 0)
+ printf("[xapian] %s\n", xqparser_desc);
+ } catch (const Xapian::QueryParserError & e) {
+ printf("[xapian] error %s\n", e.get_msg ().c_str ());
+ }
+
+ talloc_free (local);
+}
+
+static _notmuch_qparser_t *
+create_qparser (void *ctx)
+{
+ _notmuch_qparser_t *qparser = _notmuch_qparser_create (ctx, NULL);
+ _notmuch_qparser_add_db_prefix (qparser, "prob", "P", FALSE);
+ _notmuch_qparser_add_db_prefix (qparser, "lit", "L", TRUE);
+ _notmuch_qparser_add_db_prefix (qparser, "tag", "K", TRUE);
+ return qparser;
+}
+
+static Xapian::QueryParser
+create_xapian_qparser (void)
+{
+ Xapian::QueryParser xq;
+ xq.set_default_op (Xapian::Query::OP_AND);
+ xq.add_prefix ("prob", "P");
+ xq.add_boolean_prefix ("lit", "L");
+ xq.add_boolean_prefix ("tag", "K");
+ return xq;
+}
+
+int
+main (int argc, char **argv)
+{
+ void *ctx;
+
+ ctx = talloc_new (NULL);
+
+ qparser = create_qparser (ctx);
+ xqparser = create_xapian_qparser ();
+
+ if (argc > 1) {
+ char *query_str;
+ query_str = query_string_from_args (ctx, argc - 1, argv + 1);
+ test_one (ctx, query_str);
+ } else {
+ /* Echo mode */
+ char line[512];
+ while (fgets (line, sizeof (line), stdin)) {
+ if (line[0] == '\n' || line[0] == '#') {
+ /* Comment or whitespace. Echo it */
+ printf("%s", line);
+ } else if (line[0] == '[') {
+ /* Ignore line */
+ } else {
+ /* Query */
+ if (line[strlen (line) - 1] == '\n')
+ line[strlen (line) - 1] = 0;
+ printf("%s\n", line);
+ test_one (ctx, line);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/test/qparser.expected-output/operators b/test/qparser.expected-output/operators
new file mode 100644
index 0000000..788f007
--- /dev/null
+++ b/test/qparser.expected-output/operators
@@ -0,0 +1,145 @@
+# Boolean operators
+
+x and y
+[lex] "x" AND "y"
+[parse] (AND "x" "y")
+[gen] (x:(pos=1) AND y:(pos=2))
+
+x or y
+[lex] "x" OR "y"
+[parse] (OR "x" "y")
+[gen] (x:(pos=1) OR y:(pos=2))
+
+x xor y
+[lex] "x" XOR "y"
+[parse] (XOR "x" "y")
+[gen] (x:(pos=1) XOR y:(pos=2))
+
+x and y or x and w
+[lex] "x" AND "y" OR "x" AND "w"
+[parse] (OR (AND "x" "y") (AND "x" "w"))
+[gen] ((x:(pos=1) AND y:(pos=2)) OR (x:(pos=3) AND w:(pos=4)))
+
+x and -y
+[lex] "x" AND HATE "y"
+[parse] (AND "x" (NOT "y"))
+[gen] (x:(pos=1) AND_NOT y:(pos=2))
+
+x or not y
+[lex] "x" OR NOT "y"
+[parse] (OR "x" (NOT "y"))
+[gen] (x:(pos=1) OR (<alldocuments> AND_NOT y:(pos=2)))
+
+# The following three are Xapian-incompatible because they're syntax errors.
+x and
+[lex] "x" AND
+[parse] "x"
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+and x
+[lex] AND "x"
+[parse] "x"
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+and
+[lex] AND
+[parse] <nil>
+[gen] <alldocuments>
+[xapian] error Syntax: <expression> AND <expression>
+
+# Unary NOT
+
+x not y
+[lex] "x" NOT "y"
+[parse] (AND "x" (NOT "y"))
+[gen] (x:(pos=1) AND_NOT y:(pos=2))
+
+x not y or z
+[lex] "x" NOT "y" OR "z"
+[parse] (OR (AND "x" (NOT "y")) "z")
+[gen] ((x:(pos=1) AND_NOT y:(pos=2)) OR z:(pos=3))
+
+x not y and z
+[lex] "x" NOT "y" AND "z"
+[parse] (AND (AND "x" (NOT "y")) "z")
+[gen] ((x:(pos=1) AND_NOT y:(pos=2)) AND z:(pos=3))
+
+not not x
+[lex] NOT NOT "x"
+[parse] (NOT (NOT "x"))
+[gen] (<alldocuments> AND_NOT (<alldocuments> AND_NOT x:(pos=1)))
+[xapian] error Syntax: <expression> NOT <expression>
+
+# Empty subexpressions
+# These are all Xapian-incompatible because they're syntax errors.
+
+x and ()
+[lex] "x" AND BRA KET
+[parse] "x"
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+() and x
+[lex] BRA KET AND "x"
+[parse] "x"
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+# NULL phrases
+# These are all Xapian-incompatible because they're syntax errors.
+
+and
+[lex] AND
+[parse] <nil>
+[gen] <alldocuments>
+[xapian] error Syntax: <expression> AND <expression>
+
+@
+[lex] "@"
+[parse] "@"
+[gen] <alldocuments>
+[xapian]
+
+@ AND x
+[lex] "@" AND "x"
+[parse] (AND "@" "x")
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+x AND @
+[lex] "x" AND "@"
+[parse] (AND "x" "@")
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> AND <expression>
+
+@ AND NOT x
+[lex] "@" AND NOT "x"
+[parse] (AND "@" (NOT "x"))
+[gen] (<alldocuments> AND_NOT x:(pos=1))
+[xapian] error Syntax: <expression> AND NOT <expression>
+
+x AND NOT @
+[lex] "x" AND NOT "@"
+[parse] (AND "x" (NOT "@"))
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> AND NOT <expression>
+
+NOT @
+[lex] NOT "@"
+[parse] (NOT "@")
+[gen] <alldocuments>
+[xapian] error Syntax: <expression> NOT <expression>
+
+@ OR x
+[lex] "@" OR "x"
+[parse] (OR "@" "x")
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> OR <expression>
+
+x OR @
+[lex] "x" OR "@"
+[parse] (OR "x" "@")
+[gen] x:(pos=1)
+[xapian] error Syntax: <expression> OR <expression>
diff --git a/test/qparser.expected-output/prefixes b/test/qparser.expected-output/prefixes
new file mode 100644
index 0000000..04c4f90
--- /dev/null
+++ b/test/qparser.expected-output/prefixes
@@ -0,0 +1,33 @@
+prob:x lit:y none:z
+[lex] PREFIX/prob "x" PREFIX/lit "y" "none:z"
+[parse] (AND (AND (PREFIX/prob "x") "none:z") (FILTER (PREFIX/lit 'y')))
+[gen] ((Px:(pos=1) AND (none:(pos=2) PHRASE 2 z:(pos=3))) FILTER Ly)
+
+prob:"x y" lit:"x y" none:"x y"
+[lex] PREFIX/prob "x y" PREFIX/lit "x y" "none:" "x y"
+[parse] (AND (AND (AND (PREFIX/prob "x y") "none:") "x y") (FILTER (PREFIX/lit 'x y')))
+[gen] (((Px:(pos=1) PHRASE 2 Py:(pos=2)) AND none:(pos=3) AND (x:(pos=4) PHRASE 2 y:(pos=5))) FILTER Lx y)
+
+# Incompatible; Xapian bails and re-parses everything with no flags
+prob:(x y) lit:(x y) none:(x y)
+[lex] PREFIX/prob BRA "x" "y" KET PREFIX/lit "(x" "y" KET "none:" BRA "x" "y" KET
+[parse] (AND (AND (AND (AND (PREFIX/prob (AND "x" "y")) "y") "none:") (AND "x" "y")) (FILTER (PREFIX/lit '(x')))
+[gen] ((Px:(pos=1) AND Py:(pos=2) AND y:(pos=3) AND none:(pos=4) AND x:(pos=5) AND y:(pos=6)) FILTER L(x)
+[xapian] ((prob:(pos=1) AND x:(pos=2) AND y:(pos=3) AND y:(pos=4) AND none:(pos=5) AND x:(pos=6) AND y:(pos=7)) FILTER L(x)
+
+# This is Xapian-compatible, but seems ridiculous
+lit:(x)
+[lex] PREFIX/lit "(x" KET
+[parse] (FILTER (PREFIX/lit '(x'))
+[gen] 0 * L(x
+
+# Test characters accepted after the prefix colon
+lit:#
+[lex] PREFIX/lit "#"
+[parse] (FILTER (PREFIX/lit '#'))
+[gen] 0 * L#
+
+prob:#
+[lex] "prob:#"
+[parse] "prob:#"
+[gen] prob:(pos=1)
diff --git a/test/qparser.expected-output/probs b/test/qparser.expected-output/probs
new file mode 100644
index 0000000..3c166f7
--- /dev/null
+++ b/test/qparser.expected-output/probs
@@ -0,0 +1,46 @@
+(x OR y) AND z
+[lex] BRA "x" OR "y" KET AND "z"
+[parse] (AND (OR "x" "y") "z")
+[gen] ((x:(pos=1) OR y:(pos=2)) AND z:(pos=3))
+
+# Incompatible; Xapian bails on the syntax error, we forge ahead.
+(x OR y)) AND z
+[lex] BRA "x" OR "y" KET KET AND "z"
+[parse] (AND (OR "x" "y") "z")
+[gen] ((x:(pos=1) OR y:(pos=2)) AND z:(pos=3))
+[xapian] (x:(pos=1) AND or:(pos=2) AND y:(pos=3) AND and:(pos=4) AND z:(pos=5))
+
+# Empty subexpression after prefix
+# Incompatible; Xapian treats as a syntax error.
+prob:() AND x
+[lex] PREFIX/prob BRA KET AND "x"
+[parse] "x"
+[gen] x:(pos=1)
+[xapian] (prob:(pos=1) AND and:(pos=2) AND x:(pos=3))
+
+# Subqueries with same boolean prefix
+lit:x lit:y
+[lex] PREFIX/lit "x" PREFIX/lit "y"
+[parse] (FILTER (OR (PREFIX/lit 'x') (PREFIX/lit 'y')))
+[gen] 0 * (Lx OR Ly)
+
+# Combining prob components
+x -y lit:z
+[lex] "x" HATE "y" PREFIX/lit "z"
+[parse] (AND (AND "x" (FILTER (PREFIX/lit 'z'))) (NOT "y"))
+[gen] ((x:(pos=1) FILTER Lz) AND_NOT y:(pos=2))
+
+x lit:z
+[lex] "x" PREFIX/lit "z"
+[parse] (AND "x" (FILTER (PREFIX/lit 'z')))
+[gen] (x:(pos=1) FILTER Lz)
+
+-y lit:z
+[lex] HATE "y" PREFIX/lit "z"
+[parse] (AND (FILTER (PREFIX/lit 'z')) (NOT "y"))
+[gen] (0 * Lz AND_NOT y:(pos=1))
+
+x -y
+[lex] "x" HATE "y"
+[parse] (AND "x" (NOT "y"))
+[gen] (x:(pos=1) AND_NOT y:(pos=2))
diff --git a/test/qparser.expected-output/quoted-phrases b/test/qparser.expected-output/quoted-phrases
new file mode 100644
index 0000000..e223366
--- /dev/null
+++ b/test/qparser.expected-output/quoted-phrases
@@ -0,0 +1,29 @@
+x "y z" w
+[lex] "x" "y z" "w"
+[parse] (AND (AND "x" "y z") "w")
+[gen] (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3)) AND w:(pos=4))
+
+x "y z
+[lex] "x" "y z"
+[parse] (AND "x" "y z")
+[gen] (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3)))
+
+x "" y
+[lex] "x" "" "y"
+[parse] (AND (AND "x" "") "y")
+[gen] (x:(pos=1) AND y:(pos=2))
+
+x " " y
+[lex] "x" " " "y"
+[parse] (AND (AND "x" " ") "y")
+[gen] (x:(pos=1) AND y:(pos=2))
+
+lit:" x y"
+[lex] PREFIX/lit " x y"
+[parse] (FILTER (PREFIX/lit ' x y'))
+[gen] 0 * L x y
+
+lit:"x""y"
+[lex] PREFIX/lit "x"y"
+[parse] (FILTER (PREFIX/lit 'x"y'))
+[gen] 0 * Lx"y
diff --git a/test/qparser.expected-output/terms b/test/qparser.expected-output/terms
new file mode 100644
index 0000000..9316c54
--- /dev/null
+++ b/test/qparser.expected-output/terms
@@ -0,0 +1,136 @@
+# Term lexing
+
+x y z
+[lex] "x" "y" "z"
+[parse] (AND (AND "x" "y") "z")
+[gen] (x:(pos=1) AND y:(pos=2) AND z:(pos=3))
+
+x"y z"w
+[lex] "x" "y z" "w"
+[parse] (AND (AND "x" "y z") "w")
+[gen] (x:(pos=1) AND (y:(pos=2) PHRASE 2 z:(pos=3)) AND w:(pos=4))
+
+x(y z)w
+[lex] "x" BRA "y" "z" KET "w"
+[parse] (AND (AND "x" (AND "y" "z")) "w")
+[gen] (x:(pos=1) AND y:(pos=2) AND z:(pos=3) AND w:(pos=4))
+
+# The first query below is Xapian-compatible, while the second one
+# isn't. We use much simpler term lexing rules than Xapian.
+x/y
+[lex] "x/y"
+[parse] "x/y"
+[gen] (x:(pos=1) PHRASE 2 y:(pos=2))
+
+x!y
+[lex] "x!y"
+[parse] "x!y"
+[gen] (x:(pos=1) PHRASE 2 y:(pos=2))
+[xapian] (x:(pos=1) AND y:(pos=2))
+
+# Incompatible; our simpler term parsing sees ! as a term
+x -! y
+[lex] "x" HATE "!" "y"
+[parse] (AND (AND "x" "y") (NOT "!"))
+[gen] (x:(pos=1) AND y:(pos=2))
+[xapian] (x:(pos=1) AND_NOT y:(pos=2))
+
+# Term parsing
+
+x -
+[lex] "x" HATE
+[parse] "x"
+[gen] x:(pos=1)
+
+x +
+[lex] "x" LOVE
+[parse] "x"
+[gen] x:(pos=1)
+
+(x)
+[lex] BRA "x" KET
+[parse] "x"
+[gen] x:(pos=1)
+
+# Prefixed operators get demoted to terms
+prob:AND
+[lex] PREFIX/prob AND
+[parse] (PREFIX/prob "AND")
+[gen] Pand:(pos=1)
+
+# The first query below is Xapian-compatible, but the second isn't
+# because Xapian handles hate very differently from love.
++AND
+[lex] LOVE AND
+[parse] "AND"
+[gen] and:(pos=1)
+
+-AND
+[lex] HATE AND
+[parse] (NOT "AND")
+[gen] (<alldocuments> AND_NOT and:(pos=1))
+[xapian] and:(pos=1)
+
+# Incompatible; Xapian sees this as prob:"prob:x"
+prob:prob:x
+[lex] PREFIX/prob PREFIX/prob "x"
+[parse] (PREFIX/prob "x")
+[gen] Px:(pos=1)
+[xapian] (Pprob:(pos=1) PHRASE 2 Px:(pos=2))
+
+# The rest are Xapian-incompatible because they're all considered
+# syntax errors
+(
+[lex] BRA
+[parse] <nil>
+[gen] <alldocuments>
+[xapian]
+
+()
+[lex] BRA KET
+[parse] <nil>
+[gen] <alldocuments>
+[xapian]
+
+)
+[lex] KET
+[parse] <nil>
+[gen] <alldocuments>
+[xapian]
+
+(x)) OR y
+[lex] BRA "x" KET KET OR "y"
+[parse] (OR "x" "y")
+[gen] (x:(pos=1) OR y:(pos=2))
+[xapian] (x:(pos=1) AND or:(pos=2) AND y:(pos=3))
+
+# This one's only Xapian-compatible by chance.
+(x))
+[lex] BRA "x" KET KET
+[parse] "x"
+[gen] x:(pos=1)
+
+# Term generating
+
+c++ x
+[lex] "c++" "x"
+[parse] (AND "c++" "x")
+[gen] (c++:(pos=1) AND x:(pos=2))
+
+# Incompatible; + is not a "phrase generator" in Xapian.
+c+x
+[lex] "c+x"
+[parse] "c+x"
+[gen] (c:(pos=1) PHRASE 2 x:(pos=2))
+[xapian] (c:(pos=1) AND x:(pos=2))
+
+c-x
+[lex] "c-x"
+[parse] "c-x"
+[gen] (c:(pos=1) PHRASE 2 x:(pos=2))
+
+w "x y z"
+[lex] "w" "x y z"
+[parse] (AND "w" "x y z")
+[gen] (w:(pos=1) AND (x:(pos=2) PHRASE 3 y:(pos=3) PHRASE 3 z:(pos=4)))
+
--
1.7.2.3
More information about the notmuch
mailing list