sc/Library_sc.mk                       |    1 
 sc/inc/queryevaluator.hxx              |  123 ++++
 sc/inc/table.hxx                       |   17 
 sc/source/core/data/dociter.cxx        |   23 
 sc/source/core/data/queryevaluator.cxx |  957 +++++++++++++++++++++++++++++++++
 sc/source/core/data/table2.cxx         |    8 
 sc/source/core/data/table3.cxx         |  954 --------------------------------
 7 files changed, 1107 insertions(+), 976 deletions(-)

New commits:
commit 75b1938276da58a3f28ece362cc389049e79d1c0
Author:     Luboš Luňák <l.lu...@collabora.com>
AuthorDate: Sat Nov 27 23:55:41 2021 +0100
Commit:     Adolfo Jayme Barrientos <fit...@ubuntu.com>
CommitDate: Sun Dec 5 18:09:00 2021 +0100

    move entire ScTable::ValidQuery() into (Sc)QueryEvaluator
    
    This reduces the number of arguments passed around, removed the need
    for ValidQueryCache (as the data can be now cached in the class
    itself), it'll allow even more optimizations, and it also makes
    the by now rather large (almost 1000 lines) helper class a proper
    class instead of tons of inline code.
    
    Change-Id: I585cf580b3e7b2d4512aa535176e97c0abfd547a
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/126367
    Tested-by: Jenkins
    Reviewed-by: Luboš Luňák <l.lu...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/126299
    Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com>

diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index ef9c62f42d53..b3c63122f8f6 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -177,6 +177,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
     sc/source/core/data/pivot2 \
     sc/source/core/data/poolhelp \
     sc/source/core/data/postit \
+    sc/source/core/data/queryevaluator \
     sc/source/core/data/refupdatecontext \
     sc/source/core/data/rowheightcontext \
     sc/source/core/data/segmenttree \
diff --git a/sc/inc/queryevaluator.hxx b/sc/inc/queryevaluator.hxx
new file mode 100644
index 000000000000..3caa1c57dfcf
--- /dev/null
+++ b/sc/inc/queryevaluator.hxx
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <unordered_map>
+
+#include "queryentry.hxx"
+
+class ScDocument;
+class ScTable;
+struct ScQueryParam;
+class CollatorWrapper;
+struct ScRefCellValue;
+struct ScInterpreterContext;
+
+namespace sc
+{
+class TableColumnBlockPositionSet;
+}
+namespace svl
+{
+class SharedStringPool;
+}
+namespace utl
+{
+class TransliterationWrapper;
+}
+
+class ScQueryEvaluator
+{
+    ScDocument& mrDoc;
+    svl::SharedStringPool& mrStrPool;
+    const ScTable& mrTab;
+    const ScQueryParam& mrParam;
+    bool* mpTestEqualCondition;
+    utl::TransliterationWrapper* mpTransliteration;
+    CollatorWrapper* mpCollator;
+    const bool mbMatchWholeCell;
+    const bool mbCaseSensitive;
+    const ScInterpreterContext* mpContext;
+
+    const SCSIZE mnEntryCount;
+    bool* mpPasst;
+    bool* mpTest;
+    static constexpr SCSIZE nFixedBools = 32;
+    bool maBool[nFixedBools];
+    bool maTest[nFixedBools];
+    std::unique_ptr<bool[]> mpBoolDynamic;
+    std::unique_ptr<bool[]> mpTestDynamic;
+
+    std::unordered_map<FormulaError, svl::SharedString> 
mCachedSharedErrorStrings;
+    std::vector<double> mCachedSortedItemValues;
+    std::vector<const rtl_uString*> mCachedSortedItemStrings;
+    bool mCachedSortedItemValuesReady = false;
+    bool mCachedSortedItemStringsReady = false;
+
+    static bool isPartialTextMatchOp(const ScQueryEntry& rEntry);
+    static bool isTextMatchOp(const ScQueryEntry& rEntry);
+    void setupTransliteratorIfNeeded();
+    void setupCollatorIfNeeded();
+
+    bool isRealWildOrRegExp(const ScQueryEntry& rEntry) const;
+    bool isTestWildOrRegExp(const ScQueryEntry& rEntry) const;
+    static bool isQueryByValue(const ScQueryEntry::Item& rItem, const 
ScRefCellValue& rCell);
+    static bool isQueryByValueForCell(const ScRefCellValue& rCell);
+    static bool isQueryByString(const ScQueryEntry& rEntry, const 
ScQueryEntry::Item& rItem,
+                                const ScRefCellValue& rCell);
+
+    sal_uInt32 getNumFmt(SCCOL nCol, SCROW nRow);
+
+    std::pair<bool, bool> compareByValue(const ScRefCellValue& rCell, SCCOL 
nCol, SCROW nRow,
+                                         const ScQueryEntry& rEntry,
+                                         const ScQueryEntry::Item& rItem);
+
+    OUString getCellString(const ScRefCellValue& rCell, SCROW nRow, const 
ScQueryEntry& rEntry,
+                           const svl::SharedString** sharedString);
+
+    bool isFastCompareByString(const ScQueryEntry& rEntry) const;
+    template <bool bFast = false>
+    std::pair<bool, bool>
+    compareByString(const ScQueryEntry& rEntry, const ScQueryEntry::Item& 
rItem,
+                    const svl::SharedString* pValueSource1, const OUString* 
pValueSource2);
+    std::pair<bool, bool> compareByTextColor(SCCOL nCol, SCROW nRow,
+                                             const ScQueryEntry::Item& rItem);
+    std::pair<bool, bool> compareByBackgroundColor(SCCOL nCol, SCROW nRow,
+                                                   const ScQueryEntry::Item& 
rItem);
+
+    static std::pair<bool, bool> compareByRangeLookup(const ScRefCellValue& 
rCell,
+                                                      const ScQueryEntry& 
rEntry,
+                                                      const 
ScQueryEntry::Item& rItem);
+
+    std::pair<bool, bool> processEntry(SCROW nRow, SCCOL nCol, ScRefCellValue& 
aCell,
+                                       const ScQueryEntry& rEntry);
+
+public:
+    ScQueryEvaluator(ScDocument& rDoc, const ScTable& rTab, const 
ScQueryParam& rParam,
+                     const ScInterpreterContext* pContext = nullptr,
+                     bool* pTestEqualCondition = nullptr);
+
+    bool ValidQuery(SCROW nRow, const ScRefCellValue* pCell = nullptr,
+                    sc::TableColumnBlockPositionSet* pBlockPos = nullptr);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx
index 0bb6548d5dd9..672d8431dfe4 100644
--- a/sc/inc/table.hxx
+++ b/sc/inc/table.hxx
@@ -466,6 +466,7 @@ public:
                         return aCol[rPos.Col()].GetCellType( rPos.Row() );
                     }
     CellType    GetCellType( SCCOL nCol, SCROW nRow ) const;
+    ScRefCellValue GetCellValue( SCCOL nCol, sc::ColumnBlockPosition& 
rBlockPos, SCROW nRow );
     ScRefCellValue GetCellValue( SCCOL nCol, SCROW nRow ) const;
 
     void        GetFirstDataPos(SCCOL& rCol, SCROW& rRow) const;
@@ -954,21 +955,7 @@ public:
 
     void Reorder( const sc::ReorderParam& rParam );
 
-    // Internal cache that can be repeatedly used for a sequence of related 
ValidQuery()
-    // calls (related meaning done in a loop for the same document and query 
data).
-    struct ValidQueryCache
-    {
-        std::unordered_map<FormulaError, svl::SharedString> 
mCachedSharedErrorStrings;
-        std::vector<double> mCachedSortedItemValues;
-        std::vector<const rtl_uString*> mCachedSortedItemStrings;
-        bool mCachedSortedItemValuesReady = false;
-        bool mCachedSortedItemStringsReady = false;
-    };
-    bool ValidQuery(
-        SCROW nRow, const ScQueryParam& rQueryParam, const ScRefCellValue* 
pCell = nullptr,
-        bool* pbTestEqualCondition = nullptr, const ScInterpreterContext* 
pContext = nullptr,
-        sc::TableColumnBlockPositionSet* pBlockPos = nullptr,
-        ValidQueryCache* pQueryCache = nullptr );
+    // For ValidQuery() see ScQueryEvalutor class.
     void        TopTenQuery( ScQueryParam& );
     void        PrepareQuery( ScQueryParam& rQueryParam );
     SCSIZE      Query(const ScQueryParam& rQueryParam, bool bKeepSub);
diff --git a/sc/source/core/data/dociter.cxx b/sc/source/core/data/dociter.cxx
index 91cc0a2c213c..cafbbc9c0a1b 100644
--- a/sc/source/core/data/dociter.cxx
+++ b/sc/source/core/data/dociter.cxx
@@ -39,6 +39,7 @@
 #include <cellvalue.hxx>
 #include <scmatrix.hxx>
 #include <rowheightcontext.hxx>
+#include <queryevaluator.hxx>
 
 #include <o3tl/safeint.hxx>
 #include <tools/fract.hxx>
@@ -344,7 +345,8 @@ bool ScDBQueryDataIterator::IsQueryValid(
     ScDocument& rDoc, const ScQueryParam& rParam, SCTAB nTab, SCROW nRow, 
const ScRefCellValue* pCell)
 {
     assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
-    return rDoc.maTabs[nTab]->ValidQuery(nRow, rParam, pCell);
+    ScQueryEvaluator queryEvaluator(rDoc, *rDoc.maTabs[nTab], rParam);
+    return queryEvaluator.ValidQuery(nRow, pCell);
 }
 
 
ScDBQueryDataIterator::DataAccessInternal::DataAccessInternal(ScDBQueryParamInternal*
 pParam, ScDocument& rDoc, const ScInterpreterContext& rContext)
@@ -1117,7 +1119,9 @@ bool ScQueryCellIterator::GetThis()
         !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString &&
         ((maParam.bByRow && nRow == maParam.nRow1) ||
          (!maParam.bByRow && nCol == maParam.nCol1));
-    ScTable::ValidQueryCache validQueryCache;
+    bool bTestEqualCondition = false;
+    ScQueryEvaluator queryEvaluator(rDoc, *rDoc.maTabs[nTab], maParam, 
&mrContext,
+        (nTestEqualCondition ? &bTestEqualCondition : nullptr));
 
     ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
     while (true)
