Ticket #50: opsynonym_changes_12490_12492.patch

File opsynonym_changes_12490_12492.patch, 62.8 KB (added by Richard Boulton, 16 years ago)

Latest patch from trunk to the opsynonym branch

  • xapian-maintainer-tools/win32msvc/win32_matcher.mak

     
    3434    $(INTDIR)\queryoptimiser.obj\
    3535    $(INTDIR)\rset.obj\
    3636    $(INTDIR)\selectpostlist.obj\
     37    $(INTDIR)\synonympostlist.obj\
    3738    $(INTDIR)\valuerangepostlist.obj\
    3839    $(INTDIR)\valuegepostlist.obj\
    3940    $(INTDIR)\xorpostlist.obj\
     
    6061    $(INTDIR)\queryoptimiser.cc\
    6162    $(INTDIR)\rset.cc\
    6263    $(INTDIR)\selectpostlist.cc\
     64    $(INTDIR)\synonympostlist.cc\
    6365    $(INTDIR)\valuerangepostlist.cc\
    6466    $(INTDIR)\valuegepostlist.cc\
    6567    $(INTDIR)\xorpostlist.cc\
  • xapian-core/queryparser/queryparser.lemony

     
    22/* queryparser.lemony: build a Xapian::Query object from a user query string.
    33 *
    44 * Copyright (C) 2004,2005,2006,2007,2008 Olly Betts
     5 * Copyright (C) 2007,2008,2009 Lemur Consulting Ltd
    56 *
    67 * This program is free software; you can redistribute it and/or
    78 * modify it under the terms of the GNU General Public License as
     
    287288            end = db.synonyms_end(term);
    288289        }
    289290        while (syn != end) {
    290             q = Query(Query::OP_OR, q, Query(*syn, 1, pos));
     291            q = Query(Query::OP_SYNONYM, q, Query(*syn, 1, pos));
    291292            ++syn;
    292293        }
    293294    }
     
    353354        }
    354355    }
    355356    delete this;
    356     return new Query(Query::OP_OR, subqs.begin(), subqs.end());
     357    return new Query(Query::OP_SYNONYM, subqs.begin(), subqs.end());
    357358}
    358359
    359360Query *
    360361Term::as_partial_query(State * state_) const
    361362{
    362363    Database db = state_->get_database();
    363     vector<Query> subqs;
     364    vector<Query> subqs_partial; // A synonym of all the partial terms.
     365    vector<Query> subqs_full; // A synonym of all the full terms.
    364366    list<string>::const_iterator piter;
    365367    for (piter = prefixes.begin(); piter != prefixes.end(); ++piter) {
    366368        string root = *piter;
    367369        root += name;
    368370        TermIterator t = db.allterms_begin(root);
    369371        while (t != db.allterms_end(root)) {
    370             subqs.push_back(Query(*t, 1, pos));
     372            subqs_partial.push_back(Query(*t, 1, pos));
    371373            ++t;
    372374        }
    373375        // Add the term, as it would normally be handled, as an alternative.
    374         subqs.push_back(Query(make_term(*piter), 1, pos));
     376        subqs_full.push_back(Query(make_term(*piter), 1, pos));
    375377    }
    376378    delete this;
    377     return new Query(Query::OP_OR, subqs.begin(), subqs.end());
     379    return new Query(Query::OP_OR,
     380                     Query(Query::OP_SYNONYM,
     381                           subqs_partial.begin(), subqs_partial.end()),
     382                     Query(Query::OP_SYNONYM,
     383                           subqs_full.begin(), subqs_full.end()));
    378384}
    379385
    380386inline bool
     
    11761182                subqs2.push_back(Query(*syn, 1, pos));
    11771183                ++syn;
    11781184            }
    1179             Query q_synonym_terms(Query::OP_OR, subqs2.begin(), subqs2.end());
     1185            Query q_synonym_terms(Query::OP_SYNONYM, subqs2.begin(), subqs2.end());
    11801186            subqs2.clear();
    1181             subqs.push_back(Query(Query::OP_OR,
     1187            subqs.push_back(Query(Query::OP_SYNONYM,
    11821188                                  q_original_terms, q_synonym_terms));
    11831189        }
    11841190    } else {
  • xapian-core/matcher/Makefile.mk

     
    1818        matcher/queryoptimiser.h\
    1919        matcher/remotesubmatch.h\
    2020        matcher/selectpostlist.h\
     21        matcher/synonympostlist.h\
    2122        matcher/valuegepostlist.h\
    2223        matcher/valuerangepostlist.h\
    2324        matcher/xorpostlist.h
     
    5455        matcher/queryoptimiser.cc\
    5556        matcher/rset.cc\
    5657        matcher/selectpostlist.cc\
     58        matcher/synonympostlist.cc\
    5759        matcher/valuegepostlist.cc\
    5860        matcher/valuerangepostlist.cc\
    5961        matcher/xorpostlist.cc
  • xapian-core/matcher/andpostlist.h

     
    22 *
    33 * Copyright 2002 Ananova Ltd
    44 * Copyright 2003,2004,2009 Olly Betts
     5 * Copyright 2009 Lemur Consulting Ltd
    56 *
    67 * This program is free software; you can redistribute it and/or
    78 * modify it under the terms of the GNU General Public License as
     
    7071                    MultiMatch *matcher_,
    7172                    Xapian::doccount dbsize_,
    7273                    bool replacement = false);
     74
     75        /** get_wdf() for AND postlists returns the sum of the wdfs of the sub
     76         *  postlists - this is desirable when the AND is part of a synonym.
     77         */
     78        Xapian::termcount get_wdf() const;
    7379};
    7480
    7581#endif /* OM_HGUARD_ANDPOSTLIST_H */
  • xapian-core/matcher/multimatch.cc

     
    790790
    791791                LOGVALUE(MATCH, denom);
    792792                LOGVALUE(MATCH, percent_scale);
    793                 Assert(percent_scale <= denom);
    794                 denom *= greatest_wt;
    795                 Assert(denom > 0);
    796                 percent_scale /= denom;
     793                AssertRel(percent_scale,<=,denom);
     794                if (denom == 0) {
     795                    // This happens if the top-level operator is OP_SYNONYM.
     796                    percent_scale = 1.0 / greatest_wt;
     797                } else {
     798                    denom *= greatest_wt;
     799                    AssertRel(denom,>,0);
     800                    percent_scale /= denom;
     801                }
    797802            } else {
    798803                // If all the terms match, the 2 sums of weights cancel
    799804                percent_scale = 1.0 / greatest_wt;
  • xapian-core/matcher/localmatch.cc

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2002 Ananova Ltd
    55 * Copyright 2002,2003,2004,2005,2006,2007,2008,2009 Olly Betts
    6  * Copyright 2007 Lemur Consulting Ltd
     6 * Copyright 2007,2008,2009 Lemur Consulting Ltd
    77 *
    88 * This program is free software; you can redistribute it and/or
    99 * modify it under the terms of the GNU General Public License as
     
    3131#include "omdebug.h"
    3232#include "omqueryinternal.h"
    3333#include "queryoptimiser.h"
     34#include "synonympostlist.h"
    3435#include "weightinternal.h"
    3536
    3637#include <cfloat>
     
    111112}
    112113
    113114PostList *
     115LocalSubMatch::make_synonym_postlist(PostList * or_pl, MultiMatch * matcher,
     116                                     double factor)
     117{
     118    DEBUGCALL(MATCH, PostList *, "LocalSubMatch::make_synonym_postlist",
     119              "[or_pl], [matcher], " << factor);
     120    LOGVALUE(MATCH, or_pl->get_termfreq_est());
     121    AutoPtr<SynonymPostList> res(new SynonymPostList(or_pl, matcher));
     122    AutoPtr<Xapian::Weight> wt(wt_factory->clone_());
     123
     124    // FIXME - calculate the reltermfreq to use and pass it in?
     125    wt->init_(*stats, qlen, factor, or_pl->get_termfreq_est());
     126
     127    res->set_weight(wt.release());
     128    RETURN(res.release());
     129}
     130
     131PostList *
    114132LocalSubMatch::postlist_from_op_leaf_query(const Xapian::Query::Internal *query,
    115133                                           double factor)
    116134{
  • xapian-core/matcher/localmatch.h

     
    22 *  @brief SubMatch class for a local database.
    33 */
    44/* Copyright (C) 2006,2007,2009 Olly Betts
     5 * Copyright (C) 2007 Lemur Consulting Ltd
    56 *
    67 * This program is free software; you can redistribute it and/or modify
    78 * it under the terms of the GNU General Public License as published by
     
    8283    PostList * get_postlist_and_term_info(MultiMatch *matcher,
    8384        std::map<string, Xapian::MSet::Internal::TermFreqAndWeight> *termfreqandwts);
    8485
     86    /** Convert a postlist into a synonym postlist.
     87     */
     88    PostList * make_synonym_postlist(PostList * or_pl, MultiMatch * matcher,
     89                                     double factor);
     90
    8591    /** Convert an OP_LEAF query to a PostList.
    8692     *
    8793     *  This is called by QueryOptimiser when it reaches an OP_LEAF query.
  • xapian-core/matcher/xorpostlist.h

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2002 Ananova Ltd
    55 * Copyright 2003,2004,2009 Olly Betts
     6 * Copyright 2009 Lemur Consulting Ltd
    67 *
    78 * This program is free software; you can redistribute it and/or
    89 * modify it under the terms of the GNU General Public License as
     
    6970                    PostList * right_,
    7071                    MultiMatch * matcher_,
    7172                    Xapian::doccount dbsize_);
     73
     74        /** get_wdf() for XOR postlists returns the wdf of the sub postlist
     75         *  which is at the current document.
     76         */
     77        Xapian::termcount get_wdf() const;
    7278};
    7379
    7480#endif /* OM_HGUARD_XORPOSTLIST_H */
  • xapian-core/matcher/synonympostlist.h

     
     1/** @file synonympostlist.h
     2 * @brief Combine subqueries, weighting as if they are synonyms
     3 */
     4/* Copyright 2007,2009 Lemur Consulting Ltd
     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#ifndef XAPIAN_INCLUDED_SYNONYMPOSTLIST_H
     22#define XAPIAN_INCLUDED_SYNONYMPOSTLIST_H
     23
     24#include "multimatch.h"
     25#include "postlist.h"
     26
     27/** A postlist comprising several postlists SYNONYMed together.
     28 *
     29 *  This postlist returns all postings in the OR of the sub postlists, but
     30 *  returns weights as if they represented a single term.  The term frequency
     31 *  portion of the weight is approximated.
     32 */
     33class SynonymPostList : public PostList {
     34    /** The subtree, which starts as an OR of all the sub-postlists being
     35     *  joined with Synonym, but may decay into something else.
     36     */
     37    PostList * subtree;
     38
     39    /** The object which is using this postlist to perform a match.
     40     *
     41     *  This object needs to be notified when the tree changes such that the
     42     *  maximum weights need to be recalculated.
     43     */
     44    MultiMatch * matcher;
     45
     46    /** Weighting object used for calculating the synonym weights.
     47     */
     48    const Xapian::Weight * wt;
     49
     50    /** Flag indicating whether the weighting object needs the doclength.
     51     */
     52    bool want_doclength;
     53
     54    /** Flag indicating whether the weighting object needs the wdf.
     55     */
     56    bool want_wdf;
     57
     58  public:
     59    SynonymPostList(PostList * subtree_, MultiMatch * matcher_)
     60        : subtree(subtree_), matcher(matcher_), wt(NULL),
     61          want_doclength(false), want_wdf(false) { }
     62
     63    ~SynonymPostList();
     64
     65    /** Set the weight object to be used for the synonym postlist.
     66     *
     67     *  Ownership of the weight object passes to the synonym postlist - the
     68     *  caller must not delete it after use.
     69     */
     70    void set_weight(const Xapian::Weight * wt_);
     71
     72    PostList *next(Xapian::weight w_min);
     73    PostList *skip_to(Xapian::docid did, Xapian::weight w_min);
     74
     75    Xapian::weight get_weight() const;
     76    Xapian::weight get_maxweight() const;
     77    Xapian::weight recalc_maxweight();
     78
     79    // The following methods just call through to the subtree.
     80    Xapian::termcount get_wdf() const;
     81    Xapian::doccount get_termfreq_min() const;
     82    Xapian::doccount get_termfreq_est() const;
     83    Xapian::doccount get_termfreq_max() const;
     84    Xapian::docid get_docid() const;
     85    Xapian::termcount get_doclength() const;
     86    bool at_end() const;
     87
     88    std::string get_description() const;
     89};
     90
     91#endif /* XAPIAN_INCLUDED_SYNONYMPOSTLIST_H */
  • xapian-core/matcher/andmaybepostlist.h

    Property changes on: xapian-core/matcher/synonympostlist.h
    ___________________________________________________________________
    Added: svn:eol-style
       + native
    
     
    66 * Copyright 1999,2000,2001 BrightStation PLC
    77 * Copyright 2002 Ananova Ltd
    88 * Copyright 2003,2004,2009 Olly Betts
     9 * Copyright 2009 Lemur Consulting Ltd
    910 *
    1011 * This program is free software; you can redistribute it and/or
    1112 * modify it under the terms of the GNU General Public License as
     
    103104            lmax = l->get_maxweight();
    104105            rmax = r->get_maxweight();
    105106        }
     107
     108        /** get_wdf() for ANDMAYBE postlists returns the sum of the wdfs of the
     109         *  sub postlists which are at the current document - this is desirable
     110         *  when the ANDMAYBE is part of a synonym.
     111         */
     112        Xapian::termcount get_wdf() const;
    106113};
    107114
    108115#endif /* OM_HGUARD_ANDMAYBEPOSTLIST_H */
  • xapian-core/matcher/orpostlist.h

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2002 Ananova Ltd
    55 * Copyright 2003,2004,2009 Olly Betts
     6 * Copyright 2009 Lemur Consulting Ltd
    67 *
    78 * This program is free software; you can redistribute it and/or
    89 * modify it under the terms of the GNU General Public License as
     
    6768                   PostList * right_,
    6869                   MultiMatch * matcher_,
    6970                   Xapian::doccount dbsize_);
     71
     72        /** get_wdf() for OR postlists returns the sum of the wdfs of the
     73         *  sub postlists which are at the current document - this is desirable
     74         *  when the OR is part of a synonym.
     75         */
     76        Xapian::termcount get_wdf() const;
    7077};
    7178
    7279#endif /* OM_HGUARD_ORPOSTLIST_H */
  • xapian-core/matcher/andnotpostlist.cc

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2002 Ananova Ltd
    55 * Copyright 2003,2004,2007,2009 Olly Betts
     6 * Copyright 2009 Lemur Consulting Ltd
    67 *
    78 * This program is free software; you can redistribute it and/or
    89 * modify it under the terms of the GNU General Public License as
     
    175176    DEBUGCALL(MATCH, Xapian::termcount, "AndNotPostList::get_doclength", "");
    176177    RETURN(l->get_doclength());
    177178}
     179
     180Xapian::termcount
     181AndNotPostList::get_wdf() const
     182{
     183    DEBUGCALL(MATCH, Xapian::termcount, "AndNotPostList::get_wdf", "");
     184    RETURN(l->get_wdf());
     185}
  • xapian-core/matcher/andnotpostlist.h

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2002 Ananova Ltd
    55 * Copyright 2003,2004,2009 Olly Betts
     6 * Copyright 2009 Lemur Consulting Ltd
    67 *
    78 * This program is free software; you can redistribute it and/or
    89 * modify it under the terms of the GNU General Public License as
     
    6970                                   Xapian::weight w_min,
    7071                                   Xapian::docid lh,
    7172                                   Xapian::docid rh);
     73
     74        /** get_wdf() for ANDNOT postlists returns the wdf of the left hand
     75         * side.
     76         */
     77        Xapian::termcount get_wdf() const;
    7278};
    7379
    7480#endif /* OM_HGUARD_ANDNOTPOSTLIST_H */
  • xapian-core/matcher/queryoptimiser.cc

     
    8282        case Xapian::Query::OP_ELITE_SET:
    8383            RETURN(do_or_like(query, factor));
    8484
     85        case Xapian::Query::OP_SYNONYM:
     86            RETURN(do_synonym(query, factor));
     87
    8588        case Xapian::Query::OP_AND_NOT: {
    8689            AssertEq(query->subqs.size(), 2);
    8790            PostList * l = do_subquery(query->subqs[0], factor);
     
    304307    // for AND-like operations.
    305308    Xapian::Query::Internal::op_t op = query->op;
    306309    Assert(op == Xapian::Query::OP_ELITE_SET || op == Xapian::Query::OP_OR ||
    307            op == Xapian::Query::OP_XOR);
     310           op == Xapian::Query::OP_XOR || op == Xapian::Query::OP_SYNONYM);
    308311
    309312    const Xapian::Query::Internal::subquery_list &queries = query->subqs;
    310313    AssertRel(queries.size(), >=, 2);
     
    382385                  ComparePostListTermFreqAscending());
    383386    }
    384387}
     388
     389PostList *
     390QueryOptimiser::do_synonym(const Xapian::Query::Internal *query, double factor)
     391{
     392    DEBUGCALL(MATCH, PostList *, "QueryOptimiser::do_synonym",
     393              query << ", " << factor);
     394    if (factor == 0.0) {
     395        // If we have a factor of 0, we don't care about the weights, so
     396        // we're just like a normal OR query.
     397        RETURN(do_or_like(query, 0.0));
     398    }
     399
     400    // We currently assume wqf is 1 for calculating the synonym's weight
     401    // since conceptually the synonym is one "virtual" term.  If we were
     402    // to combine multiple occurrences of the same synonym expansion into
     403    // a single instance with wqf set, we would want to use the wqf.
     404    AssertEq(query->wqf, 0);
     405
     406    // We build an OP_OR tree for OP_SYNONYM and then wrap it in a
     407    // SynonymPostList, which supplies the weights.
     408    RETURN(localsubmatch.make_synonym_postlist(do_or_like(query, 0.0),
     409                                               matcher, factor));
     410}
  • xapian-core/matcher/queryoptimiser.h

     
    22 * @brief Convert a Xapian::Query::Internal tree into an optimal PostList tree.
    33 */
    44/* Copyright (C) 2007,2008,2009 Olly Betts
     5 * Copyright (C) 2008 Lemur Consulting Ltd
    56 *
    67 * This program is free software; you can redistribute it and/or
    78 * modify it under the terms of the GNU General Public License as
     
    8889     */
    8990    PostList * do_or_like(const Xapian::Query::Internal *query, double factor);
    9091
     92    /** Optimise a synonym Xapian::Query::Internal subtree into a PostList
     93     *
     94     *  @param query    The subtree to optimise.
     95     *  @param factor   How much to scale weights for this subtree by.
     96     *
     97     *  @return         A PostList subtree.
     98     */
     99    PostList * do_synonym(const Xapian::Query::Internal *query, double factor);
     100
    91101  public:
    92102    QueryOptimiser(const Xapian::Database::Internal & db_,
    93103                   LocalSubMatch & localsubmatch_,
  • xapian-core/matcher/andpostlist.cc

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2002 Ananova Ltd
    55 * Copyright 2003,2004,2007,2008,2009 Olly Betts
    6  * Copyright 2007 Lemur Consulting Ltd
     6 * Copyright 2007,2009 Lemur Consulting Ltd
    77 *
    88 * This program is free software; you can redistribute it and/or
    99 * modify it under the terms of the GNU General Public License as
     
    203203    AssertEq(doclength, r->get_doclength());
    204204    RETURN(doclength);
    205205}
     206
     207Xapian::termcount
     208AndPostList::get_wdf() const
     209{
     210    DEBUGCALL(MATCH, Xapian::termcount, "AndPostList::get_wdf", "");
     211    RETURN(l->get_wdf() + r->get_wdf());
     212}
  • xapian-core/matcher/xorpostlist.cc

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2002 Ananova Ltd
    55 * Copyright 2003,2004,2007,2008,2009 Olly Betts
     6 * Copyright 2009 Lemur Consulting Ltd
    67 *
    78 * This program is free software; you can redistribute it and/or
    89 * modify it under the terms of the GNU General Public License as
     
    294295    Assert(lhead > rhead);
    295296    return r->get_doclength();
    296297}
     298
     299Xapian::termcount
     300XorPostList::get_wdf() const
     301{
     302    DEBUGCALL(MATCH, Xapian::termcount, "XorPostList::get_wdf", "");
     303    if (lhead < rhead) RETURN(l->get_wdf());
     304    RETURN(r->get_wdf());
     305}
  • xapian-core/matcher/synonympostlist.cc

     
     1/** @file synonympostlist.cc
     2 * @brief Combine subqueries, weighting as if they are synonyms
     3 */
     4/* Copyright 2007,2009 Lemur Consulting Ltd
     5 *
     6 * This program is free software; you can redistribute it and/or
     7 * modify it under the terms of the GNU General Public License as
     8 * published by the Free Software Foundation; either version 2 of the
     9 * License, or (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
     19 * USA
     20 */
     21
     22#include <config.h>
     23
     24#include "synonympostlist.h"
     25
     26#include "branchpostlist.h"
     27#include "debuglog.h"
     28
     29SynonymPostList::~SynonymPostList()
     30{
     31    delete wt;
     32    delete subtree;
     33}
     34
     35void
     36SynonymPostList::set_weight(const Xapian::Weight * wt_)
     37{
     38    delete wt;
     39    wt = wt_;
     40    want_doclength = wt->get_sumpart_needs_doclength_();
     41    want_wdf = wt->get_sumpart_needs_wdf_();
     42}
     43
     44PostList *
     45SynonymPostList::next(Xapian::weight w_min)
     46{
     47    LOGCALL(MATCH, PostList *, "SynonymPostList::next", w_min);
     48    (void)w_min;
     49    next_handling_prune(subtree, 0, matcher);
     50    RETURN(NULL);
     51}
     52
     53PostList *
     54SynonymPostList::skip_to(Xapian::docid did, Xapian::weight w_min)
     55{
     56    LOGCALL(MATCH, PostList *, "SynonymPostList::skip_to", did << ", " << w_min);
     57    (void)w_min;
     58    skip_to_handling_prune(subtree, did, 0, matcher);
     59    RETURN(NULL);
     60}
     61
     62Xapian::weight
     63SynonymPostList::get_weight() const
     64{
     65    LOGCALL(MATCH, Xapian::weight, "SynonymPostList::get_weight", "");
     66    // The wdf returned can be higher than the doclength.  In particular, this
     67    // can currently occur if the query contains a term more than once; the wdf
     68    // of each occurrence is added up.
     69    //
     70    // However, it's reasonable for weighting algorithms to optimise by
     71    // assuming that get_wdf() will never return more than get_doclength(),
     72    // since the doclength is the sum of the wdfs.
     73    //
     74    // Therefore, we simply clamp the wdf value to the doclength, to ensure
     75    // that this is true.  Note that this requires the doclength to be
     76    // calculated even if the weight object doesn't want it.
     77
     78    if (want_wdf) {
     79        Xapian::termcount wdf = get_wdf();
     80        Xapian::termcount doclen = get_doclength();
     81        if (wdf > doclen) wdf = doclen;
     82        RETURN(wt->get_sumpart(wdf, doclen));
     83    }
     84    RETURN(wt->get_sumpart(0, want_doclength ? get_doclength() : 0));
     85}
     86
     87Xapian::weight
     88SynonymPostList::get_maxweight() const
     89{
     90    LOGCALL(MATCH, Xapian::weight, "SynonymPostList::get_maxweight", "");
     91    RETURN(wt->get_maxpart());
     92}
     93
     94Xapian::weight
     95SynonymPostList::recalc_maxweight()
     96{
     97    LOGCALL(MATCH, Xapian::weight, "SynonymPostList::recalc_maxweight", "");
     98    RETURN(SynonymPostList::get_maxweight());
     99}
     100
     101Xapian::termcount
     102SynonymPostList::get_wdf() const {
     103    LOGCALL(MATCH, Xapian::termcount, "SynonymPostList::get_wdf", "");
     104    RETURN(subtree->get_wdf());
     105}
     106
     107Xapian::doccount
     108SynonymPostList::get_termfreq_min() const {
     109    LOGCALL(MATCH, Xapian::doccount, "SynonymPostList::get_termfreq_min", "");
     110    RETURN(subtree->get_termfreq_min());
     111}
     112
     113Xapian::doccount
     114SynonymPostList::get_termfreq_est() const {
     115    LOGCALL(MATCH, Xapian::doccount, "SynonymPostList::get_termfreq_min", "");
     116    RETURN(subtree->get_termfreq_est());
     117}
     118
     119Xapian::doccount
     120SynonymPostList::get_termfreq_max() const {
     121    LOGCALL(MATCH, Xapian::doccount, "SynonymPostList::get_termfreq_min", "");
     122    RETURN(subtree->get_termfreq_max());
     123}
     124
     125Xapian::docid
     126SynonymPostList::get_docid() const {
     127    LOGCALL(MATCH, Xapian::docid, "SynonymPostList::get_docid", "");
     128    RETURN(subtree->get_docid());
     129}
     130
     131Xapian::termcount
     132SynonymPostList::get_doclength() const {
     133    LOGCALL(MATCH, Xapian::termcount, "SynonymPostList::get_doclength", "");
     134    RETURN(subtree->get_doclength());
     135}
     136
     137bool
     138SynonymPostList::at_end() const {
     139    LOGCALL(MATCH, bool, "SynonymPostList::at_end", "");
     140    RETURN(subtree->at_end());
     141}
     142
     143std::string
     144SynonymPostList::get_description() const
     145{
     146    return "(Synonym " + subtree->get_description() + ")";
     147}
  • xapian-core/matcher/multiandpostlist.h

    Property changes on: xapian-core/matcher/synonympostlist.cc
    ___________________________________________________________________
    Added: svn:eol-style
       + native
    
     
    154154    std::string get_description() const;
    155155
    156156    /** get_wdf() for MultiAndPostlists returns the sum of the wdfs of the
    157      *  sub postlists.  The wdf isn't really meaningful in many situations,
    158      *  but if the lists are being combined as a synonym we want the sum of
    159      *  the wdfs, so we do that in general.
     157     *  sub postlists.
     158     *
     159     *  The wdf isn't really meaningful in many situations, but if the lists
     160     *  are being combined as a synonym we want the sum of the wdfs, so we do
     161     *  that in general.
    160162     */
    161     virtual Xapian::termcount get_wdf() const;
    162 };
     163    Xapian::termcount get_wdf() const; };
    163164
    164165#endif // XAPIAN_INCLUDED_MULTIANDPOSTLIST_H
  • xapian-core/matcher/orpostlist.cc

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2001,2002 Ananova Ltd
    55 * Copyright 2003,2004,2007,2008,2009 Olly Betts
     6 * Copyright 2009 Lemur Consulting Ltd
    67 *
    78 * This program is free software; you can redistribute it and/or
    89 * modify it under the terms of the GNU General Public License as
     
    258259
    259260    RETURN(doclength);
    260261}
     262
     263Xapian::termcount
     264OrPostList::get_wdf() const
     265{
     266    DEBUGCALL(MATCH, Xapian::termcount, "OrPostList::get_wdf", "");
     267    if (lhead < rhead) RETURN(l->get_wdf());
     268    if (lhead > rhead) RETURN(r->get_wdf());
     269    RETURN(l->get_wdf() + r->get_wdf());
     270}
  • xapian-core/matcher/andmaybepostlist.cc

     
    33 * Copyright 1999,2000,2001 BrightStation PLC
    44 * Copyright 2002 Ananova Ltd
    55 * Copyright 2003,2004,2005,2008,2009 Olly Betts
     6 * Copyright 2009 Lemur Consulting Ltd
    67 *
    78 * This program is free software; you can redistribute it and/or
    89 * modify it under the terms of the GNU General Public License as
     
    169170    if (lhead == rhead) AssertEq(l->get_doclength(), r->get_doclength());
    170171    RETURN(l->get_doclength());
    171172}
     173
     174Xapian::termcount
     175AndMaybePostList::get_wdf() const
     176{
     177    DEBUGCALL(MATCH, Xapian::termcount, "AndMaybePostList::get_wdf", "");
     178    if (lhead == rhead) RETURN(l->get_wdf() + r->get_wdf());
     179    RETURN(l->get_wdf());
     180}
  • xapian-core/weight/weight.cc

     
    7777    init(factor);
    7878}
    7979
     80void
     81Weight::init_(const Internal & stats, Xapian::termcount query_length,
     82              double factor, Xapian::doccount termfreq)
     83{
     84    LOGCALL_VOID(MATCH, "Weight::init_", stats << ", " << query_length <<
     85            ", " << factor << ", " << termfreq);
     86    // Synonym case.
     87    collection_size_ = stats.collection_size;
     88    rset_size_ = stats.rset_size;
     89    if (stats_needed & AVERAGE_LENGTH)
     90        average_length_ = stats.get_average_length();
     91    if (stats_needed & DOC_LENGTH_MAX)
     92        doclength_upper_bound_ = stats.db.get_doclength_upper_bound();
     93    if (stats_needed & DOC_LENGTH_MIN)
     94        doclength_lower_bound_ = stats.db.get_doclength_lower_bound();
     95
     96    // The doclength is an upper bound on the wdf.  This is obviously true for
     97    // normal terms, but SynonymPostList ensures that it is also true for
     98    // synonym terms by clamping the wdf values returned to the doclength.
     99    //
     100    // (This clamping is only actually necessary in cases where a constituent
     101    // term of the synonym is repeated.)
     102    if (stats_needed & WDF_MAX)
     103        wdf_upper_bound_ = stats.db.get_doclength_upper_bound();
     104
     105    termfreq_ = termfreq;
     106    reltermfreq_ = 0;
     107    query_length_ = query_length;
     108    wqf_ = 1;
     109    init(factor);
     110}
     111
    80112Weight::~Weight() { }
    81113
    82114}
  • xapian-core/tests/api_opsynonym.cc

     
     1/* api_opsynonym.cc: tests which need a backend
     2 *
     3 * Copyright 2009 Olly Betts
     4 * Copyright 2007,2008,2009 Lemur Consulting Ltd
     5 *
     6 * This program is free software; you can redistribute it and/or
     7 * modify it under the terms of the GNU General Public License as
     8 * published by the Free Software Foundation; either version 2 of the
     9 * License, or (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
     19 * USA
     20 */
     21
     22#include <config.h>
     23
     24#include "api_opsynonym.h"
     25
     26#include <map>
     27#include <vector>
     28
     29#include <xapian.h>
     30
     31#include "backendmanager.h"
     32#include "backendmanager_local.h"
     33#include "testsuite.h"
     34#include "testutils.h"
     35
     36#include "apitest.h"
     37
     38using namespace std;
     39
     40// #######################################################################
     41// # Tests start here
     42
     43// Check a synonym search
     44DEFINE_TESTCASE(synonym1, backend) {
     45    Xapian::Database db(get_database("etext"));
     46
     47    TEST_REL(db.get_doclength_upper_bound(), >, 0);
     48
     49    Xapian::doccount lots = 214;
     50
     51    // Make a list of lists of subqueries, which are going to be joined
     52    // together as a synonym.
     53    vector<vector<Xapian::Query> > subqueries_list;
     54
     55    vector<Xapian::Query> subqueries;
     56    subqueries.push_back(Xapian::Query("date"));
     57    subqueries_list.push_back(subqueries);
     58
     59    // Two terms, which co-occur in some documents.
     60    subqueries.clear();
     61    subqueries.push_back(Xapian::Query("sky"));
     62    subqueries.push_back(Xapian::Query("date"));
     63    subqueries_list.push_back(subqueries);
     64
     65    // Two terms which are entirely disjoint, and where the maximum weight
     66    // doesn't occur in the first or second match.
     67    subqueries.clear();
     68    subqueries.push_back(Xapian::Query("gutenberg"));
     69    subqueries.push_back(Xapian::Query("blockhead"));
     70    subqueries_list.push_back(subqueries);
     71
     72    subqueries.clear();
     73    subqueries.push_back(Xapian::Query("date"));
     74    subqueries.push_back(Xapian::Query(Xapian::Query::OP_OR,
     75                                       Xapian::Query("sky"),
     76                                       Xapian::Query("glove")));
     77    subqueries_list.push_back(subqueries);
     78
     79    subqueries.clear();
     80    subqueries.push_back(Xapian::Query("sky"));
     81    subqueries.push_back(Xapian::Query("date"));
     82    subqueries.push_back(Xapian::Query("stein"));
     83    subqueries.push_back(Xapian::Query("ally"));
     84    subqueries_list.push_back(subqueries);
     85
     86    subqueries.clear();
     87    subqueries.push_back(Xapian::Query("attitud"));
     88    subqueries.push_back(Xapian::Query(Xapian::Query::OP_PHRASE,
     89                                       Xapian::Query("german"),
     90                                       Xapian::Query("adventur")));
     91    subqueries_list.push_back(subqueries);
     92
     93    for (vector<vector<Xapian::Query> >::const_iterator
     94         qlist = subqueries_list.begin();
     95         qlist != subqueries_list.end(); ++qlist)
     96    {
     97        // Run two queries, one joining the subqueries with OR and one joining them
     98        // with SYNONYM.
     99        Xapian::Enquire enquire(db);
     100
     101        // Do the search with OR
     102        Xapian::Query orquery(Xapian::Query(Xapian::Query::OP_OR, qlist->begin(), qlist->end()));
     103        enquire.set_query(orquery);
     104        Xapian::MSet ormset = enquire.get_mset(0, lots);
     105
     106        // Do the search with synonym, getting all the results.
     107        Xapian::Query synquery(Xapian::Query::OP_SYNONYM, qlist->begin(), qlist->end());
     108        enquire.set_query(synquery);
     109        Xapian::MSet mset = enquire.get_mset(0, lots);
     110
     111        // Check that the queries return some results.
     112        TEST_NOT_EQUAL(mset.size(), 0);
     113        // Check that the queries return the same number of results.
     114        TEST_EQUAL(mset.size(), ormset.size());
     115        map<Xapian::docid, Xapian::weight> values_or;
     116        map<Xapian::docid, Xapian::weight> values_synonym;
     117        for (Xapian::doccount i = 0; i < mset.size(); ++i) {
     118            values_or[*ormset[i]] = ormset[i].get_weight();
     119            values_synonym[*mset[i]] = mset[i].get_weight();
     120        }
     121        TEST_EQUAL(values_or.size(), values_synonym.size());
     122
     123        /* Check that the most of the weights for items in the "or" mset are
     124         * different from those in the "synonym" mset. */
     125        int same_weight = 0;
     126        int different_weight = 0;
     127        for (map<Xapian::docid, Xapian::weight>::const_iterator
     128             j = values_or.begin();
     129             j != values_or.end(); ++j)
     130        {
     131            Xapian::docid did = j->first;
     132            // Check that all the results in the or tree make it to the synonym tree.
     133            TEST(values_synonym.find(did) != values_synonym.end());
     134            if (values_or[did] == values_synonym[did]) {
     135                same_weight += 1;
     136            } else {
     137                different_weight += 1;
     138            }
     139        }
     140        if (qlist->size() == 1) {
     141            // Had a single term - check that all the weights were the same.
     142            TEST_EQUAL(different_weight, 0);
     143            TEST_NOT_EQUAL(same_weight, 0);
     144        } else {
     145            // Check that most of the weights differ.
     146            TEST_NOT_EQUAL(different_weight, 0);
     147            TEST_REL(same_weight, <, different_weight);
     148        }
     149
     150        // Do the search with synonym, but just get the top result.
     151        // (Regression test - the OR subquery in the synonym postlist tree used
     152        // to shortcut incorrectly, and return the wrong result here).
     153        Xapian::MSet mset_top = enquire.get_mset(0, 1);
     154        TEST_EQUAL(mset_top.size(), 1);
     155        TEST(mset_range_is_same(mset_top, 0, mset, 0, 1));
     156    }
     157    return true;
     158}
     159
     160// Regression test - test a synonym search with a MultiAndPostlist.
     161DEFINE_TESTCASE(synonym2, backend) {
     162    Xapian::Query query;
     163    vector<Xapian::Query> subqueries;
     164    subqueries.push_back(Xapian::Query("file"));
     165    subqueries.push_back(Xapian::Query("the"));
     166    subqueries.push_back(Xapian::Query("next"));
     167    subqueries.push_back(Xapian::Query("reader"));
     168    query = Xapian::Query(Xapian::Query::OP_AND, subqueries.begin(), subqueries.end());
     169    subqueries.clear();
     170    subqueries.push_back(query);
     171    subqueries.push_back(Xapian::Query("gutenberg"));
     172    query = Xapian::Query(Xapian::Query::OP_SYNONYM, subqueries.begin(), subqueries.end());
     173
     174    tout << query.get_description() << endl;
     175
     176    Xapian::Database db(get_database("etext"));
     177    Xapian::Enquire enquire(db);
     178    enquire.set_query(query);
     179    Xapian::MSet mset = enquire.get_mset(0, 10);
     180    tout << mset.get_description() << endl;
     181
     182    // Regression test that OP_SCALE_WEIGHT works with OP_SYNONYM
     183    double maxposs = mset.get_max_possible();
     184    query = Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT, query, 10.0);
     185    enquire.set_query(query);
     186    mset = enquire.get_mset(0, 10);
     187    double maxposs2 = mset.get_max_possible();
     188
     189    TEST_EQUAL_DOUBLE(maxposs * 10.0, maxposs2);
     190
     191    return true;
     192}
     193
     194// Test a synonym search which has had its weight scaled to 0.
     195DEFINE_TESTCASE(synonym3, backend) {
     196    Xapian::Query query = Xapian::Query(Xapian::Query::OP_SYNONYM,
     197                                        Xapian::Query("sky"),
     198                                        Xapian::Query("date"));
     199
     200    Xapian::Database db(get_database("etext"));
     201    Xapian::Enquire enquire(db);
     202    enquire.set_query(query);
     203    Xapian::MSet mset_orig = enquire.get_mset(0, db.get_doccount());
     204
     205    tout << query.get_description() << endl;
     206    tout << mset_orig.get_description() << endl;
     207
     208    // Test that OP_SCALE_WEIGHT with a factor of 0.0 works with OP_SYNONYM
     209    // (this has a special codepath to avoid doing the synonym calculation).
     210    query = Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT, query, 0.0);
     211    enquire.set_query(query);
     212    Xapian::MSet mset_zero = enquire.get_mset(0, db.get_doccount());
     213
     214    tout << query.get_description() << endl;
     215    tout << mset_zero.get_description() << endl;
     216
     217    // Check that the queries return some results.
     218    TEST_NOT_EQUAL(mset_zero.size(), 0);
     219    // Check that the queries return the same document IDs, and the the zero
     220    // one has zero weight.
     221    TEST_EQUAL(mset_zero.size(), mset_orig.size());
     222
     223    map<Xapian::docid, Xapian::weight> values_orig;
     224    map<Xapian::docid, Xapian::weight> values_zero;
     225    for (Xapian::doccount i = 0; i < mset_zero.size(); ++i) {
     226        TEST_NOT_EQUAL(mset_orig[i].get_weight(), 0.0);
     227        TEST_EQUAL(mset_zero[i].get_weight(), 0.0);
     228
     229        values_orig[*mset_orig[i]] = mset_orig[i].get_weight();
     230        values_zero[*mset_zero[i]] = mset_zero[i].get_weight();
     231    }
     232
     233    for (map<Xapian::docid, Xapian::weight>::const_iterator
     234         j = values_orig.begin();
     235         j != values_orig.end(); ++j)
     236    {
     237        Xapian::docid did = j->first;
     238        // Check that all the results in the orig mset are in the zero mset.
     239        TEST(values_zero.find(did) != values_zero.end());
     240    }
     241    TEST_EQUAL(values_orig.size(), values_zero.size());
     242
     243    return true;
     244}
  • xapian-core/tests/queryparsertest.cc

    Property changes on: xapian-core/tests/api_opsynonym.cc
    ___________________________________________________________________
    Added: svn:eol-style
       + native
    
     
    11/* queryparsertest.cc: Tests of Xapian::QueryParser
    22 *
    33 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009 Olly Betts
     4 * Copyright (C) 2007,2009 Lemur Consulting Ltd
    45 *
    56 * This program is free software; you can redistribute it and/or
    67 * modify it under the terms of the GNU General Public License as
     
    786787    Xapian::Query qobj = qp.parse_query("ab*", Xapian::QueryParser::FLAG_WILDCARD);
    787788    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(abc:(pos=1))");
    788789    qobj = qp.parse_query("muscle*", Xapian::QueryParser::FLAG_WILDCARD);
    789     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((muscle:(pos=1) OR musclebound:(pos=1)))");
     790    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((muscle:(pos=1) SYNONYM musclebound:(pos=1)))");
    790791    qobj = qp.parse_query("meat*", Xapian::QueryParser::FLAG_WILDCARD);
    791792    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query()");
    792793    qobj = qp.parse_query("musc*", Xapian::QueryParser::FLAG_WILDCARD);
    793     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((muscat:(pos=1) OR muscle:(pos=1) OR musclebound:(pos=1) OR muscular:(pos=1)))");
     794    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((muscat:(pos=1) SYNONYM muscle:(pos=1) SYNONYM musclebound:(pos=1) SYNONYM muscular:(pos=1)))");
    794795    qobj = qp.parse_query("mutt*", Xapian::QueryParser::FLAG_WILDCARD);
    795796    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(mutton:(pos=1))");
    796797    // Regression test (we weren't lowercasing terms before checking if they
     
    879880    qp.add_prefix("author", "A");
    880881    Xapian::Query qobj;
    881882    qobj = qp.parse_query("author:h*", Xapian::QueryParser::FLAG_WILDCARD);
    882     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((Aheinlein:(pos=1) OR Ahuxley:(pos=1)))");
     883    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((Aheinlein:(pos=1) SYNONYM Ahuxley:(pos=1)))");
    883884    qobj = qp.parse_query("author:h* test", Xapian::QueryParser::FLAG_WILDCARD);
    884     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((Aheinlein:(pos=1) OR Ahuxley:(pos=1) OR test:(pos=2)))");
     885    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((Aheinlein:(pos=1) SYNONYM Ahuxley:(pos=1)) OR test:(pos=2)))");
    885886    return true;
    886887}
    887888
     
    907908    doc.add_term("XTcowl");
    908909    doc.add_term("XTcox");
    909910    doc.add_term("ZXTcow");
     911    doc.add_term("XONEpartial");
     912    doc.add_term("XONEpartial2");
     913    doc.add_term("XTWOpartial3");
     914    doc.add_term("XTWOpartial4");
    910915    db.add_document(doc);
    911916    Xapian::QueryParser qp;
    912917    qp.set_database(db);
     
    922927    qobj = qp.parse_query("ab", Xapian::QueryParser::FLAG_PARTIAL);
    923928    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((abc:(pos=1) OR Zab:(pos=1)))");
    924929    qobj = qp.parse_query("muscle", Xapian::QueryParser::FLAG_PARTIAL);
    925     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((muscle:(pos=1) OR musclebound:(pos=1) OR Zmuscl:(pos=1)))");
     930    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((muscle:(pos=1) SYNONYM musclebound:(pos=1)) OR Zmuscl:(pos=1)))");
    926931    qobj = qp.parse_query("meat", Xapian::QueryParser::FLAG_PARTIAL);
    927932    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(Zmeat:(pos=1))");
    928933    qobj = qp.parse_query("musc", Xapian::QueryParser::FLAG_PARTIAL);
    929     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((muscat:(pos=1) OR muscle:(pos=1) OR musclebound:(pos=1) OR muscular:(pos=1) OR Zmusc:(pos=1)))");
     934    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((muscat:(pos=1) SYNONYM muscle:(pos=1) SYNONYM musclebound:(pos=1) SYNONYM muscular:(pos=1)) OR Zmusc:(pos=1)))");
    930935    qobj = qp.parse_query("mutt", Xapian::QueryParser::FLAG_PARTIAL);
    931936    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((mutton:(pos=1) OR Zmutt:(pos=1)))");
    932937    qobj = qp.parse_query("abc musc", Xapian::QueryParser::FLAG_PARTIAL);
    933     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((Zabc:(pos=1) OR muscat:(pos=2) OR muscle:(pos=2) OR musclebound:(pos=2) OR muscular:(pos=2) OR Zmusc:(pos=2)))");
     938    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((Zabc:(pos=1) OR (muscat:(pos=2) SYNONYM muscle:(pos=2) SYNONYM musclebound:(pos=2) SYNONYM muscular:(pos=2)) OR Zmusc:(pos=2)))");
    934939    qobj = qp.parse_query("a* mutt", Xapian::QueryParser::FLAG_PARTIAL | Xapian::QueryParser::FLAG_WILDCARD);
    935940    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((abc:(pos=1) OR mutton:(pos=2) OR Zmutt:(pos=2)))");
    936941
    937942    // Check behaviour with stemmed terms, and stem strategy STEM_SOME.
    938943    qobj = qp.parse_query("o", Xapian::QueryParser::FLAG_PARTIAL);
    939     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((out:(pos=1) OR outside:(pos=1) OR Zo:(pos=1)))");
     944    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((out:(pos=1) SYNONYM outside:(pos=1)) OR Zo:(pos=1)))");
    940945    qobj = qp.parse_query("ou", Xapian::QueryParser::FLAG_PARTIAL);
    941     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((out:(pos=1) OR outside:(pos=1) OR Zou:(pos=1)))");
     946    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((out:(pos=1) SYNONYM outside:(pos=1)) OR Zou:(pos=1)))");
    942947    qobj = qp.parse_query("out", Xapian::QueryParser::FLAG_PARTIAL);
    943     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((out:(pos=1) OR outside:(pos=1) OR Zout:(pos=1)))");
     948    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((out:(pos=1) SYNONYM outside:(pos=1)) OR Zout:(pos=1)))");
    944949    qobj = qp.parse_query("outs", Xapian::QueryParser::FLAG_PARTIAL);
    945950    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((outside:(pos=1) OR Zout:(pos=1)))");
    946951    qobj = qp.parse_query("outsi", Xapian::QueryParser::FLAG_PARTIAL);
     
    952957
    953958    // Check behaviour with capitalised terms, and stem strategy STEM_SOME.
    954959    qobj = qp.parse_query("Out", Xapian::QueryParser::FLAG_PARTIAL);
    955     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((out:(pos=1,wqf=2) OR outside:(pos=1)))");
     960    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((out:(pos=1) SYNONYM outside:(pos=1)) OR out:(pos=1)))");
    956961    qobj = qp.parse_query("Outs", Xapian::QueryParser::FLAG_PARTIAL);
    957962    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((outside:(pos=1) OR outs:(pos=1)))");
    958963    qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
     
    961966    // And now with stemming strategy STEM_ALL.
    962967    qp.set_stemming_strategy(Xapian::QueryParser::STEM_ALL);
    963968    qobj = qp.parse_query("Out", Xapian::QueryParser::FLAG_PARTIAL);
    964     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((out:(pos=1,wqf=2) OR outside:(pos=1)))");
     969    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((out:(pos=1) SYNONYM outside:(pos=1)) OR out:(pos=1)))");
    965970    qobj = qp.parse_query("Outs", Xapian::QueryParser::FLAG_PARTIAL);
    966971    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((outside:(pos=1) OR out:(pos=1)))");
    967972    qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
     
    970975    // Check handling of a case with a prefix.
    971976    qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
    972977    qobj = qp.parse_query("title:cow", Xapian::QueryParser::FLAG_PARTIAL);
    973     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((XTcowl:(pos=1) OR XTcows:(pos=1) OR ZXTcow:(pos=1)))");
     978    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((XTcowl:(pos=1) SYNONYM XTcows:(pos=1)) OR ZXTcow:(pos=1)))");
    974979    qobj = qp.parse_query("title:cows", Xapian::QueryParser::FLAG_PARTIAL);
    975980    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((XTcows:(pos=1) OR ZXTcow:(pos=1)))");
    976981    qobj = qp.parse_query("title:Cow", Xapian::QueryParser::FLAG_PARTIAL);
    977     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((XTcowl:(pos=1) OR XTcows:(pos=1) OR XTcow:(pos=1)))");
     982    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((XTcowl:(pos=1) SYNONYM XTcows:(pos=1)) OR XTcow:(pos=1)))");
    978983    qobj = qp.parse_query("title:Cows", Xapian::QueryParser::FLAG_PARTIAL);
    979984    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(XTcows:(pos=1,wqf=2))");
    980985
     
    982987    // inflate the wqf of the "parsed as normal" version of a partial term
    983988    // by multiplying it by the number of prefixes mapped to.
    984989    qobj = qp.parse_query("double:vision", Xapian::QueryParser::FLAG_PARTIAL);
    985     TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((ZXONEvision:(pos=1) OR ZXTWOvision:(pos=1)))");
     990    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query((ZXONEvision:(pos=1) SYNONYM ZXTWOvision:(pos=1)))");
     991
     992    // Test handling of FLAG_PARTIAL when there's more than one prefix.
     993    qobj = qp.parse_query("double:part", Xapian::QueryParser::FLAG_PARTIAL);
     994    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((XONEpartial:(pos=1) SYNONYM XONEpartial2:(pos=1) SYNONYM XTWOpartial3:(pos=1) SYNONYM XTWOpartial4:(pos=1)) OR (ZXONEpart:(pos=1) SYNONYM ZXTWOpart:(pos=1))))");
     995
     996    // Test handling of FLAG_PARTIAL when there's more than one prefix, without
     997    // stemming.
     998    qp.set_stemming_strategy(Xapian::QueryParser::STEM_NONE);
     999    qobj = qp.parse_query("double:part", Xapian::QueryParser::FLAG_PARTIAL);
     1000    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((XONEpartial:(pos=1) SYNONYM XONEpartial2:(pos=1) SYNONYM XTWOpartial3:(pos=1) SYNONYM XTWOpartial4:(pos=1)) OR (XONEpart:(pos=1) SYNONYM XTWOpart:(pos=1))))");
     1001    qobj = qp.parse_query("double:partial", Xapian::QueryParser::FLAG_PARTIAL);
     1002    TEST_STRINGS_EQUAL(qobj.get_description(), "Xapian::Query(((XONEpartial:(pos=1) SYNONYM XONEpartial2:(pos=1) SYNONYM XTWOpartial3:(pos=1) SYNONYM XTWOpartial4:(pos=1)) OR (XONEpartial:(pos=1) SYNONYM XTWOpartial:(pos=1))))");
    9861003
    9871004    return true;
    9881005}
     
    15471564}
    15481565
    15491566static test test_synonym_queries[] = {
    1550     { "searching", "(Zsearch:(pos=1) OR Zfind:(pos=1) OR Zlocate:(pos=1))" },
    1551     { "search", "(Zsearch:(pos=1) OR find:(pos=1))" },
    1552     { "Search", "(search:(pos=1) OR find:(pos=1))" },
     1567    { "searching", "(Zsearch:(pos=1) SYNONYM Zfind:(pos=1) SYNONYM Zlocate:(pos=1))" },
     1568    { "search", "(Zsearch:(pos=1) SYNONYM find:(pos=1))" },
     1569    { "Search", "(search:(pos=1) SYNONYM find:(pos=1))" },
    15531570    { "Searching", "searching:(pos=1)" },
    1554     { "searching OR terms", "(Zsearch:(pos=1) OR Zfind:(pos=1) OR Zlocate:(pos=1) OR Zterm:(pos=2))" },
    1555     { "search OR terms", "(Zsearch:(pos=1) OR find:(pos=1) OR Zterm:(pos=2))" },
    1556     { "search +terms", "(Zterm:(pos=2) AND_MAYBE (Zsearch:(pos=1) OR find:(pos=1)))" },
    1557     { "search -terms", "((Zsearch:(pos=1) OR find:(pos=1)) AND_NOT Zterm:(pos=2))" },
    1558     { "+search terms", "((Zsearch:(pos=1) OR find:(pos=1)) AND_MAYBE Zterm:(pos=2))" },
    1559     { "-search terms", "(Zterm:(pos=2) AND_NOT (Zsearch:(pos=1) OR find:(pos=1)))" },
    1560     { "search terms", "(Zsearch:(pos=1) OR find:(pos=1) OR Zterm:(pos=2))" },
     1571    { "searching OR terms", "((Zsearch:(pos=1) SYNONYM Zfind:(pos=1) SYNONYM Zlocate:(pos=1)) OR Zterm:(pos=2))" },
     1572    { "search OR terms", "((Zsearch:(pos=1) SYNONYM find:(pos=1)) OR Zterm:(pos=2))" },
     1573    { "search +terms", "(Zterm:(pos=2) AND_MAYBE (Zsearch:(pos=1) SYNONYM find:(pos=1)))" },
     1574    { "search -terms", "((Zsearch:(pos=1) SYNONYM find:(pos=1)) AND_NOT Zterm:(pos=2))" },
     1575    { "+search terms", "((Zsearch:(pos=1) SYNONYM find:(pos=1)) AND_MAYBE Zterm:(pos=2))" },
     1576    { "-search terms", "(Zterm:(pos=2) AND_NOT (Zsearch:(pos=1) SYNONYM find:(pos=1)))" },
     1577    { "search terms", "((Zsearch:(pos=1) SYNONYM find:(pos=1)) OR Zterm:(pos=2))" },
    15611578    // Shouldn't trigger synonyms:
    15621579    { "\"search terms\"", "(search:(pos=1) PHRASE 2 terms:(pos=2))" },
    15631580    { NULL, NULL }
     
    15971614
    15981615static test test_multi_synonym_queries[] = {
    15991616    { "sun OR tan OR cream", "(Zsun:(pos=1) OR Ztan:(pos=2) OR Zcream:(pos=3))" },
    1600     { "sun tan", "(Zsun:(pos=1) OR Ztan:(pos=2) OR bathe:(pos=1))" },
    1601     { "sun tan cream", "(Zsun:(pos=1) OR Ztan:(pos=2) OR Zcream:(pos=3) OR lotion:(pos=1))" },
    1602     { "beach sun tan holiday", "(Zbeach:(pos=1) OR Zsun:(pos=2) OR Ztan:(pos=3) OR bathe:(pos=2) OR Zholiday:(pos=4))" },
    1603     { "sun tan sun tan cream", "(Zsun:(pos=1) OR Ztan:(pos=2) OR bathe:(pos=1) OR Zsun:(pos=3) OR Ztan:(pos=4) OR Zcream:(pos=5) OR lotion:(pos=3))" },
    1604     { "single", "(Zsingl:(pos=1) OR record:(pos=1))" },
     1617    { "sun tan", "((Zsun:(pos=1) OR Ztan:(pos=2)) SYNONYM bathe:(pos=1))" },
     1618    { "sun tan cream", "((Zsun:(pos=1) OR Ztan:(pos=2) OR Zcream:(pos=3)) SYNONYM lotion:(pos=1))" },
     1619    { "beach sun tan holiday", "(Zbeach:(pos=1) OR ((Zsun:(pos=2) OR Ztan:(pos=3)) SYNONYM bathe:(pos=2)) OR Zholiday:(pos=4))" },
     1620    { "sun tan sun tan cream", "(((Zsun:(pos=1) OR Ztan:(pos=2)) SYNONYM bathe:(pos=1)) OR ((Zsun:(pos=3) OR Ztan:(pos=4) OR Zcream:(pos=5)) SYNONYM lotion:(pos=3)))" },
     1621    { "single", "(Zsingl:(pos=1) SYNONYM record:(pos=1))" },
    16051622    { NULL, NULL }
    16061623};
    16071624
     
    16401657
    16411658static test test_synonym_op_queries[] = {
    16421659    { "searching", "Zsearch:(pos=1)" },
    1643     { "~searching", "(Zsearch:(pos=1) OR Zfind:(pos=1) OR Zlocate:(pos=1))" },
    1644     { "~search", "(Zsearch:(pos=1) OR find:(pos=1))" },
    1645     { "~Search", "(search:(pos=1) OR find:(pos=1))" },
     1660    { "~searching", "(Zsearch:(pos=1) SYNONYM Zfind:(pos=1) SYNONYM Zlocate:(pos=1))" },
     1661    { "~search", "(Zsearch:(pos=1) SYNONYM find:(pos=1))" },
     1662    { "~Search", "(search:(pos=1) SYNONYM find:(pos=1))" },
    16461663    { "~Searching", "searching:(pos=1)" },
    1647     { "~searching OR terms", "(Zsearch:(pos=1) OR Zfind:(pos=1) OR Zlocate:(pos=1) OR Zterm:(pos=2))" },
    1648     { "~search OR terms", "(Zsearch:(pos=1) OR find:(pos=1) OR Zterm:(pos=2))" },
    1649     { "~search +terms", "(Zterm:(pos=2) AND_MAYBE (Zsearch:(pos=1) OR find:(pos=1)))" },
    1650     { "~search -terms", "((Zsearch:(pos=1) OR find:(pos=1)) AND_NOT Zterm:(pos=2))" },
    1651     { "+~search terms", "((Zsearch:(pos=1) OR find:(pos=1)) AND_MAYBE Zterm:(pos=2))" },
    1652     { "-~search terms", "(Zterm:(pos=2) AND_NOT (Zsearch:(pos=1) OR find:(pos=1)))" },
    1653     { "~search terms", "(Zsearch:(pos=1) OR find:(pos=1) OR Zterm:(pos=2))" },
     1664    { "~searching OR terms", "((Zsearch:(pos=1) SYNONYM Zfind:(pos=1) SYNONYM Zlocate:(pos=1)) OR Zterm:(pos=2))" },
     1665    { "~search OR terms", "((Zsearch:(pos=1) SYNONYM find:(pos=1)) OR Zterm:(pos=2))" },
     1666    { "~search +terms", "(Zterm:(pos=2) AND_MAYBE (Zsearch:(pos=1) SYNONYM find:(pos=1)))" },
     1667    { "~search -terms", "((Zsearch:(pos=1) SYNONYM find:(pos=1)) AND_NOT Zterm:(pos=2))" },
     1668    { "+~search terms", "((Zsearch:(pos=1) SYNONYM find:(pos=1)) AND_MAYBE Zterm:(pos=2))" },
     1669    { "-~search terms", "(Zterm:(pos=2) AND_NOT (Zsearch:(pos=1) SYNONYM find:(pos=1)))" },
     1670    { "~search terms", "((Zsearch:(pos=1) SYNONYM find:(pos=1)) OR Zterm:(pos=2))" },
    16541671    // FIXME: should look for multi-term synonym...
    16551672    { "~\"search terms\"", "(search:(pos=1) PHRASE 2 terms:(pos=2))" },
    16561673    { NULL, NULL }
  • xapian-core/tests/Makefile.am

     
    116116 api_db.cc \
    117117 api_generated.cc \
    118118 api_nodb.cc \
     119 api_opsynonym.cc \
    119120 api_percentages.cc \
    120121 api_posdb.cc \
    121122 api_query.cc \
  • xapian-core/include/xapian/query.h

     
    119119            OP_VALUE_GE,
    120120
    121121            /** Filter by a less-than-or-equal test on a document value. */
    122             OP_VALUE_LE
     122            OP_VALUE_LE,
     123
     124            /** Treat a set of queries as synonyms.
     125             *
     126             *  This returns all results which match at least one of the
     127             *  queries, but weighting as if all the sub-queries are instances
     128             *  of the same term: so multiple matching terms for a document
     129             *  increase the wdf value used, and the term frequency is based on
     130             *  the number of documents which would match an OR of all the
     131             *  subqueries.
     132             *
     133             *  The term frequency used will usually be an approximation,
     134             *  because calculating the precise combined term frequency would
     135             *  be overly expensive.
     136             *
     137             *  Identical to OP_OR, except for the weightings returned.
     138             */
     139            OP_SYNONYM
    123140        } op;
    124141
    125142        /** Copy constructor. */
  • xapian-core/include/xapian/weight.h

     
    22 * @brief Weighting scheme API.
    33 */
    44/* Copyright (C) 2007,2008,2009 Olly Betts
     5 * Copyright (C) 2009 Lemur Consulting Ltd
    56 *
    67 * This program is free software; you can redistribute it and/or
    78 * modify it under the terms of the GNU General Public License as
     
    212213               const std::string & term, Xapian::termcount wqf_,
    213214               double factor);
    214215
     216    /** @private @internal Initialise this object to calculate weights for a
     217     *  synonym.
     218     *
     219     *  @param stats       Source of statistics.
     220     *  @param query_len_  Query length.
     221     *  @param factor      Any scaling factor (e.g. from OP_SCALE_WEIGHT).
     222     *  @param termfreq    The termfreq to use.
     223     */
     224    void init_(const Internal & stats, Xapian::termcount query_len_,
     225               double factor, Xapian::doccount termfreq);
     226
    215227    /** @private @internal Initialise this object to calculate the extra weight
    216228     *  component.
    217229     *
     
    230242        return stats_needed & DOC_LENGTH;
    231243    }
    232244
     245    /** @private @internal Return true if the WDF is needed.
     246     *
     247     *  If this method returns true, then the WDF will be fetched and passed to
     248     *  @a get_sumpart().  Otherwise 0 may be passed for the wdf.
     249     */
     250    bool get_sumpart_needs_wdf_() const {
     251        return stats_needed & WDF;
     252    }
     253
    233254  protected:
    234255    /// Only allow subclasses to copy us.
    235256    Weight(const Weight &);
     
    372393        need_stat(TERMFREQ);
    373394        need_stat(RELTERMFREQ);
    374395        need_stat(WDF_MAX);
     396        need_stat(WDF);
    375397        if (param_k2 != 0 || (param_k1 != 0 && param_b != 0)) {
    376398            need_stat(DOC_LENGTH_MIN);
    377399            need_stat(AVERAGE_LENGTH);
     
    390412        need_stat(TERMFREQ);
    391413        need_stat(RELTERMFREQ);
    392414        need_stat(WDF_MAX);
     415        need_stat(WDF);
    393416        need_stat(DOC_LENGTH_MIN);
    394417        need_stat(AVERAGE_LENGTH);
    395418        need_stat(DOC_LENGTH);
     
    452475        need_stat(RELTERMFREQ);
    453476        need_stat(DOC_LENGTH_MIN);
    454477        need_stat(WDF_MAX);
     478        need_stat(WDF);
    455479    }
    456480
    457481    std::string name() const;
  • xapian-core/api/omqueryinternal.cc

     
    6565        case Xapian::Query::OP_VALUE_RANGE:
    6666        case Xapian::Query::OP_VALUE_GE:
    6767        case Xapian::Query::OP_VALUE_LE:
     68        case Xapian::Query::OP_SYNONYM:
    6869            return 0;
    6970        case Xapian::Query::OP_SCALE_WEIGHT:
    7071            return 1;
     
    100101        case Xapian::Query::OP_NEAR:
    101102        case Xapian::Query::OP_PHRASE:
    102103        case Xapian::Query::OP_ELITE_SET:
     104        case Xapian::Query::OP_SYNONYM:
    103105            return UINT_MAX;
    104106        default:
    105107            Assert(false);
     
    221223                result += ".";
    222224                result += str_parameter; // serialise_double(get_dbl_parameter());
    223225                break;
     226            case Xapian::Query::OP_SYNONYM:
     227                result += "=";
     228                break;
    224229        }
    225230    }
    226231    return result;
     
    251256        case Xapian::Query::OP_VALUE_GE:        name = "VALUE_GE"; break;
    252257        case Xapian::Query::OP_VALUE_LE:        name = "VALUE_LE"; break;
    253258        case Xapian::Query::OP_SCALE_WEIGHT:    name = "SCALE_WEIGHT"; break;
     259        case Xapian::Query::OP_SYNONYM:         name = "SYNONYM"; break;
    254260    }
    255261    return name;
    256262}
     
    584590                    return qint_from_vector(Xapian::Query::OP_SCALE_WEIGHT,
    585591                                            subqs, 0, param);
    586592                }
    587                 default:
     593                case '=': {
     594                    return qint_from_vector(Xapian::Query::OP_SYNONYM, subqs);
     595                }
     596                default:
    588597                    LOGLINE(UNKNOWN, "Can't parse remainder `" << p - 1 << "'");
    589598                    throw Xapian::InvalidArgumentError("Invalid query string");
    590599            }
     
    809818        case OP_ELITE_SET:
    810819        case OP_OR:
    811820        case OP_XOR:
     821        case OP_SYNONYM:
    812822            // Doing an "OR" type operation - if we've got any MatchNothing
    813823            // subnodes, drop them; except that we mustn't become an empty
    814824            // node due to this, so we never drop a MatchNothing subnode
     
    900910                }
    901911            }
    902912            break;
    903         case OP_OR: case OP_AND: case OP_XOR:
     913        case OP_OR: case OP_AND: case OP_XOR: case OP_SYNONYM:
    904914            // Remove duplicates if we can.
    905915            if (subqs.size() > 1) collapse_subqs();
    906916            break;
     
    944954void
    945955Xapian::Query::Internal::collapse_subqs()
    946956{
    947     Assert(op == OP_OR || op == OP_AND || op == OP_XOR);
     957    Assert(op == OP_OR || op == OP_AND || op == OP_XOR || op == OP_SYNONYM);
    948958    typedef set<Xapian::Query::Internal *, SortPosName> subqtable;
    949959    subqtable sqtab;
    950960
     
    10381048    Assert(!is_leaf(op));
    10391049    if (subq == 0) {
    10401050        subqs.push_back(0);
    1041     } else if (op == subq->op && (op == OP_AND || op == OP_OR || op == OP_XOR)) {
     1051    } else if (op == subq->op && (op == OP_AND || op == OP_OR || op == OP_XOR || op == OP_SYNONYM)) {
    10421052        // Distribute the subquery.
    10431053        for (subquery_list::const_iterator i = subq->subqs.begin();
    10441054             i != subq->subqs.end(); i++) {
     
    10551065    Assert(!is_leaf(op));
    10561066    if (subq == 0) {
    10571067        subqs.push_back(0);
    1058     } else if (op == subq->op && (op == OP_AND || op == OP_OR || op == OP_XOR)) {
     1068    } else if (op == subq->op && (op == OP_AND || op == OP_OR || op == OP_XOR || op == OP_SYNONYM)) {
    10591069        // Distribute the subquery.
    10601070        for (subquery_list::const_iterator i = subq->subqs.begin();
    10611071             i != subq->subqs.end(); i++) {
  • xapian-bindings/python/smoketest2.py

     
    213213    qp.set_stemming_strategy(qp.STEM_SOME)
    214214    qp.set_stemmer(xapian.Stem('en'))
    215215    expect_query(qp.parse_query("foo o", qp.FLAG_PARTIAL),
    216                  "(Zfoo:(pos=1) AND (out:(pos=2) OR outsid:(pos=2) OR Zo:(pos=2)))")
     216                 "(Zfoo:(pos=1) AND ((out:(pos=2) SYNONYM outsid:(pos=2)) OR Zo:(pos=2)))")
    217217
    218218    expect_query(qp.parse_query("foo outside", qp.FLAG_PARTIAL),
    219219                 "(Zfoo:(pos=1) AND Zoutsid:(pos=2))")
  • xapian-bindings/python/smoketest3.py

     
    153153
    154154    # Feature test for Document.values
    155155    count = 0
    156     for term in doc.values():
     156    for term in list(doc.values()):
    157157        count += 1
    158158    expect(count, 0, "Unexpected number of entries in doc.values")
    159159
     
    213213    qp.set_stemming_strategy(qp.STEM_SOME)
    214214    qp.set_stemmer(xapian.Stem('en'))
    215215    expect_query(qp.parse_query("foo o", qp.FLAG_PARTIAL),
    216                  "(Zfoo:(pos=1) AND (out:(pos=2) OR outsid:(pos=2) OR Zo:(pos=2)))")
     216                 "(Zfoo:(pos=1) AND ((out:(pos=2) SYNONYM outsid:(pos=2)) OR Zo:(pos=2)))")
    217217
    218218    expect_query(qp.parse_query("foo outside", qp.FLAG_PARTIAL),
    219219                 "(Zfoo:(pos=1) AND Zoutsid:(pos=2))")