Switch to side-by-side view

--- a/src/rcldb/searchdatatox.cpp
+++ b/src/rcldb/searchdatatox.cpp
@@ -47,10 +47,9 @@
 #include "expansiondbs.h"
 #include "base64.h"
 #include "daterange.h"
+#include "rclvalues.h"
 
 namespace Rcl {
-
-typedef  vector<SearchDataClause *>::iterator qlist_it_t;
 
 static const int original_term_wqf_booster = 10;
 
@@ -62,7 +61,7 @@
 {
     const RclConfig *cfg = db.getConf();
     if (!cfg) {
-	LOGFATAL("Db::expandFileTypes: null configuration!!\n" );
+	LOGFATAL("Db::expandFileTypes: null configuration!!\n");
 	return false;
     }
     vector<string> exptps;
@@ -110,15 +109,16 @@
 				string& reason, void *d)
 {
     Xapian::Query xq;
-    for (qlist_it_t it = query.begin(); it != query.end(); it++) {
+    for (auto& clausep : query) {
 	Xapian::Query nq;
-	if (!(*it)->toNativeQuery(db, &nq)) {
-	    LOGERR("SearchData::clausesToQuery: toNativeQuery failed: "  << ((*it)->getReason()) << "\n" );
-	    reason += (*it)->getReason() + " ";
+	if (!clausep->toNativeQuery(db, &nq)) {
+	    LOGERR("SearchData::clausesToQuery: toNativeQuery failed: "
+                   << clausep->getReason() << "\n");
+	    reason += clausep->getReason() + " ";
 	    return false;
 	}	    
         if (nq.empty()) {
-            LOGDEB("SearchData::clausesToQuery: skipping empty clause\n" );
+            LOGDEB("SearchData::clausesToQuery: skipping empty clause\n");
             continue;
         }
 	// If this structure is an AND list, must use AND_NOT for excl clauses.
@@ -126,7 +126,7 @@
 	// addClause())
 	Xapian::Query::op op;
 	if (tp == SCLT_AND) {
-            if ((*it)->getexclude()) {
+            if (clausep->getexclude()) {
                 op =  Xapian::Query::OP_AND_NOT;
             } else {
                 op =  Xapian::Query::OP_AND;
@@ -143,7 +143,7 @@
             xq = Xapian::Query(op, xq, nq);
         }
 	if (int(xq.get_length()) >= getMaxCl()) {
-	    LOGERR(""  << (maxXapClauseMsg) << "\n" );
+	    LOGERR("" << maxXapClauseMsg << "\n");
 	    m_reason += maxXapClauseMsg;
 	    if (!o_index_stripchars)
 		m_reason += maxXapClauseCaseDiacMsg;
@@ -151,7 +151,7 @@
 	}
     }
 
-    LOGDEB0("SearchData::clausesToQuery: got "  << (xq.get_length()) << " clauses\n" );
+    LOGDEB0("SearchData::clausesToQuery: got " << xq.get_length()<<" clauses\n");
 
     if (xq.empty())
 	xq = Xapian::Query::MatchAll;
@@ -162,7 +162,7 @@
 
 bool SearchData::toNativeQuery(Rcl::Db &db, void *d)
 {
-    LOGDEB("SearchData::toNativeQuery: stemlang ["  << (m_stemlang) << "]\n" );
+    LOGDEB("SearchData::toNativeQuery: stemlang [" << m_stemlang << "]\n");
     m_reason.erase();
 
     db.getConf()->getConfParam("maxTermExpand", &m_maxexp);
@@ -174,7 +174,8 @@
     // Xapian query tree
     Xapian::Query xq;
     if (!clausesToQuery(db, m_tp, m_query, m_reason, &xq)) {
-	LOGERR("SearchData::toNativeQuery: clausesToQuery failed. reason: "  << (m_reason) << "\n" );
+	LOGERR("SearchData::toNativeQuery: clausesToQuery failed. reason: "
+               << m_reason << "\n");
 	return false;
     }
 
@@ -183,7 +184,7 @@
         if (m_dates.y1 == 0 || m_dates.y2 == 0) {
             int minyear = 1970, maxyear = 2100;
             if (!db.maxYearSpan(&minyear, &maxyear)) {
-                LOGERR("Can't retrieve index min/max dates\n" );
+                LOGERR("Can't retrieve index min/max dates\n");
                 //whatever, go on.
             }
 
@@ -198,16 +199,18 @@
                 m_dates.d2 = 31;
             }
         }
-        LOGDEB("Db::toNativeQuery: date interval: "  << (m_dates.y1) << "-"  << (m_dates.m1) << "-"  << (m_dates.d1) << "/"  << (m_dates.y2) << "-"  << (m_dates.m2) << "-"  << (m_dates.d2) << "\n" );
+        LOGDEB("Db::toNativeQuery: date interval: " << m_dates.y1 <<
+               "-" << m_dates.m1 << "-" << m_dates.d1 << "/" <<
+               m_dates.y2 << "-" << m_dates.m2 << "-" << m_dates.d2 << "\n");
         Xapian::Query dq = date_range_filter(m_dates.y1, m_dates.m1, m_dates.d1,
                 m_dates.y2, m_dates.m2, m_dates.d2);
         if (dq.empty()) {
-            LOGINFO("Db::toNativeQuery: date filter is empty\n" );
+            LOGINFO("Db::toNativeQuery: date filter is empty\n");
         }
         // If no probabilistic query is provided then promote the daterange
         // filter to be THE query instead of filtering an empty query.
         if (xq.empty()) {
-            LOGINFO("Db::toNativeQuery: proba query is empty\n" );
+            LOGINFO("Db::toNativeQuery: proba query is empty\n");
             xq = dq;
         } else {
             xq = Xapian::Query(Xapian::Query::OP_FILTER, xq, dq);
@@ -239,7 +242,7 @@
         // If no probabilistic query is provided then promote the
         // filter to be THE query instead of filtering an empty query.
         if (xq.empty()) {
-            LOGINFO("Db::toNativeQuery: proba query is empty\n" );
+            LOGINFO("Db::toNativeQuery: proba query is empty\n");
             xq = sq;
         } else {
             xq = Xapian::Query(Xapian::Query::OP_FILTER, xq, sq);
@@ -263,7 +266,7 @@
 	for (vector<string>::iterator it = m_filetypes.begin(); 
 	     it != m_filetypes.end(); it++) {
 	    string term = wrap_prefix(mimetype_prefix) + *it;
-	    LOGDEB0("Adding file type term: ["  << (term) << "]\n" );
+	    LOGDEB0("Adding file type term: [" << term << "]\n");
 	    tq = tq.empty() ? Xapian::Query(term) : 
 		Xapian::Query(Xapian::Query::OP_OR, tq, Xapian::Query(term));
 	}
@@ -278,7 +281,7 @@
 	for (vector<string>::iterator it = m_nfiletypes.begin(); 
 	     it != m_nfiletypes.end(); it++) {
 	    string term = wrap_prefix(mimetype_prefix) + *it;
-	    LOGDEB0("Adding negative file type term: ["  << (term) << "]\n" );
+	    LOGDEB0("Adding negative file type term: [" << term << "]\n");
 	    tq = tq.empty() ? Xapian::Query(term) : 
 		Xapian::Query(Xapian::Query::OP_OR, tq, Xapian::Query(term));
 	}
@@ -333,7 +336,8 @@
 	if (m_lastpos < pos)
 	    m_lastpos = pos;
 	bool noexpand = be ? m_ts->nostemexp() : true;
-	LOGDEB1("TermProcQ::takeword: pushing ["  << (term) << "] pos "  << (pos) << " noexp "  << (noexpand) << "\n" );
+	LOGDEB1("TermProcQ::takeword: pushing [" << term << "] pos " <<
+                pos << " noexp " << noexpand << "\n");
 	if (m_terms[pos].size() < term.size()) {
 	    m_terms[pos] = term;
 	    m_nste[pos] = noexpand;
@@ -577,13 +581,12 @@
     }
 }
 
-void SearchDataClauseSimple::
-processSimpleSpan(Rcl::Db &db, string& ermsg,
-		  const string& span, 
-		  int mods, void * pq)
+void SearchDataClauseSimple::processSimpleSpan(
+    Rcl::Db &db, string& ermsg, const string& span, int mods, void *pq)
 {
     vector<Xapian::Query>& pqueries(*(vector<Xapian::Query>*)pq);
-    LOGDEB0("StringToXapianQ::processSimpleSpan: ["  << (span) << "] mods 0x"  << ((unsigned int)mods) << "\n" );
+    LOGDEB0("StringToXapianQ::processSimpleSpan: [" << span << "] mods 0x"
+            << (unsigned int)mods << "\n");
     vector<string> exp;  
     string sterm; // dumb version of user term
 
@@ -679,7 +682,7 @@
     vector<bool>::const_iterator nxit = splitData->nostemexps().begin();
     for (vector<string>::const_iterator it = splitData->terms().begin();
 	 it != splitData->terms().end(); it++, nxit++) {
-	LOGDEB0("ProcessPhrase: processing ["  << *it << "]\n" );
+	LOGDEB0("ProcessPhrase: processing [" << *it << "]\n");
 	// Adjust when we do stem expansion. Not if disabled by
 	// caller, not inside phrases, and some versions of xapian
 	// will accept only one OR clause inside NEAR.
@@ -695,7 +698,8 @@
 	vector<string> exp;
 	if (!expandTerm(db, ermsg, lmods, *it, exp, sterm, prefix))
 	    return;
-	LOGDEB0("ProcessPhraseOrNear: exp size "  << (exp.size()) << ", exp: "  << (stringsToString(exp)) << "\n" );
+	LOGDEB0("ProcessPhraseOrNear: exp size " << exp.size() << ", exp: " <<
+                stringsToString(exp) << "\n");
 	// groups is used for highlighting, we don't want prefixes in there.
 	vector<string> noprefs;
 	for (vector<string>::const_iterator it = exp.begin(); 
@@ -721,7 +725,8 @@
 
     // Generate an appropriate PHRASE/NEAR query with adjusted slack
     // For phrases, give a relevance boost like we do for original terms
-    LOGDEB2("PHRASE/NEAR:  alltermcount "  << (splitData->alltermcount()) << " lastpos "  << (splitData->lastpos()) << "\n" );
+    LOGDEB2("PHRASE/NEAR:  alltermcount " << splitData->alltermcount() <<
+            " lastpos " << splitData->lastpos() << "\n");
     Xapian::Query xq(op, orqueries.begin(), orqueries.end(),
 		     splitData->lastpos() + 1 + slack);
     if (op == Xapian::Query::OP_PHRASE)
@@ -791,7 +796,8 @@
     vector<Xapian::Query> &pqueries(*(vector<Xapian::Query>*)pq);
     int mods = m_modifiers;
 
-    LOGDEB("StringToXapianQ:pUS:: qstr ["  << (iq) << "] fld ["  << (m_field) << "] mods 0x"  << (mods) << " slack "  << (slack) << " near "  << (useNear) << "\n" );
+    LOGDEB("StringToXapianQ:pUS:: qstr [" << iq << "] fld [" << m_field <<
+           "] mods 0x"<<mods<<" slack " << slack << " near " << useNear <<"\n");
     ermsg.erase();
     m_curcl = 0;
     const StopList stops = db.getStopList();
@@ -811,7 +817,7 @@
     try {
 	for (vector<string>::iterator it = phrases.begin(); 
 	     it != phrases.end(); it++) {
-	    LOGDEB0("strToXapianQ: phrase/word: ["  << *it << "]\n" );
+	    LOGDEB0("strToXapianQ: phrase/word: [" << *it << "]\n");
 	    // Anchoring modifiers
 	    int amods = stringToMods(*it);
 	    int terminc = amods != 0 ? 1 : 0;
@@ -849,7 +855,7 @@
 
 	    slack += tpq.lastpos() - int(tpq.terms().size()) + 1;
 
-	    LOGDEB0("strToXapianQ: termcount: "  << (tpq.terms().size()) << "\n" );
+	    LOGDEB0("strToXapianQ: termcount: " << tpq.terms().size() << "\n");
 	    switch (tpq.terms().size() + terminc) {
 	    case 0: 
 		continue;// ??
@@ -884,7 +890,7 @@
 	ermsg = "Caught unknown exception";
     }
     if (!ermsg.empty()) {
-	LOGERR("stringToXapianQueries: "  << (ermsg) << "\n" );
+	LOGERR("stringToXapianQueries: " << ermsg << "\n");
 	return false;
     }
     return true;
@@ -893,8 +899,36 @@
 // Translate a simple OR or AND search clause. 
 bool SearchDataClauseSimple::toNativeQuery(Rcl::Db &db, void *p)
 {
-    LOGDEB("SearchDataClauseSimple::toNativeQuery: fld ["  << (m_field) << "] val ["  << (m_text) << "] stemlang ["  << (getStemLang()) << "]\n" );
-
+    LOGDEB("SearchDataClauseSimple::toNativeQuery: fld [" << m_field <<
+           "] val [" << m_text << "] stemlang [" << getStemLang() << "]\n");
+
+    // Transform (in)equalities into a range query
+    switch (getrel()) {
+    case REL_EQUALS:
+    {
+        SearchDataClauseRange cl(*this, gettext(), gettext());
+        bool ret = cl.toNativeQuery(db, p);
+        m_reason = cl.getReason();
+        return ret;
+    }
+    case REL_LT: case REL_LTE:
+    {
+        SearchDataClauseRange cl(*this, "", gettext());
+        bool ret = cl.toNativeQuery(db, p);
+        m_reason = cl.getReason();
+        return ret;
+    }
+    case REL_GT: case REL_GTE:
+    {
+        SearchDataClauseRange cl(*this, gettext(), "");
+        bool ret = cl.toNativeQuery(db, p);
+        m_reason = cl.getReason();
+        return ret;
+    }
+    default:
+        break;
+    }
+        
     Xapian::Query *qp = (Xapian::Query *)p;
     *qp = Xapian::Query();
 
@@ -903,7 +937,7 @@
     case SCLT_AND: op = Xapian::Query::OP_AND; break;
     case SCLT_OR: op = Xapian::Query::OP_OR; break;
     default:
-	LOGERR("SearchDataClauseSimple: bad m_tp "  << (m_tp) << "\n" );
+	LOGERR("SearchDataClauseSimple: bad m_tp " << m_tp << "\n");
         m_reason = "Internal error";
 	return false;
     }
@@ -912,7 +946,7 @@
     if (!processUserString(db, m_text, m_reason, &pqueries))
 	return false;
     if (pqueries.empty()) {
-	LOGERR("SearchDataClauseSimple: resolved to null query\n" );
+	LOGERR("SearchDataClauseSimple: resolved to null query\n");
         m_reason = string("Resolved to null query. Term too long ? : [" + 
                           m_text + string("]"));
 	return false;
@@ -921,6 +955,58 @@
     *qp = Xapian::Query(op, pqueries.begin(), pqueries.end());
     if  (m_weight != 1.0) {
 	*qp = Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT, *qp, m_weight);
+    }
+    return true;
+}
+
+// Translate a range clause. This only works if a Xapian value slot
+// was attributed to the field.
+bool SearchDataClauseRange::toNativeQuery(Rcl::Db &db, void *p)
+{
+    LOGDEB("SearchDataClauseRange::toNativeQuery: " << m_field <<
+           " :[" << m_text << ".." << m_t2 << "]\n");
+    Xapian::Query *qp = (Xapian::Query *)p;
+    *qp = Xapian::Query();
+
+    if (m_field.empty() || (m_text.empty() && m_t2.empty())) {
+        m_reason = "Range clause needs a field and a value";
+        return false;
+    }
+
+    // Get the value number for the field from the configuration
+    const FieldTraits *ftp;
+    if (!db.fieldToTraits(m_field, &ftp, true)) {
+        m_reason = string("field ") + m_field + " not found in configuration";
+        return false;
+    }
+    if (ftp->valueslot == 0) {
+        m_reason = string("No value slot specified in configuration for field ")
+            + m_field;
+        return false;
+    }
+    LOGDEB("SearchDataClauseRange: value slot " << ftp->valueslot << endl);
+    // Build Xapian VALUE query.
+    string errstr;
+    try {
+        if (m_text.empty()) {
+            *qp = Xapian::Query(Xapian::Query::OP_VALUE_LE,
+                                ftp->valueslot, convert_field_value(*ftp, m_t2));
+        } else if (m_t2.empty()) {
+            *qp = Xapian::Query(Xapian::Query::OP_VALUE_GE, ftp->valueslot,
+                                convert_field_value(*ftp, m_text));
+        } else {
+            *qp = Xapian::Query(Xapian::Query::OP_VALUE_RANGE, ftp->valueslot,
+                                convert_field_value(*ftp, m_text),
+                                convert_field_value(*ftp, m_t2));
+        }
+    }
+    XCATCHERROR(errstr);
+    if (!errstr.empty()) {
+        LOGERR("SearchDataClauseRange: range query creation failed for slot "<<
+               ftp->valueslot << endl);
+        m_reason = "Range query creation failed\n";
+        *qp = Xapian::Query();
+        return false;
     }
     return true;
 }
@@ -1018,7 +1104,7 @@
 // Translate NEAR or PHRASE clause. 
 bool SearchDataClauseDist::toNativeQuery(Rcl::Db &db, void *p)
 {
-    LOGDEB("SearchDataClauseDist::toNativeQuery\n" );
+    LOGDEB("SearchDataClauseDist::toNativeQuery\n");
 
     Xapian::Query *qp = (Xapian::Query *)p;
     *qp = Xapian::Query();
@@ -1037,7 +1123,7 @@
     if (!processUserString(db, s, m_reason, &pqueries, m_slack, useNear))
 	return false;
     if (pqueries.empty()) {
-	LOGERR("SearchDataClauseDist: resolved to null query\n" );
+	LOGERR("SearchDataClauseDist: resolved to null query\n");
         m_reason = string("Resolved to null query. Term too long ? : [" + 
                           m_text + string("]"));
 	return false;