@@ -1180,11 +1184,8 @@ bool ScQueryCellIterator::GetThis()
             IncPos();
         else
         {
-            bool bTestEqualCondition = false;
-            if ( rDoc.maTabs[nTab]->ValidQuery( nRow, maParam,
-                    (nCol == static_cast<SCCOL>(nFirstQueryField) ? &aCell : 
nullptr),
-                    (nTestEqualCondition ? &bTestEqualCondition : nullptr),
-                    &mrContext, nullptr, &validQueryCache) )
+            if ( queryEvaluator.ValidQuery( nRow,
+                    (nCol == static_cast<SCCOL>(nFirstQueryField) ? &aCell : 
nullptr)))
             {
                 if ( nTestEqualCondition && bTestEqualCondition )
                     nTestEqualCondition |= nTestEqualConditionMatched;
@@ -1507,7 +1508,7 @@ int ScCountIfCellIterator::GetCount()
     const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
     const bool bSingleQueryItem = rEntry.GetQueryItems().size() == 1;
     int count = 0;
-    ScTable::ValidQueryCache validQueryCache;
+    ScQueryEvaluator queryEvaluator(rDoc, *rDoc.maTabs[nTab], maParam, 
&mrContext);
 
     ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
     while (true)
@@ -1560,10 +1561,8 @@ int ScCountIfCellIterator::GetCount()
 
         ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
 
-        if ( rDoc.maTabs[nTab]->ValidQuery( nRow, maParam,
-                (nCol == static_cast<SCCOL>(rEntry.nField) ? &aCell : nullptr),
-                nullptr,
-                &mrContext, nullptr, &validQueryCache) )
+        if ( queryEvaluator.ValidQuery( nRow,
+                (nCol == static_cast<SCCOL>(rEntry.nField) ? &aCell : 
nullptr)))
         {
             if (aCell.isEmpty())
                 return count;
diff --git a/sc/source/core/data/queryevaluator.cxx 
b/sc/source/core/data/queryevaluator.cxx
new file mode 100644
index 000000000000..f5241839221d
--- /dev/null
+++ b/sc/source/core/data/queryevaluator.cxx
@@ -0,0 +1,957 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <queryevaluator.hxx>
+
+#include <cellform.hxx>
+#include <cellvalue.hxx>
+#include <colorscale.hxx>
+#include <document.hxx>
+#include <docoptio.hxx>
+#include <queryparam.hxx>
+#include <scitems.hxx>
+#include <table.hxx>
+
+#include <editeng/brushitem.hxx>
+#include <editeng/colritem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <svl/zformat.hxx>
+#include <unotools/collatorwrapper.hxx>
+
+bool ScQueryEvaluator::isPartialTextMatchOp(const ScQueryEntry& rEntry)
+{
+    switch (rEntry.eOp)
+    {
+        // these operators can only be used with textural comparisons.
+        case SC_CONTAINS:
+        case SC_DOES_NOT_CONTAIN:
+        case SC_BEGINS_WITH:
+        case SC_ENDS_WITH:
+        case SC_DOES_NOT_BEGIN_WITH:
+        case SC_DOES_NOT_END_WITH:
+            return true;
+        default:;
+    }
+    return false;
+}
+
+bool ScQueryEvaluator::isTextMatchOp(const ScQueryEntry& rEntry)
+{
+    if (isPartialTextMatchOp(rEntry))
+        return true;
+
+    switch (rEntry.eOp)
+    {
+        // these operators can be used for either textural or value comparison.
+        case SC_EQUAL:
+        case SC_NOT_EQUAL:
+            return true;
+        default:;
+    }
+    return false;
+}
+
+void ScQueryEvaluator::setupTransliteratorIfNeeded()
+{
+    if (!mpTransliteration)
+        mpTransliteration = &ScGlobal::GetTransliteration(mrParam.bCaseSens);
+}
+
+void ScQueryEvaluator::setupCollatorIfNeeded()
+{
+    if (!mpCollator)
+        mpCollator = &ScGlobal::GetCollator(mrParam.bCaseSens);
+}
+
+ScQueryEvaluator::ScQueryEvaluator(ScDocument& rDoc, const ScTable& rTab,
+                                   const ScQueryParam& rParam, const 
ScInterpreterContext* pContext,
+                                   bool* pTestEqualCondition)
+    : mrDoc(rDoc)
+    , mrStrPool(rDoc.GetSharedStringPool())
+    , mrTab(rTab)
+    , mrParam(rParam)
+    , mpTestEqualCondition(pTestEqualCondition)
+    , mpTransliteration(nullptr)
+    , mpCollator(nullptr)
+    , mbMatchWholeCell(rDoc.GetDocOptions().IsMatchWholeCell())
+    , mbCaseSensitive(rParam.bCaseSens)
+    , mpContext(pContext)
+    , mnEntryCount(mrParam.GetEntryCount())
+{
+    if (mnEntryCount <= nFixedBools)
+    {
+        mpPasst = &maBool[0];
+        mpTest = &maTest[0];
+    }
+    else
+    {
+        mpBoolDynamic.reset(new bool[mnEntryCount]);
+        mpTestDynamic.reset(new bool[mnEntryCount]);
+        mpPasst = mpBoolDynamic.get();
+        mpTest = mpTestDynamic.get();
+    }
+}
+
+bool ScQueryEvaluator::isRealWildOrRegExp(const ScQueryEntry& rEntry) const
+{
+    if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
+        return false;
+
+    return isTextMatchOp(rEntry);
+}
+
+bool ScQueryEvaluator::isTestWildOrRegExp(const ScQueryEntry& rEntry) const
+{
+    if (!mpTestEqualCondition)
+        return false;
+
+    if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
+        return false;
+
+    return (rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL);
+}
+
+bool ScQueryEvaluator::isQueryByValue(const ScQueryEntry::Item& rItem, const 
ScRefCellValue& rCell)
+{
+    if (rItem.meType == ScQueryEntry::ByString)
+        return false;
+
+    return isQueryByValueForCell(rCell);
+}
+
+bool ScQueryEvaluator::isQueryByValueForCell(const ScRefCellValue& rCell)
+{
+    if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != 
FormulaError::NONE)
+        // Error values are compared as string.
+        return false;
+
+    return rCell.hasNumeric();
+}
+
+bool ScQueryEvaluator::isQueryByString(const ScQueryEntry& rEntry, const 
ScQueryEntry::Item& rItem,
+                                       const ScRefCellValue& rCell)
+{
+    if (isTextMatchOp(rEntry))
+        return true;
+
+    if (rItem.meType != ScQueryEntry::ByString)
+        return false;
+
+    return rCell.hasString();
+}
+
+sal_uInt32 ScQueryEvaluator::getNumFmt(SCCOL nCol, SCROW nRow)
+{
+    sal_uInt32 nNumFmt
+        = (mpContext ? mrTab.GetNumberFormat(*mpContext, ScAddress(nCol, nRow, 
mrTab.GetTab()))
+                     : mrTab.GetNumberFormat(nCol, nRow));
+    if (nNumFmt && (nNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+        // Any General of any locale is irrelevant for rounding.
+        nNumFmt = 0;
+    return nNumFmt;
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByValue(const ScRefCellValue& 
rCell, SCCOL nCol,
+                                                       SCROW nRow, const 
ScQueryEntry& rEntry,
+                                                       const 
ScQueryEntry::Item& rItem)
+{
+    bool bOk = false;
+    bool bTestEqual = false;
+    double nCellVal;
+    double fQueryVal = rItem.mfVal;
+    // Defer all number format detection to as late as possible as it's a
+    // bottle neck, even if that complicates the code. Also do not
+    // unnecessarily call ScDocument::RoundValueAsShown() for the same
+    // reason.
+    sal_uInt32 nNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND;
+
+    switch (rCell.meType)
+    {
+        case CELLTYPE_VALUE:
+            nCellVal = rCell.mfValue;
+            break;
+        case CELLTYPE_FORMULA:
+            nCellVal = rCell.mpFormula->GetValue();
+            break;
+        default:
+            nCellVal = 0.0;
+    }
+    if (rItem.mbRoundForFilter && nCellVal != 0.0)
+    {
+        nNumFmt = getNumFmt(nCol, nRow);
+        if (nNumFmt)
+        {
+            switch (rCell.meType)
+            {
+                case CELLTYPE_VALUE:
+                case CELLTYPE_FORMULA:
+                    nCellVal = mrDoc.RoundValueAsShown(nCellVal, nNumFmt, 
mpContext);
+                    break;
+                default:
+                    assert(!"can't be");
+            }
+        }
+    }
+
+    /* NOTE: lcl_PrepareQuery() prepares a filter query such that if a
+     * date+time format was queried rEntry.bQueryByDate is not set. In
+     * case other queries wanted to use this mechanism they should do
+     * the same, in other words only if rEntry.nVal is an integer value
+     * rEntry.bQueryByDate should be true and the time fraction be
+     * stripped here. */
+
+    if (rItem.meType == ScQueryEntry::ByDate)
+    {
+        if (nNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND)
+            nNumFmt = getNumFmt(nCol, nRow);
+        if (nNumFmt)
+        {
+            SvNumberFormatter* pFormatter
+                = mpContext ? mpContext->GetFormatTable() : 
mrDoc.GetFormatTable();
+            const SvNumberformat* pEntry = pFormatter->GetEntry(nNumFmt);
+            if (pEntry)
+            {
+                SvNumFormatType nNumFmtType = pEntry->GetType();
+                /* NOTE: Omitting the check for absence of
+                 * css::util::NumberFormat::TIME would include also date+time 
formatted
+                 * values of the same day. That may be desired in some
+                 * cases, querying all time values of a day, but confusing
+                 * in other cases. A user can always setup a standard
+                 * filter query for x >= date AND x < date+1 */
+                if ((nNumFmtType & SvNumFormatType::DATE) && !(nNumFmtType & 
SvNumFormatType::TIME))
+                {
+                    // The format is of date type.  Strip off the time
+                    // element.
+                    nCellVal = ::rtl::math::approxFloor(nCellVal);
+                }
+            }
+        }
+    }
+
+    switch (rEntry.eOp)
+    {
+        case SC_EQUAL:
+            bOk = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+            break;
+        case SC_LESS:
+            bOk = (nCellVal < fQueryVal) && 
!::rtl::math::approxEqual(nCellVal, fQueryVal);
+            break;
+        case SC_GREATER:
+            bOk = (nCellVal > fQueryVal) && 
!::rtl::math::approxEqual(nCellVal, fQueryVal);
+            break;
+        case SC_LESS_EQUAL:
+            bOk = (nCellVal < fQueryVal) || ::rtl::math::approxEqual(nCellVal, 
fQueryVal);
+            if (bOk && mpTestEqualCondition)
+                bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+            break;
+        case SC_GREATER_EQUAL:
+            bOk = (nCellVal > fQueryVal) || ::rtl::math::approxEqual(nCellVal, 
fQueryVal);
+            if (bOk && mpTestEqualCondition)
+                bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+            break;
+        case SC_NOT_EQUAL:
+            bOk = !::rtl::math::approxEqual(nCellVal, fQueryVal);
+            break;
+        default:
+        {
+            // added to avoid warnings
+        }
+    }
+
+    return std::pair<bool, bool>(bOk, bTestEqual);
+}
+
+OUString ScQueryEvaluator::getCellString(const ScRefCellValue& rCell, SCROW 
nRow,
+                                         const ScQueryEntry& rEntry,
+                                         const svl::SharedString** 
sharedString)
+{
+    if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != 
FormulaError::NONE)
+    {
+        // Error cell is evaluated as string (for now).
+        const FormulaError error = rCell.mpFormula->GetErrCode();
+        auto it = mCachedSharedErrorStrings.find(error);
+        if (it == mCachedSharedErrorStrings.end())
+        {
+            svl::SharedString str = 
mrStrPool.intern(ScGlobal::GetErrorString(error));
+            auto pos = mCachedSharedErrorStrings.insert({ error, str });
+            assert(pos.second); // inserted
+            it = pos.first;
+        }
+        *sharedString = &it->second;
+        return OUString();
+    }
+    else if (rCell.meType == CELLTYPE_STRING)
+    {
+        *sharedString = rCell.mpString;
+        return OUString();
+    }
+    else
+    {
+        sal_uInt32 nFormat
+            = mpContext
+                  ? mrTab.GetNumberFormat(*mpContext, 
ScAddress(static_cast<SCCOL>(rEntry.nField),
+                                                                nRow, 
mrTab.GetTab()))
+                  : mrTab.GetNumberFormat(static_cast<SCCOL>(rEntry.nField), 
nRow);
+        SvNumberFormatter* pFormatter
+            = mpContext ? mpContext->GetFormatTable() : mrDoc.GetFormatTable();
+        return ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, 
mrDoc, sharedString,
+                                            rEntry.bDoQuery);
+    }
+}
+
+bool ScQueryEvaluator::isFastCompareByString(const ScQueryEntry& rEntry) const
+{
+    // If this is true, then there's a fast path in compareByString() which
+    // can be selected using the template argument to get fast code
+    // that will not check the same conditions every time. This makes a 
difference
+    // in fast lookups that search for an exact value (case sensitive or not).
+    bool bMatchWholeCell = mbMatchWholeCell;
+    if (isPartialTextMatchOp(rEntry))
+        // may have to do partial textural comparison.
+        bMatchWholeCell = false;
+
+    const bool bRealWildOrRegExp = isRealWildOrRegExp(rEntry);
+    const bool bTestWildOrRegExp = isTestWildOrRegExp(rEntry);
+
+    // SC_EQUAL is part of isTextMatchOp(rEntry)
+    return rEntry.eOp == SC_EQUAL && !bRealWildOrRegExp && !bTestWildOrRegExp 
&& bMatchWholeCell;
+}
+
+// The value is placed inside one parameter: [pValueSource1] or 
[pValueSource2] but never in both.
+// For the template argument see isFastCompareByString().
+template <bool bFast>
+std::pair<bool, bool> ScQueryEvaluator::compareByString(const ScQueryEntry& 
rEntry,
+                                                        const 
ScQueryEntry::Item& rItem,
+                                                        const 
svl::SharedString* pValueSource1,
+                                                        const OUString* 
pValueSource2)
+{
+    bool bOk = false;
+    bool bTestEqual = false;
+    bool bMatchWholeCell;
+    if (bFast)
+        bMatchWholeCell = true;
+    else
+    {
+        bMatchWholeCell = mbMatchWholeCell;
+        if (isPartialTextMatchOp(rEntry))
+            // may have to do partial textural comparison.
+            bMatchWholeCell = false;
+    }
+
+    const bool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry);
+    const bool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
+
+    assert(!bFast || pValueSource1 != nullptr); // shared string for fast path
+    // [pValueSource1] or [pValueSource2] but never both of them or none of 
them
+    assert((pValueSource1 != nullptr) != (pValueSource2 != nullptr));
+
+    if (!bFast && (bRealWildOrRegExp || bTestWildOrRegExp))
+    {
+        const OUString& rValue = pValueSource1 ? pValueSource1->getString() : 
*pValueSource2;
+
+        sal_Int32 nStart = 0;
+        sal_Int32 nEnd = rValue.getLength();
+
+        // from 614 on, nEnd is behind the found text
+        bool bMatch = false;
+        if (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+        {
+            nEnd = 0;
+            nStart = rValue.getLength();
+            bMatch
+                = rEntry.GetSearchTextPtr(mrParam.eSearchType, 
mrParam.bCaseSens, bMatchWholeCell)
+                      ->SearchBackward(rValue, &nStart, &nEnd);
+        }
+        else
+        {
+            bMatch
+                = rEntry.GetSearchTextPtr(mrParam.eSearchType, 
mrParam.bCaseSens, bMatchWholeCell)
+                      ->SearchForward(rValue, &nStart, &nEnd);
+        }
+        if (bMatch && bMatchWholeCell && (nStart != 0 || nEnd != 
rValue.getLength()))
+            bMatch = false; // RegExp must match entire cell string
+        if (bRealWildOrRegExp)
+        {
+            switch (rEntry.eOp)
+            {
+                case SC_EQUAL:
+                case SC_CONTAINS:
+                    bOk = bMatch;
+                    break;
+                case SC_NOT_EQUAL:
+                case SC_DOES_NOT_CONTAIN:
+                    bOk = !bMatch;
+                    break;
+                case SC_BEGINS_WITH:
+                    bOk = (bMatch && (nStart == 0));
+                    break;
+                case SC_DOES_NOT_BEGIN_WITH:
+                    bOk = !(bMatch && (nStart == 0));
+                    break;
+                case SC_ENDS_WITH:
+                    bOk = (bMatch && (nEnd == rValue.getLength()));
+                    break;
+                case SC_DOES_NOT_END_WITH:
+                    bOk = !(bMatch && (nEnd == rValue.getLength()));
+                    break;
+                default:
+                {
+                    // added to avoid warnings
+                }
+            }
+        }
+        else
+            bTestEqual = bMatch;
+    }
+    if (bFast || !bRealWildOrRegExp)
+    {
+        // Simple string matching i.e. no regexp match.
+        if (bFast || isTextMatchOp(rEntry))
+        {
+            // Check this even with bFast.
+            if (rItem.meType != ScQueryEntry::ByString && 
rItem.maString.isEmpty())
+            {
+                // #i18374# When used from functions (match, countif, sumif, 
vlookup, hlookup, lookup),
+                // the query value is assigned directly, and the string is 
empty. In that case,
+                // don't find any string (isEqual would find empty string 
results in formula cells).
+                bOk = false;
+                if (rEntry.eOp == SC_NOT_EQUAL)
+                    bOk = !bOk;
+            }
+            else if (bFast || bMatchWholeCell)
+            {
+                if (bFast || pValueSource1)
+                {
+                    // Fast string equality check by comparing string 
identifiers.
+                    // This is the bFast path, all conditions should lead here 
on bFast == true.
+                    if (mrParam.bCaseSens)
+                    {
+                        bOk = pValueSource1->getData() == 
rItem.maString.getData();
+                    }
+                    else
+                    {
+                        bOk = pValueSource1->getDataIgnoreCase()
+                              == rItem.maString.getDataIgnoreCase();
+                    }
+                }
+                else // if (pValueSource2)
+                {
+                    if (mrParam.bCaseSens)
+                    {
+                        bOk = (*pValueSource2 == rItem.maString.getString());
+                    }
+                    else
+                    {
+                        // fallback
+                        const svl::SharedString 
rSource2(mrStrPool.intern(*pValueSource2));
+                        // Fast string equality check by comparing string 
identifiers.
+                        bOk = rSource2.getDataIgnoreCase() == 
rItem.maString.getDataIgnoreCase();
+                    }
+                }
+
+                if (!bFast && rEntry.eOp == SC_NOT_EQUAL)
+                    bOk = !bOk;
+            }
+            else
+            {
+                // Where do we find a match (if at all)
+                sal_Int32 nStrPos;
+
+                if (!mbCaseSensitive)
+                { // Common case for vlookup etc.
+                    const svl::SharedString rSource(
+                        pValueSource1 ? *pValueSource1 : 
mrStrPool.intern(*pValueSource2));
+
+                    const rtl_uString* pQuer = 
rItem.maString.getDataIgnoreCase();
+                    const rtl_uString* pCellStr = rSource.getDataIgnoreCase();
+
+                    assert(pQuer != nullptr);
+                    assert(pCellStr != nullptr);
+
+                    const sal_Int32 nIndex
+                        = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == 
SC_DOES_NOT_END_WITH)
+                              ? (pCellStr->length - pQuer->length)
+                              : 0;
+
+                    if (nIndex < 0)
+                        nStrPos = -1;
+                    else
+                    { // OUString::indexOf
+                        nStrPos = 
rtl_ustr_indexOfStr_WithLength(pCellStr->buffer + nIndex,
+                                                                 
pCellStr->length - nIndex,
+                                                                 
pQuer->buffer, pQuer->length);
+
+                        if (nStrPos >= 0)
+                            nStrPos += nIndex;
+                    }
+                }
+                else
+                {
+                    const OUString& rValue
+                        = pValueSource1 ? pValueSource1->getString() : 
*pValueSource2;
+                    const OUString aQueryStr = rItem.maString.getString();
+                    const LanguageType nLang
+                        = 
ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
+                    setupTransliteratorIfNeeded();
+                    const OUString aCell(mpTransliteration->transliterate(
+                        rValue, nLang, 0, rValue.getLength(), nullptr));
+
+                    const OUString aQuer(mpTransliteration->transliterate(
+                        aQueryStr, nLang, 0, aQueryStr.getLength(), nullptr));
+
+                    const sal_Int32 nIndex
+                        = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == 
SC_DOES_NOT_END_WITH)
+                              ? (aCell.getLength() - aQuer.getLength())
+                              : 0;
+                    nStrPos = ((nIndex < 0) ? -1 : aCell.indexOf(aQuer, 
nIndex));
+                }
+                switch (rEntry.eOp)
+                {
+                    case SC_EQUAL:
+                    case SC_CONTAINS:
+                        bOk = (nStrPos != -1);
+                        break;
+                    case SC_NOT_EQUAL:
+                    case SC_DOES_NOT_CONTAIN:
+                        bOk = (nStrPos == -1);
+                        break;
+                    case SC_BEGINS_WITH:
+                        bOk = (nStrPos == 0);
+                        break;
+                    case SC_DOES_NOT_BEGIN_WITH:
+                        bOk = (nStrPos != 0);
+                        break;
+                    case SC_ENDS_WITH:
+                        bOk = (nStrPos >= 0);
+                        break;
+                    case SC_DOES_NOT_END_WITH:
+                        bOk = (nStrPos < 0);
+                        break;
+                    default:
+                    {
+                        // added to avoid warnings
+                    }
+                }
+            }
+        }
+        else
+        { // use collator here because data was probably sorted
+            const OUString& rValue = pValueSource1 ? 
pValueSource1->getString() : *pValueSource2;
+            setupCollatorIfNeeded();
+            sal_Int32 nCompare = mpCollator->compareString(rValue, 
rItem.maString.getString());
+            switch (rEntry.eOp)
+            {
+                case SC_LESS:
+                    bOk = (nCompare < 0);
+                    break;
+                case SC_GREATER:
+                    bOk = (nCompare > 0);
+                    break;
+                case SC_LESS_EQUAL:
+                    bOk = (nCompare <= 0);
+                    if (bOk && mpTestEqualCondition && !bTestEqual)
+                        bTestEqual = (nCompare == 0);
+                    break;
+                case SC_GREATER_EQUAL:
+                    bOk = (nCompare >= 0);
+                    if (bOk && mpTestEqualCondition && !bTestEqual)
+                        bTestEqual = (nCompare == 0);
+                    break;
+                default:
+                {
+                    // added to avoid warnings
+                }
+            }
+        }
+    }
+
+    return std::pair<bool, bool>(bOk, bTestEqual);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByTextColor(SCCOL nCol, SCROW 
nRow,
+                                                           const 
ScQueryEntry::Item& rItem)
+{
+    ScAddress aPos(nCol, nRow, mrTab.GetTab());
+    Color color;
+    bool bHasConditionalColor = false;
+    // Text color can be set via conditional formatting - check that first
+    const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, 
mrTab.GetTab());
+    if (pPattern)
+    {
+        if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+        {
+            const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, 
mrTab.GetTab());
+            const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, 
pCondSet);
+            color = pColor->GetValue();
+            bHasConditionalColor = true;
+        }
+    }
+
+    if (!bHasConditionalColor)
+    {
+        const SvxColorItem* pColor = mrDoc.GetAttr(aPos, ATTR_FONT_COLOR);
+        color = pColor->GetValue();
+    }
+
+    bool bMatch = rItem.maColor == color;
+    return std::pair<bool, bool>(bMatch, false);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByBackgroundColor(SCCOL nCol, 
SCROW nRow,
+                                                                 const 
ScQueryEntry::Item& rItem)
+{
+    ScAddress aPos(nCol, nRow, mrTab.GetTab());
+    Color color;
+
+    // Background color can be set via conditional formatting - check that 
first
+    bool bHasConditionalColor = false;
+    const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, 
mrTab.GetTab());
+    if (pPattern)
+    {
+        if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+        {
+            const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, 
mrTab.GetTab());
+            const SvxBrushItem* pBackgroundColor = 
&pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
+            color = pBackgroundColor->GetColor();
+            bHasConditionalColor = true;
+        }
+    }
+
+    ScConditionalFormat* pCondFormat = mrDoc.GetCondFormat(nCol, nRow, 
mrTab.GetTab());
+    if (pCondFormat)
+    {
+        for (size_t i = 0; i < pCondFormat->size(); i++)
+        {
+            auto aEntry = pCondFormat->GetEntry(i);
+            if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
+            {
+                const ScColorScaleFormat* pColFormat
+                    = static_cast<const ScColorScaleFormat*>(aEntry);
+                color = *(pColFormat->GetColor(aPos));
+                bHasConditionalColor = true;
+            }
+        }
+    }
+
+    if (!bHasConditionalColor)
+    {
+        const SvxBrushItem* pBrush = mrDoc.GetAttr(aPos, ATTR_BACKGROUND);
+        color = pBrush->GetColor();
+    }
+
+    bool bMatch = rItem.maColor == color;
+    return std::pair<bool, bool>(bMatch, false);
+}
+
+// To be called only if both isQueryByValue() and isQueryByString()
+// returned false and range lookup is wanted! In range lookup comparison
+// numbers are less than strings. Nothing else is compared.
+std::pair<bool, bool> ScQueryEvaluator::compareByRangeLookup(const 
ScRefCellValue& rCell,
+                                                             const 
ScQueryEntry& rEntry,
+                                                             const 
ScQueryEntry::Item& rItem)
+{
+    bool bTestEqual = false;
+
+    if (rItem.meType == ScQueryEntry::ByString && rEntry.eOp != SC_LESS
+        && rEntry.eOp != SC_LESS_EQUAL)
+        return std::pair<bool, bool>(false, bTestEqual);
+
+    if (rItem.meType != ScQueryEntry::ByString && rEntry.eOp != SC_GREATER
+        && rEntry.eOp != SC_GREATER_EQUAL)
+        return std::pair<bool, bool>(false, bTestEqual);
+
+    if (rItem.meType == ScQueryEntry::ByString)
+    {
+        if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() 
!= FormulaError::NONE)
+            // Error values are compared as string.
+            return std::pair<bool, bool>(false, bTestEqual);
+
+        return std::pair<bool, bool>(rCell.hasNumeric(), bTestEqual);
+    }
+
+    return std::pair<bool, bool>(!rCell.hasNumeric(), bTestEqual);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::processEntry(SCROW nRow, SCCOL nCol, 
ScRefCellValue& aCell,
+                                                     const ScQueryEntry& 
rEntry)
+{
+    std::pair<bool, bool> aRes(false, false);
+    const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+    if (rItems.size() == 1 && rItems.front().meType == ScQueryEntry::ByEmpty)
+    {
+        if (rEntry.IsQueryByEmpty())
+            aRes.first = aCell.isEmpty();
+        else
+        {
+            assert(rEntry.IsQueryByNonEmpty());
+            aRes.first = !aCell.isEmpty();
+        }
+        return aRes;
+    }
+    if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10)
+    {
+        // If there are many items to query for (autofilter does this), then 
try to search
+        // efficiently in those items. So first search all the items of the 
relevant type,
+        // If that does not find anything, fall back to the generic code.
+        double value = 0;
+        bool valid = true;
+        // For ScQueryEntry::ByValue check that the cell either is a value or 
is a formula
+        // that has a value and is not an error (those are compared as 
strings). This
+        // is basically simplified isQueryByValue().
+        if (aCell.meType == CELLTYPE_VALUE)
+            value = aCell.mfValue;
+        else if (aCell.meType == CELLTYPE_FORMULA
+                 && aCell.mpFormula->GetErrCode() != FormulaError::NONE
+                 && aCell.mpFormula->IsValue())
+        {
+            value = aCell.mpFormula->GetValue();
+        }
+        else
+            valid = false;
+        if (valid)
+        {
+            if (rItems.size() >= 100)
+            {
+                // Sort, cache and binary search for the value in items.
+                // Don't bother comparing approximately.
+                auto& values = mCachedSortedItemValues;
+                if (!mCachedSortedItemValuesReady)
+                {
+                    values.reserve(rItems.size());
+                    for (const auto& rItem : rItems)
+                        if (rItem.meType == ScQueryEntry::ByValue)
+                            values.push_back(rItem.mfVal);
+                    std::sort(values.begin(), values.end());
+                    mCachedSortedItemValuesReady = true;
+                }
+                auto it = std::lower_bound(values.begin(), values.end(), 
value);
+                if (it != values.end() && *it == value)
+                    return std::make_pair(true, true);
+            }
+            else
+            {
+                for (const auto& rItem : rItems)
+                {
+                    // For speed don't bother comparing approximately here, 
usually there either
+                    // will be an exact match or it wouldn't match anyway.
+                    if (rItem.meType == ScQueryEntry::ByValue && value == 
rItem.mfVal)
+                    {
+                        return std::make_pair(true, true);
+                    }
+                }
+            }
+        }
+    }
+    const svl::SharedString* cellSharedString = nullptr;
+    OUString cellString;
+    bool cellStringSet = false;
+    const bool bFastCompareByString = isFastCompareByString(rEntry);
+    if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10 && bFastCompareByString)
+    {
+        // The same as above but for strings. Try to optimize the case when
+        // it's a svl::SharedString comparison. That happens when SC_EQUAL is 
used
+        // and simple matching is used, see compareByString()
+        if (!cellStringSet)
+        {
+            cellString = getCellString(aCell, nRow, rEntry, &cellSharedString);
+            cellStringSet = true;
+        }
+        // Allow also checking ScQueryEntry::ByValue if the cell is not 
numeric,
+        // as in that case isQueryByNumeric() would be false and 
isQueryByString() would
+        // be true because of SC_EQUAL making isTextMatchOp() true.
+        bool compareByValue = !isQueryByValueForCell(aCell);
+        // For ScQueryEntry::ByString check that the cell is represented by a 
shared string,
+        // which means it's either a string cell or a formula error. This is 
not as
+        // generous as isQueryByString() but it should be enough and better be 
safe.
+        if (cellSharedString != nullptr)
+        {
+            if (rItems.size() >= 100)
+            {
+                // Sort, cache and binary search for the string in items.
+                // Since each SharedString is identified by pointer value,
+                // sorting by pointer value is enough.
+                auto& values = mCachedSortedItemStrings;
+                if (!mCachedSortedItemStringsReady)
+                {
+                    values.reserve(rItems.size());
+                    for (const auto& rItem : rItems)
+                    {
+                        if (rItem.meType == ScQueryEntry::ByString
+                            || (compareByValue && rItem.meType == 
ScQueryEntry::ByValue))
+                        {
+                            values.push_back(mrParam.bCaseSens
+                                                 ? rItem.maString.getData()
+                                                 : 
rItem.maString.getDataIgnoreCase());
+                        }
+                    }
+                    std::sort(values.begin(), values.end());
+                    mCachedSortedItemStringsReady = true;
+                }
+                const rtl_uString* string = mrParam.bCaseSens
+                                                ? cellSharedString->getData()
+                                                : 
cellSharedString->getDataIgnoreCase();
+                auto it = std::lower_bound(values.begin(), values.end(), 
string);
+                if (it != values.end() && *it == string)
+                    return std::make_pair(true, true);
+            }
+            else
+            {
+                for (const auto& rItem : rItems)
+                {
+                    if ((rItem.meType == ScQueryEntry::ByString
+                         || (compareByValue && rItem.meType == 
ScQueryEntry::ByValue))
+                        && (mrParam.bCaseSens
+                                ? cellSharedString->getData() == 
rItem.maString.getData()
+                                : cellSharedString->getDataIgnoreCase()
+                                      == rItem.maString.getDataIgnoreCase()))
+                    {
+                        return std::make_pair(true, true);
+                    }
+                }
+            }
+        }
+    }
+    // Generic handling.
+    for (const auto& rItem : rItems)
+    {
+        if (rItem.meType == ScQueryEntry::ByTextColor)
+        {
+            std::pair<bool, bool> aThisRes = compareByTextColor(nCol, nRow, 
rItem);
+            aRes.first |= aThisRes.first;
+            aRes.second |= aThisRes.second;
+        }
+        else if (rItem.meType == ScQueryEntry::ByBackgroundColor)
+        {
+            std::pair<bool, bool> aThisRes = compareByBackgroundColor(nCol, 
nRow, rItem);
+            aRes.first |= aThisRes.first;
+            aRes.second |= aThisRes.second;
+        }
+        else if (isQueryByValue(rItem, aCell))
+        {
+            std::pair<bool, bool> aThisRes = compareByValue(aCell, nCol, nRow, 
rEntry, rItem);
+            aRes.first |= aThisRes.first;
+            aRes.second |= aThisRes.second;
+        }
+        else if (isQueryByString(rEntry, rItem, aCell))
+        {
+            if (!cellStringSet)
+            {
+                cellString = getCellString(aCell, nRow, rEntry, 
&cellSharedString);
+                cellStringSet = true;
+            }
+            std::pair<bool, bool> aThisRes;
+            if (cellSharedString && bFastCompareByString) // fast
+                aThisRes = compareByString<true>(rEntry, rItem, 
cellSharedString, nullptr);
+            else if (cellSharedString)
+                aThisRes = compareByString(rEntry, rItem, cellSharedString, 
nullptr);
+            else
+                aThisRes = compareByString(rEntry, rItem, nullptr, 
&cellString);
+            aRes.first |= aThisRes.first;
+            aRes.second |= aThisRes.second;
+        }
+        else if (mrParam.mbRangeLookup)
+        {
+            std::pair<bool, bool> aThisRes = compareByRangeLookup(aCell, 
rEntry, rItem);
+            aRes.first |= aThisRes.first;
+            aRes.second |= aThisRes.second;
+        }
+
+        if (aRes.first && (aRes.second || mpTestEqualCondition == nullptr))
+            break;
+    }
+    return aRes;
+}
+
+bool ScQueryEvaluator::ValidQuery(SCROW nRow, const ScRefCellValue* pCell,
+                                  sc::TableColumnBlockPositionSet* pBlockPos)
+{
+    if (!mrParam.GetEntry(0).bDoQuery)
+        return true;
+
+    tools::Long nPos = -1;
+    ScQueryParam::const_iterator it, itBeg = mrParam.begin(), itEnd = 
mrParam.end();
+    for (it = itBeg; it != itEnd && (*it)->bDoQuery; ++it)
+    {
+        const ScQueryEntry& rEntry = **it;
+
+        // Short-circuit the test at the end of the loop - if this is SC_AND
+        // and the previous value is false, this value will not be needed.
+        // Disable this if pbTestEqualCondition is present as that one may get 
set
+        // even if the result is false (that also means pTest doesn't need to 
be
+        // handled here).
+        if (rEntry.eConnect == SC_AND && mpTestEqualCondition == nullptr && 
nPos != -1
+            && !mpPasst[nPos])
+        {
+            continue;
+        }
+
+        SCCOL nCol = static_cast<SCCOL>(rEntry.nField);
+
+        // We can only handle one single direct query passed as a known pCell,
+        // subsequent queries have to obtain the cell.
+        ScRefCellValue aCell;
+        if (pCell && it == itBeg)
+            aCell = *pCell;
+        else if (pBlockPos)
+        { // hinted mdds access
+            aCell = const_cast<ScTable&>(mrTab).GetCellValue(
+                nCol, *pBlockPos->getBlockPosition(nCol), nRow);
+        }
+        else
+            aCell = mrTab.GetCellValue(nCol, nRow);
+
+        std::pair<bool, bool> aRes = processEntry(nRow, nCol, aCell, rEntry);
+
+        if (nPos == -1)
+        {
+            nPos++;
+            mpPasst[nPos] = aRes.first;
+            mpTest[nPos] = aRes.second;
+        }
+        else
+        {
+            if (rEntry.eConnect == SC_AND)
+            {
+                mpPasst[nPos] = mpPasst[nPos] && aRes.first;
+                mpTest[nPos] = mpTest[nPos] && aRes.second;
+            }
+            else
+            {
+                nPos++;
+                mpPasst[nPos] = aRes.first;
+                mpTest[nPos] = aRes.second;
+            }
+        }
+    }
+
+    for (tools::Long j = 1; j <= nPos; j++)
+    {
+        mpPasst[0] = mpPasst[0] || mpPasst[j];
+        mpTest[0] = mpTest[0] || mpTest[j];
+    }
+
+    bool bRet = mpPasst[0];
+    if (mpTestEqualCondition)
+        *mpTestEqualCondition = mpTest[0];
+
+    return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table2.cxx b/sc/source/core/data/table2.cxx
index 9c337b810e22..46dab0217c27 100644
--- a/sc/source/core/data/table2.cxx
+++ b/sc/source/core/data/table2.cxx
@@ -1935,6 +1935,14 @@ ScRefCellValue ScTable::GetCellValue( SCCOL nCol, SCROW 
nRow ) const
     return aCol[nCol].GetCellValue(nRow);
 }
 
