fts-backend-solr.c revision 0685853771a658d42174ed82763acc1626222032
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2006-2015 Dovecot authors, see the included COPYING file */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen#include "lib.h"
cf1797248b02eadfd7d63aabc0b64678a4239540Timo Sirainen#include "array.h"
cf1797248b02eadfd7d63aabc0b64678a4239540Timo Sirainen#include "str.h"
2423da95ee20fd4b3c260c1389cf2952d25f099cTimo Sirainen#include "hash.h"
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen#include "strescape.h"
a8fe899601735459641edae975c0fa08be8482e2Timo Sirainen#include "unichar.h"
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include "http-url.h"
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen#include "mail-storage-private.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include "mailbox-list-private.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include "mail-search.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include "fts-api.h"
29543188462c9348f365ec29115d777ffe4769d3Timo Sirainen#include "solr-connection.h"
29543188462c9348f365ec29115d777ffe4769d3Timo Sirainen#include "fts-solr-plugin.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
fa780a18c41881036af582f7a3473d6399e9d34dTimo Sirainen#include <ctype.h>
37847ec8eaec9ad55c9df10ae109efe7b37ac573Timo Sirainen
37847ec8eaec9ad55c9df10ae109efe7b37ac573Timo Sirainen#define SOLR_CMDBUF_SIZE (1024*64)
bdb026e2dc8a7c77585ed5ba489f0056df8074d4Timo Sirainen#define SOLR_CMDBUF_FLUSH_SIZE (SOLR_CMDBUF_SIZE-128)
bdb026e2dc8a7c77585ed5ba489f0056df8074d4Timo Sirainen#define SOLR_MAX_MULTI_ROWS 100000
bdb026e2dc8a7c77585ed5ba489f0056df8074d4Timo Sirainen
bdb026e2dc8a7c77585ed5ba489f0056df8074d4Timo Sirainen/* If header is larger than this, truncate it. */
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen#define SOLR_HEADER_MAX_SIZE (1024*1024)
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen/* If SOLR_HEADER_MAX_SIZE was already reached, write still to individual
9393445a6dabd17ce62ebfc12fd73545b0065824Timo Sirainen header fields as long as they're smaller than this */
ef50336eefcb9ba99f73c6af37420eaf8857a39bTimo Sirainen#define SOLR_HEADER_LINE_MAX_TRUNC_SIZE 1024
ef50336eefcb9ba99f73c6af37420eaf8857a39bTimo Sirainen
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen#define SOLR_QUERY_MAX_MAILBOX_COUNT 10
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainenstruct solr_fts_backend {
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen struct fts_backend backend;
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen struct solr_connection *solr_conn;
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen};
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainenstruct solr_fts_field {
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen char *key;
917498e6f84969d2b93410c1e479735abe8e0ed7Timo Sirainen string_t *value;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen};
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainenstruct solr_fts_backend_update_context {
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen struct fts_backend_update_context ctx;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen struct mailbox *cur_box;
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen char box_guid[MAILBOX_GUID_HEX_LENGTH+1];
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen struct solr_connection_post *post;
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen uint32_t prev_uid;
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen string_t *cmd, *cur_value, *cur_value2;
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen string_t *cmd_expunge;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen ARRAY(struct solr_fts_field) fields;
c040ee67d0ac0fb7375bb543965bf67dcae6affaTimo Sirainen
533bfba437e4120aa29dd45bca2aa87e30ee28a2Timo Sirainen uint32_t last_indexed_uid;
533bfba437e4120aa29dd45bca2aa87e30ee28a2Timo Sirainen
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen unsigned int tokenized_input:1;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen unsigned int last_indexed_uid_set:1;
d92f33f13830ba23d814342bf3ea8db721a15bb1Timo Sirainen unsigned int body_open:1;
d92f33f13830ba23d814342bf3ea8db721a15bb1Timo Sirainen unsigned int documents_added:1;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen unsigned int expunges:1;
61e84692827b6a64912343f515c984853021483aTimo Sirainen unsigned int truncate_header:1;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen};
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainenstatic bool is_valid_xml_char(unichar_t chr)
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen{
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen /* Valid characters in XML:
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen [#x10000-#x10FFFF]
61b0637759146621cbb7edcbd0b03a71cfd66dfeTimo Sirainen
dca6d617a23e3f93af3b8df59acb46478179fe55Timo Sirainen This function gets called only for #x80 and higher */
b039dabf4c53f72454e795930e7643b6e0e625f9Timo Sirainen if (chr > 0xd7ff && chr < 0xe000)
4ba962c3e78f140facdcfb1e093c4c46de75ae24Timo Sirainen return FALSE;
4ba962c3e78f140facdcfb1e093c4c46de75ae24Timo Sirainen if (chr > 0xfffd && chr < 0x10000)
4ba962c3e78f140facdcfb1e093c4c46de75ae24Timo Sirainen return FALSE;
4ba962c3e78f140facdcfb1e093c4c46de75ae24Timo Sirainen return chr < 0x10ffff;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen}
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
b039dabf4c53f72454e795930e7643b6e0e625f9Timo Sirainenstatic unsigned int
2f30b72d49fbff0c4096125c139e4bdfef45669cTimo Sirainenxml_encode_data_max(string_t *dest, const unsigned char *data, unsigned int len,
0b25846ba794ce19536a24d4065beaf2a0bd9464Timo Sirainen unsigned int max_len)
91b203fd2132510a47a4b34252c0ae0efd688a19Timo Sirainen{
71e88fae3be360e9a93b3398e743f99a6f05d2edTimo Sirainen unichar_t chr;
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen unsigned int i;
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen i_assert(max_len > 0 || len == 0);
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen if (max_len > len)
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen max_len = len;
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen for (i = 0; i < max_len; i++) {
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen switch (data[i]) {
8854395cdd21ca521b37ce669f3acb8445792b20Timo Sirainen case '&':
58a89627905e3590381cdd5eb931b9537c4b4ea6Timo Sirainen str_append(dest, "&amp;");
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen break;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen case '<':
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen str_append(dest, "&lt;");
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen break;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen case '>':
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen str_append(dest, "&gt;");
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen break;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen case '\t':
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen case '\n':
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen case '\r':
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen /* exceptions to the following control char check */
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen str_append_c(dest, data[i]);
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen break;
bbd0a870f8639767e4e4011d2aedadac08d5c66fTimo Sirainen default:
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen if (data[i] < 32) {
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen /* SOLR doesn't like control characters.
539977f9257bd8985be5a8093658da266ae9cd19Timo Sirainen replace them with spaces. */
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen str_append_c(dest, ' ');
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen } else if (data[i] >= 0x80) {
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen /* make sure the character is valid for XML
c58906589cafc32df4c04ffbef933baadd3f2276Timo Sirainen so we don't get XML parser errors */
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen unsigned int char_len =
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen uni_utf8_get_char_n(data + i, len - i, &chr);
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen if (char_len > 0 && is_valid_xml_char(chr))
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen str_append_n(dest, data + i, char_len);
76b91bac787101e6b0075122ab6478dd98c8a884Timo Sirainen else {
76b91bac787101e6b0075122ab6478dd98c8a884Timo Sirainen str_append_n(dest, utf8_replacement_char,
5694eeb99b69dea8033ca77ad69743c6b4871370Timo Sirainen UTF8_REPLACEMENT_CHAR_LEN);
b13f738e8eb3f24dc2abf2c804f954b4d864ac6fTimo Sirainen }
b13f738e8eb3f24dc2abf2c804f954b4d864ac6fTimo Sirainen i += char_len - 1;
b13f738e8eb3f24dc2abf2c804f954b4d864ac6fTimo Sirainen } else {
37847ec8eaec9ad55c9df10ae109efe7b37ac573Timo Sirainen str_append_c(dest, data[i]);
3fe67ec75ccae1230bb9eb9f16affc48377f6441Timo Sirainen }
37847ec8eaec9ad55c9df10ae109efe7b37ac573Timo Sirainen break;
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen }
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen }
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen return i;
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen}
8ca217bf3aa23c7922d0d4aa44fcd2320416d61cMartti Rannanjärvi
8ca217bf3aa23c7922d0d4aa44fcd2320416d61cMartti Rannanjärvistatic void
8ca217bf3aa23c7922d0d4aa44fcd2320416d61cMartti Rannanjärvixml_encode_data(string_t *dest, const unsigned char *data, unsigned int len)
ff7056842f14fd3b30a2d327dfab165b9d15dd30Timo Sirainen{
ff7056842f14fd3b30a2d327dfab165b9d15dd30Timo Sirainen (void)xml_encode_data_max(dest, data, len, len);
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen}
4654cf737f538f5de032b8c9908913f121917366Timo Sirainen
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainenstatic void xml_encode(string_t *dest, const char *str)
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen{
4d84348ffcbb60de566108562c95ad64629e7a53Timo Sirainen xml_encode_data(dest, (const unsigned char *)str, strlen(str));
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen}
49c48631cfd07017d5f93d83713fffe4f13730c4Timo Sirainen
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainenstatic void solr_quote_http(string_t *dest, const char *str)
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen{
8ca217bf3aa23c7922d0d4aa44fcd2320416d61cMartti Rannanjärvi str_append(dest, "%22");
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen http_url_escape_param(dest, str);
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen str_append(dest, "%22");
9f46aa48a9982567a37bb08dd95af8bee5100c7eTimo Sirainen}
9f46aa48a9982567a37bb08dd95af8bee5100c7eTimo Sirainen
9f46aa48a9982567a37bb08dd95af8bee5100c7eTimo Sirainenstatic struct fts_backend *fts_backend_solr_alloc(void)
9f46aa48a9982567a37bb08dd95af8bee5100c7eTimo Sirainen{
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen struct solr_fts_backend *backend;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
7ff6268cc35102675d73d44d680bed13d0709f7bTimo Sirainen backend = i_new(struct solr_fts_backend, 1);
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen backend->backend = fts_backend_solr;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen return &backend->backend;
f8a67af9b7bde79c186e6b82ea200d7fcf85571bTimo Sirainen}
9f46aa48a9982567a37bb08dd95af8bee5100c7eTimo Sirainen
9f46aa48a9982567a37bb08dd95af8bee5100c7eTimo Sirainenstatic int
eb1572d7c44ebc7b0b039d085c3dbab2ef7043ddTimo Sirainenfts_backend_solr_init(struct fts_backend *_backend, const char **error_r)
ed354926406e28254b581f821bb052f38d9c14e8Timo Sirainen{
9f46aa48a9982567a37bb08dd95af8bee5100c7eTimo Sirainen struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen struct fts_solr_user *fuser = FTS_SOLR_USER_CONTEXT(_backend->ns->user);
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen if (fuser == NULL) {
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen *error_r = "Invalid fts_solr setting";
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen return -1;
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen }
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen if (fuser->set.use_libfts) {
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen /* change our flags so we get proper input */
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen _backend->flags &= ~FTS_BACKEND_FLAG_FUZZY_SEARCH;
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen _backend->flags |= FTS_BACKEND_FLAG_TOKENIZED_INPUT;
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen }
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen return solr_connection_init(fuser->set.url, fuser->set.debug,
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen &backend->solr_conn, error_r);
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen}
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainenstatic void fts_backend_solr_deinit(struct fts_backend *_backend)
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen{
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
8a0ad174adb1eb5108511b90e97f4e5f9089b0eeTimo Sirainen
8a0ad174adb1eb5108511b90e97f4e5f9089b0eeTimo Sirainen solr_connection_deinit(&backend->solr_conn);
8a0ad174adb1eb5108511b90e97f4e5f9089b0eeTimo Sirainen i_free(backend);
33525312d3f45995686aa0b538dea1cd6eb936e2Timo Sirainen}
b365bd121cdc87f63e1dd47c5085a27091118e00Timo Sirainen
b365bd121cdc87f63e1dd47c5085a27091118e00Timo Sirainenstatic int
4645cc6c911a95991d7af43b40f88e99506ea5e9Timo Sirainenget_last_uid_fallback(struct fts_backend *_backend, struct mailbox *box,
d3e5a14ea363264dcc7640ca7226249d0c27a793Timo Sirainen uint32_t *last_uid_r)
d3e5a14ea363264dcc7640ca7226249d0c27a793Timo Sirainen{
d3e5a14ea363264dcc7640ca7226249d0c27a793Timo Sirainen struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
a4e2101473cfd7ce960fc49b3ce097c3f89ec2adTimo Sirainen const struct seq_range *uidvals;
a4e2101473cfd7ce960fc49b3ce097c3f89ec2adTimo Sirainen const char *box_guid;
a4e2101473cfd7ce960fc49b3ce097c3f89ec2adTimo Sirainen unsigned int count;
a4e2101473cfd7ce960fc49b3ce097c3f89ec2adTimo Sirainen struct solr_result **results;
d3e5a14ea363264dcc7640ca7226249d0c27a793Timo Sirainen string_t *str;
a4e2101473cfd7ce960fc49b3ce097c3f89ec2adTimo Sirainen pool_t pool;
a4e2101473cfd7ce960fc49b3ce097c3f89ec2adTimo Sirainen int ret = 0;
a4e2101473cfd7ce960fc49b3ce097c3f89ec2adTimo Sirainen
a4e2101473cfd7ce960fc49b3ce097c3f89ec2adTimo Sirainen str = t_str_new(256);
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen str_append(str, "fl=uid&rows=1&sort=uid+desc&q=");
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen if (fts_mailbox_get_guid(box, &box_guid) < 0)
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen return -1;
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen str_printfa(str, "box:%s+AND+user:", box_guid);
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen if (_backend->ns->owner != NULL)
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen solr_quote_http(str, _backend->ns->owner->username);
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen else
adb6413686e52e00dded4932babcc08ff041876bTimo Sirainen str_append(str, "%22%22");
b039dabf4c53f72454e795930e7643b6e0e625f9Timo Sirainen
b039dabf4c53f72454e795930e7643b6e0e625f9Timo Sirainen pool = pool_alloconly_create("solr last uid lookup", 1024);
b039dabf4c53f72454e795930e7643b6e0e625f9Timo Sirainen if (solr_connection_select(backend->solr_conn, str_c(str),
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen pool, &results) < 0)
4366a21968093172d9b757fe6894b1ee8916434eTimo Sirainen ret = -1;
24acd68c82dc137b88bb3ba3258b9d1f7ebcb44dTimo Sirainen else if (results[0] == NULL) {
34b724d1d7e50b1ab24267a3b6fc089b1147c1abAki Tuomi /* no UIDs */
34b724d1d7e50b1ab24267a3b6fc089b1147c1abAki Tuomi *last_uid_r = 0;
ef50336eefcb9ba99f73c6af37420eaf8857a39bTimo Sirainen } else {
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen uidvals = array_get(&results[0]->uids, &count);
77b5fd56e5a06d624f3ab92198272287333114f4Timo Sirainen i_assert(count > 0);
77b5fd56e5a06d624f3ab92198272287333114f4Timo Sirainen if (count == 1 && uidvals[0].seq1 == uidvals[0].seq2) {
77b5fd56e5a06d624f3ab92198272287333114f4Timo Sirainen *last_uid_r = uidvals[0].seq1;
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen } else {
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen i_error("fts_solr: Last UID lookup returned multiple rows");
5069b6adc4acb0efb3c6e87e778b820bae9bae9bTimo Sirainen ret = -1;
5069b6adc4acb0efb3c6e87e778b820bae9bae9bTimo Sirainen }
5069b6adc4acb0efb3c6e87e778b820bae9bae9bTimo Sirainen }
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen pool_unref(&pool);
37847ec8eaec9ad55c9df10ae109efe7b37ac573Timo Sirainen return ret;
37847ec8eaec9ad55c9df10ae109efe7b37ac573Timo Sirainen}
de62ce819d59a529530da4b57be1b8d6dad13d6bTimo Sirainen
de62ce819d59a529530da4b57be1b8d6dad13d6bTimo Sirainenstatic int
37847ec8eaec9ad55c9df10ae109efe7b37ac573Timo Sirainenfts_backend_solr_get_last_uid(struct fts_backend *_backend,
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen struct mailbox *box, uint32_t *last_uid_r)
ff7056842f14fd3b30a2d327dfab165b9d15dd30Timo Sirainen{
ff7056842f14fd3b30a2d327dfab165b9d15dd30Timo Sirainen struct fts_index_header hdr;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
43d32cbe60fdaef2699d99f1ca259053e9350411Timo Sirainen if (fts_index_get_header(box, &hdr)) {
5fb3bff645380804c9db2510940c41db6b8fdb01Timo Sirainen *last_uid_r = hdr.last_indexed_uid;
ff7056842f14fd3b30a2d327dfab165b9d15dd30Timo Sirainen return 0;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen }
b5c6ce2ab8dc1a5817e8adc989d21a9f603a6673Aki Tuomi
b5c6ce2ab8dc1a5817e8adc989d21a9f603a6673Aki Tuomi /* either nothing has been indexed, or the index was corrupted.
b5c6ce2ab8dc1a5817e8adc989d21a9f603a6673Aki Tuomi do it the slow way. */
b5c6ce2ab8dc1a5817e8adc989d21a9f603a6673Aki Tuomi if (get_last_uid_fallback(_backend, box, last_uid_r) < 0)
b5c6ce2ab8dc1a5817e8adc989d21a9f603a6673Aki Tuomi return -1;
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen
fts_index_set_last_uid(box, *last_uid_r);
return 0;
}
static struct fts_backend_update_context *
fts_backend_solr_update_init(struct fts_backend *_backend)
{
struct solr_fts_backend_update_context *ctx;
ctx = i_new(struct solr_fts_backend_update_context, 1);
ctx->ctx.backend = _backend;
ctx->tokenized_input =
(_backend->flags & FTS_BACKEND_FLAG_TOKENIZED_INPUT) != 0;
i_array_init(&ctx->fields, 16);
return &ctx->ctx;
}
static void xml_encode_id(struct solr_fts_backend_update_context *ctx,
string_t *str, uint32_t uid)
{
str_printfa(str, "%u/%s", uid, ctx->box_guid);
if (ctx->ctx.backend->ns->owner != NULL) {
str_append_c(str, '/');
xml_encode(str, ctx->ctx.backend->ns->owner->username);
}
}
static void
fts_backend_solr_doc_open(struct solr_fts_backend_update_context *ctx,
uint32_t uid)
{
ctx->documents_added = TRUE;
str_printfa(ctx->cmd, "<doc>"
"<field name=\"uid\">%u</field>"
"<field name=\"box\">%s</field>",
uid, ctx->box_guid);
str_append(ctx->cmd, "<field name=\"user\">");
if (ctx->ctx.backend->ns->owner != NULL)
xml_encode(ctx->cmd, ctx->ctx.backend->ns->owner->username);
str_append(ctx->cmd, "</field>");
str_printfa(ctx->cmd, "<field name=\"id\">");
xml_encode_id(ctx, ctx->cmd, uid);
str_append(ctx->cmd, "</field>");
}
static string_t *
fts_solr_field_get(struct solr_fts_backend_update_context *ctx, const char *key)
{
const struct solr_fts_field *field;
struct solr_fts_field new_field;
/* there are only a few fields. this lookup is fast enough. */
array_foreach(&ctx->fields, field) {
if (strcasecmp(field->key, key) == 0)
return field->value;
}
memset(&new_field, 0, sizeof(new_field));
new_field.key = str_lcase(i_strdup(key));
new_field.value = str_new(default_pool, 128);
array_append(&ctx->fields, &new_field, 1);
return new_field.value;
}
static void
fts_backend_solr_doc_close(struct solr_fts_backend_update_context *ctx)
{
struct solr_fts_field *field;
if (ctx->body_open) {
ctx->body_open = FALSE;
str_append(ctx->cmd, "</field>");
}
array_foreach_modifiable(&ctx->fields, field) {
str_printfa(ctx->cmd, "<field name=\"%s\">", field->key);
xml_encode_data(ctx->cmd, str_data(field->value), str_len(field->value));
str_append(ctx->cmd, "</field>");
str_truncate(field->value, 0);
}
str_append(ctx->cmd, "</doc>");
}
static int
fts_backed_solr_build_commit(struct solr_fts_backend_update_context *ctx)
{
if (ctx->post == NULL)
return 0;
fts_backend_solr_doc_close(ctx);
str_append(ctx->cmd, "</add>");
solr_connection_post_more(ctx->post, str_data(ctx->cmd),
str_len(ctx->cmd));
str_truncate(ctx->cmd, 0);
return solr_connection_post_end(&ctx->post);
}
static void
fts_backend_solr_expunge_flush(struct solr_fts_backend_update_context *ctx)
{
struct solr_fts_backend *backend =
(struct solr_fts_backend *)ctx->ctx.backend;
str_append(ctx->cmd_expunge, "</delete>");
(void)solr_connection_post(backend->solr_conn, str_c(ctx->cmd_expunge));
str_truncate(ctx->cmd_expunge, 0);
str_append(ctx->cmd_expunge, "<delete>");
}
static int
fts_backend_solr_update_deinit(struct fts_backend_update_context *_ctx)
{
struct solr_fts_backend_update_context *ctx =
(struct solr_fts_backend_update_context *)_ctx;
struct solr_fts_backend *backend =
(struct solr_fts_backend *)_ctx->backend;
struct solr_fts_field *field;
const char *str;
int ret = _ctx->failed ? -1 : 0;
if (fts_backed_solr_build_commit(ctx) < 0)
ret = -1;
if (ctx->documents_added || ctx->expunges) {
/* commit and wait until the documents we just indexed are
visible to the following search */
if (ctx->expunges)
fts_backend_solr_expunge_flush(ctx);
str = t_strdup_printf("<commit softCommit=\"true\" waitSearcher=\"%s\"/>",
ctx->documents_added ? "true" : "false");
if (solr_connection_post(backend->solr_conn, str) < 0)
ret = -1;
}
if (ctx->cmd != NULL)
str_free(&ctx->cmd);
if (ctx->cmd_expunge != NULL)
str_free(&ctx->cmd_expunge);
array_foreach_modifiable(&ctx->fields, field) {
str_free(&field->value);
i_free(field->key);
}
array_free(&ctx->fields);
i_free(ctx);
return ret;
}
static void
fts_backend_solr_update_set_mailbox(struct fts_backend_update_context *_ctx,
struct mailbox *box)
{
struct solr_fts_backend_update_context *ctx =
(struct solr_fts_backend_update_context *)_ctx;
const char *box_guid;
if (ctx->prev_uid != 0) {
/* flush solr between mailboxes, so we don't wrongly update
last_uid before we know it has succeeded */
if (fts_backed_solr_build_commit(ctx) < 0)
_ctx->failed = TRUE;
else if (!_ctx->failed)
fts_index_set_last_uid(ctx->cur_box, ctx->prev_uid);
ctx->prev_uid = 0;
}
if (box != NULL) {
if (fts_mailbox_get_guid(box, &box_guid) < 0)
_ctx->failed = TRUE;
i_assert(strlen(box_guid) == sizeof(ctx->box_guid)-1);
memcpy(ctx->box_guid, box_guid, sizeof(ctx->box_guid)-1);
} else {
memset(ctx->box_guid, 0, sizeof(ctx->box_guid));
}
ctx->cur_box = box;
}
static void
fts_backend_solr_update_expunge(struct fts_backend_update_context *_ctx,
uint32_t uid)
{
struct solr_fts_backend_update_context *ctx =
(struct solr_fts_backend_update_context *)_ctx;
struct fts_index_header hdr;
if (!ctx->last_indexed_uid_set) {
if (!fts_index_get_header(ctx->cur_box, &hdr))
ctx->last_indexed_uid = 0;
else
ctx->last_indexed_uid = hdr.last_indexed_uid;
ctx->last_indexed_uid_set = TRUE;
}
if (ctx->last_indexed_uid == 0 ||
uid > ctx->last_indexed_uid + 100) {
/* don't waste time asking Solr to expunge a message that is
highly unlikely to be indexed at this time. */
return;
}
if (!ctx->expunges) {
ctx->expunges = TRUE;
ctx->cmd_expunge = str_new(default_pool, 1024);
str_append(ctx->cmd_expunge, "<delete>");
}
if (str_len(ctx->cmd_expunge) >= SOLR_CMDBUF_FLUSH_SIZE)
fts_backend_solr_expunge_flush(ctx);
str_append(ctx->cmd_expunge, "<id>");
xml_encode_id(ctx, ctx->cmd_expunge, uid);
str_append(ctx->cmd_expunge, "</id>");
}
static void
fts_backend_solr_uid_changed(struct solr_fts_backend_update_context *ctx,
uint32_t uid)
{
struct solr_fts_backend *backend =
(struct solr_fts_backend *)ctx->ctx.backend;
if (ctx->post == NULL) {
i_assert(ctx->prev_uid == 0);
if (ctx->cmd == NULL)
ctx->cmd = str_new(default_pool, SOLR_CMDBUF_SIZE);
ctx->post = solr_connection_post_begin(backend->solr_conn);
str_append(ctx->cmd, "<add>");
} else {
fts_backend_solr_doc_close(ctx);
}
ctx->prev_uid = uid;
ctx->truncate_header = FALSE;
fts_backend_solr_doc_open(ctx, uid);
}
static bool
fts_backend_solr_update_set_build_key(struct fts_backend_update_context *_ctx,
const struct fts_backend_build_key *key)
{
struct solr_fts_backend_update_context *ctx =
(struct solr_fts_backend_update_context *)_ctx;
if (key->uid != ctx->prev_uid)
fts_backend_solr_uid_changed(ctx, key->uid);
switch (key->type) {
case FTS_BACKEND_BUILD_KEY_HDR:
if (fts_header_want_indexed(key->hdr_name)) {
ctx->cur_value2 =
fts_solr_field_get(ctx, key->hdr_name);
}
/* fall through */
case FTS_BACKEND_BUILD_KEY_MIME_HDR:
ctx->cur_value = fts_solr_field_get(ctx, "hdr");
xml_encode(ctx->cur_value, key->hdr_name);
str_append(ctx->cur_value, ": ");
break;
case FTS_BACKEND_BUILD_KEY_BODY_PART:
if (!ctx->body_open) {
ctx->body_open = TRUE;
str_append(ctx->cmd, "<field name=\"body\">");
}
ctx->cur_value = ctx->cmd;
break;
case FTS_BACKEND_BUILD_KEY_BODY_PART_BINARY:
i_unreached();
}
return TRUE;
}
static void
fts_backend_solr_update_unset_build_key(struct fts_backend_update_context *_ctx)
{
struct solr_fts_backend_update_context *ctx =
(struct solr_fts_backend_update_context *)_ctx;
/* There can be multiple duplicate keys (duplicate header lines,
multiple MIME body parts). Make sure they are separated by
whitespace. */
str_append_c(ctx->cur_value, '\n');
ctx->cur_value = NULL;
if (ctx->cur_value2 != NULL) {
str_append_c(ctx->cur_value2, '\n');
ctx->cur_value2 = NULL;
}
}
static int
fts_backend_solr_update_build_more(struct fts_backend_update_context *_ctx,
const unsigned char *data, size_t size)
{
struct solr_fts_backend_update_context *ctx =
(struct solr_fts_backend_update_context *)_ctx;
unsigned int len;
if (_ctx->failed)
return -1;
if (ctx->cur_value2 == NULL && ctx->cur_value == ctx->cmd) {
/* we're writing to message body. if size is huge,
flush it once in a while */
while (size >= SOLR_CMDBUF_FLUSH_SIZE) {
if (str_len(ctx->cmd) >= SOLR_CMDBUF_FLUSH_SIZE) {
solr_connection_post_more(ctx->post,
str_data(ctx->cmd),
str_len(ctx->cmd));
str_truncate(ctx->cmd, 0);
}
len = xml_encode_data_max(ctx->cmd, data, size,
SOLR_CMDBUF_FLUSH_SIZE -
str_len(ctx->cmd));
i_assert(len > 0);
i_assert(len <= size);
data += len;
size -= len;
}
xml_encode_data(ctx->cmd, data, size);
if (ctx->tokenized_input)
str_append_c(ctx->cmd, ' ');
} else {
if (!ctx->truncate_header) {
xml_encode_data(ctx->cur_value, data, size);
if (ctx->tokenized_input)
str_append_c(ctx->cur_value, ' ');
}
if (ctx->cur_value2 != NULL &&
(!ctx->truncate_header ||
str_len(ctx->cur_value2) < SOLR_HEADER_LINE_MAX_TRUNC_SIZE)) {
xml_encode_data(ctx->cur_value2, data, size);
if (ctx->tokenized_input)
str_append_c(ctx->cur_value2, ' ');
}
}
if (str_len(ctx->cmd) >= SOLR_CMDBUF_FLUSH_SIZE) {
solr_connection_post_more(ctx->post, str_data(ctx->cmd),
str_len(ctx->cmd));
str_truncate(ctx->cmd, 0);
}
if (!ctx->truncate_header &&
str_len(ctx->cur_value) >= SOLR_HEADER_MAX_SIZE) {
/* a large header */
i_assert(ctx->cur_value != ctx->cmd);
i_warning("fts-solr(%s): Mailbox %s UID=%u header size is huge, truncating",
ctx->cur_box->storage->user->username,
mailbox_get_vname(ctx->cur_box), ctx->prev_uid);
ctx->truncate_header = TRUE;
}
return 0;
}
static int fts_backend_solr_refresh(struct fts_backend *backend ATTR_UNUSED)
{
return 0;
}
static int fts_backend_solr_rescan(struct fts_backend *backend)
{
/* FIXME: proper rescan needed. for now we'll just reset the
last-uids */
return fts_backend_reset_last_uids(backend);
}
static int fts_backend_solr_optimize(struct fts_backend *backend ATTR_UNUSED)
{
return 0;
}
static bool solr_need_escaping(const char *str)
{
const char *solr_escape_chars = "+-&|!(){}[]^\"~*?:\\ ";
for (; *str != '\0'; str++) {
if (strchr(solr_escape_chars, *str) != NULL)
return TRUE;
}
return FALSE;
}
static void solr_add_str_arg(string_t *str, struct mail_search_arg *arg)
{
/* currently we'll just disable fuzzy searching if there are any
parameters that need escaping. solr doesn't seem to give good
fuzzy results even if we did escape them.. */
if (!arg->fuzzy || solr_need_escaping(arg->value.str))
solr_quote_http(str, arg->value.str);
else {
str_append(str, arg->value.str);
str_append_c(str, '~');
}
}
static bool
solr_add_definite_query(string_t *str, struct mail_search_arg *arg)
{
switch (arg->type) {
case SEARCH_TEXT: {
if (arg->match_not)
str_append_c(str, '-');
str_append(str, "(hdr:");
solr_add_str_arg(str, arg);
str_append(str, "+OR+body:");
solr_add_str_arg(str, arg);
str_append(str, ")");
break;
}
case SEARCH_BODY:
if (arg->match_not)
str_append_c(str, '-');
str_append(str, "body:");
solr_add_str_arg(str, arg);
break;
case SEARCH_HEADER:
case SEARCH_HEADER_ADDRESS:
case SEARCH_HEADER_COMPRESS_LWSP:
if (!fts_header_want_indexed(arg->hdr_field_name))
return FALSE;
if (arg->match_not)
str_append_c(str, '-');
str_append(str, t_str_lcase(arg->hdr_field_name));
str_append_c(str, ':');
solr_add_str_arg(str, arg);
break;
default:
return FALSE;
}
return TRUE;
}
static bool
solr_add_definite_query_args(string_t *str, struct mail_search_arg *arg,
bool and_args)
{
unsigned int last_len;
last_len = str_len(str);
for (; arg != NULL; arg = arg->next) {
if (solr_add_definite_query(str, arg)) {
arg->match_always = TRUE;
last_len = str_len(str);
if (and_args)
str_append(str, "+AND+");
else
str_append(str, "+OR+");
}
}
if (str_len(str) == last_len)
return FALSE;
str_truncate(str, last_len);
return TRUE;
}
static bool
solr_add_maybe_query(string_t *str, struct mail_search_arg *arg)
{
switch (arg->type) {
case SEARCH_HEADER:
case SEARCH_HEADER_ADDRESS:
case SEARCH_HEADER_COMPRESS_LWSP:
if (fts_header_want_indexed(arg->hdr_field_name))
return FALSE;
if (arg->match_not) {
/* all matches would be definite, but all non-matches
would be maybies. too much trouble to optimize. */
return FALSE;
}
/* we can check if the search key exists in some header and
filter out the messages that have no chance of matching */
str_append(str, "hdr:");
if (*arg->value.str != '\0')
solr_quote_http(str, arg->value.str);
else {
/* checking potential existence of the header name */
solr_quote_http(str, t_str_lcase(arg->hdr_field_name));
}
break;
default:
return FALSE;
}
return TRUE;
}
static bool
solr_add_maybe_query_args(string_t *str, struct mail_search_arg *arg,
bool and_args)
{
unsigned int last_len;
last_len = str_len(str);
for (; arg != NULL; arg = arg->next) {
if (solr_add_maybe_query(str, arg)) {
arg->match_always = TRUE;
last_len = str_len(str);
if (and_args)
str_append(str, "+AND+");
else
str_append(str, "+OR+");
}
}
if (str_len(str) == last_len)
return FALSE;
str_truncate(str, last_len);
return TRUE;
}
static int solr_search(struct fts_backend *_backend, string_t *str,
const char *box_guid, ARRAY_TYPE(seq_range) *uids_r,
ARRAY_TYPE(fts_score_map) *scores_r)
{
struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
pool_t pool = pool_alloconly_create("fts solr search", 1024);
struct solr_result **results;
int ret;
/* use a separate filter query for selecting the mailbox. it shouldn't
affect the score and there could be some caching benefits too. */
str_printfa(str, "&fq=%%2Bbox:%s+%%2Buser:", box_guid);
if (_backend->ns->owner != NULL)
solr_quote_http(str, _backend->ns->owner->username);
else
str_append(str, "%22%22");
ret = solr_connection_select(backend->solr_conn, str_c(str),
pool, &results);
if (ret == 0 && results[0] != NULL) {
array_append_array(uids_r, &results[0]->uids);
array_append_array(scores_r, &results[0]->scores);
}
pool_unref(&pool);
return ret;
}
static int
fts_backend_solr_lookup(struct fts_backend *_backend, struct mailbox *box,
struct mail_search_arg *args,
enum fts_lookup_flags flags,
struct fts_result *result)
{
bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
struct mailbox_status status;
string_t *str;
const char *box_guid;
unsigned int prefix_len;
if (fts_mailbox_get_guid(box, &box_guid) < 0)
return -1;
mailbox_get_open_status(box, STATUS_UIDNEXT, &status);
str = t_str_new(256);
str_printfa(str, "fl=uid,score&rows=%u&sort=uid+asc&q=",
status.uidnext);
prefix_len = str_len(str);
if (solr_add_definite_query_args(str, args, and_args)) {
ARRAY_TYPE(seq_range) *uids_arr =
(flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0 ?
&result->definite_uids : &result->maybe_uids;
if (solr_search(_backend, str, box_guid,
uids_arr, &result->scores) < 0)
return -1;
}
str_truncate(str, prefix_len);
if (solr_add_maybe_query_args(str, args, and_args)) {
if (solr_search(_backend, str, box_guid,
&result->maybe_uids, &result->scores) < 0)
return -1;
}
result->scores_sorted = TRUE;
return 0;
}
static int
solr_search_multi(struct fts_backend *_backend, string_t *str,
struct mailbox *const boxes[], enum fts_lookup_flags flags,
struct fts_multi_result *result)
{
struct solr_fts_backend *backend = (struct solr_fts_backend *)_backend;
struct solr_result **solr_results;
struct fts_result *fts_result;
ARRAY(struct fts_result) fts_results;
HASH_TABLE(char *, struct mailbox *) mailboxes;
struct mailbox *box;
const char *box_guid;
unsigned int i, len;
bool search_all_mailboxes;
/* use a separate filter query for selecting the mailbox. it shouldn't
affect the score and there could be some caching benefits too. */
str_append(str, "&fq=%2Buser:");
if (_backend->ns->owner != NULL)
solr_quote_http(str, _backend->ns->owner->username);
else
str_append(str, "%22%22");
hash_table_create(&mailboxes, default_pool, 0, str_hash, strcmp);
for (i = 0; boxes[i] != NULL; i++) ;
search_all_mailboxes = i > SOLR_QUERY_MAX_MAILBOX_COUNT;
if (!search_all_mailboxes)
str_append(str, "%2B(");
len = str_len(str);
for (i = 0; boxes[i] != NULL; i++) {
if (fts_mailbox_get_guid(boxes[i], &box_guid) < 0)
continue;
if (!search_all_mailboxes) {
if (str_len(str) != len)
str_append(str, "+OR+");
str_printfa(str, "box:%s", box_guid);
}
hash_table_insert(mailboxes, t_strdup_noconst(box_guid),
boxes[i]);
}
if (!search_all_mailboxes)
str_append_c(str, ')');
if (solr_connection_select(backend->solr_conn, str_c(str),
result->pool, &solr_results) < 0) {
hash_table_destroy(&mailboxes);
return -1;
}
p_array_init(&fts_results, result->pool, 32);
for (i = 0; solr_results[i] != NULL; i++) {
box = hash_table_lookup(mailboxes, solr_results[i]->box_id);
if (box == NULL) {
if (!search_all_mailboxes) {
i_warning("fts_solr: Lookup returned unexpected mailbox "
"with guid=%s", solr_results[i]->box_id);
}
continue;
}
fts_result = array_append_space(&fts_results);
fts_result->box = box;
if ((flags & FTS_LOOKUP_FLAG_NO_AUTO_FUZZY) == 0)
fts_result->definite_uids = solr_results[i]->uids;
else
fts_result->maybe_uids = solr_results[i]->uids;
fts_result->scores = solr_results[i]->scores;
fts_result->scores_sorted = TRUE;
}
array_append_zero(&fts_results);
result->box_results = array_idx_modifiable(&fts_results, 0);
hash_table_destroy(&mailboxes);
return 0;
}
static int
fts_backend_solr_lookup_multi(struct fts_backend *backend,
struct mailbox *const boxes[],
struct mail_search_arg *args,
enum fts_lookup_flags flags,
struct fts_multi_result *result)
{
bool and_args = (flags & FTS_LOOKUP_FLAG_AND_ARGS) != 0;
string_t *str;
str = t_str_new(256);
str_printfa(str, "fl=box,uid,score&rows=%u&sort=box+asc,uid+asc&q=",
SOLR_MAX_MULTI_ROWS);
if (solr_add_definite_query_args(str, args, and_args)) {
if (solr_search_multi(backend, str, boxes, flags, result) < 0)
return -1;
}
/* FIXME: maybe_uids could be handled also with some more work.. */
return 0;
}
struct fts_backend fts_backend_solr = {
.name = "solr",
.flags = FTS_BACKEND_FLAG_FUZZY_SEARCH,
{
fts_backend_solr_alloc,
fts_backend_solr_init,
fts_backend_solr_deinit,
fts_backend_solr_get_last_uid,
fts_backend_solr_update_init,
fts_backend_solr_update_deinit,
fts_backend_solr_update_set_mailbox,
fts_backend_solr_update_expunge,
fts_backend_solr_update_set_build_key,
fts_backend_solr_update_unset_build_key,
fts_backend_solr_update_build_more,
fts_backend_solr_refresh,
fts_backend_solr_rescan,
fts_backend_solr_optimize,
fts_backend_default_can_lookup,
fts_backend_solr_lookup,
fts_backend_solr_lookup_multi,
NULL
}
};