LCOV - code coverage report
Current view: top level - api - valuerangeproc.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core fcfb185a9dd5 Lines: 161 171 94.2 %
Date: 2019-04-18 16:33:14 Functions: 11 11 100.0 %
Branches: 203 302 67.2 %

           Branch data     Line data    Source code
       1                 :            : /** @file valuerangeproc.cc
       2                 :            :  * @brief Standard RangeProcessor subclass implementations
       3                 :            :  */
       4                 :            : /* Copyright (C) 2007,2008,2009,2010,2012,2016,2018 Olly Betts
       5                 :            :  *
       6                 :            :  * This program is free software; you can redistribute it and/or modify
       7                 :            :  * it under the terms of the GNU General Public License as published by
       8                 :            :  * the Free Software Foundation; either version 2 of the License, or
       9                 :            :  * (at your option) any later version.
      10                 :            :  *
      11                 :            :  * This program is distributed in the hope that it will be useful,
      12                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14                 :            :  * GNU General Public License for more details.
      15                 :            :  *
      16                 :            :  * You should have received a copy of the GNU General Public License
      17                 :            :  * along with this program; if not, write to the Free Software
      18                 :            :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
      19                 :            :  */
      20                 :            : 
      21                 :            : #include <config.h>
      22                 :            : 
      23                 :            : #include <xapian/queryparser.h>
      24                 :            : 
      25                 :            : #include <cerrno>
      26                 :            : #include <cstdlib> // For atoi().
      27                 :            : 
      28                 :            : #include <string>
      29                 :            : #include "stringutils.h"
      30                 :            : 
      31                 :            : using namespace std;
      32                 :            : 
      33                 :            : namespace Xapian {
      34                 :            : 
      35                 :            : static bool
      36                 :         43 : decode_xxy(const string & s, int & x1, int &x2, int &y)
      37                 :            : {
      38         [ +  + ]:         43 :     if (s.size() == 0) {
      39                 :          1 :         x1 = x2 = y = -1;
      40                 :          1 :         return true;
      41                 :            :     }
      42 [ +  + ][ -  + ]:         42 :     if (s.size() < 5 || s.size() > 10) return false;
                 [ +  + ]
      43                 :         23 :     size_t i = s.find_first_not_of("0123456789");
      44 [ +  + ][ +  + ]:         23 :     if (i < 1 || i > 2 || !(s[i] == '/' || s[i] == '-' || s[i] == '.'))
         [ +  + ][ -  + ]
         [ #  # ][ +  + ]
      45                 :          2 :         return false;
      46                 :         21 :     size_t j = s.find_first_not_of("0123456789", i + 1);
      47         [ +  - ]:         48 :     if (j - (i + 1) < 1 || j - (i + 1) > 2 ||
           [ +  -  +  + ]
                 [ -  + ]
      48 [ -  + ][ #  # ]:         27 :         !(s[j] == '/' || s[j] == '-' || s[j] == '.'))
      49                 :          0 :         return false;
      50         [ -  + ]:         21 :     if (s.size() - j > 4 + 1) return false;
      51         [ -  + ]:         21 :     if (s.find_first_not_of("0123456789", j + 1) != string::npos)
      52                 :          0 :         return false;
      53                 :         21 :     x1 = atoi(s.c_str());
      54 [ +  - ][ -  + ]:         21 :     if (x1 < 1 || x1 > 31) return false;
      55                 :         21 :     x2 = atoi(s.c_str() + i + 1);
      56 [ +  - ][ -  + ]:         21 :     if (x2 < 1 || x2 > 31) return false;
      57                 :         21 :     y = atoi(s.c_str() + j + 1);
      58                 :         21 :     return true;
      59                 :            : }
      60                 :            : 
      61                 :            : // We just use this to decide if an ambiguous aa/bb/cc date could be a
      62                 :            : // particular format, so there's no need to be anal about the exact number of
      63                 :            : // days in February.  The most useful check is that the month field is <= 12
      64                 :            : // so we could just check the day is <= 31 really.
      65                 :            : static const char max_month_length[12] = {
      66                 :            :     31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
      67                 :            : };
      68                 :            : 
      69                 :            : static bool
      70                 :         22 : vet_dm(int d, int m)
      71                 :            : {
      72         [ -  + ]:         22 :     if (m == -1) return true;
      73 [ +  + ][ -  + ]:         22 :     if (m > 12 || m < 1) return false;
      74 [ +  - ][ -  + ]:         21 :     if (d < 1 || d > max_month_length[m - 1]) return false;
      75                 :         21 :     return true;
      76                 :            : }
      77                 :            : 
      78                 :            : // NB Assumes the length has been checked to be 10 already.
      79                 :            : static bool
      80                 :         10 : is_yyyy_mm_dd(const string &s)
      81                 :            : {
      82         [ +  - ]:         20 :     return (s.find_first_not_of("0123456789") == 4 &&
      83         [ +  - ]:         20 :             s.find_first_not_of("0123456789", 5) == 7 &&
      84         [ +  - ]:         20 :             s.find_first_not_of("0123456789", 8) == string::npos &&
      85 [ +  - ][ +  + ]:         36 :             s[4] == s[7] &&
      86 [ +  + ][ +  - ]:         26 :             (s[4] == '-' || s[4] == '.' || s[4] == '/'));
      87                 :            : }
      88                 :            : 
      89                 :            : // Write exactly w chars to buffer p representing integer v.
      90                 :            : //
      91                 :            : // The result is left padded with zeros if v < pow(10, w - 1).
      92                 :            : //
      93                 :            : // If v >= pow(10, w), then the output will show v % pow(10, w) (i.e. the
      94                 :            : // most significant digits are lost).
      95                 :            : static void
      96                 :         60 : format_int_fixed_width(char * p, int v, int w)
      97                 :            : {
      98         [ +  + ]:        220 :     while (--w >= 0) {
      99                 :        160 :         p[w] = '0' + (v % 10);
     100                 :        160 :         v /= 10;
     101                 :            :     }
     102                 :         60 : }
     103                 :            : 
     104                 :            : static void
     105                 :         20 : format_yyyymmdd(char * p, int y, int m, int d)
     106                 :            : {
     107                 :         20 :     format_int_fixed_width(p, y, 4);
     108                 :         20 :     format_int_fixed_width(p + 4, m, 2);
     109                 :         20 :     format_int_fixed_width(p + 6, d, 2);
     110                 :         20 : }
     111                 :            : 
     112                 :            : Xapian::Query
     113                 :      18767 : RangeProcessor::check_range(const string& b, const string& e)
     114                 :            : {
     115         [ +  + ]:      18767 :     if (str.empty())
     116                 :      18688 :         return operator()(b, e);
     117                 :            : 
     118                 :         79 :     size_t off_b = 0, len_b = string::npos;
     119                 :         79 :     size_t off_e = 0, len_e = string::npos;
     120                 :            : 
     121                 :         79 :     bool prefix = !(flags & Xapian::RP_SUFFIX);
     122                 :         79 :     bool repeated = (flags & Xapian::RP_REPEATED);
     123                 :            : 
     124         [ +  + ]:         79 :     if (prefix) {
     125                 :            :         // If there's a prefix, require it on the start of the range.
     126         [ +  + ]:         66 :         if (!startswith(b, str)) {
     127                 :            :             // Prefix not given.
     128                 :         42 :             goto not_our_range;
     129                 :            :         }
     130                 :         24 :         off_b = str.size();
     131                 :            :         // Optionally allow it on the end of the range, e.g. $10..50
     132 [ +  + ][ +  + ]:         24 :         if (repeated && startswith(e, str)) {
                 [ +  + ]
     133                 :          1 :             off_e = off_b;
     134                 :            :         }
     135                 :            :     } else {
     136                 :            :         // If there's a suffix, require it on the end of the range.
     137         [ +  + ]:         13 :         if (!endswith(e, str)) {
     138                 :            :             // Suffix not given.
     139                 :          7 :             goto not_our_range;
     140                 :            :         }
     141                 :          6 :         len_e = e.size() - str.size();
     142                 :            :         // Optionally allow it on the start of the range, e.g. 10..50kg
     143 [ +  - ][ +  + ]:          6 :         if (repeated && endswith(b, str)) {
                 [ +  + ]
     144                 :          1 :             len_b = b.size() - str.size();
     145                 :            :         }
     146                 :            :     }
     147                 :            : 
     148 [ +  - ][ +  - ]:         30 :     return operator()(string(b, off_b, len_b), string(e, off_e, len_e));
     149                 :            : 
     150                 :            : not_our_range:
     151                 :      18767 :     return Xapian::Query(Xapian::Query::OP_INVALID);
     152                 :            : }
     153                 :            : 
     154                 :            : Xapian::Query
     155                 :      18671 : RangeProcessor::operator()(const string& b, const string& e)
     156                 :            : {
     157         [ +  + ]:      18671 :     if (e.empty())
     158                 :          4 :         return Xapian::Query(Xapian::Query::OP_VALUE_GE, slot, b);
     159                 :      18667 :     return Xapian::Query(Xapian::Query::OP_VALUE_RANGE, slot, b, e);
     160                 :            : }
     161                 :            : 
     162                 :            : Xapian::Query
     163                 :         38 : DateRangeProcessor::operator()(const string& b, const string& e)
     164                 :            : {
     165   [ +  +  +  + ]:        116 :     if ((b.size() == 8 || b.size() == 0) &&
                 [ +  + ]
     166 [ -  + ][ +  + ]:         26 :         (e.size() == 8 || e.size() == 0) &&
     167 [ +  + ][ +  - ]:         86 :         b.find_first_not_of("0123456789") == string::npos &&
                 [ +  - ]
     168         [ +  - ]:          1 :         e.find_first_not_of("0123456789") == string::npos) {
     169                 :            :         // YYYYMMDD
     170         [ +  - ]:          1 :         return RangeProcessor::operator()(b, e);
     171                 :            :     }
     172         [ +  + ]:         48 :     if ((b.size() == 10 || b.size() == 0) &&
           [ +  +  +  + ]
                 [ +  + ]
     173         [ +  + ]:         11 :         (e.size() == 10 || e.size() == 0)) {
     174 [ +  + ][ +  - ]:         17 :         if ((b.empty() || is_yyyy_mm_dd(b)) &&
           [ +  -  +  + ]
                 [ +  - ]
     175 [ +  - ][ +  - ]:         11 :             (e.empty() || is_yyyy_mm_dd(e))) {
     176 [ +  - ][ +  - ]:         12 :             string begin = b, end = e;
     177                 :            :             // YYYY-MM-DD
     178         [ +  + ]:          6 :             if (!begin.empty()) {
     179         [ +  - ]:          5 :                 begin.erase(7, 1);
     180         [ +  - ]:          5 :                 begin.erase(4, 1);
     181                 :            :             }
     182         [ +  + ]:          6 :             if (!end.empty()) {
     183         [ +  - ]:          5 :                 end.erase(7, 1);
     184         [ +  - ]:          5 :                 end.erase(4, 1);
     185                 :            :             }
     186         [ +  - ]:         12 :             return RangeProcessor::operator()(begin, end);
     187                 :            :         }
     188                 :            :     }
     189                 :            : 
     190                 :         31 :     bool prefer_mdy = (flags & Xapian::RP_DATE_PREFER_MDY);
     191                 :            :     int b_d, b_m, b_y;
     192                 :            :     int e_d, e_m, e_y;
     193 [ +  - ][ +  + ]:         31 :     if (!decode_xxy(b, b_d, b_m, b_y) || !decode_xxy(e, e_d, e_m, e_y))
         [ +  - ][ +  + ]
                 [ +  + ]
     194                 :         21 :         goto not_our_range;
     195                 :            : 
     196                 :            :     // Check that the month and day are within range.  Also assume "start" <=
     197                 :            :     // "e" to help decide ambiguous cases.
     198 [ +  + ][ +  - ]:         11 :     if (!prefer_mdy && vet_dm(b_d, b_m) && vet_dm(e_d, e_m) &&
         [ +  + ][ +  + ]
                 [ +  + ]
     199 [ +  - ][ +  - ]:          1 :         (b_y != e_y || b_m < e_m || (b_m == e_m && b_d <= e_d))) {
                 [ +  - ]
     200                 :            :         // OK.
     201 [ +  - ][ +  - ]:          7 :     } else if (vet_dm(b_m, b_d) && vet_dm(e_m, e_d) &&
         [ -  + ][ +  - ]
     202 [ #  # ][ #  # ]:          0 :         (b_y != e_y || b_d < e_d || (b_d == e_d && b_m <= e_m))) {
                 [ #  # ]
     203                 :          7 :         swap(b_m, b_d);
     204                 :          7 :         swap(e_m, e_d);
     205 [ #  # ][ #  # ]:          0 :     } else if (prefer_mdy && vet_dm(b_d, b_m) && vet_dm(e_d, e_m) &&
         [ #  # ][ #  # ]
                 [ #  # ]
     206 [ #  # ][ #  # ]:          0 :                (b_y != e_y || b_m < e_m || (b_m == e_m && b_d <= e_d))) {
                 [ #  # ]
     207                 :            :         // OK.
     208                 :            :     } else {
     209                 :          0 :         goto not_our_range;
     210                 :            :     }
     211                 :            : 
     212                 :            :     {
     213                 :            :         char buf_b[8], buf_e[8];
     214                 :         10 :         size_t len_b = 0, len_e = 0;
     215         [ +  - ]:         10 :         if (!b.empty()) {
     216         [ +  + ]:         10 :             if (b_y < 100) {
     217                 :          9 :                 b_y += 1900;
     218         [ -  + ]:          9 :                 if (b_y < epoch_year) b_y += 100;
     219                 :            :             }
     220                 :         10 :             format_yyyymmdd(buf_b, b_y, b_m, b_d);
     221                 :         10 :             len_b = 8;
     222                 :            :         }
     223         [ +  - ]:         10 :         if (!e.empty()) {
     224         [ +  + ]:         10 :             if (e_y < 100) {
     225                 :          9 :                 e_y += 1900;
     226         [ +  - ]:          9 :                 if (e_y < epoch_year) e_y += 100;
     227                 :            :             }
     228                 :         10 :             format_yyyymmdd(buf_e, e_y, e_m, e_d);
     229                 :         10 :             len_e = 8;
     230                 :            :         }
     231                 :            :         return RangeProcessor::operator()(string(buf_b, len_b),
     232 [ +  - ][ +  - ]:         10 :                                           string(buf_e, len_e));
                 [ +  - ]
     233                 :            :     }
     234                 :            : 
     235                 :            : not_our_range:
     236         [ +  - ]:         38 :     return Xapian::Query(Xapian::Query::OP_INVALID);
     237                 :            : }
     238                 :            : 
     239                 :            : Xapian::Query
     240                 :      18637 : NumberRangeProcessor::operator()(const string& b, const string& e)
     241                 :            : {
     242                 :            :     // Parse the numbers to floating point.
     243                 :            :     double num_b, num_e;
     244                 :            : 
     245         [ +  + ]:      18637 :     if (!b.empty()) {
     246                 :      18635 :         errno = 0;
     247                 :      18635 :         const char * startptr = b.c_str();
     248                 :            :         char * endptr;
     249                 :      18635 :         num_b = strtod(startptr, &endptr);
     250 [ +  + ][ -  + ]:      18635 :         if (endptr != startptr + b.size() || errno) {
                 [ +  + ]
     251                 :            :             // Invalid characters in string || overflow or underflow.
     252                 :      18635 :             goto not_our_range;
     253                 :            :         }
     254                 :            :     } else {
     255                 :            :         // Silence GCC warning.
     256                 :          2 :         num_b = 0.0;
     257                 :            :     }
     258                 :            : 
     259         [ +  + ]:      18624 :     if (!e.empty()) {
     260                 :      18623 :         errno = 0;
     261                 :      18623 :         const char * startptr = e.c_str();
     262                 :            :         char * endptr;
     263                 :      18623 :         num_e = strtod(startptr, &endptr);
     264 [ +  + ][ -  + ]:      18623 :         if (endptr != startptr + e.size() || errno) {
                 [ +  + ]
     265                 :            :             // Invalid characters in string || overflow or underflow.
     266                 :      18623 :             goto not_our_range;
     267                 :            :         }
     268                 :            :     } else {
     269                 :            :         // Silence GCC warning.
     270                 :          1 :         num_e = 0.0;
     271                 :            :     }
     272                 :            : 
     273                 :            :     return RangeProcessor::operator()(
     274                 :      18615 :             b.empty() ? b : Xapian::sortable_serialise(num_b),
     275 [ +  + ][ +  + ]:      37230 :             e.empty() ? e : Xapian::sortable_serialise(num_e));
         [ +  - ][ +  - ]
                 [ +  - ]
     276                 :            : 
     277                 :            : not_our_range:
     278                 :      18637 :     return Xapian::Query(Xapian::Query::OP_INVALID);
     279                 :            : }
     280                 :            : 
     281                 :            : static const char byte_units[4][2] = {
     282                 :            :     "B", "K", "M", "G"
     283                 :            : };
     284                 :            : 
     285                 :            : // Return factor for byte unit
     286                 :            : // if string is a valid byte unit
     287                 :            : // else return -1
     288                 :            : static double
     289                 :          8 : check_byte_unit(const string &s) {
     290                 :          8 :     double factor = 1;
     291         [ +  - ]:         18 :     for (int i = 0; i < 4; ++i) {
     292         [ +  + ]:         18 :         if (endswith(s, byte_units[i])) {
     293                 :          8 :             return factor;
     294                 :            :         }
     295                 :         10 :         factor *= 1024;
     296                 :            :     }
     297                 :            : 
     298                 :          0 :     return -1;
     299                 :            : }
     300                 :            : 
     301                 :            : Xapian::Query
     302                 :          9 : UnitRangeProcessor::operator()(const string& b, const string& e)
     303                 :            : {
     304                 :            :     // Parse the numbers to floating point.
     305                 :            :     double num_b, num_e;
     306                 :            : 
     307                 :            :     // True if b has unit, e.g. 20K..
     308                 :          9 :     bool b_has_unit = false;
     309                 :            : 
     310         [ +  + ]:          9 :     if (!b.empty()) {
     311                 :          7 :         errno = 0;
     312                 :          7 :         const char * startptr = b.c_str();
     313                 :            :         char * endptr;
     314                 :          7 :         num_b = strtod(startptr, &endptr);
     315                 :            : 
     316         [ -  + ]:          7 :         if (errno) {
     317                 :            :             // overflow or underflow
     318                 :          0 :             goto not_our_range;
     319                 :            :         }
     320                 :            : 
     321                 :            :         // For lower range having a unit, e.g. 100K..
     322         [ +  + ]:          7 :         if (endptr == startptr + b.size() - 1) {
     323                 :          4 :             double factor_b = check_byte_unit(b);
     324         [ -  + ]:          4 :             if (factor_b == -1) {
     325                 :            :                 // Not a valid byte unit
     326                 :          0 :                 goto not_our_range;
     327                 :            :             }
     328                 :          4 :             b_has_unit = true;
     329                 :          7 :             num_b *= factor_b;
     330                 :            :         }
     331                 :            :     } else {
     332                 :            :         // Silence GCC warning.
     333                 :          2 :         num_b = 0.0;
     334                 :            :     }
     335                 :            : 
     336         [ +  + ]:          9 :     if (!e.empty()) {
     337                 :          7 :         errno = 0;
     338                 :          7 :         const char * startptr = e.c_str();
     339                 :            :         char * endptr;
     340                 :          7 :         num_e = strtod(startptr, &endptr);
     341                 :            : 
     342         [ -  + ]:          7 :         if (errno) {
     343                 :            :             // overflow or underflow
     344                 :          3 :             goto not_our_range;
     345                 :            :         }
     346                 :            : 
     347                 :            :         // For upper range having a unit, e.g. ..100K
     348         [ +  + ]:          7 :         if (endptr == startptr + e.size() - 1) {
     349                 :          4 :             double factor_e = check_byte_unit(e);
     350         [ -  + ]:          4 :             if (factor_e == -1) {
     351                 :            :                 // Not a valid byte unit
     352                 :          0 :                 goto not_our_range;
     353                 :            :             }
     354                 :          4 :             num_e *= factor_e;
     355                 :            : 
     356                 :            :             // When lower range is not empty and
     357                 :            :             // only upper range unit, e.g. 20..100K
     358 [ +  + ][ +  + ]:          4 :             if (!b.empty() && !b_has_unit) {
                 [ +  + ]
     359                 :          4 :                 num_b *= factor_e;
     360                 :            :             }
     361                 :            :         } else {
     362                 :            :             // When lower range has no unit
     363                 :          7 :             goto not_our_range;
     364                 :            :         }
     365                 :            :     } else {
     366                 :            :         // Silence GCC warning.
     367                 :          2 :         num_e = 0.0;
     368                 :            : 
     369                 :            :         // Fail case when lower range
     370                 :            :         // has no unit, e.g. 200..
     371 [ +  - ][ +  + ]:          2 :         if (!b.empty() && !b_has_unit) {
                 [ +  + ]
     372                 :          1 :             goto not_our_range;
     373                 :            :         }
     374                 :            :     }
     375                 :            : 
     376                 :            :     return RangeProcessor::operator()(
     377                 :          5 :             b.empty() ? b : Xapian::sortable_serialise(num_b),
     378 [ +  + ][ +  + ]:         10 :             e.empty() ? e : Xapian::sortable_serialise(num_e));
         [ +  - ][ +  - ]
                 [ +  - ]
     379                 :            : 
     380                 :            : not_our_range:
     381                 :          9 :     return Xapian::Query(Xapian::Query::OP_INVALID);
     382                 :            : }
     383                 :            : 
     384                 :            : }

Generated by: LCOV version 1.11