+ScRefCellValue ScTable::GetCellValue( SCCOL nCol, sc::ColumnBlockPosition& 
rBlockPos, SCROW nRow )
+{
+    if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+        return ScRefCellValue();
+
+    return aCol[nCol].GetCellValue(rBlockPos, nRow);
+}
+
 void ScTable::GetFirstDataPos(SCCOL& rCol, SCROW& rRow) const
 {
     rCol = 0;
diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
index 4da4f23c59b4..7ccae5442178 100644
--- a/sc/source/core/data/table3.cxx
+++ b/sc/source/core/data/table3.cxx
@@ -19,16 +19,12 @@
 
 #include <comphelper/processfactory.hxx>
 #include <comphelper/random.hxx>
-#include <editeng/brushitem.hxx>
-#include <editeng/colritem.hxx>
 #include <unotools/textsearch.hxx>
 #include <svl/numformat.hxx>
 #include <svl/zforlist.hxx>
 #include <svl/zformat.hxx>
-#include <unotools/charclass.hxx>
 #include <unotools/collatorwrapper.hxx>
 #include <stdlib.h>
-#include <unotools/transliterationwrapper.hxx>
 #include <com/sun/star/i18n/KParseTokens.hpp>
 #include <com/sun/star/i18n/KParseType.hpp>
 #include <sal/log.hxx>
@@ -45,7 +41,6 @@
 #include <stlpool.hxx>
 #include <patattr.hxx>
 #include <subtotal.hxx>
-#include <docoptio.hxx>
 #include <markdata.hxx>
 #include <rangelst.hxx>
 #include <userlist.hxx>
@@ -69,7 +64,7 @@
 #include <reordermap.hxx>
 #include <drwlayer.hxx>
 #include <conditio.hxx>
-#include <colorscale.hxx>
+#include <queryevaluator.hxx>
 
 #include <svl/sharedstringpool.hxx>
 
@@ -2317,946 +2312,6 @@ bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
     return bSpaceLeft;
 }
 
-namespace {
-
-class QueryEvaluator
-{
-    ScDocument& mrDoc;
-    svl::SharedStringPool& mrStrPool;
-    const ScTable& mrTab;
-    const ScQueryParam& mrParam;
-    bool mpTestEqualCondition;
-    utl::TransliterationWrapper* mpTransliteration;
-    CollatorWrapper* mpCollator;
-    const bool mbMatchWholeCell;
-    const bool mbCaseSensitive;
-    ScTable::ValidQueryCache* mpValidQueryCache;
-
-    static bool isPartialTextMatchOp(const ScQueryEntry& rEntry)
-    {
-        switch (rEntry.eOp)
-        {
-            // these operators can only be used with textural comparisons.
-            case SC_CONTAINS:
-            case SC_DOES_NOT_CONTAIN:
-            case SC_BEGINS_WITH:
-            case SC_ENDS_WITH:
-            case SC_DOES_NOT_BEGIN_WITH:
-            case SC_DOES_NOT_END_WITH:
-                return true;
-            default:
-                ;
-        }
-        return false;
-    }
-
-    static bool isTextMatchOp(const ScQueryEntry& rEntry)
-    {
-        if (isPartialTextMatchOp(rEntry))
-            return true;
-
-        switch (rEntry.eOp)
-        {
-            // these operators can be used for either textural or value 
comparison.
-            case SC_EQUAL:
-            case SC_NOT_EQUAL:
-                return true;
-            default:
-                ;
-        }
-        return false;
-    }
-
-    void setupTransliteratorIfNeeded()
-    {
-        if (!mpTransliteration)
-            mpTransliteration = 
&ScGlobal::GetTransliteration(mrParam.bCaseSens);
-    }
-
-    void setupCollatorIfNeeded()
-    {
-        if (!mpCollator)
-            mpCollator = &ScGlobal::GetCollator(mrParam.bCaseSens);
-    }
-
-public:
-    QueryEvaluator(ScDocument& rDoc, const ScTable& rTab, const ScQueryParam& 
rParam,
-                   bool pTestEqualCondition, ScTable::ValidQueryCache* 
pValidQueryCache) :
-        mrDoc(rDoc),
-        mrStrPool(rDoc.GetSharedStringPool()),
-        mrTab(rTab),
-        mrParam(rParam),
-        mpTestEqualCondition(pTestEqualCondition),
-        mpTransliteration(nullptr),
-        mpCollator(nullptr),
-        mbMatchWholeCell(rDoc.GetDocOptions().IsMatchWholeCell()),
-        mbCaseSensitive( rParam.bCaseSens ),
-        mpValidQueryCache( pValidQueryCache )
-    {
-    }
-
-    bool isRealWildOrRegExp(const ScQueryEntry& rEntry) const
-    {
-        if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
-            return false;
-
-        return isTextMatchOp(rEntry);
-    }
-
-    bool isTestWildOrRegExp(const ScQueryEntry& rEntry) const
-    {
-        if (!mpTestEqualCondition)
-            return false;
-
-        if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
-            return false;
-
-        return (rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL);
-    }
-
-    static bool isQueryByValue(
-        const ScQueryEntry::Item& rItem, const ScRefCellValue& rCell)
-    {
-        if (rItem.meType == ScQueryEntry::ByString)
-            return false;
-
-        return isQueryByValueForCell(rCell);
-    }
-
-    static bool isQueryByValueForCell(const ScRefCellValue& rCell)
-    {
-        if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() 
!= FormulaError::NONE)
-            // Error values are compared as string.
-            return false;
-
-        return rCell.hasNumeric();
-    }
-
-    static bool isQueryByString(
-        const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem,
-        const ScRefCellValue& rCell)
-    {
-        if (isTextMatchOp(rEntry))
-            return true;
-
-        if (rItem.meType != ScQueryEntry::ByString)
-            return false;
-
-        return rCell.hasString();
-    }
-
-    sal_uInt32 getNumFmt( SCCOL nCol, SCROW nRow, const ScInterpreterContext* 
pContext )
-    {
-        sal_uInt32 nNumFmt = (pContext ?
-                mrTab.GetNumberFormat(*pContext, ScAddress(nCol, nRow, 
mrTab.GetTab())) :
-                mrTab.GetNumberFormat(nCol, nRow));
-        if (nNumFmt && (nNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
-            // Any General of any locale is irrelevant for rounding.
-            nNumFmt = 0;
-        return nNumFmt;
-    }
-
-    std::pair<bool,bool> compareByValue(
-        const ScRefCellValue& rCell, SCCOL nCol, SCROW nRow,
-        const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem,
-        const ScInterpreterContext* pContext)
-    {
-        bool bOk = false;
-        bool bTestEqual = false;
-        double nCellVal;
-        double fQueryVal = rItem.mfVal;
-        // Defer all number format detection to as late as possible as it's a
-        // bottle neck, even if that complicates the code. Also do not
-        // unnecessarily call ScDocument::RoundValueAsShown() for the same
-        // reason.
-        sal_uInt32 nNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND;
-
-        switch (rCell.meType)
-        {
-            case CELLTYPE_VALUE :
-                nCellVal = rCell.mfValue;
-            break;
-            case CELLTYPE_FORMULA :
-                nCellVal = rCell.mpFormula->GetValue();
-            break;
-            default:
-                nCellVal = 0.0;
-        }
-        if (rItem.mbRoundForFilter && nCellVal != 0.0)
-        {
-            nNumFmt = getNumFmt( nCol, nRow, pContext);
-            if (nNumFmt)
-            {
-                switch (rCell.meType)
-                {
-                    case CELLTYPE_VALUE :
-                    case CELLTYPE_FORMULA :
-                        nCellVal = mrDoc.RoundValueAsShown(nCellVal, nNumFmt, 
pContext);
-                    break;
-                    default:
-                        assert(!"can't be");
-                }
-            }
-        }
-
-        /* NOTE: lcl_PrepareQuery() prepares a filter query such that if a
-         * date+time format was queried rEntry.bQueryByDate is not set. In
-         * case other queries wanted to use this mechanism they should do
-         * the same, in other words only if rEntry.nVal is an integer value
-         * rEntry.bQueryByDate should be true and the time fraction be
-         * stripped here. */
-
-        if (rItem.meType == ScQueryEntry::ByDate)
-        {
-            if (nNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND)
-                nNumFmt = getNumFmt( nCol, nRow, pContext);
-            if (nNumFmt)
-            {
-                SvNumberFormatter* pFormatter = pContext ? 
pContext->GetFormatTable() : mrDoc.GetFormatTable();
-                const SvNumberformat* pEntry = pFormatter->GetEntry(nNumFmt);
-                if (pEntry)
-                {
-                    SvNumFormatType nNumFmtType = pEntry->GetType();
-                    /* NOTE: Omitting the check for absence of
-                     * css::util::NumberFormat::TIME would include also 
date+time formatted
-                     * values of the same day. That may be desired in some
-                     * cases, querying all time values of a day, but confusing
-                     * in other cases. A user can always setup a standard
-                     * filter query for x >= date AND x < date+1 */
-                    if ((nNumFmtType & SvNumFormatType::DATE) && !(nNumFmtType 
& SvNumFormatType::TIME))
-                    {
-                        // The format is of date type.  Strip off the time
-                        // element.
-                        nCellVal = ::rtl::math::approxFloor(nCellVal);
-                    }
-                }
-            }
-        }
-
-        switch (rEntry.eOp)
-        {
-            case SC_EQUAL :
-                bOk = ::rtl::math::approxEqual(nCellVal, fQueryVal);
-                break;
-            case SC_LESS :
-                bOk = (nCellVal < fQueryVal) && 
!::rtl::math::approxEqual(nCellVal, fQueryVal);
-                break;
-            case SC_GREATER :
-                bOk = (nCellVal > fQueryVal) && 
!::rtl::math::approxEqual(nCellVal, fQueryVal);
-                break;
-            case SC_LESS_EQUAL :
-                bOk = (nCellVal < fQueryVal) || 
::rtl::math::approxEqual(nCellVal, fQueryVal);
-                if ( bOk && mpTestEqualCondition )
-                    bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
-                break;
-            case SC_GREATER_EQUAL :
-                bOk = (nCellVal > fQueryVal) || ::rtl::math::approxEqual( 
nCellVal, fQueryVal);
-                if ( bOk && mpTestEqualCondition )
-                    bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
-                break;
-            case SC_NOT_EQUAL :
-                bOk = !::rtl::math::approxEqual(nCellVal, fQueryVal);
-                break;
-            default:
-            {
-                // added to avoid warnings
-            }
-        }
-
-        return std::pair<bool,bool>(bOk, bTestEqual);
-    }
-
-    OUString getCellString(const ScRefCellValue& rCell, SCROW nRow, const 
ScQueryEntry& rEntry,
-        const ScInterpreterContext* pContext, const svl::SharedString** 
sharedString)
-    {
-        if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() 
!= FormulaError::NONE)
-        {
-            // Error cell is evaluated as string (for now).
-            if(mpValidQueryCache)
-            {
-                const FormulaError error = rCell.mpFormula->GetErrCode();
-                auto it = 
mpValidQueryCache->mCachedSharedErrorStrings.find(error);
-                if( it == mpValidQueryCache->mCachedSharedErrorStrings.end())
-                {
-                    svl::SharedString str = 
mrStrPool.intern(ScGlobal::GetErrorString(error));
-                    auto pos = 
mpValidQueryCache->mCachedSharedErrorStrings.insert({error, str});
-                    assert(pos.second); // inserted
-                    it = pos.first;
-                }
-                *sharedString = &it->second;
-                return OUString();
-            }
-            return ScGlobal::GetErrorString(rCell.mpFormula->GetErrCode());
-        }
-        else if (rCell.meType == CELLTYPE_STRING)
-        {
-            *sharedString = rCell.mpString;
-            return OUString();
-        }
-        else
-        {
-            sal_uInt32 nFormat = pContext ? mrTab.GetNumberFormat( *pContext, 
ScAddress(static_cast<SCCOL>(rEntry.nField), nRow, mrTab.GetTab()) ) :
-                mrTab.GetNumberFormat( static_cast<SCCOL>(rEntry.nField), nRow 
);
-            SvNumberFormatter* pFormatter = pContext ? 
pContext->GetFormatTable() : mrDoc.GetFormatTable();
-            return ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, 
mrDoc, sharedString, rEntry.bDoQuery);
-        }
-    }
-
-    bool isFastCompareByString(const ScQueryEntry& rEntry) const
-    {
-        // If this is true, then there's a fast path in compareByString() which
-        // can be selected using the template argument to get fast code
-        // that will not check the same conditions every time. This makes a 
difference
-        // in fast lookups that search for an exact value (case sensitive or 
not).
-        bool bMatchWholeCell = mbMatchWholeCell;
-        if (isPartialTextMatchOp(rEntry))
-            // may have to do partial textural comparison.
-            bMatchWholeCell = false;
-
-        const bool bRealWildOrRegExp = isRealWildOrRegExp(rEntry);
-        const bool bTestWildOrRegExp = isTestWildOrRegExp(rEntry);
-
-        // SC_EQUAL is part of isTextMatchOp(rEntry)
-        return rEntry.eOp == SC_EQUAL && !bRealWildOrRegExp && 
!bTestWildOrRegExp && bMatchWholeCell;
-    }
-
-    // The value is placed inside one parameter: [pValueSource1] or 
[pValueSource2] but never in both.
-    // For the template argument see isFastCompareByString().
-    template<bool bFast = false>
-    std::pair<bool,bool> compareByString(const ScQueryEntry& rEntry, const 
ScQueryEntry::Item& rItem,
-        const svl::SharedString* pValueSource1, const OUString * pValueSource2)
-    {
-        bool bOk = false;
-        bool bTestEqual = false;
-        bool bMatchWholeCell;
-        if(bFast)
-            bMatchWholeCell = true;
-        else
-        {
-            bMatchWholeCell = mbMatchWholeCell;
-            if (isPartialTextMatchOp(rEntry))
-                // may have to do partial textural comparison.
-                bMatchWholeCell = false;
-        }
-
-        const bool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry);
-        const bool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
-
-        assert(!bFast || pValueSource1 != nullptr); // shared string for fast 
path
-        // [pValueSource1] or [pValueSource2] but never both of them or none 
of them
-        assert((pValueSource1 != nullptr) != (pValueSource2 != nullptr));
-
-        if ( !bFast && ( bRealWildOrRegExp || bTestWildOrRegExp ))
-        {
-            const OUString & rValue = pValueSource1 ? 
pValueSource1->getString() : *pValueSource2;
-
-            sal_Int32 nStart = 0;
-            sal_Int32 nEnd   = rValue.getLength();
-
-            // from 614 on, nEnd is behind the found text
-            bool bMatch = false;
-            if ( rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == 
SC_DOES_NOT_END_WITH )
-            {
-                nEnd = 0;
-                nStart = rValue.getLength();
-                bMatch = rEntry.GetSearchTextPtr( mrParam.eSearchType, 
mrParam.bCaseSens, bMatchWholeCell )
-                    ->SearchBackward(rValue, &nStart, &nEnd);
-            }
-            else
-            {
-                bMatch = rEntry.GetSearchTextPtr( mrParam.eSearchType, 
mrParam.bCaseSens, bMatchWholeCell )
-                    ->SearchForward(rValue, &nStart, &nEnd);
-            }
-            if ( bMatch && bMatchWholeCell
-                    && (nStart != 0 || nEnd != rValue.getLength()) )
-                bMatch = false;    // RegExp must match entire cell string
-            if ( bRealWildOrRegExp )
-            {
-                switch (rEntry.eOp)
-                {
-                    case SC_EQUAL:
-                    case SC_CONTAINS:
-                        bOk = bMatch;
-                        break;
-                    case SC_NOT_EQUAL:
-                    case SC_DOES_NOT_CONTAIN:
-                        bOk = !bMatch;
-                        break;
-                    case SC_BEGINS_WITH:
-                        bOk = ( bMatch && (nStart == 0) );
-                        break;
-                    case SC_DOES_NOT_BEGIN_WITH:
-                        bOk = !( bMatch && (nStart == 0) );
-                        break;
-                    case SC_ENDS_WITH:
-                        bOk = ( bMatch && (nEnd == rValue.getLength()) );
-                        break;
-                    case SC_DOES_NOT_END_WITH:
-                        bOk = !( bMatch && (nEnd == rValue.getLength()) );
-                        break;
-                    default:
-                        {
-                            // added to avoid warnings
-                        }
-                }
-            }
-            else
-                bTestEqual = bMatch;
-        }
-        if ( bFast || !bRealWildOrRegExp )
-        {
-            // Simple string matching i.e. no regexp match.
-            if (bFast || isTextMatchOp(rEntry))
-            {
-                // Check this even with bFast.
-                if (rItem.meType != ScQueryEntry::ByString && 
rItem.maString.isEmpty())
-                {
-                    // #i18374# When used from functions (match, countif, 
sumif, vlookup, hlookup, lookup),
-                    // the query value is assigned directly, and the string is 
empty. In that case,
-                    // don't find any string (isEqual would find empty string 
results in formula cells).
-                    bOk = false;
-                    if ( rEntry.eOp == SC_NOT_EQUAL )
-                        bOk = !bOk;
-                }
-                else if ( bFast || bMatchWholeCell )
-                {
-                    if (bFast || pValueSource1)
-                    {
-                        // Fast string equality check by comparing string 
identifiers.
-                        // This is the bFast path, all conditions should lead 
here on bFast == true.
-                        if (mrParam.bCaseSens)
-                        {
-                            bOk = pValueSource1->getData() == 
rItem.maString.getData();
-                        }
-                        else
-                        {
-                            bOk = pValueSource1->getDataIgnoreCase() == 
rItem.maString.getDataIgnoreCase();
-                        }
-                    }
-                    else // if (pValueSource2)
-                    {
-                        if (mrParam.bCaseSens)
-                        {
-                            bOk = (*pValueSource2 == 
rItem.maString.getString());
-                        }
-                        else
-                        {
-                            // fallback
-                            const svl::SharedString 
rSource2(mrStrPool.intern(*pValueSource2));
-                            // Fast string equality check by comparing string 
identifiers.
-                            bOk = rSource2.getDataIgnoreCase() == 
rItem.maString.getDataIgnoreCase();
-                        }
-                    }
-
-                    if ( !bFast && rEntry.eOp == SC_NOT_EQUAL )
-                        bOk = !bOk;
-                }
-                else
-                {
-                    // Where do we find a match (if at all)
-                    sal_Int32 nStrPos;
-
-                    if (!mbCaseSensitive)
-                    { // Common case for vlookup etc.
-                        const svl::SharedString rSource(pValueSource1? 
*pValueSource1 : mrStrPool.intern(*pValueSource2));
-
-                        const rtl_uString *pQuer = 
rItem.maString.getDataIgnoreCase();
-                        const rtl_uString *pCellStr = 
rSource.getDataIgnoreCase();
-
-                        assert(pQuer != nullptr);
-                        assert(pCellStr != nullptr);
-
-                        const sal_Int32 nIndex = (rEntry.eOp == SC_ENDS_WITH ||
-                                            rEntry.eOp == 
SC_DOES_NOT_END_WITH) ?
-                            (pCellStr->length - pQuer->length) : 0;
-
-                        if (nIndex < 0)
-                            nStrPos = -1;
-                        else
-                        { // OUString::indexOf
-                            nStrPos = rtl_ustr_indexOfStr_WithLength(
-                                pCellStr->buffer + nIndex, pCellStr->length - 
nIndex,
-                                pQuer->buffer, pQuer->length );
-
-                            if (nStrPos >= 0)
-                                nStrPos += nIndex;
-                        }
-                    }
-                    else
-                    {
-                        const OUString & rValue = pValueSource1 ? 
pValueSource1->getString() : *pValueSource2;
-                        const OUString aQueryStr = rItem.maString.getString();
-                        const LanguageType nLang = 
ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
-                        setupTransliteratorIfNeeded();
-                        const OUString aCell( mpTransliteration->transliterate(
-                                            rValue, nLang, 0, 
rValue.getLength(),
-                                            nullptr ) );
-
-                        const OUString aQuer( mpTransliteration->transliterate(
-                                            aQueryStr, nLang, 0, 
aQueryStr.getLength(),
-                                            nullptr ) );
-
-                        const sal_Int32 nIndex = (rEntry.eOp == SC_ENDS_WITH 
|| rEntry.eOp == SC_DOES_NOT_END_WITH) ?
-                            (aCell.getLength() - aQuer.getLength()) : 0;
-                        nStrPos = ((nIndex < 0) ? -1 : aCell.indexOf( aQuer, 
nIndex ));
-                    }
-                    switch (rEntry.eOp)
-                    {
-                    case SC_EQUAL:
-                    case SC_CONTAINS:
-                        bOk = ( nStrPos != -1 );
-                        break;
-                    case SC_NOT_EQUAL:
-                    case SC_DOES_NOT_CONTAIN:
-                        bOk = ( nStrPos == -1 );
-                        break;
-                    case SC_BEGINS_WITH:
-                        bOk = ( nStrPos == 0 );
-                        break;
-                    case SC_DOES_NOT_BEGIN_WITH:
-                        bOk = ( nStrPos != 0 );
-                        break;
-                    case SC_ENDS_WITH:
-                        bOk = ( nStrPos >= 0 );
-                        break;
-                    case SC_DOES_NOT_END_WITH:
-                        bOk = ( nStrPos < 0 );
-                        break;
-                    default:
-                        {
-                            // added to avoid warnings
-                        }
-                    }
-                }
-            }
-            else
-            {   // use collator here because data was probably sorted
-                const OUString & rValue = pValueSource1 ? 
pValueSource1->getString() : *pValueSource2;
-                setupCollatorIfNeeded();
-                sal_Int32 nCompare = mpCollator->compareString(
-                    rValue, rItem.maString.getString());
-                switch (rEntry.eOp)
-                {
-                    case SC_LESS :
-                        bOk = (nCompare < 0);
-                        break;
-                    case SC_GREATER :
-                        bOk = (nCompare > 0);
-                        break;
-                    case SC_LESS_EQUAL :
-                        bOk = (nCompare <= 0);
-                        if ( bOk && mpTestEqualCondition && !bTestEqual )
-                            bTestEqual = (nCompare == 0);
-                        break;
-                    case SC_GREATER_EQUAL :
-                        bOk = (nCompare >= 0);
-                        if ( bOk && mpTestEqualCondition && !bTestEqual )
-                            bTestEqual = (nCompare == 0);
-                        break;
-                    default:
-                    {
-                        // added to avoid warnings
-                    }
-                }
-            }
-        }
-
-        return std::pair<bool,bool>(bOk, bTestEqual);
-    }
-
-    std::pair<bool, bool> compareByTextColor(SCCOL nCol, SCROW nRow, SCTAB 
nTab,
-                                             const ScQueryEntry::Item& rItem)
-    {
-        ScAddress aPos(nCol, nRow, nTab);
-        Color color;
-        bool bHasConditionalColor = false;
-        // Text color can be set via conditional formatting - check that first
-        const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, nTab);
-        if (pPattern)
-        {
-            if 
(!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
-            {
-                const SfxItemSet* pCondSet
-                    = mrDoc.GetCondResult(nCol, nRow, nTab);
-                const SvxColorItem* pColor = 
&pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
-                color = pColor->GetValue();
-                bHasConditionalColor = true;
-            }
-        }
-
-        if (!bHasConditionalColor)
-        {
-            const SvxColorItem* pColor = mrDoc.GetAttr(aPos, ATTR_FONT_COLOR);
-            color = pColor->GetValue();
-        }
-
-        bool bMatch = rItem.maColor == color;
-        return std::pair<bool, bool>(bMatch, false);
-    }
-
-    std::pair<bool, bool> compareByBackgroundColor(SCCOL nCol, SCROW nRow, 
SCTAB nTab,
-                                                   const ScQueryEntry::Item& 
rItem)
-    {
-        ScAddress aPos(nCol, nRow, nTab);
-        Color color;
-
-        // Background color can be set via conditional formatting - check that 
first
-        bool bHasConditionalColor = false;
-        const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, nTab);
-        if (pPattern)
-        {
-            if 
(!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
-            {
-                const SfxItemSet* pCondSet
-                    = mrDoc.GetCondResult(nCol, nRow, nTab);
-                const SvxBrushItem* pBackgroundColor = 
&pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
-                color = pBackgroundColor->GetColor();
-                bHasConditionalColor = true;
-            }
-        }
-
-        ScConditionalFormat* pCondFormat = mrDoc.GetCondFormat(nCol, nRow, 
nTab);
-        if (pCondFormat)
-        {
-            for (size_t i = 0; i < pCondFormat->size(); i++)
-            {
-                auto aEntry = pCondFormat->GetEntry(i);
-                if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
-                {
-                    const ScColorScaleFormat* pColFormat
-                        = static_cast<const ScColorScaleFormat*>(aEntry);
-                    color = *(pColFormat->GetColor(aPos));
-                    bHasConditionalColor = true;
-                }
-            }
-        }
-
-        if (!bHasConditionalColor)
-        {
-            const SvxBrushItem* pBrush = mrDoc.GetAttr(aPos, ATTR_BACKGROUND);
-            color = pBrush->GetColor();
-        }
-
-        bool bMatch = rItem.maColor == color;
-        return std::pair<bool, bool>(bMatch, false);
-    }
-
-    // To be called only if both isQueryByValue() and isQueryByString()
-    // returned false and range lookup is wanted! In range lookup comparison
-    // numbers are less than strings. Nothing else is compared.
-    static std::pair<bool,bool> compareByRangeLookup(
-        const ScRefCellValue& rCell,
-        const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem)
-    {
-        bool bTestEqual = false;
-
-        if (rItem.meType == ScQueryEntry::ByString && rEntry.eOp != SC_LESS && 
rEntry.eOp != SC_LESS_EQUAL)
-            return std::pair<bool,bool>(false, bTestEqual);
-
-        if (rItem.meType != ScQueryEntry::ByString && rEntry.eOp != SC_GREATER 
&& rEntry.eOp != SC_GREATER_EQUAL)
-            return std::pair<bool,bool>(false, bTestEqual);
-
-        if (rItem.meType == ScQueryEntry::ByString)
-        {
-            if (rCell.meType == CELLTYPE_FORMULA && 
rCell.mpFormula->GetErrCode() != FormulaError::NONE)
-                // Error values are compared as string.
-                return std::pair<bool,bool>(false, bTestEqual);
-
-            return std::pair<bool,bool>(rCell.hasNumeric(), bTestEqual);
-        }
-
-        return std::pair<bool,bool>(!rCell.hasNumeric(), bTestEqual);
-    }
-};
-
-std::pair<bool,bool> validQueryProcessEntry(SCROW nRow, SCCOL nCol, SCTAB 
nTab, const ScQueryParam& rParam,
-    ScRefCellValue& aCell, bool* pbTestEqualCondition, const 
ScInterpreterContext* pContext, QueryEvaluator& aEval,
-    ScTable::ValidQueryCache* pValidQueryCache, const ScQueryEntry& rEntry )
-{
-    std::pair<bool,bool> aRes(false, false);
-    const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
-    if (rItems.size() == 1 && rItems.front().meType == ScQueryEntry::ByEmpty)
-    {
-        if (rEntry.IsQueryByEmpty())
-            aRes.first = aCell.isEmpty();
-        else
-        {
-            assert(rEntry.IsQueryByNonEmpty());
-            aRes.first = !aCell.isEmpty();
-        }
-        return aRes;
-    }
-    if( rEntry.eOp == SC_EQUAL && rItems.size() >= 10 )
-    {
-        // If there are many items to query for (autofilter does this), then 
try to search
-        // efficiently in those items. So first search all the items of the 
relevant type,
-        // If that does not find anything, fall back to the generic code.
-        double value = 0;
-        bool valid = true;
-        // For ScQueryEntry::ByValue check that the cell either is a value or 
is a formula
-        // that has a value and is not an error (those are compared as 
strings). This
-        // is basically simplified isQueryByValue().
-        if( aCell.meType == CELLTYPE_VALUE )
-            value = aCell.mfValue;
-        else if (aCell.meType == CELLTYPE_FORMULA && 
aCell.mpFormula->GetErrCode() != FormulaError::NONE
-            && aCell.mpFormula->IsValue())
-        {
-            value = aCell.mpFormula->GetValue();
-        }
-        else
-            valid = false;
-        if(valid)
-        {
-            if(rItems.size() >= 100 && pValidQueryCache)
-            {
-                // Sort, cache and binary search for the value in items.
-                // Don't bother comparing approximately.
-                auto& values = pValidQueryCache->mCachedSortedItemValues;
-                if(!pValidQueryCache->mCachedSortedItemValuesReady)
-                {
-                    values.reserve(rItems.size());
-                    for (const auto& rItem : rItems)
-                        if (rItem.meType == ScQueryEntry::ByValue)
-                            values.push_back(rItem.mfVal);
-                    std::sort(values.begin(), values.end());
-                    pValidQueryCache->mCachedSortedItemValuesReady = true;
-                }
-                auto it = std::lower_bound(values.begin(), values.end(), 
value);
-                if( it != values.end() && *it == value )
-                    return std::make_pair(true, true);
-            }
-            else
-            {
-                for (const auto& rItem : rItems)
-                {
-                    // For speed don't bother comparing approximately here, 
usually there either
-                    // will be an exact match or it wouldn't match anyway.
-                    if (rItem.meType == ScQueryEntry::ByValue
-                        && value == rItem.mfVal)
-                    {
-                        return std::make_pair(true, true);
-                    }
-                }
-            }
-        }
-    }
-    const svl::SharedString* cellSharedString = nullptr;
-    OUString cellString;
-    bool cellStringSet = false;
-    const bool bFastCompareByString = aEval.isFastCompareByString(rEntry);
-    if(rEntry.eOp == SC_EQUAL && rItems.size() >= 10 && bFastCompareByString)
-    {
-        // The same as above but for strings. Try to optimize the case when
-        // it's a svl::SharedString comparison. That happens when SC_EQUAL is 
used
-        // and simple matching is used, see compareByString()
-        if(!cellStringSet)
-        {
-            cellString = aEval.getCellString(aCell, nRow, rEntry, pContext, 
&cellSharedString);
-            cellStringSet = true;
-        }
-        // Allow also checking ScQueryEntry::ByValue if the cell is not 
numeric,
-        // as in that case isQueryByNumeric() would be false and 
isQueryByString() would
-        // be true because of SC_EQUAL making isTextMatchOp() true.
-        bool compareByValue = !QueryEvaluator::isQueryByValueForCell(aCell);
-        // For ScQueryEntry::ByString check that the cell is represented by a 
shared string,
-        // which means it's either a string cell or a formula error. This is 
not as
-        // generous as isQueryByString() but it should be enough and better be 
safe.
-        if(cellSharedString != nullptr)
-        {
-            if(rItems.size() >= 100 && pValidQueryCache)
-            {
-                // Sort, cache and binary search for the string in items.
-                // Since each SharedString is identified by pointer value,
-                // sorting by pointer value is enough.
-                auto& values = pValidQueryCache->mCachedSortedItemStrings;
-                if(!pValidQueryCache->mCachedSortedItemStringsReady)
-                {
-                    values.reserve(rItems.size());
-                    for (const auto& rItem : rItems)
-                    {
-                        if (rItem.meType == ScQueryEntry::ByString
-                            || (compareByValue && rItem.meType == 
ScQueryEntry::ByValue))
-                        {
-                            values.push_back(rParam.bCaseSens
-                                ? rItem.maString.getData()
-                                : rItem.maString.getDataIgnoreCase());
-                        }
-                    }
-                    std::sort(values.begin(), values.end());
-                    pValidQueryCache->mCachedSortedItemStringsReady = true;
-                }
-                const rtl_uString* string = rParam.bCaseSens
-                    ? cellSharedString->getData()
-                    : cellSharedString->getDataIgnoreCase();
-                auto it = std::lower_bound(values.begin(), values.end(), 
string);
-                if( it != values.end() && *it == string )
-                    return std::make_pair(true, true);
-            }
-            else
-            {
-                for (const auto& rItem : rItems)
-                {
-                    if ((rItem.meType == ScQueryEntry::ByString
-                            || (compareByValue && rItem.meType == 
ScQueryEntry::ByValue))
-                        && ( rParam.bCaseSens
-                            ? cellSharedString->getData() == 
rItem.maString.getData()
-                            : cellSharedString->getDataIgnoreCase() == 
rItem.maString.getDataIgnoreCase()))
-                    {
-                        return std::make_pair(true, true);
-                    }
-                }
-            }
-        }
-    }
-    // Generic handling.
-    for (const auto& rItem : rItems)
-    {
-        if (rItem.meType == ScQueryEntry::ByTextColor)
-        {
-            std::pair<bool, bool> aThisRes
-                = aEval.compareByTextColor(nCol, nRow, nTab, rItem);
-            aRes.first |= aThisRes.first;
-            aRes.second |= aThisRes.second;
-        }
-        else if (rItem.meType == ScQueryEntry::ByBackgroundColor)
-        {
-            std::pair<bool,bool> aThisRes =
-                aEval.compareByBackgroundColor(nCol, nRow, nTab, rItem);
-            aRes.first |= aThisRes.first;
-            aRes.second |= aThisRes.second;
-        }
-        else if (QueryEvaluator::isQueryByValue(rItem, aCell))
-        {
-            std::pair<bool,bool> aThisRes =
-                aEval.compareByValue(aCell, nCol, nRow, rEntry, rItem, 
pContext);
-            aRes.first |= aThisRes.first;
-            aRes.second |= aThisRes.second;
-        }
-        else if (QueryEvaluator::isQueryByString(rEntry, rItem, aCell))
-        {
-            if(!cellStringSet)
-            {
-                cellString = aEval.getCellString(aCell, nRow, rEntry, 
pContext, &cellSharedString);
-                cellStringSet = true;
-            }
-            std::pair<bool,bool> aThisRes;
-            if( cellSharedString && bFastCompareByString ) // fast
-                aThisRes = aEval.compareByString<true>(rEntry, rItem, 
cellSharedString, nullptr);
-            else if( cellSharedString )
-                aThisRes = aEval.compareByString(rEntry, rItem, 
cellSharedString, nullptr);
-            else
-                aThisRes = aEval.compareByString(rEntry, rItem, nullptr, 
&cellString);
-            aRes.first |= aThisRes.first;
-            aRes.second |= aThisRes.second;
-        }
-        else if (rParam.mbRangeLookup)
-        {
-            std::pair<bool,bool> aThisRes =
-                QueryEvaluator::compareByRangeLookup(aCell, rEntry, rItem);
-            aRes.first |= aThisRes.first;
-            aRes.second |= aThisRes.second;
-        }
-
-        if (aRes.first && (aRes.second || pbTestEqualCondition == nullptr))
-            break;
-    }
-    return aRes;
-}
-
-}
-
-bool ScTable::ValidQuery(
-    SCROW nRow, const ScQueryParam& rParam, const ScRefCellValue* pCell, bool* 
pbTestEqualCondition,
-    const ScInterpreterContext* pContext, sc::TableColumnBlockPositionSet* 
pBlockPos,
-    ValidQueryCache* pValidQueryCache)
-{
-    if (!rParam.GetEntry(0).bDoQuery)
-        return true;
-
-    //---------------------------------------------------------------
-
-    const SCSIZE nFixedBools = 32;
-    bool aBool[nFixedBools];
-    bool aTest[nFixedBools];
-    SCSIZE nEntryCount = rParam.GetEntryCount();
-    bool* pPasst = ( nEntryCount <= nFixedBools ? &aBool[0] : new 
bool[nEntryCount] );
-    bool* pTest = ( nEntryCount <= nFixedBools ? &aTest[0] : new 
bool[nEntryCount] );
-
-    tools::Long    nPos = -1;
-    QueryEvaluator aEval(rDocument, *this, rParam, pbTestEqualCondition != 
nullptr, pValidQueryCache);
-    ScQueryParam::const_iterator it, itBeg = rParam.begin(), itEnd = 
rParam.end();
-    for (it = itBeg; it != itEnd && (*it)->bDoQuery; ++it)
-    {
-        const ScQueryEntry& rEntry = **it;
-
-        // Short-circuit the test at the end of the loop - if this is SC_AND
-        // and the previous value is false, this value will not be needed.
-        // Disable this if pbTestEqualCondition is present as that one may get 
set
-        // even if the result is false (that also means pTest doesn't need to 
be
-        // handled here).
-        if (rEntry.eConnect == SC_AND && pbTestEqualCondition == nullptr
-             && nPos != -1 && !pPasst[nPos])
-        {
-            continue;
-        }
-
-        SCCOL nCol = static_cast<SCCOL>(rEntry.nField);
-
-        // We can only handle one single direct query passed as a known pCell,
-        // subsequent queries have to obtain the cell.
-        ScRefCellValue aCell;
-        if(pCell && it == itBeg)
-            aCell = *pCell;
-        else if( pBlockPos )
-        {   // hinted mdds access
-            ScColumn* column = FetchColumn(nCol);
-            aCell = column->GetCellValue(*pBlockPos->getBlockPosition( nCol ), 
nRow);
-        }
-        else
-            aCell = GetCellValue(nCol, nRow);
-
-        std::pair<bool,bool> aRes = validQueryProcessEntry(nRow, nCol, nTab, 
rParam, aCell,
-            pbTestEqualCondition, pContext, aEval, pValidQueryCache, rEntry);
-
-        if (nPos == -1)
-        {
-            nPos++;
-            pPasst[nPos] = aRes.first;
-            pTest[nPos] = aRes.second;
-        }
-        else
-        {
-            if (rEntry.eConnect == SC_AND)
-            {
-                pPasst[nPos] = pPasst[nPos] && aRes.first;
-                pTest[nPos] = pTest[nPos] && aRes.second;
-            }
-            else
-            {
-                nPos++;
-                pPasst[nPos] = aRes.first;
-                pTest[nPos] = aRes.second;
-            }
-        }
-    }
-
-    for ( tools::Long j=1; j <= nPos; j++ )
-    {
-        pPasst[0] = pPasst[0] || pPasst[j];
-        pTest[0] = pTest[0] || pTest[j];
-    }
-
-    bool bRet = pPasst[0];
-    if ( pPasst != &aBool[0] )
-        delete [] pPasst;
-    if ( pbTestEqualCondition )
-        *pbTestEqualCondition = pTest[0];
-    if ( pTest != &aTest[0] )
-        delete [] pTest;
-
-    return bRet;
-}
-
 void ScTable::TopTenQuery( ScQueryParam& rParam )
 {
     bool bSortCollatorInitialized = false;
@@ -3530,13 +2585,13 @@ SCSIZE ScTable::Query(const ScQueryParam& rParamOrg, 
bool bKeepSub)
     }
 
     sc::TableColumnBlockPositionSet blockPos( GetDoc(), nTab ); // cache mdds 
access
-    ValidQueryCache validQueryCache;
+    ScQueryEvaluator queryEvaluator(GetDoc(), *this, aParam);
 
     SCROW nRealRow2 = aParam.nRow2;
     for (SCROW j = aParam.nRow1 + nHeader; j <= nRealRow2; ++j)
     {
         bool bResult;                                   // Filter result
-        bool bValid = ValidQuery(j, aParam, nullptr, nullptr, nullptr, 
&blockPos, &validQueryCache);
+        bool bValid = queryEvaluator.ValidQuery(j, nullptr, &blockPos);
         if (!bValid && bKeepSub)                        // Keep subtotals
         {
             for (SCCOL nCol=aParam.nCol1; nCol<=aParam.nCol2 && !bValid; 
nCol++)
@@ -3929,9 +2984,10 @@ void ScTable::GetFilteredFilterEntries(
     aParam.RemoveEntryByField(nCol);
 
     lcl_PrepareQuery(&rDocument, this, aParam, true);
+    ScQueryEvaluator queryEvaluator(GetDoc(), *this, aParam);
     for ( SCROW j = nRow1; j <= nRow2; ++j )
     {
-        if (ValidQuery(j, aParam))
+        if (queryEvaluator.ValidQuery(j))
         {
             aCol[nCol].GetFilterEntries(aBlockPos, j, j, rFilterEntries, 
bFiltering);
         }

Reply via email to